backlog.md 0.1.0 → 0.1.1
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
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
CODE_PATH_PATTERNS,
|
|
4
|
-
extractCodePaths,
|
|
5
|
-
isCodePath,
|
|
6
|
-
styleCodePath,
|
|
7
|
-
transformCodePaths,
|
|
8
|
-
transformCodePathsPlain,
|
|
9
|
-
} from "../ui/code-path.ts";
|
|
10
|
-
|
|
11
|
-
describe("Code path utilities", () => {
|
|
12
|
-
describe("CODE_PATH_PATTERNS", () => {
|
|
13
|
-
test("should match backticked file paths", () => {
|
|
14
|
-
const testCases = [
|
|
15
|
-
"`src/cli.ts`",
|
|
16
|
-
"`package.json`",
|
|
17
|
-
"`/Users/name/project/file.ts`",
|
|
18
|
-
"`./relative/path.js`",
|
|
19
|
-
"`../parent/file.md`",
|
|
20
|
-
"`C:\\Windows\\file.exe`",
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
for (const testCase of testCases) {
|
|
24
|
-
// Reset regex for each test case
|
|
25
|
-
CODE_PATH_PATTERNS.BACKTICKED_PATH.lastIndex = 0;
|
|
26
|
-
expect(CODE_PATH_PATTERNS.BACKTICKED_PATH.test(testCase)).toBe(true);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("should not match non-path backticks", () => {
|
|
31
|
-
const testCases = ["`just code`", "`function name`", "`variable`", "`123`"];
|
|
32
|
-
|
|
33
|
-
for (const testCase of testCases) {
|
|
34
|
-
// Reset regex lastIndex
|
|
35
|
-
CODE_PATH_PATTERNS.BACKTICKED_PATH.lastIndex = 0;
|
|
36
|
-
const match = testCase.match(CODE_PATH_PATTERNS.BACKTICKED_PATH);
|
|
37
|
-
if (match) {
|
|
38
|
-
const content = match[0].slice(1, -1);
|
|
39
|
-
expect(isCodePath(content)).toBe(false);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
describe("isCodePath", () => {
|
|
46
|
-
test("should detect file paths with extensions", () => {
|
|
47
|
-
expect(isCodePath("src/cli.ts")).toBe(true);
|
|
48
|
-
expect(isCodePath("package.json")).toBe(true);
|
|
49
|
-
expect(isCodePath("file.md")).toBe(true);
|
|
50
|
-
expect(isCodePath("/full/path/file.js")).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("should detect paths with separators", () => {
|
|
54
|
-
expect(isCodePath("src/utils")).toBe(true);
|
|
55
|
-
expect(isCodePath("folder/subfolder")).toBe(true);
|
|
56
|
-
expect(isCodePath("/absolute/path")).toBe(true);
|
|
57
|
-
expect(isCodePath("C:\\Windows\\path")).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("should not detect non-paths", () => {
|
|
61
|
-
expect(isCodePath("just text")).toBe(false);
|
|
62
|
-
expect(isCodePath("function")).toBe(false);
|
|
63
|
-
expect(isCodePath("variable")).toBe(false);
|
|
64
|
-
expect(isCodePath("123")).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe("extractCodePaths", () => {
|
|
69
|
-
test("should extract file paths from text", () => {
|
|
70
|
-
const text = "Check `src/cli.ts` and `package.json` for details.";
|
|
71
|
-
const result = extractCodePaths(text);
|
|
72
|
-
expect(result).toEqual(["src/cli.ts", "package.json"]);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("should ignore non-path backticks", () => {
|
|
76
|
-
const text = "Use `function` to call `src/cli.ts` method.";
|
|
77
|
-
const result = extractCodePaths(text);
|
|
78
|
-
expect(result).toEqual(["src/cli.ts"]);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("should handle empty or no matches", () => {
|
|
82
|
-
expect(extractCodePaths("No paths here")).toEqual([]);
|
|
83
|
-
expect(extractCodePaths("Only `variables` here")).toEqual([]);
|
|
84
|
-
expect(extractCodePaths("")).toEqual([]);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("should handle complex paths", () => {
|
|
88
|
-
const text = "Files: `/absolute/path/file.ts`, `./relative/file.js`, `../parent/file.md`";
|
|
89
|
-
const result = extractCodePaths(text);
|
|
90
|
-
expect(result).toEqual(["/absolute/path/file.ts", "./relative/file.js", "../parent/file.md"]);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("styleCodePath", () => {
|
|
95
|
-
test("should wrap path in gray styling tags", () => {
|
|
96
|
-
const result = styleCodePath("src/cli.ts");
|
|
97
|
-
expect(result).toBe("{gray-fg}`src/cli.ts`{/gray-fg}");
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("should handle paths with special characters", () => {
|
|
101
|
-
const result = styleCodePath("/path/with-dashes_and.underscores.ts");
|
|
102
|
-
expect(result).toBe("{gray-fg}`/path/with-dashes_and.underscores.ts`{/gray-fg}");
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe("transformCodePaths", () => {
|
|
107
|
-
test("should style isolated code paths", () => {
|
|
108
|
-
const text = "Check this file: `src/cli.ts`";
|
|
109
|
-
const result = transformCodePaths(text);
|
|
110
|
-
expect(result).toBe("Check this file:\n{gray-fg}`src/cli.ts`{/gray-fg}");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("should extract and separate multiple paths in prose", () => {
|
|
114
|
-
const text = "Modify `src/cli.ts` and `src/ui/board.ts` to implement the feature.";
|
|
115
|
-
const result = transformCodePaths(text);
|
|
116
|
-
expect(result).toBe(
|
|
117
|
-
"Modify and to implement the feature.\n{gray-fg}`src/cli.ts`{/gray-fg}\n{gray-fg}`src/ui/board.ts`{/gray-fg}",
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("should preserve line breaks", () => {
|
|
122
|
-
const text = "First line with `file1.ts`\nSecond line with `file2.js`";
|
|
123
|
-
const result = transformCodePaths(text);
|
|
124
|
-
expect(result).toContain("First line with\n{gray-fg}`file1.ts`{/gray-fg}");
|
|
125
|
-
expect(result).toContain("Second line with\n{gray-fg}`file2.js`{/gray-fg}");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("should handle text without code paths", () => {
|
|
129
|
-
const text = "This is just regular text with `variables` and `functions`.";
|
|
130
|
-
const result = transformCodePaths(text);
|
|
131
|
-
expect(result).toBe(text);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("should handle empty input", () => {
|
|
135
|
-
expect(transformCodePaths("")).toBe("");
|
|
136
|
-
// biome-ignore lint/suspicious/noExplicitAny: testing null/undefined inputs
|
|
137
|
-
expect(transformCodePaths(null as any)).toBe(null);
|
|
138
|
-
// biome-ignore lint/suspicious/noExplicitAny: testing null/undefined inputs
|
|
139
|
-
expect(transformCodePaths(undefined as any)).toBe(undefined);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("should handle only a path on a line", () => {
|
|
143
|
-
const text = "`src/cli.ts`";
|
|
144
|
-
const result = transformCodePaths(text);
|
|
145
|
-
expect(result).toBe("{gray-fg}`src/cli.ts`{/gray-fg}");
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
describe("transformCodePathsPlain", () => {
|
|
150
|
-
test("should preserve code paths in plain text", () => {
|
|
151
|
-
const text = "Check `src/cli.ts` and `package.json` files.";
|
|
152
|
-
const result = transformCodePathsPlain(text);
|
|
153
|
-
expect(result).toBe("Check `src/cli.ts` and `package.json` files.");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("should ignore non-path backticks", () => {
|
|
157
|
-
const text = "Use `function` to call `src/cli.ts` method.";
|
|
158
|
-
const result = transformCodePathsPlain(text);
|
|
159
|
-
expect(result).toBe("Use `function` to call `src/cli.ts` method.");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("should handle empty input", () => {
|
|
163
|
-
expect(transformCodePathsPlain("")).toBe("");
|
|
164
|
-
// biome-ignore lint/suspicious/noExplicitAny: testing null/undefined inputs
|
|
165
|
-
expect(transformCodePathsPlain(null as any)).toBe(null);
|
|
166
|
-
// biome-ignore lint/suspicious/noExplicitAny: testing null/undefined inputs
|
|
167
|
-
expect(transformCodePathsPlain(undefined as any)).toBe(undefined);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe("comprehensive path detection", () => {
|
|
172
|
-
test("should capture 100% of code paths in test fixture", () => {
|
|
173
|
-
const testFixture = `
|
|
174
|
-
Implementation details:
|
|
175
|
-
- Update \`src/cli.ts\` to add new command
|
|
176
|
-
- Modify \`src/ui/task-viewer.ts\` for display
|
|
177
|
-
- Check \`package.json\` for dependencies
|
|
178
|
-
- Test with \`/absolute/path/test.js\`
|
|
179
|
-
- Relative paths: \`./src/utils.ts\` and \`../config/settings.json\`
|
|
180
|
-
|
|
181
|
-
Also review the \`README.md\` file and \`biome.json\` configuration.
|
|
182
|
-
Windows paths like \`C:\\Users\\name\\file.txt\` should work too.
|
|
183
|
-
`.trim();
|
|
184
|
-
|
|
185
|
-
const extractedPaths = extractCodePaths(testFixture);
|
|
186
|
-
|
|
187
|
-
// Verify we captured all expected paths
|
|
188
|
-
const expectedPaths = [
|
|
189
|
-
"src/cli.ts",
|
|
190
|
-
"src/ui/task-viewer.ts",
|
|
191
|
-
"package.json",
|
|
192
|
-
"/absolute/path/test.js",
|
|
193
|
-
"./src/utils.ts",
|
|
194
|
-
"../config/settings.json",
|
|
195
|
-
"README.md",
|
|
196
|
-
"biome.json",
|
|
197
|
-
"C:\\Users\\name\\file.txt",
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
expect(extractedPaths).toEqual(expectedPaths);
|
|
201
|
-
expect(extractedPaths.length).toBe(9);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
});
|
package/src/test/core.test.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { rm } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { Core } from "../core/backlog.ts";
|
|
5
|
-
import type { Task } from "../types/index.ts";
|
|
6
|
-
|
|
7
|
-
const TEST_DIR = join(process.cwd(), "test-core");
|
|
8
|
-
|
|
9
|
-
describe("Core", () => {
|
|
10
|
-
let core: Core;
|
|
11
|
-
|
|
12
|
-
beforeEach(async () => {
|
|
13
|
-
core = new Core(TEST_DIR);
|
|
14
|
-
await core.filesystem.ensureBacklogStructure();
|
|
15
|
-
|
|
16
|
-
// Initialize git repository for testing
|
|
17
|
-
await Bun.spawn(["git", "init"], { cwd: TEST_DIR }).exited;
|
|
18
|
-
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: TEST_DIR }).exited;
|
|
19
|
-
await Bun.spawn(["git", "config", "user.email", "test@example.com"], { cwd: TEST_DIR }).exited;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
try {
|
|
24
|
-
await rm(TEST_DIR, { recursive: true, force: true });
|
|
25
|
-
} catch {
|
|
26
|
-
// Ignore cleanup errors
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("initialization", () => {
|
|
31
|
-
it("should have filesystem and git operations available", () => {
|
|
32
|
-
expect(core.filesystem).toBeDefined();
|
|
33
|
-
expect(core.gitOps).toBeDefined();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("should initialize project with default config", async () => {
|
|
37
|
-
await core.initializeProject("Test Project");
|
|
38
|
-
|
|
39
|
-
const config = await core.filesystem.loadConfig();
|
|
40
|
-
expect(config?.projectName).toBe("Test Project");
|
|
41
|
-
expect(config?.statuses).toEqual(["To Do", "In Progress", "Done"]);
|
|
42
|
-
expect(config?.defaultStatus).toBe("To Do");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("task operations", () => {
|
|
47
|
-
const sampleTask: Task = {
|
|
48
|
-
id: "task-1",
|
|
49
|
-
title: "Test Task",
|
|
50
|
-
status: "To Do",
|
|
51
|
-
assignee: [],
|
|
52
|
-
createdDate: "2025-06-07",
|
|
53
|
-
labels: ["test"],
|
|
54
|
-
dependencies: [],
|
|
55
|
-
description: "This is a test task",
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
beforeEach(async () => {
|
|
59
|
-
await core.initializeProject("Test Project");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("should create task without auto-commit", async () => {
|
|
63
|
-
await core.createTask(sampleTask, false);
|
|
64
|
-
|
|
65
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
66
|
-
expect(loadedTask?.id).toBe("task-1");
|
|
67
|
-
expect(loadedTask?.title).toBe("Test Task");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("should create task with auto-commit", async () => {
|
|
71
|
-
await core.createTask(sampleTask, true);
|
|
72
|
-
|
|
73
|
-
// Check if task file was created
|
|
74
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
75
|
-
expect(loadedTask?.id).toBe("task-1");
|
|
76
|
-
|
|
77
|
-
// Check git status to see if there are uncommitted changes
|
|
78
|
-
const hasChanges = await core.gitOps.hasUncommittedChanges();
|
|
79
|
-
|
|
80
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
81
|
-
// For now, just check that we have a commit (could be initialization or task)
|
|
82
|
-
expect(lastCommit).toBeDefined();
|
|
83
|
-
expect(lastCommit.length).toBeGreaterThan(0);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("should update task with auto-commit", async () => {
|
|
87
|
-
await core.createTask(sampleTask, true);
|
|
88
|
-
|
|
89
|
-
// Check original task
|
|
90
|
-
const originalTask = await core.filesystem.loadTask("task-1");
|
|
91
|
-
expect(originalTask?.title).toBe("Test Task");
|
|
92
|
-
|
|
93
|
-
const updatedTask = { ...sampleTask, title: "Updated Task" };
|
|
94
|
-
await core.updateTask(updatedTask, true);
|
|
95
|
-
|
|
96
|
-
// Check if task was updated
|
|
97
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
98
|
-
expect(loadedTask?.title).toBe("Updated Task");
|
|
99
|
-
|
|
100
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
101
|
-
// For now, just check that we have a commit (could be initialization or task)
|
|
102
|
-
expect(lastCommit).toBeDefined();
|
|
103
|
-
expect(lastCommit.length).toBeGreaterThan(0);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should archive task with auto-commit", async () => {
|
|
107
|
-
await core.createTask(sampleTask, true);
|
|
108
|
-
|
|
109
|
-
const archived = await core.archiveTask("task-1", true);
|
|
110
|
-
expect(archived).toBe(true);
|
|
111
|
-
|
|
112
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
113
|
-
expect(lastCommit).toContain("backlog: Archive task task-1");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("should demote task with auto-commit", async () => {
|
|
117
|
-
await core.createTask(sampleTask, true);
|
|
118
|
-
|
|
119
|
-
const demoted = await core.demoteTask("task-1", true);
|
|
120
|
-
expect(demoted).toBe(true);
|
|
121
|
-
|
|
122
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
123
|
-
expect(lastCommit).toContain("backlog: Demote task task-1");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("should return false when archiving non-existent task", async () => {
|
|
127
|
-
const archived = await core.archiveTask("non-existent", true);
|
|
128
|
-
expect(archived).toBe(false);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("should apply default status when task has empty status", async () => {
|
|
132
|
-
const taskWithoutStatus: Task = {
|
|
133
|
-
...sampleTask,
|
|
134
|
-
status: "",
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
await core.createTask(taskWithoutStatus, false);
|
|
138
|
-
|
|
139
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
140
|
-
expect(loadedTask?.status).toBe("To Do"); // Should use default from config
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("should not override existing status", async () => {
|
|
144
|
-
const taskWithStatus: Task = {
|
|
145
|
-
...sampleTask,
|
|
146
|
-
status: "In Progress",
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
await core.createTask(taskWithStatus, false);
|
|
150
|
-
|
|
151
|
-
const loadedTask = await core.filesystem.loadTask("task-1");
|
|
152
|
-
expect(loadedTask?.status).toBe("In Progress");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it("should add description header when missing", async () => {
|
|
156
|
-
const taskNoHeader: Task = {
|
|
157
|
-
...sampleTask,
|
|
158
|
-
id: "task-2",
|
|
159
|
-
description: "Just text",
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
await core.createTask(taskNoHeader, false);
|
|
163
|
-
const loaded = await core.filesystem.loadTask("task-2");
|
|
164
|
-
expect(loaded?.description.startsWith("## Description")).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("should not duplicate description header", async () => {
|
|
168
|
-
const taskWithHeader: Task = {
|
|
169
|
-
...sampleTask,
|
|
170
|
-
id: "task-3",
|
|
171
|
-
description: "## Description\n\nExisting",
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
await core.createTask(taskWithHeader, false);
|
|
175
|
-
const loaded = await core.filesystem.loadTask("task-3");
|
|
176
|
-
const matches = loaded?.description.match(/## Description/g) || [];
|
|
177
|
-
expect(matches.length).toBe(1);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("should handle task creation without auto-commit when git fails", async () => {
|
|
181
|
-
// Create task in directory without git
|
|
182
|
-
const nonGitCore = new Core(join(TEST_DIR, "no-git"));
|
|
183
|
-
await nonGitCore.filesystem.ensureBacklogStructure();
|
|
184
|
-
|
|
185
|
-
// This should succeed even without git
|
|
186
|
-
await nonGitCore.createTask(sampleTask, false);
|
|
187
|
-
|
|
188
|
-
const loadedTask = await nonGitCore.filesystem.loadTask("task-1");
|
|
189
|
-
expect(loadedTask?.id).toBe("task-1");
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe("draft operations", () => {
|
|
194
|
-
const sampleDraft: Task = {
|
|
195
|
-
id: "task-draft",
|
|
196
|
-
title: "Draft Task",
|
|
197
|
-
status: "Draft",
|
|
198
|
-
assignee: [],
|
|
199
|
-
createdDate: "2025-06-07",
|
|
200
|
-
labels: [],
|
|
201
|
-
dependencies: [],
|
|
202
|
-
description: "Draft task",
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
beforeEach(async () => {
|
|
206
|
-
await core.initializeProject("Draft Project");
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("should create draft without auto-commit", async () => {
|
|
210
|
-
await core.createDraft(sampleDraft, false);
|
|
211
|
-
|
|
212
|
-
const loaded = await core.filesystem.loadDraft("task-draft");
|
|
213
|
-
expect(loaded?.id).toBe("task-draft");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("should create draft with auto-commit", async () => {
|
|
217
|
-
await core.createDraft(sampleDraft, true);
|
|
218
|
-
|
|
219
|
-
const loaded = await core.filesystem.loadDraft("task-draft");
|
|
220
|
-
expect(loaded?.id).toBe("task-draft");
|
|
221
|
-
|
|
222
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
223
|
-
expect(lastCommit).toBeDefined();
|
|
224
|
-
expect(lastCommit.length).toBeGreaterThan(0);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("should promote draft with auto-commit", async () => {
|
|
228
|
-
await core.createDraft(sampleDraft, true);
|
|
229
|
-
|
|
230
|
-
const promoted = await core.promoteDraft("task-draft", true);
|
|
231
|
-
expect(promoted).toBe(true);
|
|
232
|
-
|
|
233
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
234
|
-
expect(lastCommit).toContain("backlog: Promote draft task-draft");
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("should archive draft with auto-commit", async () => {
|
|
238
|
-
await core.createDraft(sampleDraft, true);
|
|
239
|
-
|
|
240
|
-
const archived = await core.archiveDraft("task-draft", true);
|
|
241
|
-
expect(archived).toBe(true);
|
|
242
|
-
|
|
243
|
-
const lastCommit = await core.gitOps.getLastCommitMessage();
|
|
244
|
-
expect(lastCommit).toContain("backlog: Archive draft task-draft");
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe("integration with config", () => {
|
|
249
|
-
it("should use custom default status from config", async () => {
|
|
250
|
-
// Initialize with custom config
|
|
251
|
-
await core.initializeProject("Custom Project");
|
|
252
|
-
|
|
253
|
-
// Update config with custom default status
|
|
254
|
-
const config = await core.filesystem.loadConfig();
|
|
255
|
-
if (config) {
|
|
256
|
-
config.defaultStatus = "Custom Status";
|
|
257
|
-
await core.filesystem.saveConfig(config);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const taskWithoutStatus: Task = {
|
|
261
|
-
id: "task-custom",
|
|
262
|
-
title: "Custom Task",
|
|
263
|
-
status: "",
|
|
264
|
-
assignee: [],
|
|
265
|
-
createdDate: "2025-06-07",
|
|
266
|
-
labels: [],
|
|
267
|
-
dependencies: [],
|
|
268
|
-
description: "Task without status",
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
await core.createTask(taskWithoutStatus, false);
|
|
272
|
-
|
|
273
|
-
const loadedTask = await core.filesystem.loadTask("task-custom");
|
|
274
|
-
expect(loadedTask?.status).toBe("Custom Status");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("should fall back to To Do when config has no default status", async () => {
|
|
278
|
-
// Initialize project
|
|
279
|
-
await core.initializeProject("Fallback Project");
|
|
280
|
-
|
|
281
|
-
// Update config to remove default status
|
|
282
|
-
const config = await core.filesystem.loadConfig();
|
|
283
|
-
if (config) {
|
|
284
|
-
config.defaultStatus = undefined;
|
|
285
|
-
await core.filesystem.saveConfig(config);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const taskWithoutStatus: Task = {
|
|
289
|
-
id: "task-fallback",
|
|
290
|
-
title: "Fallback Task",
|
|
291
|
-
status: "",
|
|
292
|
-
assignee: [],
|
|
293
|
-
createdDate: "2025-06-07",
|
|
294
|
-
labels: [],
|
|
295
|
-
dependencies: [],
|
|
296
|
-
description: "Task without status",
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
await core.createTask(taskWithoutStatus, false);
|
|
300
|
-
|
|
301
|
-
const loadedTask = await core.filesystem.loadTask("task-fallback");
|
|
302
|
-
expect(loadedTask?.status).toBe("To Do");
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
describe("directory accessor integration", () => {
|
|
307
|
-
it("should use FileSystem directory accessors for git operations", async () => {
|
|
308
|
-
await core.initializeProject("Accessor Test");
|
|
309
|
-
|
|
310
|
-
const task: Task = {
|
|
311
|
-
id: "task-accessor",
|
|
312
|
-
title: "Accessor Test Task",
|
|
313
|
-
status: "To Do",
|
|
314
|
-
assignee: [],
|
|
315
|
-
createdDate: "2025-06-07",
|
|
316
|
-
labels: [],
|
|
317
|
-
dependencies: [],
|
|
318
|
-
description: "Testing directory accessors",
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
await core.createTask(task, true);
|
|
322
|
-
|
|
323
|
-
// Verify the task file was created in the correct directory
|
|
324
|
-
const tasksDir = core.filesystem.tasksDir;
|
|
325
|
-
const files = await Array.fromAsync(new Bun.Glob("*.md").scan({ cwd: tasksDir }));
|
|
326
|
-
|
|
327
|
-
expect(files.some((f) => f.startsWith("task-accessor"))).toBe(true);
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
});
|