@sascha384/tic 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.
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/app.d.ts +17 -0
- package/dist/app.js +27 -0
- package/dist/app.js.map +1 -0
- package/dist/backends/factory.d.ts +5 -0
- package/dist/backends/factory.js +42 -0
- package/dist/backends/factory.js.map +1 -0
- package/dist/backends/github/gh.d.ts +2 -0
- package/dist/backends/github/gh.js +17 -0
- package/dist/backends/github/gh.js.map +1 -0
- package/dist/backends/github/index.d.ts +26 -0
- package/dist/backends/github/index.js +188 -0
- package/dist/backends/github/index.js.map +1 -0
- package/dist/backends/github/mappers.d.ts +33 -0
- package/dist/backends/github/mappers.js +26 -0
- package/dist/backends/github/mappers.js.map +1 -0
- package/dist/backends/gitlab/glab.d.ts +2 -0
- package/dist/backends/gitlab/glab.js +17 -0
- package/dist/backends/gitlab/glab.js.map +1 -0
- package/dist/backends/gitlab/group.d.ts +1 -0
- package/dist/backends/gitlab/group.js +33 -0
- package/dist/backends/gitlab/group.js.map +1 -0
- package/dist/backends/gitlab/index.d.ts +29 -0
- package/dist/backends/gitlab/index.js +296 -0
- package/dist/backends/gitlab/index.js.map +1 -0
- package/dist/backends/gitlab/mappers.d.ts +43 -0
- package/dist/backends/gitlab/mappers.js +44 -0
- package/dist/backends/gitlab/mappers.js.map +1 -0
- package/dist/backends/local/config.d.ts +12 -0
- package/dist/backends/local/config.js +28 -0
- package/dist/backends/local/config.js.map +1 -0
- package/dist/backends/local/index.d.ts +26 -0
- package/dist/backends/local/index.js +212 -0
- package/dist/backends/local/index.js.map +1 -0
- package/dist/backends/local/items.d.ts +6 -0
- package/dist/backends/local/items.js +118 -0
- package/dist/backends/local/items.js.map +1 -0
- package/dist/backends/types.d.ts +56 -0
- package/dist/backends/types.js +39 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/cli/commands/config.d.ts +2 -0
- package/dist/cli/commands/config.js +40 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.js +12 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/item.d.ts +41 -0
- package/dist/cli/commands/item.js +80 -0
- package/dist/cli/commands/item.js.map +1 -0
- package/dist/cli/commands/iteration.d.ts +7 -0
- package/dist/cli/commands/iteration.js +10 -0
- package/dist/cli/commands/iteration.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +85 -0
- package/dist/cli/commands/mcp.js +448 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/format.d.ts +3 -0
- package/dist/cli/format.js +10 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +426 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/components/IterationPicker.d.ts +1 -0
- package/dist/components/IterationPicker.js +18 -0
- package/dist/components/IterationPicker.js.map +1 -0
- package/dist/components/Settings.d.ts +1 -0
- package/dist/components/Settings.js +40 -0
- package/dist/components/Settings.js.map +1 -0
- package/dist/components/WorkItemForm.d.ts +1 -0
- package/dist/components/WorkItemForm.js +270 -0
- package/dist/components/WorkItemForm.js.map +1 -0
- package/dist/components/WorkItemList.d.ts +1 -0
- package/dist/components/WorkItemList.js +219 -0
- package/dist/components/WorkItemList.js.map +1 -0
- package/dist/git.d.ts +38 -0
- package/dist/git.js +115 -0
- package/dist/git.js.map +1 -0
- package/dist/implement.d.ts +25 -0
- package/dist/implement.js +130 -0
- package/dist/implement.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { BaseBackend } from '../types.js';
|
|
2
|
+
import { readConfig, writeConfig } from './config.js';
|
|
3
|
+
import { readWorkItem, writeWorkItem, deleteWorkItem as removeWorkItemFile, listItemFiles, parseWorkItemFile, } from './items.js';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
export class LocalBackend extends BaseBackend {
|
|
8
|
+
root;
|
|
9
|
+
config;
|
|
10
|
+
constructor(root) {
|
|
11
|
+
super();
|
|
12
|
+
this.root = root;
|
|
13
|
+
this.config = readConfig(root);
|
|
14
|
+
}
|
|
15
|
+
getCapabilities() {
|
|
16
|
+
return {
|
|
17
|
+
relationships: true,
|
|
18
|
+
customTypes: true,
|
|
19
|
+
customStatuses: true,
|
|
20
|
+
iterations: true,
|
|
21
|
+
comments: true,
|
|
22
|
+
fields: {
|
|
23
|
+
priority: true,
|
|
24
|
+
assignee: true,
|
|
25
|
+
labels: true,
|
|
26
|
+
parent: true,
|
|
27
|
+
dependsOn: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
save() {
|
|
32
|
+
writeConfig(this.root, this.config);
|
|
33
|
+
}
|
|
34
|
+
getStatuses() {
|
|
35
|
+
return this.config.statuses;
|
|
36
|
+
}
|
|
37
|
+
getIterations() {
|
|
38
|
+
return this.config.iterations;
|
|
39
|
+
}
|
|
40
|
+
getWorkItemTypes() {
|
|
41
|
+
return this.config.types;
|
|
42
|
+
}
|
|
43
|
+
getCurrentIteration() {
|
|
44
|
+
return this.config.current_iteration;
|
|
45
|
+
}
|
|
46
|
+
setCurrentIteration(name) {
|
|
47
|
+
this.config.current_iteration = name;
|
|
48
|
+
if (!this.config.iterations.includes(name)) {
|
|
49
|
+
this.config.iterations.push(name);
|
|
50
|
+
}
|
|
51
|
+
this.save();
|
|
52
|
+
}
|
|
53
|
+
validateRelationships(id, parent, dependsOn) {
|
|
54
|
+
const all = this.listWorkItems();
|
|
55
|
+
const allIds = new Set(all.map((item) => item.id));
|
|
56
|
+
// Validate parent
|
|
57
|
+
if (parent !== null && parent !== undefined) {
|
|
58
|
+
if (parent === id) {
|
|
59
|
+
throw new Error(`Work item #${id} cannot be its own parent`);
|
|
60
|
+
}
|
|
61
|
+
if (!allIds.has(parent)) {
|
|
62
|
+
throw new Error(`Parent #${parent} does not exist`);
|
|
63
|
+
}
|
|
64
|
+
// Check for circular parent chain: walk up from proposed parent
|
|
65
|
+
let current = parent;
|
|
66
|
+
const visited = new Set();
|
|
67
|
+
while (current !== null) {
|
|
68
|
+
if (current === id) {
|
|
69
|
+
throw new Error(`Circular parent chain detected for #${id}`);
|
|
70
|
+
}
|
|
71
|
+
if (visited.has(current))
|
|
72
|
+
break;
|
|
73
|
+
visited.add(current);
|
|
74
|
+
const parentItem = all.find((item) => item.id === current);
|
|
75
|
+
current = parentItem?.parent ?? null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Validate dependsOn
|
|
79
|
+
if (dependsOn !== undefined) {
|
|
80
|
+
for (const depId of dependsOn) {
|
|
81
|
+
if (depId === id) {
|
|
82
|
+
throw new Error(`Work item #${id} cannot depend on itself`);
|
|
83
|
+
}
|
|
84
|
+
if (!allIds.has(depId)) {
|
|
85
|
+
throw new Error(`Dependency #${depId} does not exist`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Check for circular dependency chain: DFS from each dependency
|
|
89
|
+
const hasCycle = (startId, targetId) => {
|
|
90
|
+
const visited = new Set();
|
|
91
|
+
const stack = [startId];
|
|
92
|
+
while (stack.length > 0) {
|
|
93
|
+
const current = stack.pop();
|
|
94
|
+
if (current === targetId)
|
|
95
|
+
return true;
|
|
96
|
+
if (visited.has(current))
|
|
97
|
+
continue;
|
|
98
|
+
visited.add(current);
|
|
99
|
+
const item = all.find((i) => i.id === current);
|
|
100
|
+
if (item) {
|
|
101
|
+
for (const dep of item.dependsOn) {
|
|
102
|
+
stack.push(dep);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
for (const depId of dependsOn) {
|
|
109
|
+
if (hasCycle(depId, id)) {
|
|
110
|
+
throw new Error(`Circular dependency chain detected for #${id}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
listWorkItems(iteration) {
|
|
116
|
+
const files = listItemFiles(this.root);
|
|
117
|
+
const items = files.map((f) => {
|
|
118
|
+
const raw = fs.readFileSync(f, 'utf-8');
|
|
119
|
+
return parseWorkItemFile(raw);
|
|
120
|
+
});
|
|
121
|
+
if (iteration)
|
|
122
|
+
return items.filter((i) => i.iteration === iteration);
|
|
123
|
+
return items;
|
|
124
|
+
}
|
|
125
|
+
getWorkItem(id) {
|
|
126
|
+
return readWorkItem(this.root, id);
|
|
127
|
+
}
|
|
128
|
+
createWorkItem(data) {
|
|
129
|
+
this.validateFields(data);
|
|
130
|
+
const now = new Date().toISOString();
|
|
131
|
+
const id = String(this.config.next_id);
|
|
132
|
+
this.validateRelationships(id, data.parent, data.dependsOn);
|
|
133
|
+
const item = {
|
|
134
|
+
...data,
|
|
135
|
+
id,
|
|
136
|
+
created: now,
|
|
137
|
+
updated: now,
|
|
138
|
+
comments: [],
|
|
139
|
+
};
|
|
140
|
+
this.config.next_id = this.config.next_id + 1;
|
|
141
|
+
if (data.iteration && !this.config.iterations.includes(data.iteration)) {
|
|
142
|
+
this.config.iterations.push(data.iteration);
|
|
143
|
+
}
|
|
144
|
+
this.save();
|
|
145
|
+
writeWorkItem(this.root, item);
|
|
146
|
+
return item;
|
|
147
|
+
}
|
|
148
|
+
updateWorkItem(id, data) {
|
|
149
|
+
this.validateFields(data);
|
|
150
|
+
const item = this.getWorkItem(id);
|
|
151
|
+
this.validateRelationships(id, data.parent, data.dependsOn);
|
|
152
|
+
const updated = {
|
|
153
|
+
...item,
|
|
154
|
+
...data,
|
|
155
|
+
id,
|
|
156
|
+
updated: new Date().toISOString(),
|
|
157
|
+
};
|
|
158
|
+
writeWorkItem(this.root, updated);
|
|
159
|
+
return updated;
|
|
160
|
+
}
|
|
161
|
+
deleteWorkItem(id) {
|
|
162
|
+
removeWorkItemFile(this.root, id);
|
|
163
|
+
// Clean up references in other items
|
|
164
|
+
const all = this.listWorkItems();
|
|
165
|
+
for (const item of all) {
|
|
166
|
+
let changed = false;
|
|
167
|
+
if (item.parent === id) {
|
|
168
|
+
item.parent = null;
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
171
|
+
if (item.dependsOn.includes(id)) {
|
|
172
|
+
item.dependsOn = item.dependsOn.filter((d) => d !== id);
|
|
173
|
+
changed = true;
|
|
174
|
+
}
|
|
175
|
+
if (changed) {
|
|
176
|
+
writeWorkItem(this.root, item);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
addComment(workItemId, comment) {
|
|
181
|
+
const item = this.getWorkItem(workItemId);
|
|
182
|
+
const newComment = {
|
|
183
|
+
author: comment.author,
|
|
184
|
+
date: new Date().toISOString(),
|
|
185
|
+
body: comment.body,
|
|
186
|
+
};
|
|
187
|
+
item.comments.push(newComment);
|
|
188
|
+
item.updated = new Date().toISOString();
|
|
189
|
+
writeWorkItem(this.root, item);
|
|
190
|
+
return newComment;
|
|
191
|
+
}
|
|
192
|
+
getChildren(id) {
|
|
193
|
+
const all = this.listWorkItems();
|
|
194
|
+
return all.filter((item) => item.parent === id);
|
|
195
|
+
}
|
|
196
|
+
getDependents(id) {
|
|
197
|
+
const all = this.listWorkItems();
|
|
198
|
+
return all.filter((item) => item.dependsOn.includes(id));
|
|
199
|
+
}
|
|
200
|
+
getItemUrl(id) {
|
|
201
|
+
return path.resolve(this.root, '.tic', 'items', `${id}.md`);
|
|
202
|
+
}
|
|
203
|
+
openItem(id) {
|
|
204
|
+
const filePath = this.getItemUrl(id);
|
|
205
|
+
if (!fs.existsSync(filePath)) {
|
|
206
|
+
throw new Error(`Work item #${id} does not exist`);
|
|
207
|
+
}
|
|
208
|
+
const editor = process.env['VISUAL'] || process.env['EDITOR'] || 'vi';
|
|
209
|
+
execSync(`${editor} ${filePath}`, { stdio: 'inherit' });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/local/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAe,MAAM,aAAa,CAAC;AACnE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,IAAI,kBAAkB,EACpC,aAAa,EACb,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,OAAO,YAAa,SAAQ,WAAW;IACnC,IAAI,CAAS;IACb,MAAM,CAAS;IAEvB,YAAY,IAAY;QACtB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,eAAe;QACb,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,WAAW,EAAE,IAAI;YACjB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE;gBACN,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE,IAAI;gBACZ,MAAM,EAAE,IAAI;gBACZ,SAAS,EAAE,IAAI;aAChB;SACF,CAAC;IACJ,CAAC;IAEO,IAAI;QACV,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAED,aAAa;QACX,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC;IAChC,CAAC;IAED,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IAC3B,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC;IACvC,CAAC;IAED,mBAAmB,CAAC,IAAY;QAC9B,IAAI,CAAC,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAEO,qBAAqB,CAC3B,EAAU,EACV,MAAiC,EACjC,SAA+B;QAE/B,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAEnD,kBAAkB;QAClB,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC5C,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,2BAA2B,CAAC,CAAC;YAC/D,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,iBAAiB,CAAC,CAAC;YACtD,CAAC;YACD,gEAAgE;YAChE,IAAI,OAAO,GAAkB,MAAM,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;YAClC,OAAO,OAAO,KAAK,IAAI,EAAE,CAAC;gBACxB,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;oBACnB,MAAM,IAAI,KAAK,CAAC,uCAAuC,EAAE,EAAE,CAAC,CAAC;gBAC/D,CAAC;gBACD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;oBAAE,MAAM;gBAChC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACrB,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;gBAC3D,OAAO,GAAG,UAAU,EAAE,MAAM,IAAI,IAAI,CAAC;YACvC,CAAC;QACH,CAAC;QAED,qBAAqB;QACrB,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC9B,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;oBACjB,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;gBAC9D,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBACvB,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,iBAAiB,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;YACD,gEAAgE;YAChE,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAE,QAAgB,EAAW,EAAE;gBAC9D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;gBAClC,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;gBACxB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;oBAC7B,IAAI,OAAO,KAAK,QAAQ;wBAAE,OAAO,IAAI,CAAC;oBACtC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;wBAAE,SAAS;oBACnC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACrB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;oBAC/C,IAAI,IAAI,EAAE,CAAC;wBACT,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;4BACjC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;wBAClB,CAAC;oBACH,CAAC;gBACH,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;YACF,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC9B,IAAI,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC;oBACxB,MAAM,IAAI,KAAK,CAAC,2CAA2C,EAAE,EAAE,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,aAAa,CAAC,SAAkB;QAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YAC5B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACxC,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,IAAI,SAAS;YAAE,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC;QACrE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,cAAc,CAAC,IAAiB;QAC9B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAa;YACrB,GAAG,IAAI;YACP,EAAE;YACF,OAAO,EAAE,GAAG;YACZ,OAAO,EAAE,GAAG;YACZ,QAAQ,EAAE,EAAE;SACb,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;QAC9C,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACvE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,cAAc,CAAC,EAAU,EAAE,IAAuB;QAChD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG;YACd,GAAG,IAAI;YACP,GAAG,IAAI;YACP,EAAE;YACF,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SAClC,CAAC;QACF,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAClC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,cAAc,CAAC,EAAU;QACvB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClC,qCAAqC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;YACvB,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;gBACvB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;gBACxD,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,UAAU,CAAC,UAAkB,EAAE,OAAmB;QAChD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,UAAU,GAAY;YAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAC;QACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACxC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,EAAU;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IAED,QAAQ,CAAC,EAAU;QACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;QACrD,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC;QACtE,QAAQ,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WorkItem } from '../../types.js';
|
|
2
|
+
export declare function listItemFiles(root: string): string[];
|
|
3
|
+
export declare function readWorkItem(root: string, id: string): WorkItem;
|
|
4
|
+
export declare function parseWorkItemFile(raw: string): WorkItem;
|
|
5
|
+
export declare function writeWorkItem(root: string, item: WorkItem): void;
|
|
6
|
+
export declare function deleteWorkItem(root: string, id: string): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
function itemsDir(root) {
|
|
5
|
+
return path.join(root, '.tic', 'items');
|
|
6
|
+
}
|
|
7
|
+
function itemPath(root, id) {
|
|
8
|
+
return path.join(itemsDir(root), `${id}.md`);
|
|
9
|
+
}
|
|
10
|
+
export function listItemFiles(root) {
|
|
11
|
+
const dir = itemsDir(root);
|
|
12
|
+
if (!fs.existsSync(dir))
|
|
13
|
+
return [];
|
|
14
|
+
return fs
|
|
15
|
+
.readdirSync(dir)
|
|
16
|
+
.filter((f) => f.endsWith('.md'))
|
|
17
|
+
.map((f) => path.join(dir, f));
|
|
18
|
+
}
|
|
19
|
+
function serializeComments(comments) {
|
|
20
|
+
if (comments.length === 0)
|
|
21
|
+
return '';
|
|
22
|
+
const parts = comments.map((c) => `---\nauthor: ${c.author}\ndate: ${c.date}\n\n${c.body}`);
|
|
23
|
+
return '\n\n## Comments\n\n' + parts.join('\n\n');
|
|
24
|
+
}
|
|
25
|
+
function parseComments(content) {
|
|
26
|
+
const marker = '## Comments';
|
|
27
|
+
const idx = content.indexOf(marker);
|
|
28
|
+
if (idx === -1)
|
|
29
|
+
return { description: content.trim(), comments: [] };
|
|
30
|
+
const description = content.slice(0, idx).trim();
|
|
31
|
+
const commentsRaw = content.slice(idx + marker.length).trim();
|
|
32
|
+
const blocks = commentsRaw.split(/\n---\n/).filter((b) => b.trim());
|
|
33
|
+
const comments = blocks.map((block) => {
|
|
34
|
+
const lines = block.trim().split('\n');
|
|
35
|
+
let author = '';
|
|
36
|
+
let date = '';
|
|
37
|
+
const bodyLines = [];
|
|
38
|
+
let pastMeta = false;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (!pastMeta && line === '---') {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!pastMeta && line.startsWith('author: ')) {
|
|
44
|
+
author = line.slice('author: '.length).trim();
|
|
45
|
+
}
|
|
46
|
+
else if (!pastMeta && line.startsWith('date: ')) {
|
|
47
|
+
date = line.slice('date: '.length).trim();
|
|
48
|
+
}
|
|
49
|
+
else if (!pastMeta && line === '') {
|
|
50
|
+
pastMeta = true;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
pastMeta = true;
|
|
54
|
+
bodyLines.push(line);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { author, date, body: bodyLines.join('\n').trim() };
|
|
58
|
+
});
|
|
59
|
+
return { description, comments };
|
|
60
|
+
}
|
|
61
|
+
export function readWorkItem(root, id) {
|
|
62
|
+
const raw = fs.readFileSync(itemPath(root, id), 'utf-8');
|
|
63
|
+
return parseWorkItemFile(raw);
|
|
64
|
+
}
|
|
65
|
+
export function parseWorkItemFile(raw) {
|
|
66
|
+
const parsed = matter(raw);
|
|
67
|
+
const data = parsed.data;
|
|
68
|
+
const { description, comments } = parseComments(parsed.content);
|
|
69
|
+
return {
|
|
70
|
+
id: String(data['id']),
|
|
71
|
+
title: data['title'],
|
|
72
|
+
type: data['type'] || 'issue',
|
|
73
|
+
status: data['status'],
|
|
74
|
+
iteration: data['iteration'],
|
|
75
|
+
priority: data['priority'],
|
|
76
|
+
assignee: data['assignee'] || '',
|
|
77
|
+
labels: data['labels'] || [],
|
|
78
|
+
created: data['created'],
|
|
79
|
+
updated: data['updated'],
|
|
80
|
+
parent: data['parent'] != null ? String(data['parent']) : null,
|
|
81
|
+
dependsOn: Array.isArray(data['depends_on'])
|
|
82
|
+
? data['depends_on'].map(String)
|
|
83
|
+
: [],
|
|
84
|
+
description,
|
|
85
|
+
comments,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function writeWorkItem(root, item) {
|
|
89
|
+
const dir = itemsDir(root);
|
|
90
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
91
|
+
const frontmatter = {
|
|
92
|
+
id: item.id,
|
|
93
|
+
title: item.title,
|
|
94
|
+
type: item.type,
|
|
95
|
+
status: item.status,
|
|
96
|
+
iteration: item.iteration,
|
|
97
|
+
priority: item.priority,
|
|
98
|
+
assignee: item.assignee,
|
|
99
|
+
labels: item.labels,
|
|
100
|
+
created: item.created,
|
|
101
|
+
updated: item.updated,
|
|
102
|
+
};
|
|
103
|
+
if (item.parent !== null) {
|
|
104
|
+
frontmatter['parent'] = item.parent;
|
|
105
|
+
}
|
|
106
|
+
if (item.dependsOn.length > 0) {
|
|
107
|
+
frontmatter['depends_on'] = item.dependsOn;
|
|
108
|
+
}
|
|
109
|
+
const body = item.description + serializeComments(item.comments);
|
|
110
|
+
const content = matter.stringify(body, frontmatter);
|
|
111
|
+
fs.writeFileSync(itemPath(root, item.id), content);
|
|
112
|
+
}
|
|
113
|
+
export function deleteWorkItem(root, id) {
|
|
114
|
+
const p = itemPath(root, id);
|
|
115
|
+
if (fs.existsSync(p))
|
|
116
|
+
fs.unlinkSync(p);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.js","sourceRoot":"","sources":["../../../src/backends/local/items.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { WorkItem, NewWorkItem, NewComment, Comment } from '../types.js';
|
|
2
|
+
export interface BackendCapabilities {
|
|
3
|
+
relationships: boolean;
|
|
4
|
+
customTypes: boolean;
|
|
5
|
+
customStatuses: boolean;
|
|
6
|
+
iterations: boolean;
|
|
7
|
+
comments: boolean;
|
|
8
|
+
fields: {
|
|
9
|
+
priority: boolean;
|
|
10
|
+
assignee: boolean;
|
|
11
|
+
labels: boolean;
|
|
12
|
+
parent: boolean;
|
|
13
|
+
dependsOn: boolean;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class UnsupportedOperationError extends Error {
|
|
17
|
+
constructor(operation: string, backend: string);
|
|
18
|
+
}
|
|
19
|
+
export interface Backend {
|
|
20
|
+
getCapabilities(): BackendCapabilities;
|
|
21
|
+
getStatuses(): string[];
|
|
22
|
+
getIterations(): string[];
|
|
23
|
+
getWorkItemTypes(): string[];
|
|
24
|
+
getCurrentIteration(): string;
|
|
25
|
+
setCurrentIteration(name: string): void;
|
|
26
|
+
listWorkItems(iteration?: string): WorkItem[];
|
|
27
|
+
getWorkItem(id: string): WorkItem;
|
|
28
|
+
createWorkItem(data: NewWorkItem): WorkItem;
|
|
29
|
+
updateWorkItem(id: string, data: Partial<WorkItem>): WorkItem;
|
|
30
|
+
deleteWorkItem(id: string): void;
|
|
31
|
+
addComment(workItemId: string, comment: NewComment): Comment;
|
|
32
|
+
getChildren(id: string): WorkItem[];
|
|
33
|
+
getDependents(id: string): WorkItem[];
|
|
34
|
+
getItemUrl(id: string): string;
|
|
35
|
+
openItem(id: string): void;
|
|
36
|
+
}
|
|
37
|
+
export declare abstract class BaseBackend implements Backend {
|
|
38
|
+
abstract getCapabilities(): BackendCapabilities;
|
|
39
|
+
abstract getStatuses(): string[];
|
|
40
|
+
abstract getIterations(): string[];
|
|
41
|
+
abstract getWorkItemTypes(): string[];
|
|
42
|
+
abstract getCurrentIteration(): string;
|
|
43
|
+
abstract setCurrentIteration(name: string): void;
|
|
44
|
+
abstract listWorkItems(iteration?: string): WorkItem[];
|
|
45
|
+
abstract getWorkItem(id: string): WorkItem;
|
|
46
|
+
abstract createWorkItem(data: NewWorkItem): WorkItem;
|
|
47
|
+
abstract updateWorkItem(id: string, data: Partial<WorkItem>): WorkItem;
|
|
48
|
+
abstract deleteWorkItem(id: string): void;
|
|
49
|
+
abstract addComment(workItemId: string, comment: NewComment): Comment;
|
|
50
|
+
abstract getChildren(id: string): WorkItem[];
|
|
51
|
+
abstract getDependents(id: string): WorkItem[];
|
|
52
|
+
abstract getItemUrl(id: string): string;
|
|
53
|
+
abstract openItem(id: string): void;
|
|
54
|
+
protected validateFields(data: Partial<NewWorkItem>): void;
|
|
55
|
+
protected assertSupported(capability: boolean, operation: string): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export class UnsupportedOperationError extends Error {
|
|
2
|
+
constructor(operation, backend) {
|
|
3
|
+
super(`${operation} is not supported by the ${backend} backend`);
|
|
4
|
+
this.name = 'UnsupportedOperationError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class BaseBackend {
|
|
8
|
+
validateFields(data) {
|
|
9
|
+
const caps = this.getCapabilities();
|
|
10
|
+
const backendName = this.constructor.name;
|
|
11
|
+
if (!caps.fields.priority &&
|
|
12
|
+
data.priority !== undefined &&
|
|
13
|
+
data.priority !== 'medium') {
|
|
14
|
+
throw new UnsupportedOperationError('priority', backendName);
|
|
15
|
+
}
|
|
16
|
+
if (!caps.fields.assignee && data.assignee && data.assignee !== '') {
|
|
17
|
+
throw new UnsupportedOperationError('assignee', backendName);
|
|
18
|
+
}
|
|
19
|
+
if (!caps.fields.labels && data.labels && data.labels.length > 0) {
|
|
20
|
+
throw new UnsupportedOperationError('labels', backendName);
|
|
21
|
+
}
|
|
22
|
+
if (!caps.fields.parent &&
|
|
23
|
+
data.parent !== undefined &&
|
|
24
|
+
data.parent !== null) {
|
|
25
|
+
throw new UnsupportedOperationError('parent', backendName);
|
|
26
|
+
}
|
|
27
|
+
if (!caps.fields.dependsOn &&
|
|
28
|
+
data.dependsOn !== undefined &&
|
|
29
|
+
data.dependsOn.length > 0) {
|
|
30
|
+
throw new UnsupportedOperationError('dependsOn', backendName);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
assertSupported(capability, operation) {
|
|
34
|
+
if (!capability) {
|
|
35
|
+
throw new UnsupportedOperationError(operation, this.constructor.name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/backends/types.ts"],"names":[],"mappings":"AAiBA,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YAAY,SAAiB,EAAE,OAAe;QAC5C,KAAK,CAAC,GAAG,SAAS,4BAA4B,OAAO,UAAU,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAqBD,MAAM,OAAgB,WAAW;IAkBrB,cAAc,CAAC,IAA0B;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACpC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAE1C,IACE,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ;YACrB,IAAI,CAAC,QAAQ,KAAK,SAAS;YAC3B,IAAI,CAAC,QAAQ,KAAK,QAAQ,EAC1B,CAAC;YACD,MAAM,IAAI,yBAAyB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;YACnE,MAAM,IAAI,yBAAyB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,yBAAyB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC7D,CAAC;QACD,IACE,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;YACnB,IAAI,CAAC,MAAM,KAAK,SAAS;YACzB,IAAI,CAAC,MAAM,KAAK,IAAI,EACpB,CAAC;YACD,MAAM,IAAI,yBAAyB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC7D,CAAC;QACD,IACE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS;YACtB,IAAI,CAAC,SAAS,KAAK,SAAS;YAC5B,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EACzB,CAAC;YACD,MAAM,IAAI,yBAAyB,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAES,eAAe,CAAC,UAAmB,EAAE,SAAiB;QAC9D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,yBAAyB,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readConfig, writeConfig } from '../../backends/local/config.js';
|
|
2
|
+
import { VALID_BACKENDS } from '../../backends/factory.js';
|
|
3
|
+
const READABLE_KEYS = [
|
|
4
|
+
'backend',
|
|
5
|
+
'current_iteration',
|
|
6
|
+
'types',
|
|
7
|
+
'statuses',
|
|
8
|
+
'iterations',
|
|
9
|
+
'next_id',
|
|
10
|
+
];
|
|
11
|
+
function isValidKey(key) {
|
|
12
|
+
return READABLE_KEYS.includes(key);
|
|
13
|
+
}
|
|
14
|
+
export function runConfigGet(root, key) {
|
|
15
|
+
if (!isValidKey(key)) {
|
|
16
|
+
throw new Error(`Unknown config key "${key}". Valid keys: ${READABLE_KEYS.join(', ')}`);
|
|
17
|
+
}
|
|
18
|
+
const config = readConfig(root);
|
|
19
|
+
return config[key];
|
|
20
|
+
}
|
|
21
|
+
export function runConfigSet(root, key, value) {
|
|
22
|
+
if (!isValidKey(key)) {
|
|
23
|
+
throw new Error(`Unknown config key "${key}". Valid keys: ${READABLE_KEYS.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
const config = readConfig(root);
|
|
26
|
+
if (key === 'backend') {
|
|
27
|
+
if (!VALID_BACKENDS.includes(value)) {
|
|
28
|
+
throw new Error(`Invalid backend "${value}". Valid backends: ${VALID_BACKENDS.join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
config.backend = value;
|
|
31
|
+
}
|
|
32
|
+
else if (key === 'current_iteration') {
|
|
33
|
+
config.current_iteration = value;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new Error(`Config key "${key}" is read-only`);
|
|
37
|
+
}
|
|
38
|
+
writeConfig(root, config);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,MAAM,aAAa,GAAG;IACpB,SAAS;IACT,mBAAmB;IACnB,OAAO;IACP,UAAU;IACV,YAAY;IACZ,SAAS;CACD,CAAC;AAIX,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAQ,aAAmC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,GAAW;IACpD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,uBAAuB,GAAG,kBAAkB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,GAAW,EAAE,KAAa;IACnE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CACb,uBAAuB,GAAG,kBAAkB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,IAAI,CAAE,cAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CACb,oBAAoB,KAAK,sBAAsB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3E,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;IACzB,CAAC;SAAM,IAAI,GAAG,KAAK,mBAAmB,EAAE,CAAC;QACvC,MAAM,CAAC,iBAAiB,GAAG,KAAK,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,eAAe,GAAG,gBAAgB,CAAC,CAAC;IACtD,CAAC;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeConfig, defaultConfig } from '../../backends/local/config.js';
|
|
4
|
+
export function runInit(root, backend) {
|
|
5
|
+
const configPath = path.join(root, '.tic', 'config.yml');
|
|
6
|
+
if (fs.existsSync(configPath)) {
|
|
7
|
+
return { success: true, alreadyExists: true };
|
|
8
|
+
}
|
|
9
|
+
writeConfig(root, { ...defaultConfig, backend: backend ?? 'local' });
|
|
10
|
+
return { success: true, alreadyExists: false };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAO5E,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,OAAgB;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IACzD,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,WAAW,CAAC,IAAI,EAAE,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Backend } from '../../backends/types.js';
|
|
2
|
+
import type { WorkItem, Comment } from '../../types.js';
|
|
3
|
+
export interface ItemCreateOptions {
|
|
4
|
+
type?: string;
|
|
5
|
+
status?: string;
|
|
6
|
+
priority?: string;
|
|
7
|
+
assignee?: string;
|
|
8
|
+
labels?: string;
|
|
9
|
+
iteration?: string;
|
|
10
|
+
parent?: string;
|
|
11
|
+
dependsOn?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ItemListOptions {
|
|
15
|
+
status?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
iteration?: string;
|
|
18
|
+
all?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ItemUpdateOptions {
|
|
21
|
+
title?: string;
|
|
22
|
+
type?: string;
|
|
23
|
+
status?: string;
|
|
24
|
+
priority?: string;
|
|
25
|
+
assignee?: string;
|
|
26
|
+
labels?: string;
|
|
27
|
+
iteration?: string;
|
|
28
|
+
parent?: string;
|
|
29
|
+
dependsOn?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ItemCommentOptions {
|
|
33
|
+
author?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function runItemCreate(backend: Backend, title: string, opts: ItemCreateOptions): WorkItem;
|
|
36
|
+
export declare function runItemList(backend: Backend, opts: ItemListOptions): WorkItem[];
|
|
37
|
+
export declare function runItemShow(backend: Backend, id: string): WorkItem;
|
|
38
|
+
export declare function runItemUpdate(backend: Backend, id: string, opts: ItemUpdateOptions): WorkItem;
|
|
39
|
+
export declare function runItemDelete(backend: Backend, id: string): void;
|
|
40
|
+
export declare function runItemOpen(backend: Backend, id: string): void;
|
|
41
|
+
export declare function runItemComment(backend: Backend, id: string, text: string, opts: ItemCommentOptions): Comment;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function runItemCreate(backend, title, opts) {
|
|
2
|
+
const statuses = backend.getStatuses();
|
|
3
|
+
const types = backend.getWorkItemTypes();
|
|
4
|
+
return backend.createWorkItem({
|
|
5
|
+
title,
|
|
6
|
+
type: opts.type ?? (types.includes('task') ? 'task' : types[0]),
|
|
7
|
+
status: opts.status ?? statuses[0],
|
|
8
|
+
priority: opts.priority ?? 'medium',
|
|
9
|
+
assignee: opts.assignee ?? '',
|
|
10
|
+
labels: opts.labels ? opts.labels.split(',').map((l) => l.trim()) : [],
|
|
11
|
+
iteration: opts.iteration ?? backend.getCurrentIteration(),
|
|
12
|
+
parent: opts.parent ? opts.parent : null,
|
|
13
|
+
dependsOn: opts.dependsOn
|
|
14
|
+
? opts.dependsOn
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((d) => d.trim())
|
|
17
|
+
.filter((d) => d.length > 0)
|
|
18
|
+
: [],
|
|
19
|
+
description: opts.description ?? '',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function runItemList(backend, opts) {
|
|
23
|
+
const iteration = opts.all
|
|
24
|
+
? undefined
|
|
25
|
+
: (opts.iteration ?? backend.getCurrentIteration());
|
|
26
|
+
let items = backend.listWorkItems(iteration);
|
|
27
|
+
if (opts.status) {
|
|
28
|
+
items = items.filter((i) => i.status === opts.status);
|
|
29
|
+
}
|
|
30
|
+
if (opts.type) {
|
|
31
|
+
items = items.filter((i) => i.type === opts.type);
|
|
32
|
+
}
|
|
33
|
+
return items;
|
|
34
|
+
}
|
|
35
|
+
export function runItemShow(backend, id) {
|
|
36
|
+
return backend.getWorkItem(id);
|
|
37
|
+
}
|
|
38
|
+
export function runItemUpdate(backend, id, opts) {
|
|
39
|
+
const data = {};
|
|
40
|
+
if (opts.title !== undefined)
|
|
41
|
+
data.title = opts.title;
|
|
42
|
+
if (opts.type !== undefined)
|
|
43
|
+
data.type = opts.type;
|
|
44
|
+
if (opts.status !== undefined)
|
|
45
|
+
data.status = opts.status;
|
|
46
|
+
if (opts.priority !== undefined)
|
|
47
|
+
data.priority = opts.priority;
|
|
48
|
+
if (opts.assignee !== undefined)
|
|
49
|
+
data.assignee = opts.assignee;
|
|
50
|
+
if (opts.labels !== undefined)
|
|
51
|
+
data.labels = opts.labels.split(',').map((l) => l.trim());
|
|
52
|
+
if (opts.iteration !== undefined)
|
|
53
|
+
data.iteration = opts.iteration;
|
|
54
|
+
if (opts.parent !== undefined)
|
|
55
|
+
data.parent = opts.parent === '' ? null : opts.parent;
|
|
56
|
+
if (opts.dependsOn !== undefined)
|
|
57
|
+
data.dependsOn =
|
|
58
|
+
opts.dependsOn === ''
|
|
59
|
+
? []
|
|
60
|
+
: opts.dependsOn
|
|
61
|
+
.split(',')
|
|
62
|
+
.map((d) => d.trim())
|
|
63
|
+
.filter((d) => d.length > 0);
|
|
64
|
+
if (opts.description !== undefined)
|
|
65
|
+
data.description = opts.description;
|
|
66
|
+
return backend.updateWorkItem(id, data);
|
|
67
|
+
}
|
|
68
|
+
export function runItemDelete(backend, id) {
|
|
69
|
+
backend.deleteWorkItem(id);
|
|
70
|
+
}
|
|
71
|
+
export function runItemOpen(backend, id) {
|
|
72
|
+
backend.openItem(id);
|
|
73
|
+
}
|
|
74
|
+
export function runItemComment(backend, id, text, opts) {
|
|
75
|
+
return backend.addComment(id, {
|
|
76
|
+
author: opts.author ?? 'anonymous',
|
|
77
|
+
body: text,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=item.js.map
|