@assistkick/create 1.0.0
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/dist/bin/create.d.ts +2 -0
- package/dist/bin/create.js +25 -0
- package/dist/bin/create.js.map +1 -0
- package/dist/src/scaffolder.d.ts +22 -0
- package/dist/src/scaffolder.js +120 -0
- package/dist/src/scaffolder.js.map +1 -0
- package/package.json +24 -0
- package/templates/product-system/.env.example +8 -0
- package/templates/product-system/CLAUDE.md +45 -0
- package/templates/product-system/package.json +32 -0
- package/templates/product-system/packages/backend/package.json +37 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
- package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
- package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
- package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
- package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
- package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
- package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
- package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
- package/templates/product-system/packages/backend/src/server.ts +159 -0
- package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
- package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
- package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
- package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
- package/templates/product-system/packages/backend/src/services/init.ts +80 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
- package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
- package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
- package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
- package/templates/product-system/packages/backend/tsconfig.json +22 -0
- package/templates/product-system/packages/frontend/index.html +13 -0
- package/templates/product-system/packages/frontend/package-lock.json +2666 -0
- package/templates/product-system/packages/frontend/package.json +30 -0
- package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
- package/templates/product-system/packages/frontend/src/App.tsx +29 -0
- package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
- package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
- package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
- package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
- package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
- package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
- package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
- package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
- package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
- package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
- package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
- package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
- package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
- package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
- package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
- package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
- package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
- package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
- package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
- package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
- package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
- package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
- package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
- package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/main.tsx +12 -0
- package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
- package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
- package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
- package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
- package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
- package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
- package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
- package/templates/product-system/packages/frontend/tsconfig.json +21 -0
- package/templates/product-system/packages/frontend/vite.config.ts +20 -0
- package/templates/product-system/packages/shared/.env.example +3 -0
- package/templates/product-system/packages/shared/README.md +1 -0
- package/templates/product-system/packages/shared/db/migrate.ts +32 -0
- package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
- package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
- package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
- package/templates/product-system/packages/shared/db/schema.ts +137 -0
- package/templates/product-system/packages/shared/drizzle.config.js +14 -0
- package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
- package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
- package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
- package/templates/product-system/packages/shared/lib/constants.ts +327 -0
- package/templates/product-system/packages/shared/lib/db.ts +81 -0
- package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
- package/templates/product-system/packages/shared/lib/graph.ts +186 -0
- package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
- package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
- package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
- package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
- package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
- package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
- package/templates/product-system/packages/shared/lib/session.ts +152 -0
- package/templates/product-system/packages/shared/lib/validator.ts +117 -0
- package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
- package/templates/product-system/packages/shared/package.json +30 -0
- package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
- package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
- package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
- package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
- package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
- package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
- package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
- package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
- package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
- package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
- package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
- package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
- package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
- package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
- package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
- package/templates/product-system/packages/shared/tsconfig.json +24 -0
- package/templates/product-system/pnpm-workspace.yaml +2 -0
- package/templates/product-system/smoke_test.ts +219 -0
- package/templates/product-system/tests/coherence_review.test.ts +562 -0
- package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
- package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
- package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
- package/templates/product-system/tests/feature_kind.test.ts +139 -0
- package/templates/product-system/tests/gap_indicators.test.ts +199 -0
- package/templates/product-system/tests/graceful_init.test.ts +142 -0
- package/templates/product-system/tests/graph_legend.test.ts +314 -0
- package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
- package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
- package/templates/product-system/tests/kanban.test.ts +529 -0
- package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
- package/templates/product-system/tests/node_search.test.ts +340 -0
- package/templates/product-system/tests/node_sizing.test.ts +170 -0
- package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
- package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
- package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
- package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
- package/templates/product-system/tests/pipeline.test.ts +195 -0
- package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
- package/templates/product-system/tests/play_all.test.ts +296 -0
- package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
- package/templates/product-system/tests/relevance_search.test.ts +186 -0
- package/templates/product-system/tests/search_reorder.test.ts +88 -0
- package/templates/product-system/tests/serve_ui.test.ts +281 -0
- package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
- package/templates/product-system/tests/session_context_recall.test.ts +135 -0
- package/templates/product-system/tests/side_panel.test.ts +345 -0
- package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
- package/templates/product-system/tests/url_routing_test.ts +122 -0
- package/templates/product-system/tests/user_login.test.ts +150 -0
- package/templates/product-system/tests/user_registration.test.ts +205 -0
- package/templates/product-system/tests/web_terminal.test.ts +572 -0
- package/templates/product-system/tests/work_summary.test.ts +211 -0
- package/templates/product-system/tests/zoom_pan.test.ts +43 -0
- package/templates/product-system/tsconfig.json +24 -0
- package/templates/skills/product-bootstrap/SKILL.md +312 -0
- package/templates/skills/product-code-reviewer/SKILL.md +147 -0
- package/templates/skills/product-debugger/SKILL.md +206 -0
- package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
- package/templates/skills/product-developer/SKILL.md +182 -0
- package/templates/skills/product-interview/SKILL.md +220 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the automated development pipeline logic.
|
|
3
|
+
* Tests pipeline state management, prompt building, and debug-first logic.
|
|
4
|
+
* Uses node:test built-in runner.
|
|
5
|
+
*
|
|
6
|
+
* Note: Integration tests that depended on kanban.json have been removed
|
|
7
|
+
* (kanban moved to Turso). See pipeline-unit.test.js for unit tests with
|
|
8
|
+
* mocked dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it } from 'node:test';
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const SKILL_DIR = join(__dirname, '..');
|
|
17
|
+
|
|
18
|
+
describe('Pipeline state transitions', () => {
|
|
19
|
+
it('pipeline statuses include all expected values', () => {
|
|
20
|
+
const expectedStatuses = [
|
|
21
|
+
'creating_worktree', 'debugging', 'developing', 'reviewing',
|
|
22
|
+
'fixing_review', 'merging', 'completed', 'failed', 'blocked',
|
|
23
|
+
];
|
|
24
|
+
// Just verify we documented the expected statuses
|
|
25
|
+
assert.ok(expectedStatuses.length === 9);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Worktree command formation', () => {
|
|
30
|
+
it('worktree path follows expected pattern', () => {
|
|
31
|
+
const featureId = 'feat_010';
|
|
32
|
+
const worktreesDir = join(SKILL_DIR, '..', '.worktrees');
|
|
33
|
+
const expectedPath = join(worktreesDir, featureId);
|
|
34
|
+
const expectedBranch = `feature/${featureId}`;
|
|
35
|
+
|
|
36
|
+
assert.ok(expectedPath.includes('.worktrees/feat_010'));
|
|
37
|
+
assert.equal(expectedBranch, 'feature/feat_010');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('worktree command args are correctly formed', () => {
|
|
41
|
+
const featureId = 'feat_015';
|
|
42
|
+
const worktreePath = join(SKILL_DIR, '..', '.worktrees', featureId);
|
|
43
|
+
const args = ['worktree', 'add', worktreePath, '-b', `feature/${featureId}`];
|
|
44
|
+
|
|
45
|
+
assert.equal(args[0], 'worktree');
|
|
46
|
+
assert.equal(args[1], 'add');
|
|
47
|
+
assert.ok(args[2].endsWith('feat_015'));
|
|
48
|
+
assert.equal(args[3], '-b');
|
|
49
|
+
assert.equal(args[4], 'feature/feat_015');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Prompt interpolation', () => {
|
|
54
|
+
it('developer prompt includes feature ID', async () => {
|
|
55
|
+
const { readFile } = await import('node:fs/promises');
|
|
56
|
+
const skillPath = join(SKILL_DIR, '..', '.claude', 'skills', 'product-developer', 'SKILL.md');
|
|
57
|
+
let skillContent = '';
|
|
58
|
+
try {
|
|
59
|
+
skillContent = await readFile(skillPath, 'utf-8');
|
|
60
|
+
} catch { /* skill file may not exist in test env */ }
|
|
61
|
+
|
|
62
|
+
const featureId = 'feat_012';
|
|
63
|
+
const cycle = 1;
|
|
64
|
+
// Simulate prompt building
|
|
65
|
+
let prompt = skillContent + '\n\n## Current Task\n';
|
|
66
|
+
prompt += `Implement feature ${featureId}.`;
|
|
67
|
+
|
|
68
|
+
assert.ok(prompt.includes('feat_012'));
|
|
69
|
+
assert.ok(prompt.includes('## Current Task'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('developer prompt includes review notes on retry cycles', () => {
|
|
73
|
+
const featureId = 'feat_012';
|
|
74
|
+
const cycle = 2;
|
|
75
|
+
const previousNotes = 'Missing unit tests for edge cases';
|
|
76
|
+
|
|
77
|
+
let prompt = `## Current Task\nImplement feature ${featureId}.\n`;
|
|
78
|
+
prompt += `\n## Previous Review Feedback (Cycle ${cycle})\n`;
|
|
79
|
+
prompt += `The previous implementation was rejected. Fix the following issues:\n`;
|
|
80
|
+
prompt += previousNotes + '\n';
|
|
81
|
+
|
|
82
|
+
assert.ok(prompt.includes('Cycle 2'));
|
|
83
|
+
assert.ok(prompt.includes('Missing unit tests'));
|
|
84
|
+
assert.ok(prompt.includes('feat_012'));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('reviewer prompt includes feature ID', async () => {
|
|
88
|
+
const featureId = 'feat_014';
|
|
89
|
+
let prompt = `## Current Task\nReview feature ${featureId}.`;
|
|
90
|
+
|
|
91
|
+
assert.ok(prompt.includes('feat_014'));
|
|
92
|
+
assert.ok(prompt.includes('Review feature'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('developer prompt includes debugger findings when provided', () => {
|
|
96
|
+
const featureId = 'feat_012';
|
|
97
|
+
const debuggerFindings = 'Root cause: missing null check in graph_renderer.js line 42';
|
|
98
|
+
|
|
99
|
+
let prompt = `## Current Task\nImplement feature ${featureId}.\n`;
|
|
100
|
+
prompt += `\n## Debugger Investigation Findings\n`;
|
|
101
|
+
prompt += `A debugger agent has investigated the QA rejection notes and identified root causes.\n`;
|
|
102
|
+
prompt += `Use these findings to fix the actual root causes rather than just the symptom descriptions:\n\n`;
|
|
103
|
+
prompt += debuggerFindings + '\n';
|
|
104
|
+
|
|
105
|
+
assert.ok(prompt.includes('Debugger Investigation Findings'));
|
|
106
|
+
assert.ok(prompt.includes('Root cause: missing null check'));
|
|
107
|
+
assert.ok(prompt.includes('feat_012'));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('developer prompt omits debugger findings when not provided', () => {
|
|
111
|
+
const featureId = 'feat_012';
|
|
112
|
+
|
|
113
|
+
let prompt = `## Current Task\nImplement feature ${featureId}.\n`;
|
|
114
|
+
|
|
115
|
+
assert.ok(prompt.includes('feat_012'));
|
|
116
|
+
assert.ok(!prompt.includes('Debugger Investigation Findings'));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Debug-first pipeline logic', () => {
|
|
121
|
+
it('identifies unaddressed QA notes correctly', () => {
|
|
122
|
+
const notes = [
|
|
123
|
+
{ id: 'n1', text: 'Bug: missing validation', addressed: false },
|
|
124
|
+
{ id: 'n2', text: 'Bug: wrong color', addressed: true },
|
|
125
|
+
{ id: 'n3', text: 'Bug: crash on empty input' },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
const unaddressed = notes.filter(n => !n.addressed);
|
|
129
|
+
assert.equal(unaddressed.length, 2);
|
|
130
|
+
assert.equal(unaddressed[0].id, 'n1');
|
|
131
|
+
assert.equal(unaddressed[1].id, 'n3');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('marks notes as addressed after processing', () => {
|
|
135
|
+
const notes = [
|
|
136
|
+
{ id: 'n1', text: 'Bug: missing validation', addressed: false },
|
|
137
|
+
{ id: 'n2', text: 'Bug: wrong color' },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
for (const note of notes) {
|
|
141
|
+
if (!note.addressed) {
|
|
142
|
+
note.addressed = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
assert.equal(notes[0].addressed, true);
|
|
147
|
+
assert.equal(notes[1].addressed, true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('skips debugging when all notes are already addressed', () => {
|
|
151
|
+
const notes = [
|
|
152
|
+
{ id: 'n1', text: 'Bug: fixed', addressed: true },
|
|
153
|
+
{ id: 'n2', text: 'Bug: also fixed', addressed: true },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
const unaddressed = notes.filter(n => !n.addressed);
|
|
157
|
+
assert.equal(unaddressed.length, 0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('skips debugging when there are no notes', () => {
|
|
161
|
+
const notes = [];
|
|
162
|
+
const unaddressed = notes.filter(n => !n.addressed);
|
|
163
|
+
assert.equal(unaddressed.length, 0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Debugger prompt construction', () => {
|
|
168
|
+
it('debugger prompt includes QA notes', () => {
|
|
169
|
+
const featureId = 'feat_015';
|
|
170
|
+
const notes = [
|
|
171
|
+
{ id: 'n1', text: 'Breaks feat_012 neighborhood focus mode' },
|
|
172
|
+
{ id: 'n2', text: 'Missing unit tests for edge cases' },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
let prompt = `## Current Task\n`;
|
|
176
|
+
prompt += `Feature: ${featureId}\n\n`;
|
|
177
|
+
prompt += `## QA Rejection Notes to Investigate\n`;
|
|
178
|
+
for (const note of notes) {
|
|
179
|
+
prompt += `- [${note.id}] ${note.text}\n`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
assert.ok(prompt.includes('feat_015'));
|
|
183
|
+
assert.ok(prompt.includes('[n1] Breaks feat_012'));
|
|
184
|
+
assert.ok(prompt.includes('[n2] Missing unit tests'));
|
|
185
|
+
assert.ok(prompt.includes('QA Rejection Notes to Investigate'));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('debugger prompt instructs not to create bugfix features', () => {
|
|
189
|
+
let prompt = `## Instructions\n`;
|
|
190
|
+
prompt += `Do NOT create bugfix features, do NOT move kanban cards, do NOT write code fixes\n`;
|
|
191
|
+
|
|
192
|
+
assert.ok(prompt.includes('Do NOT create bugfix features'));
|
|
193
|
+
assert.ok(prompt.includes('do NOT move kanban cards'));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for feat_055: Show Pipeline Stats on All Kanban Cards.
|
|
3
|
+
* Verifies that persisted pipeline stats are fetched for cards in ALL columns,
|
|
4
|
+
* not just active ones.
|
|
5
|
+
* Uses node:test built-in runner.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The core logic change in feat_055 is removing the column guard from buildCard
|
|
12
|
+
* so that getPipelineStatus is called for every card regardless of column.
|
|
13
|
+
*
|
|
14
|
+
* Since kanban_renderer.js is a browser-side class (creates DOM elements),
|
|
15
|
+
* we test the data-level behavior: given a pipeline status response,
|
|
16
|
+
* the update functions should produce correct display data for any column.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// --- Logic extracted from updateTaskProgress ---
|
|
20
|
+
const formatTaskProgress = (tasks) => {
|
|
21
|
+
if (!tasks || tasks.total === 0) return null;
|
|
22
|
+
return `${tasks.completed}/${tasks.total} tasks`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// --- Logic extracted from updateToolCalls ---
|
|
26
|
+
const formatToolCalls = (toolCalls) => {
|
|
27
|
+
if (!toolCalls || toolCalls.total === 0) return null;
|
|
28
|
+
const items = [
|
|
29
|
+
{ label: 'Write', count: toolCalls.write },
|
|
30
|
+
{ label: 'Edit', count: toolCalls.edit },
|
|
31
|
+
{ label: 'Read', count: toolCalls.read },
|
|
32
|
+
{ label: 'Bash', count: toolCalls.bash },
|
|
33
|
+
];
|
|
34
|
+
return items
|
|
35
|
+
.filter(item => item.count > 0)
|
|
36
|
+
.map(item => `${item.label}: ${item.count}`);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// --- Logic for determining if polling should start ---
|
|
40
|
+
const TERMINAL_STATUSES = ['completed', 'failed', 'blocked', 'interrupted'];
|
|
41
|
+
|
|
42
|
+
const shouldStartPolling = (status) => {
|
|
43
|
+
if (status === 'idle') return false;
|
|
44
|
+
return !TERMINAL_STATUSES.includes(status);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const shouldShowStats = (status) => {
|
|
48
|
+
return status !== 'idle';
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe('Pipeline stats on all kanban cards', () => {
|
|
52
|
+
describe('formatTaskProgress', () => {
|
|
53
|
+
it('returns formatted string when tasks have total > 0', () => {
|
|
54
|
+
assert.equal(formatTaskProgress({ completed: 3, total: 7, items: [] }), '3/7 tasks');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns null when tasks total is 0', () => {
|
|
58
|
+
assert.equal(formatTaskProgress({ completed: 0, total: 0, items: [] }), null);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns null when tasks is undefined', () => {
|
|
62
|
+
assert.equal(formatTaskProgress(undefined), null);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('returns null when tasks is null', () => {
|
|
66
|
+
assert.equal(formatTaskProgress(null), null);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('handles fully completed tasks', () => {
|
|
70
|
+
assert.equal(formatTaskProgress({ completed: 7, total: 7, items: [] }), '7/7 tasks');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('formatToolCalls', () => {
|
|
75
|
+
it('returns badges for non-zero counts', () => {
|
|
76
|
+
const result = formatToolCalls({ total: 28, write: 5, edit: 3, read: 12, bash: 8 });
|
|
77
|
+
assert.deepEqual(result, ['Write: 5', 'Edit: 3', 'Read: 12', 'Bash: 8']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('omits zero-count tools', () => {
|
|
81
|
+
const result = formatToolCalls({ total: 17, write: 5, edit: 0, read: 12, bash: 0 });
|
|
82
|
+
assert.deepEqual(result, ['Write: 5', 'Read: 12']);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns null when total is 0', () => {
|
|
86
|
+
assert.equal(formatToolCalls({ total: 0, write: 0, edit: 0, read: 0, bash: 0 }), null);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns null when toolCalls is undefined', () => {
|
|
90
|
+
assert.equal(formatToolCalls(undefined), null);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('returns null when toolCalls is null', () => {
|
|
94
|
+
assert.equal(formatToolCalls(null), null);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('shouldShowStats — stats visibility for all columns', () => {
|
|
99
|
+
const columns = ['todo', 'in_progress', 'in_review', 'qa', 'done'];
|
|
100
|
+
|
|
101
|
+
for (const column of columns) {
|
|
102
|
+
it(`shows stats for ${column} card with completed pipeline`, () => {
|
|
103
|
+
// A card that has been through the pipeline has status 'completed'
|
|
104
|
+
assert.equal(shouldShowStats('completed'), true,
|
|
105
|
+
`${column} card with completed status should show stats`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it(`hides stats for ${column} card with no pipeline history (idle)`, () => {
|
|
109
|
+
assert.equal(shouldShowStats('idle'), false,
|
|
110
|
+
`${column} card with idle status should not show stats`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
it('shows stats for active pipeline statuses', () => {
|
|
115
|
+
const activeStatuses = ['developing', 'reviewing', 'merging', 'debugging', 'fixing_review'];
|
|
116
|
+
for (const status of activeStatuses) {
|
|
117
|
+
assert.equal(shouldShowStats(status), true, `${status} should show stats`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('shows stats for terminal pipeline statuses', () => {
|
|
122
|
+
for (const status of TERMINAL_STATUSES) {
|
|
123
|
+
assert.equal(shouldShowStats(status), true, `${status} should show stats`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('shouldStartPolling — only for active pipelines', () => {
|
|
129
|
+
it('does not poll for idle status', () => {
|
|
130
|
+
assert.equal(shouldStartPolling('idle'), false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('does not poll for completed status', () => {
|
|
134
|
+
assert.equal(shouldStartPolling('completed'), false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('does not poll for failed status', () => {
|
|
138
|
+
assert.equal(shouldStartPolling('failed'), false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('does not poll for blocked status', () => {
|
|
142
|
+
assert.equal(shouldStartPolling('blocked'), false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('does not poll for interrupted status', () => {
|
|
146
|
+
assert.equal(shouldStartPolling('interrupted'), false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('polls for developing status', () => {
|
|
150
|
+
assert.equal(shouldStartPolling('developing'), true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('polls for reviewing status', () => {
|
|
154
|
+
assert.equal(shouldStartPolling('reviewing'), true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('polls for merging status', () => {
|
|
158
|
+
assert.equal(shouldStartPolling('merging'), true);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('end-to-end scenario: Done column card shows final stats', () => {
|
|
163
|
+
it('a done card with persisted pipeline data shows task progress and tool calls', () => {
|
|
164
|
+
// Simulate what GET /api/kanban/:id/pipeline returns for a completed feature
|
|
165
|
+
const pipelineResponse = {
|
|
166
|
+
status: 'completed',
|
|
167
|
+
cycle: 1,
|
|
168
|
+
feature_id: 'feat_042',
|
|
169
|
+
tasks: { completed: 7, total: 7, items: [] },
|
|
170
|
+
toolCalls: { total: 28, write: 5, edit: 3, read: 12, bash: 8 },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// The card is in Done column — previously this was excluded from stats fetch
|
|
174
|
+
const column = 'done';
|
|
175
|
+
|
|
176
|
+
// feat_055: stats should be visible regardless of column
|
|
177
|
+
assert.equal(shouldShowStats(pipelineResponse.status), true);
|
|
178
|
+
assert.equal(formatTaskProgress(pipelineResponse.tasks), '7/7 tasks');
|
|
179
|
+
assert.deepEqual(formatToolCalls(pipelineResponse.toolCalls), ['Write: 5', 'Edit: 3', 'Read: 12', 'Bash: 8']);
|
|
180
|
+
|
|
181
|
+
// Should NOT start polling for completed pipeline
|
|
182
|
+
assert.equal(shouldStartPolling(pipelineResponse.status), false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('a todo card with no pipeline history shows nothing', () => {
|
|
186
|
+
const pipelineResponse = { status: 'idle' };
|
|
187
|
+
|
|
188
|
+
assert.equal(shouldShowStats(pipelineResponse.status), false);
|
|
189
|
+
assert.equal(formatTaskProgress(pipelineResponse.tasks), null);
|
|
190
|
+
assert.equal(formatToolCalls(pipelineResponse.toolCalls), null);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Play All button logic — dependency checking and sequential processing.
|
|
3
|
+
* Uses node:test built-in runner.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extracted dependency-checking logic matching KanbanRenderer.checkDependencies.
|
|
10
|
+
* Tests the pure data transformation without DOM dependencies.
|
|
11
|
+
*/
|
|
12
|
+
const checkDependencies = (featureId, graphData, kanbanData) => {
|
|
13
|
+
if (!graphData || !kanbanData) return { blocked: false, blockedBy: [] };
|
|
14
|
+
|
|
15
|
+
const deps = graphData.edges
|
|
16
|
+
.filter(e => e.from === featureId && e.relation === 'depends_on')
|
|
17
|
+
.map(e => e.to)
|
|
18
|
+
.filter(depId => graphData.nodes.some(n => n.id === depId && n.type === 'feature'));
|
|
19
|
+
|
|
20
|
+
const blockedBy = deps.filter(depId => {
|
|
21
|
+
const entry = kanbanData[depId];
|
|
22
|
+
return !entry || entry.column !== 'done';
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return { blocked: blockedBy.length > 0, blockedBy };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Simulates the Play All ordering logic: find next processable feature.
|
|
30
|
+
* Returns the first TODO feature whose dependencies are all in Done, or null.
|
|
31
|
+
*/
|
|
32
|
+
const findNextProcessable = (todoCards, graphData, kanbanData) => {
|
|
33
|
+
for (const card of todoCards) {
|
|
34
|
+
if (card.devBlocked) continue;
|
|
35
|
+
const { blocked } = checkDependencies(card.id, graphData, kanbanData);
|
|
36
|
+
if (!blocked) return card;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detects deadlock: returns true if all TODO features are blocked.
|
|
43
|
+
*/
|
|
44
|
+
const isDeadlocked = (todoCards, graphData, kanbanData) => {
|
|
45
|
+
const nonBlockedTodo = todoCards.filter(c => !c.devBlocked);
|
|
46
|
+
if (nonBlockedTodo.length === 0) return false;
|
|
47
|
+
return findNextProcessable(nonBlockedTodo, graphData, kanbanData) === null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('checkDependencies', () => {
|
|
51
|
+
it('returns not blocked when feature has no depends_on edges', () => {
|
|
52
|
+
const graphData = {
|
|
53
|
+
nodes: [{ id: 'feat_001', type: 'feature' }],
|
|
54
|
+
edges: [],
|
|
55
|
+
};
|
|
56
|
+
const kanbanData = { feat_001: { column: 'todo' } };
|
|
57
|
+
|
|
58
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
59
|
+
assert.equal(result.blocked, false);
|
|
60
|
+
assert.deepEqual(result.blockedBy, []);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns not blocked when all dependencies are in done column', () => {
|
|
64
|
+
const graphData = {
|
|
65
|
+
nodes: [
|
|
66
|
+
{ id: 'feat_001', type: 'feature' },
|
|
67
|
+
{ id: 'feat_002', type: 'feature' },
|
|
68
|
+
{ id: 'feat_003', type: 'feature' },
|
|
69
|
+
],
|
|
70
|
+
edges: [
|
|
71
|
+
{ from: 'feat_001', to: 'feat_002', relation: 'depends_on' },
|
|
72
|
+
{ from: 'feat_001', to: 'feat_003', relation: 'depends_on' },
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
const kanbanData = {
|
|
76
|
+
feat_001: { column: 'todo' },
|
|
77
|
+
feat_002: { column: 'done' },
|
|
78
|
+
feat_003: { column: 'done' },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
82
|
+
assert.equal(result.blocked, false);
|
|
83
|
+
assert.deepEqual(result.blockedBy, []);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('returns blocked when a dependency is not in done column', () => {
|
|
87
|
+
const graphData = {
|
|
88
|
+
nodes: [
|
|
89
|
+
{ id: 'feat_001', type: 'feature' },
|
|
90
|
+
{ id: 'feat_002', type: 'feature' },
|
|
91
|
+
],
|
|
92
|
+
edges: [
|
|
93
|
+
{ from: 'feat_001', to: 'feat_002', relation: 'depends_on' },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
const kanbanData = {
|
|
97
|
+
feat_001: { column: 'todo' },
|
|
98
|
+
feat_002: { column: 'in_progress' },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
102
|
+
assert.equal(result.blocked, true);
|
|
103
|
+
assert.deepEqual(result.blockedBy, ['feat_002']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns blocked when a dependency has no kanban entry', () => {
|
|
107
|
+
const graphData = {
|
|
108
|
+
nodes: [
|
|
109
|
+
{ id: 'feat_001', type: 'feature' },
|
|
110
|
+
{ id: 'feat_002', type: 'feature' },
|
|
111
|
+
],
|
|
112
|
+
edges: [
|
|
113
|
+
{ from: 'feat_001', to: 'feat_002', relation: 'depends_on' },
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
const kanbanData = {
|
|
117
|
+
feat_001: { column: 'todo' },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
121
|
+
assert.equal(result.blocked, true);
|
|
122
|
+
assert.deepEqual(result.blockedBy, ['feat_002']);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('ignores non-feature dependency targets', () => {
|
|
126
|
+
const graphData = {
|
|
127
|
+
nodes: [
|
|
128
|
+
{ id: 'feat_001', type: 'feature' },
|
|
129
|
+
{ id: 'comp_001', type: 'component' },
|
|
130
|
+
],
|
|
131
|
+
edges: [
|
|
132
|
+
{ from: 'feat_001', to: 'comp_001', relation: 'depends_on' },
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
const kanbanData = {
|
|
136
|
+
feat_001: { column: 'todo' },
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
140
|
+
assert.equal(result.blocked, false);
|
|
141
|
+
assert.deepEqual(result.blockedBy, []);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('ignores non-depends_on edge types', () => {
|
|
145
|
+
const graphData = {
|
|
146
|
+
nodes: [
|
|
147
|
+
{ id: 'feat_001', type: 'feature' },
|
|
148
|
+
{ id: 'feat_002', type: 'feature' },
|
|
149
|
+
],
|
|
150
|
+
edges: [
|
|
151
|
+
{ from: 'feat_001', to: 'feat_002', relation: 'relates_to' },
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
const kanbanData = {
|
|
155
|
+
feat_001: { column: 'todo' },
|
|
156
|
+
feat_002: { column: 'todo' },
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const result = checkDependencies('feat_001', graphData, kanbanData);
|
|
160
|
+
assert.equal(result.blocked, false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns safe defaults when graphData or kanbanData is null', () => {
|
|
164
|
+
assert.deepEqual(checkDependencies('feat_001', null, {}), { blocked: false, blockedBy: [] });
|
|
165
|
+
assert.deepEqual(checkDependencies('feat_001', { edges: [], nodes: [] }, null), { blocked: false, blockedBy: [] });
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('findNextProcessable', () => {
|
|
170
|
+
const graphData = {
|
|
171
|
+
nodes: [
|
|
172
|
+
{ id: 'feat_001', type: 'feature' },
|
|
173
|
+
{ id: 'feat_002', type: 'feature' },
|
|
174
|
+
{ id: 'feat_003', type: 'feature' },
|
|
175
|
+
],
|
|
176
|
+
edges: [
|
|
177
|
+
{ from: 'feat_002', to: 'feat_001', relation: 'depends_on' },
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
it('returns first unblocked feature', () => {
|
|
182
|
+
const kanbanData = {
|
|
183
|
+
feat_001: { column: 'todo' },
|
|
184
|
+
feat_002: { column: 'todo' },
|
|
185
|
+
};
|
|
186
|
+
const cards = [
|
|
187
|
+
{ id: 'feat_001', devBlocked: false },
|
|
188
|
+
{ id: 'feat_002', devBlocked: false },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const next = findNextProcessable(cards, graphData, kanbanData);
|
|
192
|
+
assert.equal(next.id, 'feat_001');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('skips features with unmet dependencies', () => {
|
|
196
|
+
const kanbanData = {
|
|
197
|
+
feat_001: { column: 'todo' },
|
|
198
|
+
feat_002: { column: 'todo' },
|
|
199
|
+
feat_003: { column: 'todo' },
|
|
200
|
+
};
|
|
201
|
+
const cards = [
|
|
202
|
+
{ id: 'feat_002', devBlocked: false }, // depends on feat_001 which is in todo
|
|
203
|
+
{ id: 'feat_001', devBlocked: false }, // no deps
|
|
204
|
+
{ id: 'feat_003', devBlocked: false }, // no deps
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const next = findNextProcessable(cards, graphData, kanbanData);
|
|
208
|
+
assert.equal(next.id, 'feat_001');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('skips devBlocked features', () => {
|
|
212
|
+
const kanbanData = {
|
|
213
|
+
feat_001: { column: 'todo' },
|
|
214
|
+
feat_003: { column: 'todo' },
|
|
215
|
+
};
|
|
216
|
+
const cards = [
|
|
217
|
+
{ id: 'feat_001', devBlocked: true },
|
|
218
|
+
{ id: 'feat_003', devBlocked: false },
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
const next = findNextProcessable(cards, graphData, kanbanData);
|
|
222
|
+
assert.equal(next.id, 'feat_003');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns null when all features are blocked', () => {
|
|
226
|
+
const kanbanData = {
|
|
227
|
+
feat_002: { column: 'todo' },
|
|
228
|
+
};
|
|
229
|
+
const cards = [
|
|
230
|
+
{ id: 'feat_002', devBlocked: false }, // depends on feat_001 not in done
|
|
231
|
+
];
|
|
232
|
+
|
|
233
|
+
const next = findNextProcessable(cards, graphData, kanbanData);
|
|
234
|
+
assert.equal(next, null);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('isDeadlocked', () => {
|
|
239
|
+
it('returns true when all TODO features have unmet dependencies', () => {
|
|
240
|
+
const graphData = {
|
|
241
|
+
nodes: [
|
|
242
|
+
{ id: 'feat_001', type: 'feature' },
|
|
243
|
+
{ id: 'feat_002', type: 'feature' },
|
|
244
|
+
],
|
|
245
|
+
edges: [
|
|
246
|
+
{ from: 'feat_001', to: 'feat_002', relation: 'depends_on' },
|
|
247
|
+
{ from: 'feat_002', to: 'feat_001', relation: 'depends_on' },
|
|
248
|
+
],
|
|
249
|
+
};
|
|
250
|
+
const kanbanData = {
|
|
251
|
+
feat_001: { column: 'todo' },
|
|
252
|
+
feat_002: { column: 'todo' },
|
|
253
|
+
};
|
|
254
|
+
const cards = [
|
|
255
|
+
{ id: 'feat_001', devBlocked: false },
|
|
256
|
+
{ id: 'feat_002', devBlocked: false },
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
assert.equal(isDeadlocked(cards, graphData, kanbanData), true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('returns false when at least one feature is processable', () => {
|
|
263
|
+
const graphData = {
|
|
264
|
+
nodes: [
|
|
265
|
+
{ id: 'feat_001', type: 'feature' },
|
|
266
|
+
{ id: 'feat_002', type: 'feature' },
|
|
267
|
+
],
|
|
268
|
+
edges: [
|
|
269
|
+
{ from: 'feat_002', to: 'feat_001', relation: 'depends_on' },
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
const kanbanData = {
|
|
273
|
+
feat_001: { column: 'todo' },
|
|
274
|
+
feat_002: { column: 'todo' },
|
|
275
|
+
};
|
|
276
|
+
const cards = [
|
|
277
|
+
{ id: 'feat_001', devBlocked: false },
|
|
278
|
+
{ id: 'feat_002', devBlocked: false },
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
assert.equal(isDeadlocked(cards, graphData, kanbanData), false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('returns false when TODO list is empty', () => {
|
|
285
|
+
const graphData = { nodes: [], edges: [] };
|
|
286
|
+
assert.equal(isDeadlocked([], graphData, {}), false);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('returns false when all TODO cards are devBlocked', () => {
|
|
290
|
+
const graphData = { nodes: [{ id: 'feat_001', type: 'feature' }], edges: [] };
|
|
291
|
+
const kanbanData = { feat_001: { column: 'todo' } };
|
|
292
|
+
const cards = [{ id: 'feat_001', devBlocked: true }];
|
|
293
|
+
|
|
294
|
+
assert.equal(isDeadlocked(cards, graphData, kanbanData), false);
|
|
295
|
+
});
|
|
296
|
+
});
|