backlog.md 0.1.0 → 0.1.3
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/bin/backlog-darwin-arm64/backlog +0 -0
- package/bin/backlog-darwin-x64/backlog +0 -0
- package/bin/backlog-linux-arm64/backlog +0 -0
- package/{cli → bin/backlog-linux-x64}/backlog +0 -0
- package/bin/backlog-win32-x64/backlog.exe +0 -0
- package/cli.js +62 -0
- package/package.json +57 -46
- package/.backlog/archive/drafts/readme.md +0 -3
- package/.backlog/archive/drafts/task-41 - temporary-test-task.md +0 -13
- package/.backlog/archive/readme.md +0 -6
- package/.backlog/archive/tasks/readme.md +0 -3
- package/.backlog/archive/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +0 -14
- package/.backlog/config.yml +0 -7
- package/.backlog/decisions/readme.md +0 -7
- package/.backlog/docs/readme.md +0 -20
- package/.backlog/drafts/readme.md +0 -3
- package/.backlog/drafts/task-26 - docs-add-board-export-step-to-agent-dod.md +0 -21
- package/.backlog/drafts/task-28 - add-code-of-conduct.md +0 -20
- package/.backlog/drafts/task-30 - create-changelog.md +0 -19
- package/.backlog/milestones/m-0 - project-setup.md +0 -8
- package/.backlog/milestones/m-1 - cli.md +0 -8
- package/.backlog/milestones/m-2 - cli-kanban.md +0 -8
- package/.backlog/milestones/m-3 - gui.md +0 -8
- package/.backlog/milestones/m-4 - gui-kanban.md +0 -8
- package/.backlog/milestones/m-5 - gui-advanced.md +0 -12
- package/.backlog/milestones/readme.md +0 -3
- package/.backlog/readme.md +0 -5
- package/.backlog/tasks/readme.md +0 -37
- package/.backlog/tasks/task-1 - cli-setup-core-project.md +0 -23
- package/.backlog/tasks/task-10 - gui-init-packaging.md +0 -23
- package/.backlog/tasks/task-11 - gui-kanban-board.md +0 -26
- package/.backlog/tasks/task-12 - gui-advanced.md +0 -25
- package/.backlog/tasks/task-13 - cli-add-agent-instruction-prompt.md +0 -53
- package/.backlog/tasks/task-13.1 - cli-agent-instruction-file-selection.md +0 -40
- package/.backlog/tasks/task-14 - gui-introduction-screens.md +0 -21
- package/.backlog/tasks/task-15 - improve-tasks-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-16 - improve-docs-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-17 - improve-drafts-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-18 - improve-decisions-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-19 - cli-fix-default-task-status-and-remove-draft-from-statuses.md +0 -55
- package/.backlog/tasks/task-2 - cli-core-logic-library.md +0 -28
- package/.backlog/tasks/task-20 - add-agent-guideline-to-mark-tasks-in-progress-on-start.md +0 -32
- package/.backlog/tasks/task-21 - kanban-board-vertical-layout.md +0 -31
- package/.backlog/tasks/task-22 - cli-prevent-double-dash-in-task-filenames.md +0 -24
- package/.backlog/tasks/task-23 - cli-kanban-board-order-tasks-by-id-asc.md +0 -30
- package/.backlog/tasks/task-24 - handle-subtasks-in-the-kanban-view.md +0 -38
- package/.backlog/tasks/task-24.1 - cli-kanban-board-milestone-view.md +0 -19
- package/.backlog/tasks/task-25 - cli-export-kanban-board-to-readme.md +0 -28
- package/.backlog/tasks/task-27 - add-contributing-guidelines.md +0 -27
- package/.backlog/tasks/task-29 - add-github-templates.md +0 -28
- package/.backlog/tasks/task-3 - cli-implement-backlog-init.md +0 -63
- package/.backlog/tasks/task-31 - update-readme-for-open-source.md +0 -26
- package/.backlog/tasks/task-32 - cli-hide-empty-'no-status'-column.md +0 -31
- package/.backlog/tasks/task-33 - cli-export-milestones-board-as-roadmap.md +0 -20
- package/.backlog/tasks/task-34 - split-readme.md-for-users-and-contributors.md +0 -26
- package/.backlog/tasks/task-35 - finalize-package.json-metadata-for-publishing.md +0 -24
- package/.backlog/tasks/task-36 - cli-prompt-for-project-name-in-init.md +0 -24
- package/.backlog/tasks/task-37 - cli-board-view-open-tasks-in-ide.md +0 -19
- package/.backlog/tasks/task-38 - cli-improved-agent-selection-for-init.md +0 -25
- package/.backlog/tasks/task-39 - cli-fix-empty-agent-instruction-files-on-init.md +0 -31
- package/.backlog/tasks/task-4 - cli-task-management-commands.md +0 -28
- package/.backlog/tasks/task-4.1 - cli-task-create.md +0 -27
- package/.backlog/tasks/task-4.10 - use-cli-to-mark-tasks-done.md +0 -51
- package/.backlog/tasks/task-4.11 - docs-add-definition-of-done-to-agent-guidelines.md +0 -23
- package/.backlog/tasks/task-4.12 - cli-handle-task-id-conflicts-across-branches.md +0 -53
- package/.backlog/tasks/task-4.13 - cli-fix-config-command-local-global-logic.md +0 -58
- package/.backlog/tasks/task-4.2 - cli-task-list-view.md +0 -25
- package/.backlog/tasks/task-4.3 - cli-task-edit.md +0 -24
- package/.backlog/tasks/task-4.4 - cli-task-archive-transition.md +0 -27
- package/.backlog/tasks/task-4.5 - cli-init-prompts-for-reporter-name-and-global-local-config.md +0 -28
- package/.backlog/tasks/task-4.6 - cli-add-empty-assignee-array-field-for-new-tasks.md +0 -35
- package/.backlog/tasks/task-4.7 - cli-parse-unquoted-created_date.md +0 -40
- package/.backlog/tasks/task-4.8 - cli-enforce-description-header.md +0 -48
- package/.backlog/tasks/task-4.9 - cli-normalize-task-id-inputs.md +0 -66
- package/.backlog/tasks/task-40 - cli-board-command-defaults-to-view.md +0 -38
- package/.backlog/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +0 -93
- package/.backlog/tasks/task-41.1 - cli-bblessed-init-wizard.md +0 -42
- package/.backlog/tasks/task-41.2 - cli-bblessed-task-view.md +0 -44
- package/.backlog/tasks/task-41.3 - cli-bblessed-doc-view.md +0 -45
- package/.backlog/tasks/task-41.4 - cli-bblessed-board-view.md +0 -49
- package/.backlog/tasks/task-41.5 - cli-audit-remaining-ui-for-bblessed.md +0 -55
- package/.backlog/tasks/task-42 - visual-hierarchy.md +0 -54
- package/.backlog/tasks/task-43 - remove-duplicate-acceptance-criteria-and-style-metadata.md +0 -56
- package/.backlog/tasks/task-44 - checklist-alignment.md +0 -24
- package/.backlog/tasks/task-45 - safe-line-wrapping.md +0 -23
- package/.backlog/tasks/task-46 - split-pane-layout.md +0 -24
- package/.backlog/tasks/task-47 - sticky-header-in-detail-view.md +0 -43
- package/.backlog/tasks/task-48 - footer-hint-line.md +0 -21
- package/.backlog/tasks/task-49 - status-styling.md +0 -53
- package/.backlog/tasks/task-5 - cli-docs-decisions.md +0 -57
- package/.backlog/tasks/task-50 - borders-&-padding.md +0 -22
- package/.backlog/tasks/task-51 - code-path-styling.md +0 -23
- package/.backlog/tasks/task-52 - cli-filter-tasks-list-by-status-or-assignee.md +0 -29
- package/.backlog/tasks/task-6 - cli-packaging.md +0 -65
- package/.backlog/tasks/task-6.1 - cli-local-installation-support-for-bunx-npx.md +0 -49
- package/.backlog/tasks/task-6.2 - cli-github-actions-for-build-&-publish.md +0 -64
- package/.backlog/tasks/task-7 - cli-kanban-view.md +0 -60
- package/.backlog/tasks/task-7.1 - cli-kanban-board-detect-remote-task-status.md +0 -62
- package/.backlog/tasks/task-8 - gui-project-setup.md +0 -21
- package/.backlog/tasks/task-9 - gui-task-crud.md +0 -24
- package/.cursorrules +0 -223
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -25
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -8
- package/.github/workflows/ci.yml +0 -36
- package/.husky/pre-commit +0 -1
- package/AGENTS.md +0 -65
- package/CLAUDE.md +0 -87
- package/CONTRIBUTING.md +0 -19
- package/DEVELOPMENT.md +0 -37
- package/biome.json +0 -31
- package/bun.lock +0 -152
- package/cli/.cursorrules-xh86jabm.md +0 -82
- package/cli/AGENTS-xh86jabm.md +0 -82
- package/cli/CLAUDE-xh86jabm.md +0 -82
- package/cli/cli.js +0 -19622
- package/cli/index.js +0 -2
- package/docs/npm-publishing.md +0 -69
- package/scripts/build.js +0 -73
- package/src/agent-instructions.ts +0 -54
- package/src/board.ts +0 -263
- package/src/cli.ts +0 -806
- package/src/constants/index.ts +0 -48
- package/src/core/backlog.ts +0 -183
- package/src/core/remote-tasks.ts +0 -168
- package/src/file-system/operations.ts +0 -515
- package/src/git/operations.ts +0 -189
- package/src/guidelines/.cursorrules.md +0 -82
- package/src/guidelines/AGENTS.md +0 -82
- package/src/guidelines/CLAUDE.md +0 -82
- package/src/guidelines/index.ts +0 -7
- package/src/index.ts +0 -30
- package/src/markdown/parser.ts +0 -145
- package/src/markdown/serializer.ts +0 -71
- package/src/test/agent-instructions.test.ts +0 -62
- package/src/test/board.test.ts +0 -291
- package/src/test/build.test.ts +0 -28
- package/src/test/checklist.test.ts +0 -273
- package/src/test/cli.test.ts +0 -1300
- package/src/test/code-path.test.ts +0 -204
- package/src/test/core.test.ts +0 -330
- package/src/test/filesystem.test.ts +0 -435
- package/src/test/git.test.ts +0 -26
- package/src/test/heading.test.ts +0 -102
- package/src/test/line-wrapping.test.ts +0 -252
- package/src/test/local-install.test.ts +0 -34
- package/src/test/markdown.test.ts +0 -526
- package/src/test/parallel-loading.test.ts +0 -160
- package/src/test/parent-id-normalization.test.ts +0 -48
- package/src/test/remote-id-conflict.test.ts +0 -60
- package/src/test/status-icon.test.ts +0 -93
- package/src/types/blessed.d.ts +0 -14
- package/src/types/index.ts +0 -55
- package/src/types/raw.d.ts +0 -4
- package/src/ui/board.ts +0 -322
- package/src/ui/checklist.ts +0 -103
- package/src/ui/code-path.ts +0 -113
- package/src/ui/heading.ts +0 -121
- package/src/ui/loading.ts +0 -216
- package/src/ui/status-icon.ts +0 -53
- package/src/ui/task-list.ts +0 -168
- package/src/ui/task-viewer.ts +0 -640
- package/src/ui/tui.ts +0 -301
- package/tsconfig.json +0 -26
package/src/test/cli.test.ts
DELETED
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { mkdir, rm, stat } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { Core, isGitRepository } from "../index.ts";
|
|
5
|
-
|
|
6
|
-
const TEST_DIR = join(process.cwd(), "test-cli");
|
|
7
|
-
const CLI_PATH = join(process.cwd(), "src", "cli.ts");
|
|
8
|
-
|
|
9
|
-
describe("CLI Integration", () => {
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
try {
|
|
12
|
-
await rm(TEST_DIR, { recursive: true, force: true });
|
|
13
|
-
} catch {
|
|
14
|
-
// Ignore cleanup errors
|
|
15
|
-
}
|
|
16
|
-
await mkdir(TEST_DIR, { recursive: true });
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
afterEach(async () => {
|
|
20
|
-
try {
|
|
21
|
-
await rm(TEST_DIR, { recursive: true, force: true });
|
|
22
|
-
} catch {
|
|
23
|
-
// Ignore cleanup errors
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("backlog init command", () => {
|
|
28
|
-
it("should initialize backlog project in existing git repo", async () => {
|
|
29
|
-
// Set up a git repository
|
|
30
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
31
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
32
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
33
|
-
|
|
34
|
-
// Initialize backlog project using Core (simulating CLI)
|
|
35
|
-
const core = new Core(TEST_DIR);
|
|
36
|
-
await core.initializeProject("CLI Test Project");
|
|
37
|
-
|
|
38
|
-
// Verify directory structure was created
|
|
39
|
-
const configExists = await Bun.file(join(TEST_DIR, ".backlog", "config.yml")).exists();
|
|
40
|
-
expect(configExists).toBe(true);
|
|
41
|
-
|
|
42
|
-
// Verify config content
|
|
43
|
-
const config = await core.filesystem.loadConfig();
|
|
44
|
-
expect(config?.projectName).toBe("CLI Test Project");
|
|
45
|
-
expect(config?.statuses).toEqual(["To Do", "In Progress", "Done"]);
|
|
46
|
-
expect(config?.defaultStatus).toBe("To Do");
|
|
47
|
-
|
|
48
|
-
// Verify git commit was created
|
|
49
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
50
|
-
expect(lastCommit).toContain("Initialize backlog project: CLI Test Project");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should create all required directories", async () => {
|
|
54
|
-
// Set up a git repository
|
|
55
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
56
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
57
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
58
|
-
|
|
59
|
-
const core = new Core(TEST_DIR);
|
|
60
|
-
await core.initializeProject("Directory Test");
|
|
61
|
-
|
|
62
|
-
// Check all expected directories exist
|
|
63
|
-
const expectedDirs = [
|
|
64
|
-
".backlog",
|
|
65
|
-
".backlog/tasks",
|
|
66
|
-
".backlog/drafts",
|
|
67
|
-
".backlog/archive",
|
|
68
|
-
".backlog/archive/tasks",
|
|
69
|
-
".backlog/archive/drafts",
|
|
70
|
-
".backlog/docs",
|
|
71
|
-
".backlog/decisions",
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
for (const dir of expectedDirs) {
|
|
75
|
-
try {
|
|
76
|
-
const stats = await stat(join(TEST_DIR, dir));
|
|
77
|
-
expect(stats.isDirectory()).toBe(true);
|
|
78
|
-
} catch {
|
|
79
|
-
// If stat fails, directory doesn't exist
|
|
80
|
-
expect(false).toBe(true);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("should handle project names with special characters", async () => {
|
|
86
|
-
// Set up a git repository
|
|
87
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
88
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
89
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
90
|
-
|
|
91
|
-
const core = new Core(TEST_DIR);
|
|
92
|
-
const specialProjectName = "My-Project_2024 (v1.0)";
|
|
93
|
-
await core.initializeProject(specialProjectName);
|
|
94
|
-
|
|
95
|
-
const config = await core.filesystem.loadConfig();
|
|
96
|
-
expect(config?.projectName).toBe(specialProjectName);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("should work when git repo exists", async () => {
|
|
100
|
-
// Set up existing git repo
|
|
101
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
102
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
103
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
104
|
-
|
|
105
|
-
const isRepo = await isGitRepository(TEST_DIR);
|
|
106
|
-
expect(isRepo).toBe(true);
|
|
107
|
-
|
|
108
|
-
const core = new Core(TEST_DIR);
|
|
109
|
-
await core.initializeProject("Existing Repo Test");
|
|
110
|
-
|
|
111
|
-
const config = await core.filesystem.loadConfig();
|
|
112
|
-
expect(config?.projectName).toBe("Existing Repo Test");
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("should accept optional project name parameter", async () => {
|
|
116
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
117
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
118
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
119
|
-
|
|
120
|
-
// Test the CLI implementation by directly using the Core functionality
|
|
121
|
-
const core = new Core(TEST_DIR);
|
|
122
|
-
await core.initializeProject("Test Project");
|
|
123
|
-
|
|
124
|
-
const config = await core.filesystem.loadConfig();
|
|
125
|
-
expect(config?.projectName).toBe("Test Project");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("should create agent instruction files when requested", async () => {
|
|
129
|
-
// Set up a git repository
|
|
130
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
131
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
132
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
133
|
-
|
|
134
|
-
// Simulate the agent instructions being added
|
|
135
|
-
const core = new Core(TEST_DIR);
|
|
136
|
-
await core.initializeProject("Agent Test Project");
|
|
137
|
-
|
|
138
|
-
// Import and call addAgentInstructions directly (simulating user saying "y")
|
|
139
|
-
const { addAgentInstructions } = await import("../index.ts");
|
|
140
|
-
await addAgentInstructions(TEST_DIR, core.gitOps);
|
|
141
|
-
|
|
142
|
-
// Verify agent files were created
|
|
143
|
-
const agentsFile = await Bun.file(join(TEST_DIR, "AGENTS.md")).exists();
|
|
144
|
-
const claudeFile = await Bun.file(join(TEST_DIR, "CLAUDE.md")).exists();
|
|
145
|
-
const cursorFile = await Bun.file(join(TEST_DIR, ".cursorrules")).exists();
|
|
146
|
-
|
|
147
|
-
expect(agentsFile).toBe(true);
|
|
148
|
-
expect(claudeFile).toBe(true);
|
|
149
|
-
expect(cursorFile).toBe(true);
|
|
150
|
-
|
|
151
|
-
// Verify content
|
|
152
|
-
const agentsContent = await Bun.file(join(TEST_DIR, "AGENTS.md")).text();
|
|
153
|
-
const claudeContent = await Bun.file(join(TEST_DIR, "CLAUDE.md")).text();
|
|
154
|
-
const cursorContent = await Bun.file(join(TEST_DIR, ".cursorrules")).text();
|
|
155
|
-
expect(agentsContent.length).toBeGreaterThan(0);
|
|
156
|
-
expect(claudeContent.length).toBeGreaterThan(0);
|
|
157
|
-
expect(cursorContent.length).toBeGreaterThan(0);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe("git integration", () => {
|
|
162
|
-
beforeEach(async () => {
|
|
163
|
-
// Set up a git repository
|
|
164
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
165
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
166
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("should create initial commit with backlog structure", async () => {
|
|
170
|
-
const core = new Core(TEST_DIR);
|
|
171
|
-
await core.initializeProject("Git Integration Test");
|
|
172
|
-
|
|
173
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
174
|
-
expect(lastCommit).toBe("backlog: Initialize backlog project: Git Integration Test");
|
|
175
|
-
|
|
176
|
-
// Verify git status is clean after initialization
|
|
177
|
-
const isClean = await core.gitOps.isClean();
|
|
178
|
-
expect(isClean).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe("task list command", () => {
|
|
183
|
-
beforeEach(async () => {
|
|
184
|
-
// Set up a git repository and initialize backlog
|
|
185
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
186
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
187
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
188
|
-
|
|
189
|
-
const core = new Core(TEST_DIR);
|
|
190
|
-
await core.initializeProject("List Test Project");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("should show 'No tasks found' when no tasks exist", async () => {
|
|
194
|
-
const core = new Core(TEST_DIR);
|
|
195
|
-
const tasks = await core.filesystem.listTasks();
|
|
196
|
-
expect(tasks).toHaveLength(0);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("should list tasks grouped by status", async () => {
|
|
200
|
-
const core = new Core(TEST_DIR);
|
|
201
|
-
|
|
202
|
-
// Create test tasks with different statuses
|
|
203
|
-
await core.createTask(
|
|
204
|
-
{
|
|
205
|
-
id: "task-1",
|
|
206
|
-
title: "First Task",
|
|
207
|
-
status: "To Do",
|
|
208
|
-
assignee: [],
|
|
209
|
-
createdDate: "2025-06-08",
|
|
210
|
-
labels: [],
|
|
211
|
-
dependencies: [],
|
|
212
|
-
description: "First test task",
|
|
213
|
-
},
|
|
214
|
-
false,
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
await core.createTask(
|
|
218
|
-
{
|
|
219
|
-
id: "task-2",
|
|
220
|
-
title: "Second Task",
|
|
221
|
-
status: "Done",
|
|
222
|
-
assignee: [],
|
|
223
|
-
createdDate: "2025-06-08",
|
|
224
|
-
labels: [],
|
|
225
|
-
dependencies: [],
|
|
226
|
-
description: "Second test task",
|
|
227
|
-
},
|
|
228
|
-
false,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
await core.createTask(
|
|
232
|
-
{
|
|
233
|
-
id: "task-3",
|
|
234
|
-
title: "Third Task",
|
|
235
|
-
status: "To Do",
|
|
236
|
-
assignee: [],
|
|
237
|
-
createdDate: "2025-06-08",
|
|
238
|
-
labels: [],
|
|
239
|
-
dependencies: [],
|
|
240
|
-
description: "Third test task",
|
|
241
|
-
},
|
|
242
|
-
false,
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const tasks = await core.filesystem.listTasks();
|
|
246
|
-
expect(tasks).toHaveLength(3);
|
|
247
|
-
|
|
248
|
-
// Verify tasks are grouped correctly by status
|
|
249
|
-
const todoTasks = tasks.filter((t) => t.status === "To Do");
|
|
250
|
-
const doneTasks = tasks.filter((t) => t.status === "Done");
|
|
251
|
-
|
|
252
|
-
expect(todoTasks).toHaveLength(2);
|
|
253
|
-
expect(doneTasks).toHaveLength(1);
|
|
254
|
-
expect(todoTasks.map((t) => t.id)).toEqual(["task-1", "task-3"]);
|
|
255
|
-
expect(doneTasks.map((t) => t.id)).toEqual(["task-2"]);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("should respect config status order", async () => {
|
|
259
|
-
const core = new Core(TEST_DIR);
|
|
260
|
-
|
|
261
|
-
// Load and verify default config status order
|
|
262
|
-
const config = await core.filesystem.loadConfig();
|
|
263
|
-
expect(config?.statuses).toEqual(["To Do", "In Progress", "Done"]);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
it("should filter tasks by status", async () => {
|
|
267
|
-
const core = new Core(TEST_DIR);
|
|
268
|
-
|
|
269
|
-
await core.createTask(
|
|
270
|
-
{
|
|
271
|
-
id: "task-1",
|
|
272
|
-
title: "First Task",
|
|
273
|
-
status: "To Do",
|
|
274
|
-
assignee: [],
|
|
275
|
-
createdDate: "2025-06-08",
|
|
276
|
-
labels: [],
|
|
277
|
-
dependencies: [],
|
|
278
|
-
description: "First test task",
|
|
279
|
-
},
|
|
280
|
-
false,
|
|
281
|
-
);
|
|
282
|
-
await core.createTask(
|
|
283
|
-
{
|
|
284
|
-
id: "task-2",
|
|
285
|
-
title: "Second Task",
|
|
286
|
-
status: "Done",
|
|
287
|
-
assignee: [],
|
|
288
|
-
createdDate: "2025-06-08",
|
|
289
|
-
labels: [],
|
|
290
|
-
dependencies: [],
|
|
291
|
-
description: "Second test task",
|
|
292
|
-
},
|
|
293
|
-
false,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
const result = Bun.spawnSync(["bun", CLI_PATH, "task", "list", "--plain", "--status", "Done"], { cwd: TEST_DIR });
|
|
297
|
-
const out = result.stdout.toString();
|
|
298
|
-
expect(out).toContain("Done:");
|
|
299
|
-
expect(out).toContain("task-2 - Second Task");
|
|
300
|
-
expect(out).not.toContain("task-1");
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("should filter tasks by assignee", async () => {
|
|
304
|
-
const core = new Core(TEST_DIR);
|
|
305
|
-
|
|
306
|
-
await core.createTask(
|
|
307
|
-
{
|
|
308
|
-
id: "task-1",
|
|
309
|
-
title: "Assigned Task",
|
|
310
|
-
status: "To Do",
|
|
311
|
-
assignee: ["alice"],
|
|
312
|
-
createdDate: "2025-06-08",
|
|
313
|
-
labels: [],
|
|
314
|
-
dependencies: [],
|
|
315
|
-
description: "Assigned task",
|
|
316
|
-
},
|
|
317
|
-
false,
|
|
318
|
-
);
|
|
319
|
-
await core.createTask(
|
|
320
|
-
{
|
|
321
|
-
id: "task-2",
|
|
322
|
-
title: "Unassigned Task",
|
|
323
|
-
status: "To Do",
|
|
324
|
-
assignee: [],
|
|
325
|
-
createdDate: "2025-06-08",
|
|
326
|
-
labels: [],
|
|
327
|
-
dependencies: [],
|
|
328
|
-
description: "Other task",
|
|
329
|
-
},
|
|
330
|
-
false,
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
const result = Bun.spawnSync(["bun", CLI_PATH, "task", "list", "--plain", "--assignee", "alice"], {
|
|
334
|
-
cwd: TEST_DIR,
|
|
335
|
-
});
|
|
336
|
-
const out = result.stdout.toString();
|
|
337
|
-
expect(out).toContain("task-1 - Assigned Task");
|
|
338
|
-
expect(out).not.toContain("task-2 - Unassigned Task");
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
describe("task view command", () => {
|
|
343
|
-
beforeEach(async () => {
|
|
344
|
-
// Set up a git repository and initialize backlog
|
|
345
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
346
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
347
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
348
|
-
|
|
349
|
-
const core = new Core(TEST_DIR);
|
|
350
|
-
await core.initializeProject("View Test Project");
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it("should display task details with markdown formatting", async () => {
|
|
354
|
-
const core = new Core(TEST_DIR);
|
|
355
|
-
|
|
356
|
-
// Create a test task
|
|
357
|
-
const testTask = {
|
|
358
|
-
id: "task-1",
|
|
359
|
-
title: "Test View Task",
|
|
360
|
-
status: "To Do",
|
|
361
|
-
assignee: ["testuser"],
|
|
362
|
-
createdDate: "2025-06-08",
|
|
363
|
-
labels: ["test", "cli"],
|
|
364
|
-
dependencies: [],
|
|
365
|
-
description: "This is a test task for view command",
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
await core.createTask(testTask, false);
|
|
369
|
-
|
|
370
|
-
// Load the task back
|
|
371
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
372
|
-
expect(loadedTask).not.toBeNull();
|
|
373
|
-
expect(loadedTask?.id).toBe("task-1");
|
|
374
|
-
expect(loadedTask?.title).toBe("Test View Task");
|
|
375
|
-
expect(loadedTask?.status).toBe("To Do");
|
|
376
|
-
expect(loadedTask?.assignee).toEqual(["testuser"]);
|
|
377
|
-
expect(loadedTask?.labels).toEqual(["test", "cli"]);
|
|
378
|
-
expect(loadedTask?.description).toBe("## Description\n\nThis is a test task for view command");
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it("should handle task IDs with and without 'task-' prefix", async () => {
|
|
382
|
-
const core = new Core(TEST_DIR);
|
|
383
|
-
|
|
384
|
-
// Create a test task
|
|
385
|
-
await core.createTask(
|
|
386
|
-
{
|
|
387
|
-
id: "task-5",
|
|
388
|
-
title: "Prefix Test Task",
|
|
389
|
-
status: "To Do",
|
|
390
|
-
assignee: [],
|
|
391
|
-
createdDate: "2025-06-08",
|
|
392
|
-
labels: [],
|
|
393
|
-
dependencies: [],
|
|
394
|
-
description: "Testing task ID normalization",
|
|
395
|
-
},
|
|
396
|
-
false,
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
// Test loading with full task-5 ID
|
|
400
|
-
const taskWithPrefix = await core.filesystem.loadTask("task-5");
|
|
401
|
-
expect(taskWithPrefix?.id).toBe("task-5");
|
|
402
|
-
|
|
403
|
-
// Test loading with just numeric ID (5)
|
|
404
|
-
const taskWithoutPrefix = await core.filesystem.loadTask("5");
|
|
405
|
-
// The filesystem loadTask should handle normalization
|
|
406
|
-
expect(taskWithoutPrefix?.id).toBe("task-5");
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it("should return null for non-existent tasks", async () => {
|
|
410
|
-
const core = new Core(TEST_DIR);
|
|
411
|
-
|
|
412
|
-
const nonExistentTask = await core.filesystem.loadTask("task-999");
|
|
413
|
-
expect(nonExistentTask).toBeNull();
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it("should not modify task files (read-only operation)", async () => {
|
|
417
|
-
const core = new Core(TEST_DIR);
|
|
418
|
-
|
|
419
|
-
// Create a test task
|
|
420
|
-
const originalTask = {
|
|
421
|
-
id: "task-1",
|
|
422
|
-
title: "Read Only Test",
|
|
423
|
-
status: "To Do",
|
|
424
|
-
assignee: [],
|
|
425
|
-
createdDate: "2025-06-08",
|
|
426
|
-
labels: ["readonly"],
|
|
427
|
-
dependencies: [],
|
|
428
|
-
description: "Original description",
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
await core.createTask(originalTask, false);
|
|
432
|
-
|
|
433
|
-
// Load the task (simulating view operation)
|
|
434
|
-
const viewedTask = await core.filesystem.loadTask("task-1");
|
|
435
|
-
|
|
436
|
-
// Load again to verify nothing changed
|
|
437
|
-
const secondView = await core.filesystem.loadTask("task-1");
|
|
438
|
-
|
|
439
|
-
expect(viewedTask).toEqual(secondView);
|
|
440
|
-
expect(viewedTask?.title).toBe("Read Only Test");
|
|
441
|
-
expect(viewedTask?.description).toBe("## Description\n\nOriginal description");
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
describe("task edit command", () => {
|
|
446
|
-
beforeEach(async () => {
|
|
447
|
-
// Set up a git repository and initialize backlog
|
|
448
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
449
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
450
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
451
|
-
|
|
452
|
-
const core = new Core(TEST_DIR);
|
|
453
|
-
await core.initializeProject("Edit Test Project");
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it("should update task title, description, and status", async () => {
|
|
457
|
-
const core = new Core(TEST_DIR);
|
|
458
|
-
|
|
459
|
-
// Create a test task
|
|
460
|
-
await core.createTask(
|
|
461
|
-
{
|
|
462
|
-
id: "task-1",
|
|
463
|
-
title: "Original Title",
|
|
464
|
-
status: "To Do",
|
|
465
|
-
assignee: [],
|
|
466
|
-
createdDate: "2025-06-08",
|
|
467
|
-
labels: [],
|
|
468
|
-
dependencies: [],
|
|
469
|
-
description: "Original description",
|
|
470
|
-
},
|
|
471
|
-
false,
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
// Load and edit the task
|
|
475
|
-
const task = await core.filesystem.loadTask("task-1");
|
|
476
|
-
expect(task).not.toBeNull();
|
|
477
|
-
|
|
478
|
-
if (task) {
|
|
479
|
-
task.title = "Updated Title";
|
|
480
|
-
task.description = "Updated description";
|
|
481
|
-
task.status = "In Progress";
|
|
482
|
-
task.updatedDate = "2025-06-08";
|
|
483
|
-
|
|
484
|
-
await core.updateTask(task, false);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Verify changes were persisted
|
|
488
|
-
const updatedTask = await core.filesystem.loadTask("task-1");
|
|
489
|
-
expect(updatedTask?.title).toBe("Updated Title");
|
|
490
|
-
expect(updatedTask?.description).toBe("## Description\n\nUpdated description");
|
|
491
|
-
expect(updatedTask?.status).toBe("In Progress");
|
|
492
|
-
expect(updatedTask?.updatedDate).toBe("2025-06-08");
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
it("should update assignee", async () => {
|
|
496
|
-
const core = new Core(TEST_DIR);
|
|
497
|
-
|
|
498
|
-
// Create a test task
|
|
499
|
-
await core.createTask(
|
|
500
|
-
{
|
|
501
|
-
id: "task-2",
|
|
502
|
-
title: "Assignee Test",
|
|
503
|
-
status: "To Do",
|
|
504
|
-
assignee: [],
|
|
505
|
-
createdDate: "2025-06-08",
|
|
506
|
-
labels: [],
|
|
507
|
-
dependencies: [],
|
|
508
|
-
description: "Testing assignee updates",
|
|
509
|
-
},
|
|
510
|
-
false,
|
|
511
|
-
);
|
|
512
|
-
|
|
513
|
-
// Update assignee
|
|
514
|
-
const task = await core.filesystem.loadTask("task-2");
|
|
515
|
-
if (task) {
|
|
516
|
-
task.assignee = ["newuser@example.com"];
|
|
517
|
-
task.updatedDate = "2025-06-08";
|
|
518
|
-
await core.updateTask(task, false);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Verify assignee was updated
|
|
522
|
-
const updatedTask = await core.filesystem.loadTask("task-2");
|
|
523
|
-
expect(updatedTask?.assignee).toEqual(["newuser@example.com"]);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
it("should replace all labels with new labels", async () => {
|
|
527
|
-
const core = new Core(TEST_DIR);
|
|
528
|
-
|
|
529
|
-
// Create a test task with existing labels
|
|
530
|
-
await core.createTask(
|
|
531
|
-
{
|
|
532
|
-
id: "task-3",
|
|
533
|
-
title: "Label Replace Test",
|
|
534
|
-
status: "To Do",
|
|
535
|
-
assignee: [],
|
|
536
|
-
createdDate: "2025-06-08",
|
|
537
|
-
labels: ["old1", "old2"],
|
|
538
|
-
dependencies: [],
|
|
539
|
-
description: "Testing label replacement",
|
|
540
|
-
},
|
|
541
|
-
false,
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
// Replace all labels
|
|
545
|
-
const task = await core.filesystem.loadTask("task-3");
|
|
546
|
-
if (task) {
|
|
547
|
-
task.labels = ["new1", "new2", "new3"];
|
|
548
|
-
task.updatedDate = "2025-06-08";
|
|
549
|
-
await core.updateTask(task, false);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// Verify labels were replaced
|
|
553
|
-
const updatedTask = await core.filesystem.loadTask("task-3");
|
|
554
|
-
expect(updatedTask?.labels).toEqual(["new1", "new2", "new3"]);
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it("should add labels without replacing existing ones", async () => {
|
|
558
|
-
const core = new Core(TEST_DIR);
|
|
559
|
-
|
|
560
|
-
// Create a test task with existing labels
|
|
561
|
-
await core.createTask(
|
|
562
|
-
{
|
|
563
|
-
id: "task-4",
|
|
564
|
-
title: "Label Add Test",
|
|
565
|
-
status: "To Do",
|
|
566
|
-
assignee: [],
|
|
567
|
-
createdDate: "2025-06-08",
|
|
568
|
-
labels: ["existing"],
|
|
569
|
-
dependencies: [],
|
|
570
|
-
description: "Testing label addition",
|
|
571
|
-
},
|
|
572
|
-
false,
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
// Add new labels
|
|
576
|
-
const task = await core.filesystem.loadTask("task-4");
|
|
577
|
-
if (task) {
|
|
578
|
-
const newLabels = [...task.labels];
|
|
579
|
-
const labelsToAdd = ["added1", "added2"];
|
|
580
|
-
for (const label of labelsToAdd) {
|
|
581
|
-
if (!newLabels.includes(label)) {
|
|
582
|
-
newLabels.push(label);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
task.labels = newLabels;
|
|
586
|
-
task.updatedDate = "2025-06-08";
|
|
587
|
-
await core.updateTask(task, false);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Verify labels were added
|
|
591
|
-
const updatedTask = await core.filesystem.loadTask("task-4");
|
|
592
|
-
expect(updatedTask?.labels).toEqual(["existing", "added1", "added2"]);
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
it("should remove specific labels", async () => {
|
|
596
|
-
const core = new Core(TEST_DIR);
|
|
597
|
-
|
|
598
|
-
// Create a test task with multiple labels
|
|
599
|
-
await core.createTask(
|
|
600
|
-
{
|
|
601
|
-
id: "task-5",
|
|
602
|
-
title: "Label Remove Test",
|
|
603
|
-
status: "To Do",
|
|
604
|
-
assignee: [],
|
|
605
|
-
createdDate: "2025-06-08",
|
|
606
|
-
labels: ["keep1", "remove", "keep2"],
|
|
607
|
-
dependencies: [],
|
|
608
|
-
description: "Testing label removal",
|
|
609
|
-
},
|
|
610
|
-
false,
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
// Remove specific label
|
|
614
|
-
const task = await core.filesystem.loadTask("task-5");
|
|
615
|
-
if (task) {
|
|
616
|
-
const newLabels = task.labels.filter((label) => label !== "remove");
|
|
617
|
-
task.labels = newLabels;
|
|
618
|
-
task.updatedDate = "2025-06-08";
|
|
619
|
-
await core.updateTask(task, false);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Verify label was removed
|
|
623
|
-
const updatedTask = await core.filesystem.loadTask("task-5");
|
|
624
|
-
expect(updatedTask?.labels).toEqual(["keep1", "keep2"]);
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
it("should handle non-existent task gracefully", async () => {
|
|
628
|
-
const core = new Core(TEST_DIR);
|
|
629
|
-
|
|
630
|
-
const nonExistentTask = await core.filesystem.loadTask("task-999");
|
|
631
|
-
expect(nonExistentTask).toBeNull();
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
it("should set updated_date field when editing", async () => {
|
|
635
|
-
const core = new Core(TEST_DIR);
|
|
636
|
-
|
|
637
|
-
// Create a test task
|
|
638
|
-
await core.createTask(
|
|
639
|
-
{
|
|
640
|
-
id: "task-6",
|
|
641
|
-
title: "Updated Date Test",
|
|
642
|
-
status: "To Do",
|
|
643
|
-
assignee: [],
|
|
644
|
-
createdDate: "2025-06-07",
|
|
645
|
-
labels: [],
|
|
646
|
-
dependencies: [],
|
|
647
|
-
description: "Testing updated date",
|
|
648
|
-
},
|
|
649
|
-
false,
|
|
650
|
-
);
|
|
651
|
-
|
|
652
|
-
// Edit the task
|
|
653
|
-
const task = await core.filesystem.loadTask("task-6");
|
|
654
|
-
if (task) {
|
|
655
|
-
task.title = "Updated Title";
|
|
656
|
-
task.updatedDate = "2025-06-08";
|
|
657
|
-
await core.updateTask(task, false);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Verify updated_date was set
|
|
661
|
-
const updatedTask = await core.filesystem.loadTask("task-6");
|
|
662
|
-
expect(updatedTask?.updatedDate).toBe("2025-06-08");
|
|
663
|
-
expect(updatedTask?.createdDate).toBe("2025-06-07"); // Should remain unchanged
|
|
664
|
-
});
|
|
665
|
-
|
|
666
|
-
it("should commit changes automatically", async () => {
|
|
667
|
-
const core = new Core(TEST_DIR);
|
|
668
|
-
|
|
669
|
-
// Create a test task
|
|
670
|
-
await core.createTask(
|
|
671
|
-
{
|
|
672
|
-
id: "task-7",
|
|
673
|
-
title: "Commit Test",
|
|
674
|
-
status: "To Do",
|
|
675
|
-
assignee: [],
|
|
676
|
-
createdDate: "2025-06-08",
|
|
677
|
-
labels: [],
|
|
678
|
-
dependencies: [],
|
|
679
|
-
description: "Testing auto-commit",
|
|
680
|
-
},
|
|
681
|
-
false,
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
// Edit the task with auto-commit enabled
|
|
685
|
-
const task = await core.filesystem.loadTask("task-7");
|
|
686
|
-
if (task) {
|
|
687
|
-
task.title = "Updated for Commit";
|
|
688
|
-
task.updatedDate = "2025-06-08";
|
|
689
|
-
await core.updateTask(task, true); // autoCommit = true
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Verify the task was updated (this confirms the update functionality works)
|
|
693
|
-
const updatedTask = await core.filesystem.loadTask("task-7");
|
|
694
|
-
expect(updatedTask?.title).toBe("Updated for Commit");
|
|
695
|
-
|
|
696
|
-
// For now, just verify that updateTask with autoCommit=true doesn't throw
|
|
697
|
-
// The actual git commit functionality is tested at the Core level
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
it("should preserve YAML frontmatter formatting", async () => {
|
|
701
|
-
const core = new Core(TEST_DIR);
|
|
702
|
-
|
|
703
|
-
// Create a test task
|
|
704
|
-
await core.createTask(
|
|
705
|
-
{
|
|
706
|
-
id: "task-8",
|
|
707
|
-
title: "YAML Test",
|
|
708
|
-
status: "To Do",
|
|
709
|
-
assignee: ["testuser"],
|
|
710
|
-
createdDate: "2025-06-08",
|
|
711
|
-
labels: ["yaml", "test"],
|
|
712
|
-
dependencies: ["task-1"],
|
|
713
|
-
description: "Testing YAML preservation",
|
|
714
|
-
},
|
|
715
|
-
false,
|
|
716
|
-
);
|
|
717
|
-
|
|
718
|
-
// Edit the task
|
|
719
|
-
const task = await core.filesystem.loadTask("task-8");
|
|
720
|
-
if (task) {
|
|
721
|
-
task.title = "Updated YAML Test";
|
|
722
|
-
task.status = "In Progress";
|
|
723
|
-
task.updatedDate = "2025-06-08";
|
|
724
|
-
await core.updateTask(task, false);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Verify all frontmatter fields are preserved
|
|
728
|
-
const updatedTask = await core.filesystem.loadTask("task-8");
|
|
729
|
-
expect(updatedTask?.id).toBe("task-8");
|
|
730
|
-
expect(updatedTask?.title).toBe("Updated YAML Test");
|
|
731
|
-
expect(updatedTask?.status).toBe("In Progress");
|
|
732
|
-
expect(updatedTask?.assignee).toEqual(["testuser"]);
|
|
733
|
-
expect(updatedTask?.createdDate).toBe("2025-06-08");
|
|
734
|
-
expect(updatedTask?.updatedDate).toBe("2025-06-08");
|
|
735
|
-
expect(updatedTask?.labels).toEqual(["yaml", "test"]);
|
|
736
|
-
expect(updatedTask?.dependencies).toEqual(["task-1"]);
|
|
737
|
-
expect(updatedTask?.description).toBe("## Description\n\nTesting YAML preservation");
|
|
738
|
-
});
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
describe("task archive and state transition commands", () => {
|
|
742
|
-
beforeEach(async () => {
|
|
743
|
-
// Set up a git repository and initialize backlog
|
|
744
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
745
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
746
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
747
|
-
|
|
748
|
-
const core = new Core(TEST_DIR);
|
|
749
|
-
await core.initializeProject("Archive Test Project");
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
it("should archive a task", async () => {
|
|
753
|
-
const core = new Core(TEST_DIR);
|
|
754
|
-
|
|
755
|
-
// Create a test task
|
|
756
|
-
await core.createTask(
|
|
757
|
-
{
|
|
758
|
-
id: "task-1",
|
|
759
|
-
title: "Archive Test Task",
|
|
760
|
-
status: "Done",
|
|
761
|
-
assignee: [],
|
|
762
|
-
createdDate: "2025-06-08",
|
|
763
|
-
labels: ["completed"],
|
|
764
|
-
dependencies: [],
|
|
765
|
-
description: "Task ready for archiving",
|
|
766
|
-
},
|
|
767
|
-
false,
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
// Archive the task
|
|
771
|
-
const success = await core.archiveTask("task-1", false);
|
|
772
|
-
expect(success).toBe(true);
|
|
773
|
-
|
|
774
|
-
// Verify task is no longer in tasks directory
|
|
775
|
-
const task = await core.filesystem.loadTask("task-1");
|
|
776
|
-
expect(task).toBeNull();
|
|
777
|
-
|
|
778
|
-
// Verify task exists in archive
|
|
779
|
-
const { readdir } = await import("node:fs/promises");
|
|
780
|
-
const archiveFiles = await readdir(join(TEST_DIR, ".backlog", "archive", "tasks"));
|
|
781
|
-
expect(archiveFiles.some((f) => f.startsWith("task-1"))).toBe(true);
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
it("should handle archiving non-existent task", async () => {
|
|
785
|
-
const core = new Core(TEST_DIR);
|
|
786
|
-
|
|
787
|
-
const success = await core.archiveTask("task-999", false);
|
|
788
|
-
expect(success).toBe(false);
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
it("should demote task to drafts", async () => {
|
|
792
|
-
const core = new Core(TEST_DIR);
|
|
793
|
-
|
|
794
|
-
// Create a test task
|
|
795
|
-
await core.createTask(
|
|
796
|
-
{
|
|
797
|
-
id: "task-2",
|
|
798
|
-
title: "Demote Test Task",
|
|
799
|
-
status: "To Do",
|
|
800
|
-
assignee: [],
|
|
801
|
-
createdDate: "2025-06-08",
|
|
802
|
-
labels: ["needs-revision"],
|
|
803
|
-
dependencies: [],
|
|
804
|
-
description: "Task that needs to go back to drafts",
|
|
805
|
-
},
|
|
806
|
-
false,
|
|
807
|
-
);
|
|
808
|
-
|
|
809
|
-
// Demote the task
|
|
810
|
-
const success = await core.demoteTask("task-2", false);
|
|
811
|
-
expect(success).toBe(true);
|
|
812
|
-
|
|
813
|
-
// Verify task is no longer in tasks directory
|
|
814
|
-
const task = await core.filesystem.loadTask("task-2");
|
|
815
|
-
expect(task).toBeNull();
|
|
816
|
-
|
|
817
|
-
// Verify task now exists as a draft
|
|
818
|
-
const draft = await core.filesystem.loadDraft("task-2");
|
|
819
|
-
expect(draft?.id).toBe("task-2");
|
|
820
|
-
expect(draft?.title).toBe("Demote Test Task");
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
it("should promote draft to tasks", async () => {
|
|
824
|
-
const core = new Core(TEST_DIR);
|
|
825
|
-
|
|
826
|
-
// Create a test draft
|
|
827
|
-
await core.createDraft(
|
|
828
|
-
{
|
|
829
|
-
id: "task-3",
|
|
830
|
-
title: "Promote Test Draft",
|
|
831
|
-
status: "Draft",
|
|
832
|
-
assignee: [],
|
|
833
|
-
createdDate: "2025-06-08",
|
|
834
|
-
labels: ["ready"],
|
|
835
|
-
dependencies: [],
|
|
836
|
-
description: "Draft ready for promotion",
|
|
837
|
-
},
|
|
838
|
-
false,
|
|
839
|
-
);
|
|
840
|
-
|
|
841
|
-
// Promote the draft
|
|
842
|
-
const success = await core.promoteDraft("task-3", false);
|
|
843
|
-
expect(success).toBe(true);
|
|
844
|
-
|
|
845
|
-
// Verify draft is no longer in drafts directory
|
|
846
|
-
const draft = await core.filesystem.loadDraft("task-3");
|
|
847
|
-
expect(draft).toBeNull();
|
|
848
|
-
|
|
849
|
-
// Verify draft now exists as a task
|
|
850
|
-
const task = await core.filesystem.loadTask("task-3");
|
|
851
|
-
expect(task?.id).toBe("task-3");
|
|
852
|
-
expect(task?.title).toBe("Promote Test Draft");
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
it("should archive a draft", async () => {
|
|
856
|
-
const core = new Core(TEST_DIR);
|
|
857
|
-
|
|
858
|
-
// Create a test draft
|
|
859
|
-
await core.createDraft(
|
|
860
|
-
{
|
|
861
|
-
id: "task-4",
|
|
862
|
-
title: "Archive Test Draft",
|
|
863
|
-
status: "Draft",
|
|
864
|
-
assignee: [],
|
|
865
|
-
createdDate: "2025-06-08",
|
|
866
|
-
labels: ["cancelled"],
|
|
867
|
-
dependencies: [],
|
|
868
|
-
description: "Draft that should be archived",
|
|
869
|
-
},
|
|
870
|
-
false,
|
|
871
|
-
);
|
|
872
|
-
|
|
873
|
-
// Archive the draft
|
|
874
|
-
const success = await core.archiveDraft("task-4", false);
|
|
875
|
-
expect(success).toBe(true);
|
|
876
|
-
|
|
877
|
-
// Verify draft is no longer in drafts directory
|
|
878
|
-
const draft = await core.filesystem.loadDraft("task-4");
|
|
879
|
-
expect(draft).toBeNull();
|
|
880
|
-
|
|
881
|
-
// Verify draft exists in archive
|
|
882
|
-
const { readdir } = await import("node:fs/promises");
|
|
883
|
-
const archiveFiles = await readdir(join(TEST_DIR, ".backlog", "archive", "drafts"));
|
|
884
|
-
expect(archiveFiles.some((f) => f.startsWith("task-4"))).toBe(true);
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
it("should handle promoting non-existent draft", async () => {
|
|
888
|
-
const core = new Core(TEST_DIR);
|
|
889
|
-
|
|
890
|
-
const success = await core.promoteDraft("task-999", false);
|
|
891
|
-
expect(success).toBe(false);
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
it("should handle demoting non-existent task", async () => {
|
|
895
|
-
const core = new Core(TEST_DIR);
|
|
896
|
-
|
|
897
|
-
const success = await core.demoteTask("task-999", false);
|
|
898
|
-
expect(success).toBe(false);
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
it("should handle archiving non-existent draft", async () => {
|
|
902
|
-
const core = new Core(TEST_DIR);
|
|
903
|
-
|
|
904
|
-
const success = await core.archiveDraft("task-999", false);
|
|
905
|
-
expect(success).toBe(false);
|
|
906
|
-
});
|
|
907
|
-
|
|
908
|
-
it("should commit archive operations automatically", async () => {
|
|
909
|
-
const core = new Core(TEST_DIR);
|
|
910
|
-
|
|
911
|
-
// Create and archive a task with auto-commit
|
|
912
|
-
await core.createTask(
|
|
913
|
-
{
|
|
914
|
-
id: "task-5",
|
|
915
|
-
title: "Commit Archive Test",
|
|
916
|
-
status: "Done",
|
|
917
|
-
assignee: [],
|
|
918
|
-
createdDate: "2025-06-08",
|
|
919
|
-
labels: [],
|
|
920
|
-
dependencies: [],
|
|
921
|
-
description: "Testing auto-commit on archive",
|
|
922
|
-
},
|
|
923
|
-
false,
|
|
924
|
-
);
|
|
925
|
-
|
|
926
|
-
const success = await core.archiveTask("task-5", true); // autoCommit = true
|
|
927
|
-
expect(success).toBe(true);
|
|
928
|
-
|
|
929
|
-
// Verify operation completed successfully
|
|
930
|
-
const task = await core.filesystem.loadTask("task-5");
|
|
931
|
-
expect(task).toBeNull();
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
it("should preserve task content through state transitions", async () => {
|
|
935
|
-
const core = new Core(TEST_DIR);
|
|
936
|
-
|
|
937
|
-
// Create a task with rich content
|
|
938
|
-
const originalTask = {
|
|
939
|
-
id: "task-6",
|
|
940
|
-
title: "Content Preservation Test",
|
|
941
|
-
status: "In Progress",
|
|
942
|
-
assignee: ["testuser"],
|
|
943
|
-
createdDate: "2025-06-08",
|
|
944
|
-
labels: ["important", "preservation-test"],
|
|
945
|
-
dependencies: ["task-1", "task-2"],
|
|
946
|
-
description: "This task has rich metadata that should be preserved through transitions",
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
await core.createTask(originalTask, false);
|
|
950
|
-
|
|
951
|
-
// Demote to draft
|
|
952
|
-
await core.demoteTask("task-6", false);
|
|
953
|
-
const asDraft = await core.filesystem.loadDraft("task-6");
|
|
954
|
-
|
|
955
|
-
expect(asDraft?.title).toBe(originalTask.title);
|
|
956
|
-
expect(asDraft?.assignee).toEqual(originalTask.assignee);
|
|
957
|
-
expect(asDraft?.labels).toEqual(originalTask.labels);
|
|
958
|
-
expect(asDraft?.dependencies).toEqual(originalTask.dependencies);
|
|
959
|
-
expect(asDraft?.description).toBe(originalTask.description);
|
|
960
|
-
|
|
961
|
-
// Promote back to task
|
|
962
|
-
await core.promoteDraft("task-6", false);
|
|
963
|
-
const backToTask = await core.filesystem.loadTask("task-6");
|
|
964
|
-
|
|
965
|
-
expect(backToTask?.title).toBe(originalTask.title);
|
|
966
|
-
expect(backToTask?.assignee).toEqual(originalTask.assignee);
|
|
967
|
-
expect(backToTask?.labels).toEqual(originalTask.labels);
|
|
968
|
-
expect(backToTask?.dependencies).toEqual(originalTask.dependencies);
|
|
969
|
-
expect(backToTask?.description).toBe(originalTask.description);
|
|
970
|
-
});
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
describe("doc and decision commands", () => {
|
|
974
|
-
beforeEach(async () => {
|
|
975
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
976
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
977
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
978
|
-
|
|
979
|
-
const core = new Core(TEST_DIR);
|
|
980
|
-
await core.initializeProject("Doc Test Project");
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
it("should create and list documents", async () => {
|
|
984
|
-
const core = new Core(TEST_DIR);
|
|
985
|
-
const doc: DocType = {
|
|
986
|
-
id: "doc-1",
|
|
987
|
-
title: "Guide",
|
|
988
|
-
type: "guide",
|
|
989
|
-
createdDate: "2025-06-08",
|
|
990
|
-
content: "Content",
|
|
991
|
-
};
|
|
992
|
-
await core.createDocument(doc, false);
|
|
993
|
-
|
|
994
|
-
const docs = await core.filesystem.listDocuments();
|
|
995
|
-
expect(docs).toHaveLength(1);
|
|
996
|
-
expect(docs[0].title).toBe("Guide");
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
it("should create and list decisions", async () => {
|
|
1000
|
-
const core = new Core(TEST_DIR);
|
|
1001
|
-
const decision: DecisionLog = {
|
|
1002
|
-
id: "decision-1",
|
|
1003
|
-
title: "Choose Stack",
|
|
1004
|
-
date: "2025-06-08",
|
|
1005
|
-
status: "accepted",
|
|
1006
|
-
context: "context",
|
|
1007
|
-
decision: "decide",
|
|
1008
|
-
consequences: "conseq",
|
|
1009
|
-
};
|
|
1010
|
-
await core.createDecisionLog(decision, false);
|
|
1011
|
-
const decisions = await core.filesystem.listDecisionLogs();
|
|
1012
|
-
expect(decisions).toHaveLength(1);
|
|
1013
|
-
expect(decisions[0].title).toBe("Choose Stack");
|
|
1014
|
-
});
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
describe("board view command", () => {
|
|
1018
|
-
beforeEach(async () => {
|
|
1019
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
1020
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
1021
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
1022
|
-
|
|
1023
|
-
const core = new Core(TEST_DIR);
|
|
1024
|
-
await core.initializeProject("Board Test Project");
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
it("should display kanban board with tasks grouped by status", async () => {
|
|
1028
|
-
const core = new Core(TEST_DIR);
|
|
1029
|
-
|
|
1030
|
-
// Create test tasks with different statuses
|
|
1031
|
-
await core.createTask(
|
|
1032
|
-
{
|
|
1033
|
-
id: "task-1",
|
|
1034
|
-
title: "Todo Task",
|
|
1035
|
-
status: "To Do",
|
|
1036
|
-
assignee: [],
|
|
1037
|
-
createdDate: "2025-06-08",
|
|
1038
|
-
labels: [],
|
|
1039
|
-
dependencies: [],
|
|
1040
|
-
description: "A task in todo",
|
|
1041
|
-
},
|
|
1042
|
-
false,
|
|
1043
|
-
);
|
|
1044
|
-
|
|
1045
|
-
await core.createTask(
|
|
1046
|
-
{
|
|
1047
|
-
id: "task-2",
|
|
1048
|
-
title: "Progress Task",
|
|
1049
|
-
status: "In Progress",
|
|
1050
|
-
assignee: [],
|
|
1051
|
-
createdDate: "2025-06-08",
|
|
1052
|
-
labels: [],
|
|
1053
|
-
dependencies: [],
|
|
1054
|
-
description: "A task in progress",
|
|
1055
|
-
},
|
|
1056
|
-
false,
|
|
1057
|
-
);
|
|
1058
|
-
|
|
1059
|
-
await core.createTask(
|
|
1060
|
-
{
|
|
1061
|
-
id: "task-3",
|
|
1062
|
-
title: "Done Task",
|
|
1063
|
-
status: "Done",
|
|
1064
|
-
assignee: [],
|
|
1065
|
-
createdDate: "2025-06-08",
|
|
1066
|
-
labels: [],
|
|
1067
|
-
dependencies: [],
|
|
1068
|
-
description: "A completed task",
|
|
1069
|
-
},
|
|
1070
|
-
false,
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1073
|
-
const tasks = await core.filesystem.listTasks();
|
|
1074
|
-
expect(tasks).toHaveLength(3);
|
|
1075
|
-
|
|
1076
|
-
const config = await core.filesystem.loadConfig();
|
|
1077
|
-
const statuses = config?.statuses || [];
|
|
1078
|
-
expect(statuses).toEqual(["To Do", "In Progress", "Done"]);
|
|
1079
|
-
|
|
1080
|
-
// Test the kanban board generation
|
|
1081
|
-
const { generateKanbanBoard } = await import("../board.ts");
|
|
1082
|
-
const board = generateKanbanBoard(tasks, statuses);
|
|
1083
|
-
|
|
1084
|
-
// Verify board contains all statuses and tasks (now on separate lines)
|
|
1085
|
-
expect(board).toContain("To Do");
|
|
1086
|
-
expect(board).toContain("In Progress");
|
|
1087
|
-
expect(board).toContain("Done");
|
|
1088
|
-
expect(board).toContain("task-1");
|
|
1089
|
-
expect(board).toContain("Todo Task");
|
|
1090
|
-
expect(board).toContain("task-2");
|
|
1091
|
-
expect(board).toContain("Progress Task");
|
|
1092
|
-
expect(board).toContain("task-3");
|
|
1093
|
-
expect(board).toContain("Done Task");
|
|
1094
|
-
|
|
1095
|
-
// Verify board structure
|
|
1096
|
-
const lines = board.split("\n");
|
|
1097
|
-
expect(lines[0]).toContain("To Do"); // Header should contain statuses with tasks
|
|
1098
|
-
expect(lines[0]).toContain("In Progress");
|
|
1099
|
-
expect(lines[0]).toContain("Done");
|
|
1100
|
-
expect(lines[1]).toContain("-"); // Separator line
|
|
1101
|
-
expect(lines.length).toBeGreaterThan(2); // Should have content rows
|
|
1102
|
-
});
|
|
1103
|
-
|
|
1104
|
-
it("should handle empty project with default statuses", async () => {
|
|
1105
|
-
const core = new Core(TEST_DIR);
|
|
1106
|
-
|
|
1107
|
-
const tasks = await core.filesystem.listTasks();
|
|
1108
|
-
expect(tasks).toHaveLength(0);
|
|
1109
|
-
|
|
1110
|
-
const config = await core.filesystem.loadConfig();
|
|
1111
|
-
const statuses = config?.statuses || [];
|
|
1112
|
-
|
|
1113
|
-
const { generateKanbanBoard } = await import("../board.ts");
|
|
1114
|
-
const board = generateKanbanBoard(tasks, statuses);
|
|
1115
|
-
|
|
1116
|
-
// Should return empty board when no tasks exist
|
|
1117
|
-
expect(board).toBe("");
|
|
1118
|
-
});
|
|
1119
|
-
|
|
1120
|
-
it("should support vertical layout option", async () => {
|
|
1121
|
-
const core = new Core(TEST_DIR);
|
|
1122
|
-
|
|
1123
|
-
await core.createTask(
|
|
1124
|
-
{
|
|
1125
|
-
id: "task-1",
|
|
1126
|
-
title: "Todo Task",
|
|
1127
|
-
status: "To Do",
|
|
1128
|
-
assignee: [],
|
|
1129
|
-
createdDate: "2025-06-08",
|
|
1130
|
-
labels: [],
|
|
1131
|
-
dependencies: [],
|
|
1132
|
-
description: "A task in todo",
|
|
1133
|
-
},
|
|
1134
|
-
false,
|
|
1135
|
-
);
|
|
1136
|
-
|
|
1137
|
-
const tasks = await core.filesystem.listTasks();
|
|
1138
|
-
const config = await core.filesystem.loadConfig();
|
|
1139
|
-
const statuses = config?.statuses || [];
|
|
1140
|
-
|
|
1141
|
-
const { generateKanbanBoard } = await import("../board.ts");
|
|
1142
|
-
const board = generateKanbanBoard(tasks, statuses, "vertical");
|
|
1143
|
-
|
|
1144
|
-
const lines = board.split("\n");
|
|
1145
|
-
expect(lines[0]).toBe("To Do");
|
|
1146
|
-
expect(board).toContain("task-1");
|
|
1147
|
-
expect(board).toContain("Todo Task");
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
it("should support --vertical shortcut flag", async () => {
|
|
1151
|
-
const core = new Core(TEST_DIR);
|
|
1152
|
-
|
|
1153
|
-
await core.createTask(
|
|
1154
|
-
{
|
|
1155
|
-
id: "task-1",
|
|
1156
|
-
title: "Shortcut Task",
|
|
1157
|
-
status: "To Do",
|
|
1158
|
-
assignee: [],
|
|
1159
|
-
createdDate: "2025-06-09",
|
|
1160
|
-
labels: [],
|
|
1161
|
-
dependencies: [],
|
|
1162
|
-
description: "Testing vertical shortcut",
|
|
1163
|
-
},
|
|
1164
|
-
false,
|
|
1165
|
-
);
|
|
1166
|
-
|
|
1167
|
-
const tasks = await core.filesystem.listTasks();
|
|
1168
|
-
const config = await core.filesystem.loadConfig();
|
|
1169
|
-
const statuses = config?.statuses || [];
|
|
1170
|
-
|
|
1171
|
-
// Test that --vertical flag produces vertical layout
|
|
1172
|
-
const { generateKanbanBoard } = await import("../board.ts");
|
|
1173
|
-
const board = generateKanbanBoard(tasks, statuses, "vertical");
|
|
1174
|
-
|
|
1175
|
-
const lines = board.split("\n");
|
|
1176
|
-
expect(lines[0]).toBe("To Do");
|
|
1177
|
-
expect(board).toContain("task-1");
|
|
1178
|
-
expect(board).toContain("Shortcut Task");
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
it("should merge task status from remote branches", async () => {
|
|
1182
|
-
const core = new Core(TEST_DIR);
|
|
1183
|
-
|
|
1184
|
-
const task = {
|
|
1185
|
-
id: "task-1",
|
|
1186
|
-
title: "Remote Task",
|
|
1187
|
-
status: "To Do",
|
|
1188
|
-
assignee: [],
|
|
1189
|
-
createdDate: "2025-06-09",
|
|
1190
|
-
labels: [],
|
|
1191
|
-
dependencies: [],
|
|
1192
|
-
description: "from remote",
|
|
1193
|
-
} as Task;
|
|
1194
|
-
|
|
1195
|
-
await core.createTask(task, true);
|
|
1196
|
-
|
|
1197
|
-
// set up remote repository
|
|
1198
|
-
const remoteDir = join(TEST_DIR, "remote.git");
|
|
1199
|
-
await Bun.spawn(["git", "init", "--bare", remoteDir]).exited;
|
|
1200
|
-
await Bun.spawn(["git", "remote", "add", "origin", remoteDir], { cwd: TEST_DIR }).exited;
|
|
1201
|
-
await Bun.spawn(["git", "push", "-u", "origin", "master"], { cwd: TEST_DIR }).exited;
|
|
1202
|
-
|
|
1203
|
-
// create branch with updated status
|
|
1204
|
-
await Bun.spawn(["git", "checkout", "-b", "feature"], { cwd: TEST_DIR }).exited;
|
|
1205
|
-
await core.updateTask({ ...task, status: "Done" }, true);
|
|
1206
|
-
await Bun.spawn(["git", "push", "-u", "origin", "feature"], { cwd: TEST_DIR }).exited;
|
|
1207
|
-
|
|
1208
|
-
// switch back to master where status is still To Do
|
|
1209
|
-
await Bun.spawn(["git", "checkout", "master"], { cwd: TEST_DIR }).exited;
|
|
1210
|
-
|
|
1211
|
-
await core.gitOps.fetch();
|
|
1212
|
-
const branches = await core.gitOps.listRemoteBranches();
|
|
1213
|
-
const config = await core.filesystem.loadConfig();
|
|
1214
|
-
const statuses = config?.statuses || [];
|
|
1215
|
-
|
|
1216
|
-
const localTasks = await core.filesystem.listTasks();
|
|
1217
|
-
const tasksById = new Map(localTasks.map((t) => [t.id, t]));
|
|
1218
|
-
|
|
1219
|
-
for (const branch of branches) {
|
|
1220
|
-
const ref = `origin/${branch}`;
|
|
1221
|
-
const files = await core.gitOps.listFilesInTree(ref, ".backlog/tasks");
|
|
1222
|
-
for (const file of files) {
|
|
1223
|
-
const content = await core.gitOps.showFile(ref, file);
|
|
1224
|
-
const remoteTask = parseTask(content);
|
|
1225
|
-
const existing = tasksById.get(remoteTask.id);
|
|
1226
|
-
const currentIdx = existing ? statuses.indexOf(existing.status) : -1;
|
|
1227
|
-
const newIdx = statuses.indexOf(remoteTask.status);
|
|
1228
|
-
if (!existing || newIdx > currentIdx || currentIdx === -1 || newIdx === currentIdx) {
|
|
1229
|
-
tasksById.set(remoteTask.id, remoteTask);
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const final = tasksById.get("task-1");
|
|
1235
|
-
expect(final?.status).toBe("Done");
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
it("should default to view when no subcommand is provided", async () => {
|
|
1239
|
-
const core = new Core(TEST_DIR);
|
|
1240
|
-
|
|
1241
|
-
await core.createTask(
|
|
1242
|
-
{
|
|
1243
|
-
id: "task-99",
|
|
1244
|
-
title: "Default Cmd Task",
|
|
1245
|
-
status: "To Do",
|
|
1246
|
-
assignee: [],
|
|
1247
|
-
createdDate: "2025-06-10",
|
|
1248
|
-
labels: [],
|
|
1249
|
-
dependencies: [],
|
|
1250
|
-
description: "test",
|
|
1251
|
-
},
|
|
1252
|
-
false,
|
|
1253
|
-
);
|
|
1254
|
-
|
|
1255
|
-
const resultDefault = Bun.spawnSync(["bun", "src/cli.ts", "board"], { cwd: TEST_DIR });
|
|
1256
|
-
const resultView = Bun.spawnSync(["bun", "src/cli.ts", "board", "view"], { cwd: TEST_DIR });
|
|
1257
|
-
|
|
1258
|
-
expect(resultDefault.stdout.toString()).toBe(resultView.stdout.toString());
|
|
1259
|
-
});
|
|
1260
|
-
|
|
1261
|
-
it("should export kanban board to file", async () => {
|
|
1262
|
-
const core = new Core(TEST_DIR);
|
|
1263
|
-
|
|
1264
|
-
// Create test tasks
|
|
1265
|
-
await core.createTask(
|
|
1266
|
-
{
|
|
1267
|
-
id: "task-1",
|
|
1268
|
-
title: "Export Test Task",
|
|
1269
|
-
status: "To Do",
|
|
1270
|
-
assignee: [],
|
|
1271
|
-
createdDate: "2025-06-09",
|
|
1272
|
-
labels: [],
|
|
1273
|
-
dependencies: [],
|
|
1274
|
-
description: "Testing board export",
|
|
1275
|
-
},
|
|
1276
|
-
false,
|
|
1277
|
-
);
|
|
1278
|
-
|
|
1279
|
-
const { exportKanbanBoardToFile } = await import("../index.ts");
|
|
1280
|
-
const outputPath = join(TEST_DIR, "test-export.md");
|
|
1281
|
-
const tasks = await core.filesystem.listTasks();
|
|
1282
|
-
const config = await core.filesystem.loadConfig();
|
|
1283
|
-
const statuses = config?.statuses || [];
|
|
1284
|
-
|
|
1285
|
-
await exportKanbanBoardToFile(tasks, statuses, outputPath);
|
|
1286
|
-
|
|
1287
|
-
// Verify file was created and contains expected content
|
|
1288
|
-
const content = await Bun.file(outputPath).text();
|
|
1289
|
-
expect(content).toContain("To Do");
|
|
1290
|
-
expect(content).toContain("task-1");
|
|
1291
|
-
expect(content).toContain("Export Test Task");
|
|
1292
|
-
|
|
1293
|
-
// Test appending behavior
|
|
1294
|
-
await exportKanbanBoardToFile(tasks, statuses, outputPath);
|
|
1295
|
-
const appendedContent = await Bun.file(outputPath).text();
|
|
1296
|
-
const occurrences = appendedContent.split("task-1").length - 1;
|
|
1297
|
-
expect(occurrences).toBe(2); // Should appear twice after appending
|
|
1298
|
-
});
|
|
1299
|
-
});
|
|
1300
|
-
});
|