backlog.md 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/.backlog/archive/drafts/readme.md +3 -0
  2. package/.backlog/archive/drafts/task-41 - temporary-test-task.md +13 -0
  3. package/.backlog/archive/readme.md +6 -0
  4. package/.backlog/archive/tasks/readme.md +3 -0
  5. package/.backlog/archive/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +14 -0
  6. package/.backlog/config.yml +7 -0
  7. package/.backlog/decisions/readme.md +7 -0
  8. package/.backlog/docs/readme.md +20 -0
  9. package/.backlog/drafts/readme.md +3 -0
  10. package/.backlog/drafts/task-26 - docs-add-board-export-step-to-agent-dod.md +21 -0
  11. package/.backlog/drafts/task-28 - add-code-of-conduct.md +20 -0
  12. package/.backlog/drafts/task-30 - create-changelog.md +19 -0
  13. package/.backlog/milestones/m-0 - project-setup.md +8 -0
  14. package/.backlog/milestones/m-1 - cli.md +8 -0
  15. package/.backlog/milestones/m-2 - cli-kanban.md +8 -0
  16. package/.backlog/milestones/m-3 - gui.md +8 -0
  17. package/.backlog/milestones/m-4 - gui-kanban.md +8 -0
  18. package/.backlog/milestones/m-5 - gui-advanced.md +12 -0
  19. package/.backlog/milestones/readme.md +3 -0
  20. package/.backlog/readme.md +5 -0
  21. package/.backlog/tasks/readme.md +37 -0
  22. package/.backlog/tasks/task-1 - cli-setup-core-project.md +23 -0
  23. package/.backlog/tasks/task-10 - gui-init-packaging.md +23 -0
  24. package/.backlog/tasks/task-11 - gui-kanban-board.md +26 -0
  25. package/.backlog/tasks/task-12 - gui-advanced.md +25 -0
  26. package/.backlog/tasks/task-13 - cli-add-agent-instruction-prompt.md +53 -0
  27. package/.backlog/tasks/task-13.1 - cli-agent-instruction-file-selection.md +40 -0
  28. package/.backlog/tasks/task-14 - gui-introduction-screens.md +21 -0
  29. package/.backlog/tasks/task-15 - improve-tasks-readme-with-generic-example-and-cli-reference.md +20 -0
  30. package/.backlog/tasks/task-16 - improve-docs-readme-with-generic-example-and-cli-reference.md +20 -0
  31. package/.backlog/tasks/task-17 - improve-drafts-readme-with-generic-example-and-cli-reference.md +20 -0
  32. package/.backlog/tasks/task-18 - improve-decisions-readme-with-generic-example-and-cli-reference.md +20 -0
  33. package/.backlog/tasks/task-19 - cli-fix-default-task-status-and-remove-draft-from-statuses.md +55 -0
  34. package/.backlog/tasks/task-2 - cli-core-logic-library.md +28 -0
  35. package/.backlog/tasks/task-20 - add-agent-guideline-to-mark-tasks-in-progress-on-start.md +32 -0
  36. package/.backlog/tasks/task-21 - kanban-board-vertical-layout.md +31 -0
  37. package/.backlog/tasks/task-22 - cli-prevent-double-dash-in-task-filenames.md +24 -0
  38. package/.backlog/tasks/task-23 - cli-kanban-board-order-tasks-by-id-asc.md +30 -0
  39. package/.backlog/tasks/task-24 - handle-subtasks-in-the-kanban-view.md +38 -0
  40. package/.backlog/tasks/task-24.1 - cli-kanban-board-milestone-view.md +19 -0
  41. package/.backlog/tasks/task-25 - cli-export-kanban-board-to-readme.md +28 -0
  42. package/.backlog/tasks/task-27 - add-contributing-guidelines.md +27 -0
  43. package/.backlog/tasks/task-29 - add-github-templates.md +28 -0
  44. package/.backlog/tasks/task-3 - cli-implement-backlog-init.md +63 -0
  45. package/.backlog/tasks/task-31 - update-readme-for-open-source.md +26 -0
  46. package/.backlog/tasks/task-32 - cli-hide-empty-'no-status'-column.md +31 -0
  47. package/.backlog/tasks/task-33 - cli-export-milestones-board-as-roadmap.md +20 -0
  48. package/.backlog/tasks/task-34 - split-readme.md-for-users-and-contributors.md +26 -0
  49. package/.backlog/tasks/task-35 - finalize-package.json-metadata-for-publishing.md +24 -0
  50. package/.backlog/tasks/task-36 - cli-prompt-for-project-name-in-init.md +24 -0
  51. package/.backlog/tasks/task-37 - cli-board-view-open-tasks-in-ide.md +19 -0
  52. package/.backlog/tasks/task-38 - cli-improved-agent-selection-for-init.md +25 -0
  53. package/.backlog/tasks/task-39 - cli-fix-empty-agent-instruction-files-on-init.md +31 -0
  54. package/.backlog/tasks/task-4 - cli-task-management-commands.md +28 -0
  55. package/.backlog/tasks/task-4.1 - cli-task-create.md +27 -0
  56. package/.backlog/tasks/task-4.10 - use-cli-to-mark-tasks-done.md +51 -0
  57. package/.backlog/tasks/task-4.11 - docs-add-definition-of-done-to-agent-guidelines.md +23 -0
  58. package/.backlog/tasks/task-4.12 - cli-handle-task-id-conflicts-across-branches.md +53 -0
  59. package/.backlog/tasks/task-4.13 - cli-fix-config-command-local-global-logic.md +58 -0
  60. package/.backlog/tasks/task-4.2 - cli-task-list-view.md +25 -0
  61. package/.backlog/tasks/task-4.3 - cli-task-edit.md +24 -0
  62. package/.backlog/tasks/task-4.4 - cli-task-archive-transition.md +27 -0
  63. package/.backlog/tasks/task-4.5 - cli-init-prompts-for-reporter-name-and-global-local-config.md +28 -0
  64. package/.backlog/tasks/task-4.6 - cli-add-empty-assignee-array-field-for-new-tasks.md +35 -0
  65. package/.backlog/tasks/task-4.7 - cli-parse-unquoted-created_date.md +40 -0
  66. package/.backlog/tasks/task-4.8 - cli-enforce-description-header.md +48 -0
  67. package/.backlog/tasks/task-4.9 - cli-normalize-task-id-inputs.md +66 -0
  68. package/.backlog/tasks/task-40 - cli-board-command-defaults-to-view.md +38 -0
  69. package/.backlog/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +93 -0
  70. package/.backlog/tasks/task-41.1 - cli-bblessed-init-wizard.md +42 -0
  71. package/.backlog/tasks/task-41.2 - cli-bblessed-task-view.md +44 -0
  72. package/.backlog/tasks/task-41.3 - cli-bblessed-doc-view.md +45 -0
  73. package/.backlog/tasks/task-41.4 - cli-bblessed-board-view.md +49 -0
  74. package/.backlog/tasks/task-41.5 - cli-audit-remaining-ui-for-bblessed.md +55 -0
  75. package/.backlog/tasks/task-42 - visual-hierarchy.md +54 -0
  76. package/.backlog/tasks/task-43 - remove-duplicate-acceptance-criteria-and-style-metadata.md +56 -0
  77. package/.backlog/tasks/task-44 - checklist-alignment.md +24 -0
  78. package/.backlog/tasks/task-45 - safe-line-wrapping.md +23 -0
  79. package/.backlog/tasks/task-46 - split-pane-layout.md +24 -0
  80. package/.backlog/tasks/task-47 - sticky-header-in-detail-view.md +43 -0
  81. package/.backlog/tasks/task-48 - footer-hint-line.md +21 -0
  82. package/.backlog/tasks/task-49 - status-styling.md +53 -0
  83. package/.backlog/tasks/task-5 - cli-docs-decisions.md +57 -0
  84. package/.backlog/tasks/task-50 - borders-&-padding.md +22 -0
  85. package/.backlog/tasks/task-51 - code-path-styling.md +23 -0
  86. package/.backlog/tasks/task-52 - cli-filter-tasks-list-by-status-or-assignee.md +29 -0
  87. package/.backlog/tasks/task-6 - cli-packaging.md +65 -0
  88. package/.backlog/tasks/task-6.1 - cli-local-installation-support-for-bunx-npx.md +49 -0
  89. package/.backlog/tasks/task-6.2 - cli-github-actions-for-build-&-publish.md +64 -0
  90. package/.backlog/tasks/task-7 - cli-kanban-view.md +60 -0
  91. package/.backlog/tasks/task-7.1 - cli-kanban-board-detect-remote-task-status.md +62 -0
  92. package/.backlog/tasks/task-8 - gui-project-setup.md +21 -0
  93. package/.backlog/tasks/task-9 - gui-task-crud.md +24 -0
  94. package/.cursorrules +223 -0
  95. package/.gitattributes +2 -0
  96. package/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  97. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  98. package/.github/PULL_REQUEST_TEMPLATE.md +8 -0
  99. package/.github/workflows/ci.yml +36 -0
  100. package/.husky/pre-commit +1 -0
  101. package/AGENTS.md +65 -0
  102. package/CLAUDE.md +87 -0
  103. package/CONTRIBUTING.md +19 -0
  104. package/DEVELOPMENT.md +37 -0
  105. package/LICENSE +21 -0
  106. package/biome.json +31 -0
  107. package/bun.lock +152 -0
  108. package/cli/.cursorrules-xh86jabm.md +82 -0
  109. package/cli/AGENTS-xh86jabm.md +82 -0
  110. package/cli/CLAUDE-xh86jabm.md +82 -0
  111. package/cli/backlog +0 -0
  112. package/cli/cli.js +19622 -0
  113. package/cli/index.js +2 -0
  114. package/docs/npm-publishing.md +69 -0
  115. package/package.json +47 -0
  116. package/readme.md +97 -0
  117. package/scripts/build.js +73 -0
  118. package/src/agent-instructions.ts +54 -0
  119. package/src/board.ts +263 -0
  120. package/src/cli.ts +806 -0
  121. package/src/constants/index.ts +48 -0
  122. package/src/core/backlog.ts +183 -0
  123. package/src/core/remote-tasks.ts +168 -0
  124. package/src/file-system/operations.ts +515 -0
  125. package/src/git/operations.ts +189 -0
  126. package/src/guidelines/.cursorrules.md +82 -0
  127. package/src/guidelines/AGENTS.md +82 -0
  128. package/src/guidelines/CLAUDE.md +82 -0
  129. package/src/guidelines/index.ts +7 -0
  130. package/src/index.ts +30 -0
  131. package/src/markdown/parser.ts +145 -0
  132. package/src/markdown/serializer.ts +71 -0
  133. package/src/test/agent-instructions.test.ts +62 -0
  134. package/src/test/board.test.ts +291 -0
  135. package/src/test/build.test.ts +28 -0
  136. package/src/test/checklist.test.ts +273 -0
  137. package/src/test/cli.test.ts +1300 -0
  138. package/src/test/code-path.test.ts +204 -0
  139. package/src/test/core.test.ts +330 -0
  140. package/src/test/filesystem.test.ts +435 -0
  141. package/src/test/git.test.ts +26 -0
  142. package/src/test/heading.test.ts +102 -0
  143. package/src/test/line-wrapping.test.ts +252 -0
  144. package/src/test/local-install.test.ts +34 -0
  145. package/src/test/markdown.test.ts +526 -0
  146. package/src/test/parallel-loading.test.ts +160 -0
  147. package/src/test/parent-id-normalization.test.ts +48 -0
  148. package/src/test/remote-id-conflict.test.ts +60 -0
  149. package/src/test/status-icon.test.ts +93 -0
  150. package/src/types/blessed.d.ts +14 -0
  151. package/src/types/index.ts +55 -0
  152. package/src/types/raw.d.ts +4 -0
  153. package/src/ui/board.ts +322 -0
  154. package/src/ui/checklist.ts +103 -0
  155. package/src/ui/code-path.ts +113 -0
  156. package/src/ui/heading.ts +121 -0
  157. package/src/ui/loading.ts +216 -0
  158. package/src/ui/status-icon.ts +53 -0
  159. package/src/ui/task-list.ts +168 -0
  160. package/src/ui/task-viewer.ts +640 -0
  161. package/src/ui/tui.ts +301 -0
  162. package/tsconfig.json +26 -0
