@sascha384/tic 1.35.0 → 2.0.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.
Files changed (101) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +17 -63
  3. package/dist/backends/availability.js +4 -2
  4. package/dist/backends/availability.js.map +1 -1
  5. package/dist/backends/factory.d.ts +5 -3
  6. package/dist/backends/factory.js +28 -43
  7. package/dist/backends/factory.js.map +1 -1
  8. package/dist/backends/files/hash.d.ts +1 -0
  9. package/dist/backends/files/hash.js +5 -0
  10. package/dist/backends/files/hash.js.map +1 -0
  11. package/dist/backends/files/index.d.ts +48 -0
  12. package/dist/backends/files/index.js +174 -0
  13. package/dist/backends/files/index.js.map +1 -0
  14. package/dist/backends/files/sync.d.ts +13 -0
  15. package/dist/backends/files/sync.js +69 -0
  16. package/dist/backends/files/sync.js.map +1 -0
  17. package/dist/backends/jira/config.d.ts +1 -1
  18. package/dist/backends/jira/config.js +6 -9
  19. package/dist/backends/jira/config.js.map +1 -1
  20. package/dist/backends/types.d.ts +12 -0
  21. package/dist/backends/types.js +5 -1
  22. package/dist/backends/types.js.map +1 -1
  23. package/dist/cli/commands/config.js +27 -14
  24. package/dist/cli/commands/config.js.map +1 -1
  25. package/dist/cli/commands/init.js +10 -3
  26. package/dist/cli/commands/init.js.map +1 -1
  27. package/dist/cli/commands/mcp.d.ts +4 -4
  28. package/dist/cli/commands/mcp.js +16 -25
  29. package/dist/cli/commands/mcp.js.map +1 -1
  30. package/dist/cli/index.js +16 -19
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/commands.js +0 -6
  33. package/dist/commands.js.map +1 -1
  34. package/dist/components/Header.js +3 -2
  35. package/dist/components/Header.js.map +1 -1
  36. package/dist/components/OverlayPanel.d.ts +2 -1
  37. package/dist/components/OverlayPanel.js +8 -1
  38. package/dist/components/OverlayPanel.js.map +1 -1
  39. package/dist/components/Settings.js +6 -11
  40. package/dist/components/Settings.js.map +1 -1
  41. package/dist/components/StatusScreen.js +1 -1
  42. package/dist/components/StatusScreen.js.map +1 -1
  43. package/dist/components/WorkItemForm.js +6 -9
  44. package/dist/components/WorkItemForm.js.map +1 -1
  45. package/dist/components/WorkItemList.js +102 -51
  46. package/dist/components/WorkItemList.js.map +1 -1
  47. package/dist/index.js +20 -8
  48. package/dist/index.js.map +1 -1
  49. package/dist/storage/config.d.ts +61 -0
  50. package/dist/storage/config.js +309 -0
  51. package/dist/storage/config.js.map +1 -0
  52. package/dist/storage/db.d.ts +11 -0
  53. package/dist/storage/db.js +34 -0
  54. package/dist/storage/db.js.map +1 -0
  55. package/dist/storage/index.d.ts +73 -0
  56. package/dist/storage/index.js +966 -0
  57. package/dist/storage/index.js.map +1 -0
  58. package/dist/storage/mappers.d.ts +35 -0
  59. package/dist/storage/mappers.js +70 -0
  60. package/dist/storage/mappers.js.map +1 -0
  61. package/dist/storage/schema.d.ts +1844 -0
  62. package/dist/storage/schema.js +197 -0
  63. package/dist/storage/schema.js.map +1 -0
  64. package/dist/storage/syncQueue.d.ts +13 -0
  65. package/dist/storage/syncQueue.js +98 -0
  66. package/dist/storage/syncQueue.js.map +1 -0
  67. package/dist/storage/undo.d.ts +22 -0
  68. package/dist/storage/undo.js +129 -0
  69. package/dist/storage/undo.js.map +1 -0
  70. package/dist/stores/backendDataStore.d.ts +4 -1
  71. package/dist/stores/backendDataStore.js +61 -40
  72. package/dist/stores/backendDataStore.js.map +1 -1
  73. package/dist/stores/configStore.d.ts +3 -1
  74. package/dist/stores/configStore.js +25 -65
  75. package/dist/stores/configStore.js.map +1 -1
  76. package/dist/stores/filterStore.d.ts +2 -0
  77. package/dist/stores/filterStore.js +7 -2
  78. package/dist/stores/filterStore.js.map +1 -1
  79. package/dist/stores/uiStore.d.ts +0 -2
  80. package/dist/stores/uiStore.js.map +1 -1
  81. package/dist/stores/undoStore.d.ts +4 -0
  82. package/dist/stores/undoStore.js +32 -0
  83. package/dist/stores/undoStore.js.map +1 -1
  84. package/dist/sync/SyncManager.d.ts +3 -4
  85. package/dist/sync/SyncManager.js +78 -37
  86. package/dist/sync/SyncManager.js.map +1 -1
  87. package/dist/sync/types.d.ts +10 -1
  88. package/drizzle/0000_next_vance_astro.sql +183 -0
  89. package/drizzle/meta/0000_snapshot.json +1188 -0
  90. package/drizzle/meta/_journal.json +13 -0
  91. package/package.json +6 -1
  92. package/skills/config/SKILL.md +2 -2
  93. package/dist/backends/local/config.d.ts +0 -38
  94. package/dist/backends/local/config.js +0 -42
  95. package/dist/backends/local/config.js.map +0 -1
  96. package/dist/backends/local/index.d.ts +0 -45
  97. package/dist/backends/local/index.js +0 -291
  98. package/dist/backends/local/index.js.map +0 -1
  99. package/dist/sync/queue.d.ts +0 -12
  100. package/dist/sync/queue.js +0 -56
  101. package/dist/sync/queue.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tic",
3
3
  "description": "Issue tracking skills for Claude Code",
4
- "version": "1.35.0",
4
+ "version": "2.0.1",
5
5
  "author": {
6
6
  "name": "Sascha Krug"
7
7
  },
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # tic
2
2
 
3
- A terminal UI for issue tracking, built for developers who live in the terminal. Track work items across multiple backends — local markdown files, GitHub Issues, GitLab Issues, Azure DevOps Work Items, and Jira.
3
+ A terminal UI for issue tracking, built for developers who live in the terminal. Track work items across multiple backends — GitHub Issues, GitLab Issues, Azure DevOps Work Items, and Jira — with local SQLite storage.
4
4
 
5
5
  Built with TypeScript and [Ink](https://github.com/vadimdemedes/ink).
6
6
 
7
7
  ## Features
8
8
 
9
9
  - **Keyboard-driven TUI** — browse, create, edit, and manage work items without leaving the terminal
10
- - **Multiple backends** — local markdown, GitHub (via `gh`), GitLab (via `glab`), Azure DevOps (via `az`), Jira (via REST API)
10
+ - **Multiple backends** — GitHub (via `gh`), GitLab (via `glab`), Azure DevOps (via `az`), Jira (via REST API)
11
11
  - **Automatic backend detection** — selects backend based on git remote, or configure manually
12
- - **Local markdown storage** — work items stored as markdown files with YAML frontmatter in a `.tic/` directory
12
+ - **SQLite storage** — all data stored locally in `.tic/tic.db` with optional sync to remote backends
13
13
  - **CLI commands** — scriptable commands for all operations (`tic item list`, `tic item create`, etc.)
14
14
  - **Work item types** — organize by epic, issue, and task (configurable)
15
15
  - **Iterations** — group work into sprints or milestones
@@ -39,16 +39,18 @@ tic init # Initialize (auto-detects backend from git remote)
39
39
  tic # Launch the TUI
40
40
  ```
41
41
 
42
- For local storage, `tic init` creates a `.tic/` directory to store your work items. For GitHub, GitLab, or Azure DevOps projects, it detects the backend from the git remote automatically. You can also specify a backend explicitly:
42
+ `tic init` creates a `.tic/` directory with a SQLite database to store your work items. For GitHub, GitLab, or Azure DevOps projects, it detects the backend from the git remote automatically. You can also specify a backend explicitly:
43
43
 
44
44
  ```bash
45
45
  tic init --backend github
46
46
  tic init --backend gitlab
47
47
  tic init --backend azure
48
48
  tic init --backend jira
49
- tic init --backend local
49
+ tic init --backend none
50
50
  ```
51
51
 
52
+ The TUI also auto-initializes on first run if no `.tic/` directory exists.
53
+
52
54
  ## Usage
53
55
 
54
56
  ### List View
@@ -123,70 +125,22 @@ Deleting an item automatically cleans up references — children have their pare
123
125
 
124
126
  ## Storage
125
127
 
126
- Work items live in `.tic/` at the root of your project:
128
+ All data lives in `.tic/` at the root of your project:
127
129
 
128
130
  ```
129
131
  .tic/
130
- ├── config.yml # Types, statuses, iterations, settings
131
- ├── items/
132
- ├── 1.md # Work item #1
133
- ├── 2.md # Work item #2
134
- └── ...
135
- ├── templates/ # Work item templates
136
- │ └── bug-report.md # Template with YAML frontmatter
137
- └── trash/ # Soft-deleted items (for undo)
132
+ ├── tic.db # SQLite database (all items, config, undo log)
133
+ └── items/ # Markdown mirrors (for sync / human-readable export)
134
+ ├── 1.md
135
+ ├── 2.md
136
+ └── ...
138
137
  ```
139
138
 
140
- Each item is a markdown file with YAML frontmatter:
141
-
142
- ```markdown
143
- ---
144
- id: 1
145
- title: Implement user login
146
- type: task
147
- status: in-progress
148
- iteration: sprint-1
149
- priority: high
150
- assignee: alice
151
- labels: auth, backend
152
- parent: 3
153
- depends_on:
154
- - 2
155
- created: 2026-01-15T10:00:00.000Z
156
- updated: 2026-01-20T14:30:00.000Z
157
- ---
158
-
159
- Full description of the work item goes here.
160
-
161
- ## Comments
162
-
163
- ---
164
- author: alice
165
- date: 2026-01-18T09:00:00.000Z
166
-
167
- Decided to use JWT tokens for this.
168
- ```
139
+ The SQLite database (`.tic/tic.db`) is the single source of truth for work items, config, templates, and undo history. When a remote backend is configured, `SyncManager` also writes markdown mirrors to `.tic/items/` via `FilesBackend`.
169
140
 
170
- Configuration in `.tic/config.yml`:
171
-
172
- ```yaml
173
- types:
174
- - epic
175
- - issue
176
- - task
177
- statuses:
178
- - backlog
179
- - todo
180
- - in-progress
181
- - review
182
- - done
183
- iterations:
184
- - default
185
- current_iteration: default
186
- next_id: 1
187
- ```
141
+ Configuration (types, statuses, iterations, etc.) is stored in the `project_config` table and managed via the TUI settings screen or `tic config` CLI commands.
188
142
 
189
- You can edit these files directly they're plain text. Customize types, statuses, and iterations by editing `config.yml`.
143
+ If upgrading from a legacy `.tic/config.yml` setup, the database will automatically migrate the YAML config on first open.
190
144
 
191
145
  ## Claude Code Integration
192
146
 
@@ -240,7 +194,7 @@ Add `--json` to any command for machine-readable output, or `--quiet` to suppres
240
194
 
241
195
  | Backend | CLI Tool | Detection |
242
196
  |---------|----------|-----------|
243
- | Local markdown | — | Default fallback |
197
+ | Local only (SQLite) | — | Default fallback |
244
198
  | GitHub Issues | [`gh`](https://cli.github.com/) | `github.com` in git remote |
245
199
  | GitLab Issues | [`glab`](https://gitlab.com/gitlab-org/cli) | `gitlab.com` in git remote |
246
200
  | Azure DevOps Work Items | [`az`](https://learn.microsoft.com/en-us/cli/azure/) | `dev.azure.com` or `visualstudio.com` in git remote |
@@ -3,7 +3,8 @@ import { execFile } from 'node:child_process';
3
3
  * Maps each backend to the CLI binary it requires, or null if no CLI is needed.
4
4
  */
5
5
  export const BACKEND_CLI = {
6
- local: null,
6
+ none: null,
7
+ filesystem: null,
7
8
  github: 'gh',
8
9
  gitlab: 'glab',
9
10
  azure: 'az',
@@ -29,7 +30,8 @@ export async function checkBackendAvailability(backend) {
29
30
  */
30
31
  export async function checkAllBackendAvailability() {
31
32
  const backends = [
32
- 'local',
33
+ 'none',
34
+ 'filesystem',
33
35
  'github',
34
36
  'gitlab',
35
37
  'azure',
@@ -1 +1 @@
1
- {"version":3,"file":"availability.js","sourceRoot":"","sources":["../../src/backends/availability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAuC;IAC7D,KAAK,EAAE,IAAI;IACX,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAoB;IAEpB,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,QAAQ,CACpB,MAAM,EACN,CAAC,WAAW,CAAC,EACb,EAAE,OAAO,EAAE,IAAI,EAAE,EACjB,CAAC,KAAK,EAAE,EAAE;YACR,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC,CACF,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAG/C,MAAM,QAAQ,GAAkB;QAC9B,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,MAAM;KACP,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,wBAAwB,CAAC,CAAC,CAAC,CAAU,CAAC,CAC3E,CAAC;IACF,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAiC,CAAC;AACrE,CAAC"}
1
+ {"version":3,"file":"availability.js","sourceRoot":"","sources":["../../src/backends/availability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C;;GAEG;AACH,MAAM,CAAC,MAAM,WAAW,GAAuC;IAC7D,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;CACX,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAoB;IAEpB,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,QAAQ,CACpB,MAAM,EACN,CAAC,WAAW,CAAC,EACb,EAAE,OAAO,EAAE,IAAI,EAAE,EACjB,CAAC,KAAK,EAAE,EAAE;YACR,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC,CACF,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B;IAG/C,MAAM,QAAQ,GAAkB;QAC9B,MAAM;QACN,YAAY;QACZ,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,MAAM;KACP,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,wBAAwB,CAAC,CAAC,CAAC,CAAU,CAAC,CAC3E,CAAC;IACF,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAiC,CAAC;AACrE,CAAC"}
@@ -1,12 +1,14 @@
1
1
  import type { Backend } from './types.js';
2
- import { LocalBackend } from './local/index.js';
2
+ import type { SyncQueueAdapter } from '../sync/types.js';
3
3
  import { SyncManager } from '../sync/SyncManager.js';
4
- export declare const VALID_BACKENDS: readonly ["local", "github", "gitlab", "azure", "jira"];
4
+ export declare const VALID_BACKENDS: readonly ["none", "filesystem", "github", "gitlab", "azure", "jira"];
5
5
  export type BackendType = (typeof VALID_BACKENDS)[number];
6
6
  export declare function detectBackend(root: string): BackendType;
7
7
  export declare function createBackend(root: string): Promise<Backend>;
8
+ export declare function createRemoteBackend(root: string, backendType: string): Promise<Backend | null>;
8
9
  export interface BackendSetup {
9
- backend: LocalBackend;
10
+ backend: Backend;
10
11
  syncManager: SyncManager | null;
12
+ queue: SyncQueueAdapter | null;
11
13
  }
12
14
  export declare function createBackendWithSync(root: string): Promise<BackendSetup>;
@@ -1,10 +1,11 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { LocalBackend } from './local/index.js';
2
+ import { Storage } from '../storage/index.js';
3
+ import { SyncQueue } from '../storage/syncQueue.js';
3
4
  import { configStore } from '../stores/configStore.js';
4
5
  import { SyncManager } from '../sync/SyncManager.js';
5
- import { SyncQueueStore } from '../sync/queue.js';
6
6
  export const VALID_BACKENDS = [
7
- 'local',
7
+ 'none',
8
+ 'filesystem',
8
9
  'github',
9
10
  'gitlab',
10
11
  'azure',
@@ -29,16 +30,24 @@ export function detectBackend(root) {
29
30
  catch {
30
31
  // Not a git repo or git not available
31
32
  }
32
- return 'local';
33
+ return 'none';
33
34
  }
34
35
  export async function createBackend(root) {
36
+ const primary = Storage.create(root);
37
+ configStore.getState().setDatabase(primary.getDatabase());
35
38
  if (!configStore.getState().loaded) {
36
39
  await configStore.getState().init(root);
37
40
  }
38
- const backend = configStore.getState().config.backend ?? 'local';
39
- switch (backend) {
40
- case 'local':
41
- return LocalBackend.create(root);
41
+ return primary;
42
+ }
43
+ export async function createRemoteBackend(root, backendType) {
44
+ switch (backendType) {
45
+ case 'none':
46
+ return null;
47
+ case 'filesystem': {
48
+ const { FilesBackend } = await import('./files/index.js');
49
+ return new FilesBackend(root);
50
+ }
42
51
  case 'github': {
43
52
  const { GitHubBackend } = await import('./github/index.js');
44
53
  return new GitHubBackend(root);
@@ -56,47 +65,23 @@ export async function createBackend(root) {
56
65
  return JiraBackend.create(root);
57
66
  }
58
67
  default:
59
- throw new Error(`Unknown backend "${backend}". Valid backends: ${VALID_BACKENDS.join(', ')}`);
68
+ return null;
60
69
  }
61
70
  }
62
71
  export async function createBackendWithSync(root) {
72
+ const primary = Storage.create(root);
73
+ configStore.getState().setDatabase(primary.getDatabase());
63
74
  if (!configStore.getState().loaded) {
64
75
  await configStore.getState().init(root);
65
76
  }
66
- const backendType = configStore.getState().config.backend ?? 'local';
67
- const local = await LocalBackend.create(root, {
68
- tempIds: backendType !== 'local',
69
- });
70
- if (backendType === 'local') {
71
- return { backend: local, syncManager: null };
72
- }
73
- let remote;
74
- switch (backendType) {
75
- case 'github': {
76
- const { GitHubBackend } = await import('./github/index.js');
77
- remote = new GitHubBackend(root);
78
- break;
79
- }
80
- case 'gitlab': {
81
- const { GitLabBackend } = await import('./gitlab/index.js');
82
- remote = new GitLabBackend(root);
83
- break;
84
- }
85
- case 'azure': {
86
- const { AzureDevOpsBackend } = await import('./ado/index.js');
87
- remote = new AzureDevOpsBackend(root);
88
- break;
89
- }
90
- case 'jira': {
91
- const { JiraBackend } = await import('./jira/index.js');
92
- remote = await JiraBackend.create(root);
93
- break;
94
- }
95
- default:
96
- throw new Error(`Unknown backend "${backendType}". Valid backends: ${VALID_BACKENDS.join(', ')}`);
77
+ const config = configStore.getState().config;
78
+ const remote = await createRemoteBackend(root, config.backend ?? 'none');
79
+ let syncManager = null;
80
+ let queue = null;
81
+ if (remote) {
82
+ queue = new SyncQueue(primary.getDatabase());
83
+ syncManager = new SyncManager(primary, remote, queue);
97
84
  }
98
- const queueStore = new SyncQueueStore(root);
99
- const syncManager = new SyncManager(local, remote, queueStore);
100
- return { backend: local, syncManager };
85
+ return { backend: primary, syncManager, queue };
101
86
  }
102
87
  //# sourceMappingURL=factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/backends/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function contentHash(content: string): string;
@@ -0,0 +1,5 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function contentHash(content) {
3
+ return createHash('sha256').update(content).digest('hex');
4
+ }
5
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../../src/backends/files/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,48 @@
1
+ import { BaseBackend } from '../types.js';
2
+ import type { BackendCapabilities, SyncableBackend } from '../types.js';
3
+ import type { WorkItem, NewWorkItem, NewComment, Comment, Template } from '../../types.js';
4
+ /**
5
+ * FilesBackend is a filesystem-based sync destination.
6
+ *
7
+ * It delegates all I/O to the existing local/items.ts and local/templates.ts
8
+ * modules. Unlike LocalBackend, it does NOT:
9
+ * - Cache items (caching is the primary backend's job)
10
+ * - Validate relationships (that's the primary backend's job)
11
+ * - Manage config (config lives in the primary/SQLite)
12
+ * - Manage next_id (IDs come from the primary)
13
+ * - Support soft-delete (that's a primary backend concept)
14
+ *
15
+ * It implements SyncableBackend so it can be used as a sync destination
16
+ * with ID-preserving imports via importWorkItem.
17
+ */
18
+ export declare class FilesBackend extends BaseBackend implements SyncableBackend {
19
+ private root;
20
+ constructor(root: string);
21
+ getRoot(): string;
22
+ getCapabilities(): BackendCapabilities;
23
+ getStatuses(): Promise<string[]>;
24
+ getIterations(): Promise<string[]>;
25
+ getWorkItemTypes(): Promise<string[]>;
26
+ getAssignees(): Promise<string[]>;
27
+ getLabels(): Promise<string[]>;
28
+ getCurrentIteration(): Promise<string>;
29
+ setCurrentIteration(_name: string): Promise<void>;
30
+ listWorkItems(_iteration?: string): Promise<WorkItem[]>;
31
+ getWorkItem(id: string): Promise<WorkItem>;
32
+ createWorkItem(_data: NewWorkItem): Promise<WorkItem>;
33
+ updateWorkItem(id: string, data: Partial<WorkItem>): Promise<WorkItem>;
34
+ deleteWorkItem(id: string): Promise<void>;
35
+ /**
36
+ * Write a complete WorkItem preserving its existing ID.
37
+ * Used during sync to replicate items from primary to files.
38
+ */
39
+ importWorkItem(item: WorkItem): Promise<WorkItem>;
40
+ addComment(workItemId: string, comment: NewComment): Promise<Comment>;
41
+ getItemUrl(id: string): string;
42
+ openItem(_id: string): Promise<void>;
43
+ listTemplates(): Promise<Template[]>;
44
+ getTemplate(slug: string): Promise<Template>;
45
+ createTemplate(template: Template): Promise<Template>;
46
+ updateTemplate(oldSlug: string, template: Template): Promise<Template>;
47
+ deleteTemplate(slug: string): Promise<void>;
48
+ }
@@ -0,0 +1,174 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { BaseBackend, UnsupportedOperationError } from '../types.js';
4
+ import { readWorkItem, writeWorkItem, deleteWorkItem as removeWorkItemFile, listItemFiles, parseWorkItemFile, } from '../local/items.js';
5
+ import { listTemplates as listTemplateFiles, readTemplate, writeTemplate, deleteTemplate as removeTemplateFile, slugifyTemplateName, } from '../local/templates.js';
6
+ /**
7
+ * FilesBackend is a filesystem-based sync destination.
8
+ *
9
+ * It delegates all I/O to the existing local/items.ts and local/templates.ts
10
+ * modules. Unlike LocalBackend, it does NOT:
11
+ * - Cache items (caching is the primary backend's job)
12
+ * - Validate relationships (that's the primary backend's job)
13
+ * - Manage config (config lives in the primary/SQLite)
14
+ * - Manage next_id (IDs come from the primary)
15
+ * - Support soft-delete (that's a primary backend concept)
16
+ *
17
+ * It implements SyncableBackend so it can be used as a sync destination
18
+ * with ID-preserving imports via importWorkItem.
19
+ */
20
+ export class FilesBackend extends BaseBackend {
21
+ root;
22
+ constructor(root) {
23
+ super(0); // no TTL cache
24
+ this.root = root;
25
+ }
26
+ getRoot() {
27
+ return this.root;
28
+ }
29
+ getCapabilities() {
30
+ return {
31
+ relationships: true,
32
+ customTypes: true,
33
+ customStatuses: true,
34
+ iterations: true,
35
+ comments: true,
36
+ fields: {
37
+ priority: true,
38
+ assignee: true,
39
+ labels: true,
40
+ parent: true,
41
+ dependsOn: true,
42
+ },
43
+ templates: true,
44
+ templateFields: {
45
+ type: true,
46
+ status: true,
47
+ priority: true,
48
+ assignee: true,
49
+ labels: true,
50
+ iteration: true,
51
+ parent: true,
52
+ dependsOn: true,
53
+ description: true,
54
+ },
55
+ };
56
+ }
57
+ // --- Metadata methods: return empty since metadata is the primary's concern ---
58
+ // eslint-disable-next-line @typescript-eslint/require-await
59
+ async getStatuses() {
60
+ return [];
61
+ }
62
+ // eslint-disable-next-line @typescript-eslint/require-await
63
+ async getIterations() {
64
+ return [];
65
+ }
66
+ // eslint-disable-next-line @typescript-eslint/require-await
67
+ async getWorkItemTypes() {
68
+ return [];
69
+ }
70
+ // eslint-disable-next-line @typescript-eslint/require-await
71
+ async getAssignees() {
72
+ return [];
73
+ }
74
+ // eslint-disable-next-line @typescript-eslint/require-await
75
+ async getLabels() {
76
+ return [];
77
+ }
78
+ // eslint-disable-next-line @typescript-eslint/require-await
79
+ async getCurrentIteration() {
80
+ return '';
81
+ }
82
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
83
+ async setCurrentIteration(_name) {
84
+ // no-op: iterations are managed by the primary backend
85
+ }
86
+ // --- Work item CRUD ---
87
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
+ async listWorkItems(_iteration) {
89
+ const files = await listItemFiles(this.root);
90
+ const items = await Promise.all(files.map(async (f) => {
91
+ const raw = await fs.readFile(f, 'utf-8');
92
+ return parseWorkItemFile(raw);
93
+ }));
94
+ // No iteration filtering — that's the primary's job
95
+ return items;
96
+ }
97
+ async getWorkItem(id) {
98
+ return readWorkItem(this.root, id);
99
+ }
100
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await
101
+ async createWorkItem(_data) {
102
+ throw new UnsupportedOperationError('createWorkItem', 'FilesBackend');
103
+ }
104
+ async updateWorkItem(id, data) {
105
+ const existing = await this.getWorkItem(id);
106
+ const updated = {
107
+ ...existing,
108
+ ...data,
109
+ id, // preserve ID
110
+ updated: new Date().toISOString(),
111
+ };
112
+ await writeWorkItem(this.root, updated);
113
+ return updated;
114
+ }
115
+ async deleteWorkItem(id) {
116
+ await removeWorkItemFile(this.root, id);
117
+ // No relationship cleanup — that's the primary's job
118
+ }
119
+ /**
120
+ * Write a complete WorkItem preserving its existing ID.
121
+ * Used during sync to replicate items from primary to files.
122
+ */
123
+ async importWorkItem(item) {
124
+ await writeWorkItem(this.root, item);
125
+ return item;
126
+ }
127
+ // --- Comments ---
128
+ async addComment(workItemId, comment) {
129
+ const item = await this.getWorkItem(workItemId);
130
+ const newComment = {
131
+ author: comment.author,
132
+ date: new Date().toISOString(),
133
+ body: comment.body,
134
+ };
135
+ item.comments.push(newComment);
136
+ item.updated = new Date().toISOString();
137
+ await writeWorkItem(this.root, item);
138
+ return newComment;
139
+ }
140
+ // --- Item URL ---
141
+ getItemUrl(id) {
142
+ return path.resolve(this.root, '.tic', 'items', `${id}.md`);
143
+ }
144
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
145
+ async openItem(_id) {
146
+ // no-op: FilesBackend is a sync destination, not interactive
147
+ }
148
+ // --- Templates ---
149
+ async listTemplates() {
150
+ return listTemplateFiles(this.root);
151
+ }
152
+ async getTemplate(slug) {
153
+ return readTemplate(this.root, slug);
154
+ }
155
+ async createTemplate(template) {
156
+ const slug = slugifyTemplateName(template.name);
157
+ const t = { ...template, slug };
158
+ await writeTemplate(this.root, t);
159
+ return t;
160
+ }
161
+ async updateTemplate(oldSlug, template) {
162
+ const newSlug = slugifyTemplateName(template.name);
163
+ if (oldSlug !== newSlug) {
164
+ await removeTemplateFile(this.root, oldSlug);
165
+ }
166
+ const t = { ...template, slug: newSlug };
167
+ await writeTemplate(this.root, t);
168
+ return t;
169
+ }
170
+ async deleteTemplate(slug) {
171
+ await removeTemplateFile(this.root, slug);
172
+ }
173
+ }
174
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/files/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { TicDatabase } from '../../storage/db.js';
2
+ /** Compute content hashes for all .tic/items/*.md files on disk. */
3
+ export declare function computeFileHashes(root: string): Promise<Map<string, string>>;
4
+ /** Detect which files changed, were added, or were deleted since last sync. */
5
+ export declare function detectChanges(db: TicDatabase, root: string): Promise<{
6
+ changed: string[];
7
+ added: string[];
8
+ deleted: string[];
9
+ }>;
10
+ /** Update the stored hash for an item (upsert). */
11
+ export declare function updateSyncState(db: TicDatabase, itemId: string, hash: string): void;
12
+ /** Remove the stored hash for an item. */
13
+ export declare function removeSyncState(db: TicDatabase, itemId: string): void;
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { eq } from 'drizzle-orm';
4
+ import * as s from '../../storage/schema.js';
5
+ import { listItemFiles } from '../local/items.js';
6
+ import { contentHash } from './hash.js';
7
+ /**
8
+ * Extract the item ID from a full file path like `/foo/.tic/items/42.md`.
9
+ */
10
+ function idFromPath(filePath) {
11
+ return path.basename(filePath, '.md');
12
+ }
13
+ /** Compute content hashes for all .tic/items/*.md files on disk. */
14
+ export async function computeFileHashes(root) {
15
+ const files = await listItemFiles(root);
16
+ const hashes = new Map();
17
+ for (const file of files) {
18
+ const id = idFromPath(file);
19
+ const raw = await fs.readFile(file, 'utf-8');
20
+ hashes.set(id, contentHash(raw));
21
+ }
22
+ return hashes;
23
+ }
24
+ /** Detect which files changed, were added, or were deleted since last sync. */
25
+ export async function detectChanges(db, root) {
26
+ const currentHashes = await computeFileHashes(root);
27
+ // Get stored hashes from the file_sync_state table
28
+ const rows = db.select().from(s.fileSyncState).all();
29
+ const storedHashes = new Map();
30
+ for (const row of rows) {
31
+ storedHashes.set(row.itemId, row.hash);
32
+ }
33
+ const changed = [];
34
+ const added = [];
35
+ const deleted = [];
36
+ // Check current files against stored
37
+ for (const [id, hash] of currentHashes) {
38
+ const stored = storedHashes.get(id);
39
+ if (!stored) {
40
+ added.push(id);
41
+ }
42
+ else if (stored !== hash) {
43
+ changed.push(id);
44
+ }
45
+ }
46
+ // Check stored against current for deletions
47
+ for (const [id] of storedHashes) {
48
+ if (!currentHashes.has(id)) {
49
+ deleted.push(id);
50
+ }
51
+ }
52
+ return { changed, added, deleted };
53
+ }
54
+ /** Update the stored hash for an item (upsert). */
55
+ export function updateSyncState(db, itemId, hash) {
56
+ const now = new Date().toISOString();
57
+ db.insert(s.fileSyncState)
58
+ .values({ itemId, hash, syncedAt: now })
59
+ .onConflictDoUpdate({
60
+ target: s.fileSyncState.itemId,
61
+ set: { hash, syncedAt: now },
62
+ })
63
+ .run();
64
+ }
65
+ /** Remove the stored hash for an item. */
66
+ export function removeSyncState(db, itemId) {
67
+ db.delete(s.fileSyncState).where(eq(s.fileSyncState.itemId, itemId)).run();
68
+ }
69
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/backends/files/sync.ts"],"names":[],"mappings":"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"}
@@ -3,4 +3,4 @@ export interface JiraConfig {
3
3
  project: string;
4
4
  boardId?: number;
5
5
  }
6
- export declare function readJiraConfig(root: string): Promise<JiraConfig>;
6
+ export declare function readJiraConfig(_root: string): Promise<JiraConfig>;