package/cli/index.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import("./cli.js");
@@ -0,0 +1,69 @@
1
+ # NPM Publishing Setup
2
+
3
+ This guide explains how to set up npm publishing for the Backlog.md project.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. An npm account with publish permissions for the `@backlog.md` package
8
+ 2. Repository admin access to configure GitHub secrets
9
+
10
+ ## Setting up the NPM Token
11
+
12
+ ### 1. Generate an npm Access Token
13
+
14
+ 1. Log in to [npmjs.com](https://www.npmjs.com)
15
+ 2. Click on your profile picture → Access Tokens
16
+ 3. Click "Generate New Token"
17
+ 4. Choose "Automation" token type (recommended for CI/CD)
18
+ 5. Name it something like "backlog-md-github-actions"
19
+ 6. Copy the generated token (it starts with `npm_`)
20
+
21
+ ### 2. Add the Token to GitHub Secrets
22
+
23
+ 1. Go to the GitHub repository settings
24
+ 2. Navigate to Settings → Secrets and variables → Actions
25
+ 3. Click "New repository secret"
26
+ 4. Name: `NPM_TOKEN`
27
+ 5. Value: Paste your npm token
28
+ 6. Click "Add secret"
29
+
30
+ ## How Publishing Works
31
+
32
+ The GitHub Actions workflow (`/.github/workflows/ci.yml`) automatically publishes to npm when:
33
+
34
+ 1. A new git tag is pushed that matches the pattern `v*.*.*` (e.g., `v1.0.0`)
35
+ 2. The build and tests pass successfully
36
+
37
+ The workflow:
38
+ - Uses `actions/setup-node@v4` to configure npm authentication
39
+ - Sets the registry URL to `https://registry.npmjs.org`
40
+ - Uses the `NPM_TOKEN` secret for authentication
41
+ - Runs `npm publish --access public` to publish the package
42
+
43
+ ## Troubleshooting
44
+
45
+ ### Error: ENEEDAUTH
46
+
47
+ If you see this error, it means the `NPM_TOKEN` secret is either:
48
+ - Not configured in GitHub
49
+ - Invalid or expired
50
+ - Missing the required publish permissions
51
+
52
+ ### Error: 403 Forbidden
53
+
54
+ This usually means:
55
+ - The token doesn't have permission to publish to the package
56
+ - The package name is already taken by another user
57
+ - You're not listed as a maintainer of the package
58
+
59
+ ## Manual Publishing (Not Recommended)
60
+
61
+ If you need to publish manually:
62
+
63
+ ```bash
64
+ npm login
65
+ bun run build
66
+ npm publish --access public
67
+ ```
68
+
69
+ Always prefer using the automated workflow to ensure consistency.
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "backlog.md",
3
+ "version": "0.1.0",
4
+ "module": "src/index.ts",
5
+ "type": "module",
6
+ "bin": {
7
+ "backlog": "cli/index.js"
8
+ },
9
+ "dependencies": {
10
+ "commander": "14.0.0",
11
+ "gray-matter": "4.0.3",
12
+ "blessed": "0.1.81"
13
+ },
14
+ "devDependencies": {
15
+ "@biomejs/biome": "1.9.4",
16
+ "@types/bun": "1.2.15",
17
+ "husky": "9.1.7",
18
+ "lint-staged": "16.1.0"
19
+ },
20
+ "scripts": {
21
+ "test": "bun test",
22
+ "format": "biome format --write .",
23
+ "lint": "biome lint --write .",
24
+ "check": "biome check .",
25
+ "prepare": "husky",
26
+ "build": "node scripts/build.js",
27
+ "build:js": "bun build src/cli.ts --outdir cli --target node",
28
+ "build:exe": "bun build src/cli.ts --compile --outfile cli/backlog",
29
+ "cli": "bun src/cli.ts"
30
+ },
31
+ "lint-staged": {
32
+ "*.{ts,js,json}": ["biome check --write --files-ignore-unknown=true"],
33
+ "src/**/*.{ts,js}": ["biome check --write --files-ignore-unknown=true"]
34
+ },
35
+ "author": "Alex Gavrilescu (https://github.com/MrLesk)",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/MrLesk/Backlog.md.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/MrLesk/Backlog.md/issues"
42
+ },
43
+ "homepage": "https://backlog.md",
44
+ "keywords": ["cli", "markdown", "kanban", "task", "project-management", "backlog", "agents"],
45
+ "license": "MIT",
46
+ "trustedDependencies": ["@biomejs/biome"]
47
+ }
package/readme.md ADDED
@@ -0,0 +1,97 @@
1
+ <h1 align="center">Backlog.md</h1>
2
+ <p align="center">✏️ Markdown‑native Task Manager &amp; Kanban visualizer for any Git repository</p>
3
+
4
+ <p align="center">
5
+ <a href="https://www.npmjs.com/package/backlog.md">
6
+ <img src="https://badgen.net/npm/v/backlog.md?icon=npm&label=npm">
7
+ </a>
8
+ <a href="https://bun.sh">
9
+ <img src="https://badgen.net/badge/bun/add%20backlog.md/black?icon=bun">
10
+ </a>
11
+ <a href="LICENSE"><img src="https://badgen.net/github/license/your-org/backlog.md"></a>
12
+ </p>
13
+
14
+ ---
15
+
16
+ > **Backlog.md** turns any folder with a Git repo into a **self‑contained project board**
17
+ > powered by plain Markdown files and a zero‑config CLI.
18
+
19
+ * 100 % offline‑friendly – your backlog lives *inside* your repository
20
+ * Works on **macOS, Linux and Windows** (Node ≥ 18 / Bun ≥ 1.0)
21
+ * Completely free & open‑source (MIT)
22
+
23
+ ---
24
+
25
+ ### Quick install
26
+
27
+ ```bash
28
+ # global – Node
29
+ npm i -g backlog.md
30
+
31
+ # global – Bun
32
+ bun add -g backlog.md
33
+ ```
34
+
35
+ > Prefer per‑project installs? `npm i -D backlog.md` → `npx backlog …`
36
+
37
+ ---
38
+
39
+ ### Five‑minute tour
40
+
41
+ ```bash
42
+ # 1. Bootstrap a repo + backlog
43
+ backlog init hello-world
44
+
45
+ # 2. Capture work
46
+ backlog task create "Render markdown as kanban"
47
+
48
+ # 3. See where you stand
49
+ backlog board view
50
+ ```
51
+
52
+ All data is saved under `.backlog` folder as human‑readable Markdown (`task‑12 - Fix typo.md`).
53
+
54
+ ---
55
+
56
+ ## CLI reference (essentials)
57
+
58
+ | Action | Example |
59
+ |-------------|------------------------------------------------------|
60
+ | Create task | `backlog task create "Add OAuth"` |
61
+ | Create sub task | `backlog task create --parent 14 "Add Google auth"`|
62
+ | List tasks | `backlog task list [-s <status>] [-a <assignee>` |
63
+ | View detail | `backlog task 7` |
64
+ | Edit | `backlog task edit 7 -a @sara -l auth,backend` |
65
+ | Archive | `backlog task archive 7` |
66
+ | Draft flow | `backlog draft create "Spike GraphQL"` → `backlog draft promote 3.1` |
67
+ | Demote to draft| `backlog task demote <id>` |
68
+ | Kanban | `backlog board view` |
69
+
70
+ Full help: `backlog --help`
71
+
72
+ ---
73
+
74
+ ## Configuration
75
+
76
+ Backlog.md merges the following layers (highest → lowest):
77
+
78
+ 1. CLI flags
79
+ 2. `.backlog/config.yml` (per‑project)
80
+ 3. `~/.backlog/user` (per‑user)
81
+ 4. Built‑ins
82
+
83
+ Key options:
84
+
85
+ | Key | Purpose | Default |
86
+ |-------------------|--------------------|-------------------------------|
87
+ | `default_assignee`| Pre‑fill assignee | `[]` |
88
+ | `default_status` | First column | `To Do` |
89
+ | `statuses` | Board columns | `[To Do, In Progress, Done]` |
90
+ | `date_format` | ISO or locale | `yyyy-mm-dd` |
91
+
92
+ ---
93
+
94
+
95
+ ## License
96
+
97
+ Backlog.md is released under the **MIT License** – do anything, just give credit. See [LICENSE](LICENSE).
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from "node:child_process";
4
+ import { chmod, mkdir, writeFile } from "node:fs/promises";
5
+ import { platform } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+
8
+ const isWindows = platform() === "win32";
9
+ const outDir = "cli";
10
+ const indexFile = join(outDir, "index.js");
11
+ const executableName = isWindows ? "backlog.exe" : "backlog";
12
+ const executablePath = join(outDir, executableName);
13
+
14
+ async function runCommand(command, args) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = spawn(command, args, {
17
+ stdio: "inherit",
18
+ shell: isWindows,
19
+ });
20
+
21
+ child.on("close", (code) => {
22
+ if (code === 0) {
23
+ resolve();
24
+ } else {
25
+ reject(new Error(`Command failed with exit code ${code}`));
26
+ }
27
+ });
28
+
29
+ child.on("error", reject);
30
+ });
31
+ }
32
+
33
+ async function build() {
34
+ try {
35
+ console.log("Building CLI...");
36
+
37
+ // Ensure output directory exists
38
+ await mkdir(dirname(indexFile), { recursive: true });
39
+
40
+ // Build JavaScript bundle
41
+ console.log("Building JavaScript bundle...");
42
+ await runCommand("bun", ["build", "src/cli.ts", "--outdir", outDir, "--target", "node"]);
43
+
44
+ // Create index.js wrapper
45
+ console.log("Creating index.js wrapper...");
46
+ const indexContent = isWindows ? 'import("./cli.js");' : '#!/usr/bin/env node\nimport("./cli.js");';
47
+
48
+ await writeFile(indexFile, indexContent);
49
+
50
+ // Make executable on Unix systems
51
+ if (!isWindows) {
52
+ await chmod(indexFile, "755");
53
+ }
54
+
55
+ // Build compiled executable
56
+ console.log("Building compiled executable...");
57
+ await runCommand("bun", ["build", "src/cli.ts", "--compile", "--outfile", executablePath]);
58
+
59
+ // Make executable on Unix systems
60
+ if (!isWindows) {
61
+ await chmod(executablePath, "755");
62
+ }
63
+
64
+ console.log("Build completed successfully!");
65
+ console.log(`Executable: ${executablePath}`);
66
+ console.log(`JS Bundle: ${indexFile}`);
67
+ } catch (error) {
68
+ console.error("Build failed:", error.message);
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ build();
@@ -0,0 +1,54 @@
1
+ import { dirname, isAbsolute, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { AGENT_GUIDELINES, CLAUDE_GUIDELINES, CURSOR_GUIDELINES, README_GUIDELINES } from "./constants/index.ts";
4
+ import type { GitOperations } from "./git/operations.ts";
5
+
6
+ export type AgentInstructionFile = "AGENTS.md" | "CLAUDE.md" | ".cursorrules" | "readme.md";
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+
10
+ async function loadContent(textOrPath: string): Promise<string> {
11
+ if (textOrPath.includes("\n")) return textOrPath;
12
+ try {
13
+ const path = isAbsolute(textOrPath) ? textOrPath : join(__dirname, textOrPath);
14
+ return await Bun.file(path).text();
15
+ } catch {
16
+ return textOrPath;
17
+ }
18
+ }
19
+
20
+ export async function addAgentInstructions(
21
+ projectRoot: string,
22
+ git?: GitOperations,
23
+ files: AgentInstructionFile[] = ["AGENTS.md", "CLAUDE.md", ".cursorrules"],
24
+ ): Promise<void> {
25
+ const mapping: Record<AgentInstructionFile, string> = {
26
+ "AGENTS.md": AGENT_GUIDELINES,
27
+ "CLAUDE.md": CLAUDE_GUIDELINES,
28
+ ".cursorrules": CURSOR_GUIDELINES,
29
+ "readme.md": README_GUIDELINES,
30
+ };
31
+
32
+ const paths: string[] = [];
33
+ for (const name of files) {
34
+ const content = await loadContent(mapping[name]);
35
+ const filePath = join(projectRoot, name);
36
+ let existing = "";
37
+ try {
38
+ existing = await Bun.file(filePath).text();
39
+ if (!existing.endsWith("\n")) existing += "\n";
40
+ existing += content;
41
+ } catch {
42
+ existing = content;
43
+ }
44
+ await Bun.write(filePath, existing);
45
+ paths.push(filePath);
46
+ }
47
+
48
+ if (git && paths.length > 0) {
49
+ await git.addFiles(paths);
50
+ await git.commitChanges("Add AI agent instructions");
51
+ }
52
+ }
53
+
54
+ export { loadContent as _loadAgentGuideline };
package/src/board.ts ADDED
@@ -0,0 +1,263 @@
1
+ export interface BoardOptions {
2
+ statuses?: string[];
3
+ }
4
+
5
+ import { mkdir } from "node:fs/promises";
6
+ import { dirname } from "node:path";
7
+ import type { Task } from "./types/index.ts";
8
+
9
+ export type BoardLayout = "horizontal" | "vertical";
10
+ export type BoardFormat = "terminal" | "markdown";
11
+
12
+ interface DisplayTask {
13
+ id: string;
14
+ title: string;
15
+ }
16
+
17
+ export function idSegments(id: string): number[] {
18
+ const normalized = id.startsWith("task-") ? id.slice(5) : id;
19
+ return normalized.split(".").map((part) => Number.parseInt(part, 10));
20
+ }
21
+
22
+ export function compareIds(a: Task, b: Task): number {
23
+ const segA = idSegments(a.id);
24
+ const segB = idSegments(b.id);
25
+ const len = Math.max(segA.length, segB.length);
26
+ for (let i = 0; i < len; i++) {
27
+ const diff = (segA[i] ?? 0) - (segB[i] ?? 0);
28
+ if (diff !== 0) return diff;
29
+ }
30
+ return 0;
31
+ }
32
+
33
+ function wrapText(text: string, maxWidth: number): string[] {
34
+ if (text.length <= maxWidth) return [text];
35
+
36
+ const words = text.split(" ");
37
+ const lines: string[] = [];
38
+ let currentLine = "";
39
+
40
+ for (const word of words) {
41
+ if (currentLine.length + word.length + 1 <= maxWidth) {
42
+ currentLine += (currentLine ? " " : "") + word;
43
+ } else {
44
+ if (currentLine) lines.push(currentLine);
45
+ currentLine = word;
46
+ }
47
+ }
48
+
49
+ if (currentLine) lines.push(currentLine);
50
+ return lines;
51
+ }
52
+
53
+ export function generateKanbanBoard(
54
+ tasks: Task[],
55
+ statuses: string[] = [],
56
+ layout: BoardLayout = "horizontal",
57
+ maxColumnWidth = 20,
58
+ format: BoardFormat = "terminal",
59
+ ): string {
60
+ const groups = new Map<string, Task[]>();
61
+ for (const task of tasks) {
62
+ const status = task.status || "";
63
+ const list = groups.get(status) || [];
64
+ list.push(task);
65
+ groups.set(status, list);
66
+ }
67
+
68
+ // Map for quick lookup by id
69
+ const byId = new Map<string, Task>(tasks.map((t) => [t.id, t]));
70
+
71
+ // Only show statuses that have tasks (filter out empty groups)
72
+ const ordered =
73
+ tasks.length > 0
74
+ ? [
75
+ ...statuses.filter((s) => groups.has(s) && (groups.get(s)?.length ?? 0) > 0),
76
+ ...Array.from(groups.keys()).filter((s) => !statuses.includes(s) && (groups.get(s)?.length ?? 0) > 0),
77
+ ]
78
+ : [];
79
+
80
+ const columns: DisplayTask[][] = ordered.map((status) => {
81
+ const items = groups.get(status) || [];
82
+ const top: Task[] = [];
83
+ const children = new Map<string, Task[]>();
84
+
85
+ // Use compareIds for sorting instead of localeCompare
86
+ for (const t of items.sort(compareIds)) {
87
+ const parent = t.parentTaskId ? byId.get(t.parentTaskId) : undefined;
88
+ if (parent && parent.status === t.status) {
89
+ const list = children.get(parent.id) || [];
90
+ list.push(t);
91
+ children.set(parent.id, list);
92
+ } else {
93
+ top.push(t);
94
+ }
95
+ }
96
+
97
+ const result: DisplayTask[] = [];
98
+ for (const t of top) {
99
+ result.push({ id: t.id, title: t.title });
100
+ const subs = children.get(t.id) || [];
101
+ subs.sort(compareIds);
102
+
103
+ for (const [subIdx, s] of subs.entries()) {
104
+ const isLastSub = subIdx === subs.length - 1;
105
+ const prefix = isLastSub ? " └─" : " |—";
106
+ result.push({ id: `${prefix} ${s.id}`, title: ` ${s.title}` });
107
+ }
108
+ }
109
+
110
+ return result;
111
+ });
112
+
113
+ if (layout === "vertical") {
114
+ const rows: string[] = [];
115
+ for (const [idx, status] of ordered.entries()) {
116
+ const header = status || "No Status";
117
+ rows.push(header);
118
+ rows.push("-".repeat(header.length));
119
+ const tasksInStatus = columns[idx];
120
+ for (const task of tasksInStatus) {
121
+ rows.push(task.id);
122
+ rows.push(task.title);
123
+ rows.push("");
124
+ }
125
+ if (tasksInStatus.length === 0) {
126
+ rows.push("");
127
+ }
128
+ }
129
+ return rows.join("\n").trimEnd();
130
+ }
131
+
132
+ // Return empty string if no columns to show
133
+ if (ordered.length === 0) {
134
+ return "";
135
+ }
136
+
137
+ const colWidths = ordered.map((status, idx) => {
138
+ const header = status || "No Status";
139
+ let width = Math.min(Math.max(header.length, 8), maxColumnWidth); // Minimum 8, max maxColumnWidth
140
+ for (const t of columns[idx]) {
141
+ // Check both task ID and title lengths separately
142
+ const idLength = t.id.length;
143
+ const titleLength = t.title.length;
144
+ const maxTaskWidth = Math.max(idLength, titleLength);
145
+ if (maxTaskWidth > width && width < maxColumnWidth) {
146
+ width = Math.min(maxTaskWidth, maxColumnWidth);
147
+ }
148
+ }
149
+ return width;
150
+ });
151
+
152
+ // For markdown format, we need simpler output without text wrapping
153
+ if (format === "markdown") {
154
+ const headerRow = `| ${ordered.map((status) => status || "No Status").join(" | ")} |`;
155
+ const separatorRow = `| ${ordered.map(() => "---").join(" | ")} |`;
156
+
157
+ const maxTasks = Math.max(...columns.map((c) => c.length), 0);
158
+ const rows = [headerRow, separatorRow];
159
+
160
+ for (let taskIdx = 0; taskIdx < maxTasks; taskIdx++) {
161
+ const row = ordered.map((_, cIdx) => {
162
+ const task = columns[cIdx][taskIdx];
163
+ if (!task) return "";
164
+ // For markdown, combine ID and title in one cell
165
+ return `${task.id}: ${task.title}`;
166
+ });
167
+ rows.push(`| ${row.join(" | ")} |`);
168
+ }
169
+
170
+ return rows.join("\n");
171
+ }
172
+
173
+ // Terminal format with text wrapping
174
+ const pad = (text: string, width: number): string => text.padEnd(width, " ");
175
+
176
+ const headerRow = ordered.map((status, i) => pad(status || "No Status", colWidths[i])).join(" | ");
177
+ const separatorRow = ordered.map((_, i) => "-".repeat(colWidths[i])).join("-|-");
178
+
179
+ // Prepare wrapped tasks for each column
180
+ const wrappedTasks = ordered.map((_, cIdx) => {
181
+ return columns[cIdx].map((task) => ({
182
+ idLines: wrapText(task.id, colWidths[cIdx]),
183
+ titleLines: wrapText(task.title, colWidths[cIdx]),
184
+ }));
185
+ });
186
+
187
+ const maxTasks = Math.max(...columns.map((c) => c.length), 0);
188
+ const rows = [headerRow, separatorRow];
189
+
190
+ for (let taskIdx = 0; taskIdx < maxTasks; taskIdx++) {
191
+ // Get the maximum number of lines needed for this task across all columns
192
+ let maxTaskLines = 0;
193
+ for (let cIdx = 0; cIdx < ordered.length; cIdx++) {
194
+ if (wrappedTasks[cIdx][taskIdx]) {
195
+ const taskLines = wrappedTasks[cIdx][taskIdx].idLines.length + wrappedTasks[cIdx][taskIdx].titleLines.length;
196
+ maxTaskLines = Math.max(maxTaskLines, taskLines);
197
+ }
198
+ }
199
+
200
+ // Render each line for this task
201
+ for (let lineIdx = 0; lineIdx < maxTaskLines; lineIdx++) {
202
+ const lineRow = ordered
203
+ .map((_, cIdx) => {
204
+ const wrappedTask = wrappedTasks[cIdx][taskIdx];
205
+ if (!wrappedTask) return pad("", colWidths[cIdx]);
206
+
207
+ const idLineCount = wrappedTask.idLines.length;
208
+ let text = "";
209
+
210
+ if (lineIdx < idLineCount) {
211
+ // Show ID lines first
212
+ text = wrappedTask.idLines[lineIdx];
213
+ } else {
214
+ // Then show title lines
215
+ const titleLineIdx = lineIdx - idLineCount;
216
+ if (titleLineIdx < wrappedTask.titleLines.length) {
217
+ text = wrappedTask.titleLines[titleLineIdx];
218
+ }
219
+ }
220
+
221
+ return pad(text, colWidths[cIdx]);
222
+ })
223
+ .join(" | ");
224
+ rows.push(lineRow);
225
+ }
226
+
227
+ // Add empty row between tasks for better separation (except after last task)
228
+ // Skip empty row if next task is a subtask (to keep parent and child together)
229
+ if (taskIdx < maxTasks - 1) {
230
+ const emptyRow = ordered.map((_, cIdx) => pad("", colWidths[cIdx])).join(" | ");
231
+ rows.push(emptyRow);
232
+ }
233
+ }
234
+
235
+ return rows.join("\n");
236
+ }
237
+
238
+ export async function exportKanbanBoardToFile(
239
+ tasks: Task[],
240
+ statuses: string[],
241
+ filePath: string,
242
+ maxColumnWidth = 20,
243
+ addTitle = false,
244
+ ): Promise<void> {
245
+ const board = generateKanbanBoard(tasks, statuses, "horizontal", maxColumnWidth, "markdown");
246
+
247
+ let existing = "";
248
+ try {
249
+ existing = await Bun.file(filePath).text();
250
+ } catch {
251
+ await mkdir(dirname(filePath), { recursive: true });
252
+ }
253
+
254
+ // Add proper spacing and title for readme export
255
+ let boardContent = board;
256
+ if (addTitle && filePath.toLowerCase().includes("readme")) {
257
+ boardContent = `\n\n## Project Board\n\n${board}`;
258
+ }
259
+
260
+ const needsNewline = existing && !existing.endsWith("\n");
261
+ const content = `${existing}${needsNewline ? "\n" : ""}${boardContent}\n`;
262
+ await Bun.write(filePath, content);
263
+ }