aglit 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/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/new.d.ts +3 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/project-new.d.ts +3 -0
- package/dist/commands/project-new.d.ts.map +1 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1206 -0
- package/dist/index.js.map +20 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/workspace.d.ts +3 -0
- package/dist/workspace.d.ts.map +1 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { run } from "@stricli/core";
|
|
6
|
+
|
|
7
|
+
// src/app.ts
|
|
8
|
+
import { buildApplication } from "@stricli/core";
|
|
9
|
+
|
|
10
|
+
// src/routes.ts
|
|
11
|
+
import { buildRouteMap } from "@stricli/core";
|
|
12
|
+
|
|
13
|
+
// src/commands/init.ts
|
|
14
|
+
import { buildCommand } from "@stricli/core";
|
|
15
|
+
|
|
16
|
+
// ../sdk/dist/index.js
|
|
17
|
+
import * as fs from "fs/promises";
|
|
18
|
+
import * as path2 from "path";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import * as path3 from "path";
|
|
21
|
+
import { randomBytes } from "crypto";
|
|
22
|
+
import * as fs2 from "fs/promises";
|
|
23
|
+
import * as path4 from "path";
|
|
24
|
+
var AGLIT_DIRNAME = ".aglit";
|
|
25
|
+
var ISSUES_DIRNAME = "issues";
|
|
26
|
+
var PROJECTS_DIRNAME = "projects";
|
|
27
|
+
var CONFIG_FILENAME = "config.json";
|
|
28
|
+
var LOCK_FILENAME = ".lock";
|
|
29
|
+
var CONFIG_SCHEMA = "aglit.config.v1";
|
|
30
|
+
var ISSUE_SCHEMA = "aglit.issue.md.v1";
|
|
31
|
+
var PROJECT_SCHEMA = "aglit.project.md.v1";
|
|
32
|
+
var ISSUE_STATUSES = [
|
|
33
|
+
"inbox",
|
|
34
|
+
"planned",
|
|
35
|
+
"active",
|
|
36
|
+
"blocked",
|
|
37
|
+
"done",
|
|
38
|
+
"canceled"
|
|
39
|
+
];
|
|
40
|
+
var PRIORITY_LEVELS = ["none", "low", "medium", "high"];
|
|
41
|
+
function resolveRoot(rootDir) {
|
|
42
|
+
return rootDir ?? process.cwd();
|
|
43
|
+
}
|
|
44
|
+
function aglitDir(rootDir) {
|
|
45
|
+
return path.join(resolveRoot(rootDir), AGLIT_DIRNAME);
|
|
46
|
+
}
|
|
47
|
+
function issuesDir(rootDir) {
|
|
48
|
+
return path.join(aglitDir(rootDir), ISSUES_DIRNAME);
|
|
49
|
+
}
|
|
50
|
+
function projectsDir(rootDir) {
|
|
51
|
+
return path.join(aglitDir(rootDir), PROJECTS_DIRNAME);
|
|
52
|
+
}
|
|
53
|
+
function configPath(rootDir) {
|
|
54
|
+
return path.join(aglitDir(rootDir), CONFIG_FILENAME);
|
|
55
|
+
}
|
|
56
|
+
function lockPath(rootDir) {
|
|
57
|
+
return path.join(aglitDir(rootDir), LOCK_FILENAME);
|
|
58
|
+
}
|
|
59
|
+
function issuePath(rootDir, key) {
|
|
60
|
+
return path.join(issuesDir(rootDir), `${key}.md`);
|
|
61
|
+
}
|
|
62
|
+
function projectPath(rootDir, slug) {
|
|
63
|
+
return path.join(projectsDir(rootDir), `${slug}.md`);
|
|
64
|
+
}
|
|
65
|
+
async function ensureDir(dirPath) {
|
|
66
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
async function ensureLayout(rootDir) {
|
|
69
|
+
await ensureDir(aglitDir(rootDir));
|
|
70
|
+
await ensureDir(issuesDir(rootDir));
|
|
71
|
+
await ensureDir(projectsDir(rootDir));
|
|
72
|
+
}
|
|
73
|
+
async function fileExists(filePath) {
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(filePath);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error.code === "ENOENT") {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function readJsonFile(filePath) {
|
|
85
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
86
|
+
return JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
async function atomicWriteJson(filePath, data) {
|
|
89
|
+
const dir = path2.dirname(filePath);
|
|
90
|
+
const base = path2.basename(filePath);
|
|
91
|
+
const tmpPath = path2.join(dir, `.${base}.tmp`);
|
|
92
|
+
const json = `${JSON.stringify(data, null, 2)}
|
|
93
|
+
`;
|
|
94
|
+
await fs.writeFile(tmpPath, json, "utf8");
|
|
95
|
+
await fs.rename(tmpPath, filePath);
|
|
96
|
+
}
|
|
97
|
+
async function atomicWriteText(filePath, text) {
|
|
98
|
+
const dir = path2.dirname(filePath);
|
|
99
|
+
const base = path2.basename(filePath);
|
|
100
|
+
const tmpPath = path2.join(dir, `.${base}.tmp`);
|
|
101
|
+
await fs.writeFile(tmpPath, text, "utf8");
|
|
102
|
+
await fs.rename(tmpPath, filePath);
|
|
103
|
+
}
|
|
104
|
+
async function readTextFile(filePath) {
|
|
105
|
+
return fs.readFile(filePath, "utf8");
|
|
106
|
+
}
|
|
107
|
+
async function listIssueFiles(rootDir) {
|
|
108
|
+
const dir = issuesDir(rootDir);
|
|
109
|
+
try {
|
|
110
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
111
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path2.join(dir, entry.name));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (error.code === "ENOENT") {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function listProjectFiles(rootDir) {
|
|
120
|
+
const dir = projectsDir(rootDir);
|
|
121
|
+
try {
|
|
122
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
123
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path2.join(dir, entry.name));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error.code === "ENOENT") {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function ensureAglit(rootDir) {
|
|
132
|
+
await ensureLayout(rootDir);
|
|
133
|
+
}
|
|
134
|
+
function normalizePrefix(value) {
|
|
135
|
+
const normalized = value.trim().toUpperCase();
|
|
136
|
+
if (!/^[A-Z][A-Z0-9]*$/.test(normalized)) {
|
|
137
|
+
throw new Error("issuePrefix must start with a letter and contain only A-Z and 0-9");
|
|
138
|
+
}
|
|
139
|
+
return normalized;
|
|
140
|
+
}
|
|
141
|
+
async function getConfig(rootDir) {
|
|
142
|
+
const path32 = configPath(rootDir);
|
|
143
|
+
if (!await fileExists(path32)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const json = await readJsonFile(path32);
|
|
147
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
148
|
+
throw new Error(`Invalid AGLIT config at ${path32}`);
|
|
149
|
+
}
|
|
150
|
+
const issuePrefix = json.issuePrefix;
|
|
151
|
+
if (typeof issuePrefix !== "string") {
|
|
152
|
+
throw new Error(`Invalid AGLIT config at ${path32}`);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
issuePrefix: normalizePrefix(issuePrefix),
|
|
156
|
+
schema: typeof json.schema === "string" ? json.schema : undefined
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function setConfig(rootDir, config) {
|
|
160
|
+
if (!config || typeof config !== "object") {
|
|
161
|
+
throw new Error("Invalid AGLIT config payload");
|
|
162
|
+
}
|
|
163
|
+
const issuePrefix = normalizePrefix(config.issuePrefix);
|
|
164
|
+
await ensureLayout(rootDir);
|
|
165
|
+
await atomicWriteJson(configPath(rootDir), {
|
|
166
|
+
schema: CONFIG_SCHEMA,
|
|
167
|
+
issuePrefix
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function resolveIssuePrefix(rootDir, inputPrefix) {
|
|
171
|
+
const existing = await getConfig(rootDir);
|
|
172
|
+
if (existing?.issuePrefix) {
|
|
173
|
+
return existing.issuePrefix;
|
|
174
|
+
}
|
|
175
|
+
if (!inputPrefix) {
|
|
176
|
+
throw new Error("Issue prefix not set. Run `aglit init --prefix ABC` or `aglit new --prefix ABC`.");
|
|
177
|
+
}
|
|
178
|
+
const prefix = normalizePrefix(inputPrefix);
|
|
179
|
+
const config = {
|
|
180
|
+
issuePrefix: prefix
|
|
181
|
+
};
|
|
182
|
+
await setConfig(rootDir, config);
|
|
183
|
+
return prefix;
|
|
184
|
+
}
|
|
185
|
+
function formatUuid(bytes) {
|
|
186
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
|
187
|
+
return [
|
|
188
|
+
hex.slice(0, 4).join(""),
|
|
189
|
+
hex.slice(4, 6).join(""),
|
|
190
|
+
hex.slice(6, 8).join(""),
|
|
191
|
+
hex.slice(8, 10).join(""),
|
|
192
|
+
hex.slice(10, 16).join("")
|
|
193
|
+
].join("-");
|
|
194
|
+
}
|
|
195
|
+
function generateId() {
|
|
196
|
+
const bun = globalThis.Bun;
|
|
197
|
+
if (bun?.randomUUIDv7) {
|
|
198
|
+
return bun.randomUUIDv7();
|
|
199
|
+
}
|
|
200
|
+
const bytes = randomBytes(16);
|
|
201
|
+
const now = BigInt(Date.now());
|
|
202
|
+
bytes[0] = Number(now >> 40n & 0xffn);
|
|
203
|
+
bytes[1] = Number(now >> 32n & 0xffn);
|
|
204
|
+
bytes[2] = Number(now >> 24n & 0xffn);
|
|
205
|
+
bytes[3] = Number(now >> 16n & 0xffn);
|
|
206
|
+
bytes[4] = Number(now >> 8n & 0xffn);
|
|
207
|
+
bytes[5] = Number(now & 0xffn);
|
|
208
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 112;
|
|
209
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
210
|
+
return formatUuid(bytes);
|
|
211
|
+
}
|
|
212
|
+
function getBun() {
|
|
213
|
+
return globalThis.Bun ?? {};
|
|
214
|
+
}
|
|
215
|
+
function parseYaml(input) {
|
|
216
|
+
const bun = getBun();
|
|
217
|
+
if (!bun.YAML?.parse) {
|
|
218
|
+
throw new Error("Bun.YAML.parse unavailable");
|
|
219
|
+
}
|
|
220
|
+
return bun.YAML.parse(input);
|
|
221
|
+
}
|
|
222
|
+
function tryRenderMarkdown(input, callbacks) {
|
|
223
|
+
const bun = getBun();
|
|
224
|
+
if (!bun.markdown?.render) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
bun.markdown.render(input, callbacks);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function parseFrontmatter(text) {
|
|
231
|
+
const input = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
232
|
+
if (!input.startsWith("---")) {
|
|
233
|
+
return { data: {}, body: text, hasFrontmatter: false };
|
|
234
|
+
}
|
|
235
|
+
const lines = input.split(/\r?\n/);
|
|
236
|
+
if (lines[0]?.trim() !== "---") {
|
|
237
|
+
return { data: {}, body: text, hasFrontmatter: false };
|
|
238
|
+
}
|
|
239
|
+
let endIndex = -1;
|
|
240
|
+
for (let i = 1;i < lines.length; i += 1) {
|
|
241
|
+
if (lines[i]?.trim() === "---") {
|
|
242
|
+
endIndex = i;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (endIndex === -1) {
|
|
247
|
+
return { data: {}, body: text, hasFrontmatter: false };
|
|
248
|
+
}
|
|
249
|
+
const raw = lines.slice(1, endIndex).join(`
|
|
250
|
+
`);
|
|
251
|
+
let data = {};
|
|
252
|
+
let error;
|
|
253
|
+
if (raw.trim()) {
|
|
254
|
+
try {
|
|
255
|
+
const parsed = parseYaml(raw);
|
|
256
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
257
|
+
data = parsed;
|
|
258
|
+
}
|
|
259
|
+
} catch (caught) {
|
|
260
|
+
error = caught instanceof Error ? caught : new Error(String(caught));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const body = lines.slice(endIndex + 1).join(`
|
|
264
|
+
`);
|
|
265
|
+
return { data, body, hasFrontmatter: true, error };
|
|
266
|
+
}
|
|
267
|
+
function getString(data, key) {
|
|
268
|
+
const value = data[key];
|
|
269
|
+
return typeof value === "string" ? value : undefined;
|
|
270
|
+
}
|
|
271
|
+
function renderFrontmatter(data, order = []) {
|
|
272
|
+
const lines = ["---"];
|
|
273
|
+
const seen = new Set;
|
|
274
|
+
for (const key of order) {
|
|
275
|
+
const value = data[key];
|
|
276
|
+
if (value !== undefined) {
|
|
277
|
+
lines.push(`${key}: ${value}`);
|
|
278
|
+
seen.add(key);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const key of Object.keys(data)) {
|
|
282
|
+
if (seen.has(key)) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const value = data[key];
|
|
286
|
+
if (value !== undefined) {
|
|
287
|
+
lines.push(`${key}: ${value}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
lines.push("---");
|
|
291
|
+
return lines.join(`
|
|
292
|
+
`);
|
|
293
|
+
}
|
|
294
|
+
var DEFAULT_LOCK_TTL_MS = 30000;
|
|
295
|
+
async function acquireLock(rootDir, options) {
|
|
296
|
+
const ttlMs = options?.ttlMs ?? DEFAULT_LOCK_TTL_MS;
|
|
297
|
+
const path32 = lockPath(rootDir);
|
|
298
|
+
await ensureLayout(rootDir);
|
|
299
|
+
try {
|
|
300
|
+
const handle = await fs2.open(path32, "wx");
|
|
301
|
+
try {
|
|
302
|
+
const payload = {
|
|
303
|
+
pid: process.pid,
|
|
304
|
+
createdAt: new Date().toISOString(),
|
|
305
|
+
expiresAt: new Date(Date.now() + ttlMs).toISOString()
|
|
306
|
+
};
|
|
307
|
+
await handle.writeFile(`${JSON.stringify(payload)}
|
|
308
|
+
`, "utf8");
|
|
309
|
+
} finally {
|
|
310
|
+
await handle.close();
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (error.code !== "EEXIST") {
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const stat2 = await fs2.stat(path32);
|
|
319
|
+
const isStale = Date.now() - stat2.mtimeMs > ttlMs;
|
|
320
|
+
if (isStale) {
|
|
321
|
+
await fs2.unlink(path32);
|
|
322
|
+
return acquireLock(rootDir, options);
|
|
323
|
+
}
|
|
324
|
+
throw new Error(`AGLIT lock already held at ${path32}`);
|
|
325
|
+
}
|
|
326
|
+
async function releaseLock(rootDir) {
|
|
327
|
+
try {
|
|
328
|
+
await fs2.unlink(lockPath(rootDir));
|
|
329
|
+
} catch (error) {
|
|
330
|
+
if (error.code === "ENOENT") {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
async function withLock(rootDir, fn, options) {
|
|
337
|
+
await acquireLock(rootDir, options);
|
|
338
|
+
try {
|
|
339
|
+
return await fn();
|
|
340
|
+
} finally {
|
|
341
|
+
await releaseLock(rootDir);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function escapeRegExp(value) {
|
|
345
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
346
|
+
}
|
|
347
|
+
async function allocateIssueKey(rootDir, prefix) {
|
|
348
|
+
const files = await listIssueFiles(rootDir);
|
|
349
|
+
const pattern = new RegExp(`^${escapeRegExp(prefix)}-(\\d+)$`);
|
|
350
|
+
let max = 0;
|
|
351
|
+
for (const filePath of files) {
|
|
352
|
+
const key = path3.basename(filePath, ".md");
|
|
353
|
+
const match = key.match(pattern);
|
|
354
|
+
if (!match)
|
|
355
|
+
continue;
|
|
356
|
+
const value = Number(match[1]);
|
|
357
|
+
if (Number.isFinite(value) && value > max) {
|
|
358
|
+
max = value;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return `${prefix}-${max + 1}`;
|
|
362
|
+
}
|
|
363
|
+
function normalizeSlug(value) {
|
|
364
|
+
const trimmed = value.trim().toLowerCase();
|
|
365
|
+
const slug = trimmed.replace(/[^a-z0-9]+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
366
|
+
return slug || "project";
|
|
367
|
+
}
|
|
368
|
+
async function allocateProjectSlug(rootDir, title, inputSlug) {
|
|
369
|
+
const base = normalizeSlug(inputSlug ?? title);
|
|
370
|
+
const files = await listProjectFiles(rootDir);
|
|
371
|
+
const existing = new Set(files.map((file) => path3.basename(file, ".md").toLowerCase()));
|
|
372
|
+
if (!existing.has(base)) {
|
|
373
|
+
return base;
|
|
374
|
+
}
|
|
375
|
+
let counter = 2;
|
|
376
|
+
while (existing.has(`${base}-${counter}`)) {
|
|
377
|
+
counter += 1;
|
|
378
|
+
}
|
|
379
|
+
return `${base}-${counter}`;
|
|
380
|
+
}
|
|
381
|
+
function issueTemplate(input) {
|
|
382
|
+
const frontmatter = renderFrontmatter({
|
|
383
|
+
schema: ISSUE_SCHEMA,
|
|
384
|
+
id: input.id,
|
|
385
|
+
status: input.status,
|
|
386
|
+
priority: input.priority,
|
|
387
|
+
...input.projectId ? { projectId: input.projectId } : {}
|
|
388
|
+
}, ["schema", "id", "status", "priority", "projectId"]);
|
|
389
|
+
return [
|
|
390
|
+
frontmatter,
|
|
391
|
+
"",
|
|
392
|
+
`# ${input.title.trim()}`,
|
|
393
|
+
"",
|
|
394
|
+
"## Description",
|
|
395
|
+
"",
|
|
396
|
+
"## Acceptance",
|
|
397
|
+
"",
|
|
398
|
+
"## Constraints",
|
|
399
|
+
"",
|
|
400
|
+
"## Plan",
|
|
401
|
+
"",
|
|
402
|
+
"## Verification",
|
|
403
|
+
""
|
|
404
|
+
].join(`
|
|
405
|
+
`);
|
|
406
|
+
}
|
|
407
|
+
function projectTemplate(input) {
|
|
408
|
+
const frontmatter = renderFrontmatter({
|
|
409
|
+
schema: PROJECT_SCHEMA,
|
|
410
|
+
id: input.id,
|
|
411
|
+
status: input.status,
|
|
412
|
+
priority: input.priority
|
|
413
|
+
}, ["schema", "id", "status", "priority"]);
|
|
414
|
+
return [
|
|
415
|
+
frontmatter,
|
|
416
|
+
"",
|
|
417
|
+
`# ${input.title.trim()}`,
|
|
418
|
+
"",
|
|
419
|
+
"## Description",
|
|
420
|
+
"",
|
|
421
|
+
"## Scope",
|
|
422
|
+
"",
|
|
423
|
+
"## Milestones",
|
|
424
|
+
"",
|
|
425
|
+
"## Notes",
|
|
426
|
+
""
|
|
427
|
+
].join(`
|
|
428
|
+
`);
|
|
429
|
+
}
|
|
430
|
+
async function createIssueFile(rootDir, input) {
|
|
431
|
+
if (!input.title?.trim()) {
|
|
432
|
+
throw new Error("Issue title is required");
|
|
433
|
+
}
|
|
434
|
+
return withLock(rootDir, async () => {
|
|
435
|
+
await ensureLayout(rootDir);
|
|
436
|
+
const prefix = await resolveIssuePrefix(rootDir, input.prefix);
|
|
437
|
+
const key = await allocateIssueKey(rootDir, prefix);
|
|
438
|
+
const id = generateId();
|
|
439
|
+
const status = input.status ?? "inbox";
|
|
440
|
+
const priority = input.priority ?? "none";
|
|
441
|
+
const projectId = input.projectId?.trim() || undefined;
|
|
442
|
+
const content = issueTemplate({
|
|
443
|
+
title: input.title,
|
|
444
|
+
id,
|
|
445
|
+
status,
|
|
446
|
+
priority,
|
|
447
|
+
projectId
|
|
448
|
+
});
|
|
449
|
+
const filePath = issuePath(rootDir, key);
|
|
450
|
+
await atomicWriteText(filePath, content);
|
|
451
|
+
return { key, path: filePath, id };
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
async function createProjectFile(rootDir, input) {
|
|
455
|
+
if (!input.title?.trim()) {
|
|
456
|
+
throw new Error("Project title is required");
|
|
457
|
+
}
|
|
458
|
+
return withLock(rootDir, async () => {
|
|
459
|
+
await ensureLayout(rootDir);
|
|
460
|
+
const slug = await allocateProjectSlug(rootDir, input.title, input.slug);
|
|
461
|
+
const id = generateId();
|
|
462
|
+
const status = input.status ?? "inbox";
|
|
463
|
+
const priority = input.priority ?? "none";
|
|
464
|
+
const content = projectTemplate({
|
|
465
|
+
title: input.title,
|
|
466
|
+
id,
|
|
467
|
+
status,
|
|
468
|
+
priority
|
|
469
|
+
});
|
|
470
|
+
const filePath = projectPath(rootDir, slug);
|
|
471
|
+
await atomicWriteText(filePath, content);
|
|
472
|
+
return { slug, path: filePath, id };
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
function normalizeStatus(value) {
|
|
476
|
+
if (value && ISSUE_STATUSES.includes(value)) {
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
return "inbox";
|
|
480
|
+
}
|
|
481
|
+
function normalizePriority(value) {
|
|
482
|
+
if (value && PRIORITY_LEVELS.includes(value)) {
|
|
483
|
+
return value;
|
|
484
|
+
}
|
|
485
|
+
return "none";
|
|
486
|
+
}
|
|
487
|
+
function extractTitle(body) {
|
|
488
|
+
let title = null;
|
|
489
|
+
const usedMarkdown = tryRenderMarkdown(body, {
|
|
490
|
+
heading: (children, info) => {
|
|
491
|
+
if (!title && info.level === 1) {
|
|
492
|
+
const text = String(children).trim();
|
|
493
|
+
if (text) {
|
|
494
|
+
title = text;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return "";
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
if (!usedMarkdown) {
|
|
501
|
+
const match = body.match(/^#\s+(.+)$/m);
|
|
502
|
+
if (match?.[1]) {
|
|
503
|
+
return match[1].trim();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return title;
|
|
507
|
+
}
|
|
508
|
+
function parseIssueHeader(filePath, text) {
|
|
509
|
+
const key = path4.basename(filePath, ".md");
|
|
510
|
+
const { data, body } = parseFrontmatter(text);
|
|
511
|
+
const schema = getString(data, "schema");
|
|
512
|
+
const status = normalizeStatus(getString(data, "status"));
|
|
513
|
+
const priority = normalizePriority(getString(data, "priority"));
|
|
514
|
+
const title = extractTitle(body) ?? key;
|
|
515
|
+
return {
|
|
516
|
+
key,
|
|
517
|
+
id: getString(data, "id"),
|
|
518
|
+
status,
|
|
519
|
+
priority,
|
|
520
|
+
projectId: getString(data, "projectId"),
|
|
521
|
+
title,
|
|
522
|
+
path: filePath,
|
|
523
|
+
schema
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function parseProjectHeader(filePath, text) {
|
|
527
|
+
const slug = path4.basename(filePath, ".md");
|
|
528
|
+
const { data, body } = parseFrontmatter(text);
|
|
529
|
+
const schema = getString(data, "schema");
|
|
530
|
+
const status = normalizeStatus(getString(data, "status"));
|
|
531
|
+
const priority = normalizePriority(getString(data, "priority"));
|
|
532
|
+
const title = extractTitle(body) ?? slug;
|
|
533
|
+
return {
|
|
534
|
+
slug,
|
|
535
|
+
id: getString(data, "id"),
|
|
536
|
+
status,
|
|
537
|
+
priority,
|
|
538
|
+
title,
|
|
539
|
+
path: filePath,
|
|
540
|
+
schema
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
var PRIORITY_ORDER = {
|
|
544
|
+
high: 0,
|
|
545
|
+
medium: 1,
|
|
546
|
+
low: 2,
|
|
547
|
+
none: 3
|
|
548
|
+
};
|
|
549
|
+
function keyNumber(key) {
|
|
550
|
+
const match = key.match(/-(\d+)$/);
|
|
551
|
+
if (!match)
|
|
552
|
+
return null;
|
|
553
|
+
const value = Number(match[1]);
|
|
554
|
+
return Number.isFinite(value) ? value : null;
|
|
555
|
+
}
|
|
556
|
+
function compareIssue(a, b) {
|
|
557
|
+
if (a.priority !== b.priority) {
|
|
558
|
+
return PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority];
|
|
559
|
+
}
|
|
560
|
+
const aNum = keyNumber(a.key);
|
|
561
|
+
const bNum = keyNumber(b.key);
|
|
562
|
+
if (aNum !== null && bNum !== null && aNum !== bNum) {
|
|
563
|
+
return aNum - bNum;
|
|
564
|
+
}
|
|
565
|
+
return a.key.localeCompare(b.key);
|
|
566
|
+
}
|
|
567
|
+
function compareProject(a, b) {
|
|
568
|
+
if (a.priority !== b.priority) {
|
|
569
|
+
return PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority];
|
|
570
|
+
}
|
|
571
|
+
return a.slug.localeCompare(b.slug);
|
|
572
|
+
}
|
|
573
|
+
async function listIssueHeaders(rootDir, filter = {}) {
|
|
574
|
+
const files = await listIssueFiles(rootDir);
|
|
575
|
+
const headers = [];
|
|
576
|
+
const projectId = filter.projectId?.trim();
|
|
577
|
+
for (const filePath of files) {
|
|
578
|
+
const text = await readTextFile(filePath);
|
|
579
|
+
const header = parseIssueHeader(filePath, text);
|
|
580
|
+
if (filter.status && header.status !== filter.status) {
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (projectId && header.projectId !== projectId) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
headers.push(header);
|
|
587
|
+
}
|
|
588
|
+
headers.sort(compareIssue);
|
|
589
|
+
if (filter.limit && headers.length > filter.limit) {
|
|
590
|
+
return headers.slice(0, filter.limit);
|
|
591
|
+
}
|
|
592
|
+
return headers;
|
|
593
|
+
}
|
|
594
|
+
async function listProjectHeaders(rootDir, filter = {}) {
|
|
595
|
+
const files = await listProjectFiles(rootDir);
|
|
596
|
+
const headers = [];
|
|
597
|
+
for (const filePath of files) {
|
|
598
|
+
const text = await readTextFile(filePath);
|
|
599
|
+
const header = parseProjectHeader(filePath, text);
|
|
600
|
+
if (filter.status && header.status !== filter.status) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
headers.push(header);
|
|
604
|
+
}
|
|
605
|
+
headers.sort(compareProject);
|
|
606
|
+
if (filter.limit && headers.length > filter.limit) {
|
|
607
|
+
return headers.slice(0, filter.limit);
|
|
608
|
+
}
|
|
609
|
+
return headers;
|
|
610
|
+
}
|
|
611
|
+
async function getProjectBySlug(rootDir, slug) {
|
|
612
|
+
const normalized = slug.trim().toLowerCase();
|
|
613
|
+
const projects = await listProjectHeaders(rootDir);
|
|
614
|
+
return projects.find((project) => project.slug.toLowerCase() === normalized) ?? null;
|
|
615
|
+
}
|
|
616
|
+
function toIssueSummary(header) {
|
|
617
|
+
return {
|
|
618
|
+
key: header.key,
|
|
619
|
+
title: header.title,
|
|
620
|
+
status: header.status,
|
|
621
|
+
priority: header.priority,
|
|
622
|
+
projectId: header.projectId
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
async function getBoardView(rootDir, filter = {}) {
|
|
626
|
+
const limited = await listIssueHeaders(rootDir, filter);
|
|
627
|
+
const groups = ISSUE_STATUSES.map((status) => ({
|
|
628
|
+
status,
|
|
629
|
+
issues: []
|
|
630
|
+
}));
|
|
631
|
+
const groupMap = new Map;
|
|
632
|
+
for (const group of groups) {
|
|
633
|
+
groupMap.set(group.status, group.issues);
|
|
634
|
+
}
|
|
635
|
+
for (const header of limited) {
|
|
636
|
+
const list = groupMap.get(header.status);
|
|
637
|
+
if (!list)
|
|
638
|
+
continue;
|
|
639
|
+
list.push(toIssueSummary(header));
|
|
640
|
+
}
|
|
641
|
+
return { groups };
|
|
642
|
+
}
|
|
643
|
+
async function getProjectsView(rootDir, filter = {}) {
|
|
644
|
+
const projects = await listProjectHeaders(rootDir, filter);
|
|
645
|
+
const issues = await listIssueHeaders(rootDir);
|
|
646
|
+
const counts = new Map;
|
|
647
|
+
for (const issue of issues) {
|
|
648
|
+
if (!issue.projectId)
|
|
649
|
+
continue;
|
|
650
|
+
counts.set(issue.projectId, (counts.get(issue.projectId) ?? 0) + 1);
|
|
651
|
+
}
|
|
652
|
+
const summaries = projects.map((project) => ({
|
|
653
|
+
slug: project.slug,
|
|
654
|
+
title: project.title,
|
|
655
|
+
status: project.status,
|
|
656
|
+
priority: project.priority,
|
|
657
|
+
issueCount: project.id ? counts.get(project.id) ?? 0 : 0,
|
|
658
|
+
id: project.id
|
|
659
|
+
}));
|
|
660
|
+
return { projects: summaries };
|
|
661
|
+
}
|
|
662
|
+
function renderBoard(view) {
|
|
663
|
+
const lines = [];
|
|
664
|
+
for (const group of view.groups) {
|
|
665
|
+
lines.push(`${group.status} (${group.issues.length})`);
|
|
666
|
+
for (const issue of group.issues) {
|
|
667
|
+
lines.push(`- ${issue.key}: ${issue.title} [${issue.priority}]`);
|
|
668
|
+
}
|
|
669
|
+
lines.push("");
|
|
670
|
+
}
|
|
671
|
+
return lines.join(`
|
|
672
|
+
`).trimEnd();
|
|
673
|
+
}
|
|
674
|
+
function renderList(issues) {
|
|
675
|
+
if (!issues.length) {
|
|
676
|
+
return "No issues.";
|
|
677
|
+
}
|
|
678
|
+
return issues.map((issue) => `- ${issue.key}: ${issue.title} [${issue.status}] [${issue.priority}]`).join(`
|
|
679
|
+
`);
|
|
680
|
+
}
|
|
681
|
+
function renderProjects(view) {
|
|
682
|
+
if (!view.projects.length) {
|
|
683
|
+
return "No projects.";
|
|
684
|
+
}
|
|
685
|
+
return view.projects.map((project) => `- ${project.slug}: ${project.title} [${project.status}] [${project.priority}] (issues: ${project.issueCount})`).join(`
|
|
686
|
+
`);
|
|
687
|
+
}
|
|
688
|
+
var UUID_V7_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
689
|
+
function isUuidV7(value) {
|
|
690
|
+
return UUID_V7_REGEX.test(value);
|
|
691
|
+
}
|
|
692
|
+
function isValidStatus(value) {
|
|
693
|
+
return !!value && ISSUE_STATUSES.includes(value);
|
|
694
|
+
}
|
|
695
|
+
function isValidPriority(value) {
|
|
696
|
+
return !!value && PRIORITY_LEVELS.includes(value);
|
|
697
|
+
}
|
|
698
|
+
async function checkWorkspace(rootDir) {
|
|
699
|
+
const problems = [];
|
|
700
|
+
const issueFiles = await listIssueFiles(rootDir);
|
|
701
|
+
const projectFiles = await listProjectFiles(rootDir);
|
|
702
|
+
const projectIds = new Map;
|
|
703
|
+
const issueIds = new Map;
|
|
704
|
+
for (const filePath of projectFiles) {
|
|
705
|
+
const text = await readTextFile(filePath);
|
|
706
|
+
const { data, hasFrontmatter, error } = parseFrontmatter(text);
|
|
707
|
+
if (!hasFrontmatter) {
|
|
708
|
+
problems.push({ filePath, message: "Missing frontmatter", level: "error" });
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (error) {
|
|
712
|
+
problems.push({
|
|
713
|
+
filePath,
|
|
714
|
+
message: `Frontmatter parse error: ${error.message}`,
|
|
715
|
+
level: "error"
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const schema = getString(data, "schema");
|
|
720
|
+
if (schema !== PROJECT_SCHEMA) {
|
|
721
|
+
problems.push({
|
|
722
|
+
filePath,
|
|
723
|
+
message: `Invalid schema (expected ${PROJECT_SCHEMA})`,
|
|
724
|
+
level: "error"
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
const id = getString(data, "id");
|
|
728
|
+
if (!id) {
|
|
729
|
+
problems.push({ filePath, message: "Missing id", level: "error" });
|
|
730
|
+
} else if (!isUuidV7(id)) {
|
|
731
|
+
problems.push({ filePath, message: "Invalid UUIDv7 id", level: "error" });
|
|
732
|
+
} else if (projectIds.has(id)) {
|
|
733
|
+
problems.push({
|
|
734
|
+
filePath,
|
|
735
|
+
message: `Duplicate project id (also in ${projectIds.get(id)})`,
|
|
736
|
+
level: "error"
|
|
737
|
+
});
|
|
738
|
+
} else {
|
|
739
|
+
projectIds.set(id, filePath);
|
|
740
|
+
}
|
|
741
|
+
const status = getString(data, "status");
|
|
742
|
+
if (!isValidStatus(status)) {
|
|
743
|
+
problems.push({ filePath, message: "Invalid or missing status", level: "error" });
|
|
744
|
+
}
|
|
745
|
+
const priority = getString(data, "priority");
|
|
746
|
+
if (!isValidPriority(priority)) {
|
|
747
|
+
problems.push({ filePath, message: "Invalid or missing priority", level: "error" });
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
for (const filePath of issueFiles) {
|
|
751
|
+
const text = await readTextFile(filePath);
|
|
752
|
+
const { data, hasFrontmatter, error } = parseFrontmatter(text);
|
|
753
|
+
if (!hasFrontmatter) {
|
|
754
|
+
problems.push({ filePath, message: "Missing frontmatter", level: "error" });
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (error) {
|
|
758
|
+
problems.push({
|
|
759
|
+
filePath,
|
|
760
|
+
message: `Frontmatter parse error: ${error.message}`,
|
|
761
|
+
level: "error"
|
|
762
|
+
});
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const schema = getString(data, "schema");
|
|
766
|
+
if (schema !== ISSUE_SCHEMA) {
|
|
767
|
+
problems.push({
|
|
768
|
+
filePath,
|
|
769
|
+
message: `Invalid schema (expected ${ISSUE_SCHEMA})`,
|
|
770
|
+
level: "error"
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
const id = getString(data, "id");
|
|
774
|
+
if (!id) {
|
|
775
|
+
problems.push({ filePath, message: "Missing id", level: "error" });
|
|
776
|
+
} else if (!isUuidV7(id)) {
|
|
777
|
+
problems.push({ filePath, message: "Invalid UUIDv7 id", level: "error" });
|
|
778
|
+
} else if (issueIds.has(id)) {
|
|
779
|
+
problems.push({
|
|
780
|
+
filePath,
|
|
781
|
+
message: `Duplicate issue id (also in ${issueIds.get(id)})`,
|
|
782
|
+
level: "error"
|
|
783
|
+
});
|
|
784
|
+
} else {
|
|
785
|
+
issueIds.set(id, filePath);
|
|
786
|
+
}
|
|
787
|
+
const status = getString(data, "status");
|
|
788
|
+
if (!isValidStatus(status)) {
|
|
789
|
+
problems.push({ filePath, message: "Invalid or missing status", level: "error" });
|
|
790
|
+
}
|
|
791
|
+
const priority = getString(data, "priority");
|
|
792
|
+
if (!isValidPriority(priority)) {
|
|
793
|
+
problems.push({ filePath, message: "Invalid or missing priority", level: "error" });
|
|
794
|
+
}
|
|
795
|
+
const projectId = getString(data, "projectId");
|
|
796
|
+
if (projectId && !isUuidV7(projectId)) {
|
|
797
|
+
problems.push({
|
|
798
|
+
filePath,
|
|
799
|
+
message: "projectId is not a UUIDv7",
|
|
800
|
+
level: "error"
|
|
801
|
+
});
|
|
802
|
+
} else if (projectId && !projectIds.has(projectId)) {
|
|
803
|
+
problems.push({
|
|
804
|
+
filePath,
|
|
805
|
+
message: "projectId does not match any project",
|
|
806
|
+
level: "warning"
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return { problems, issues: issueFiles.length, projects: projectFiles.length };
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/workspace.ts
|
|
814
|
+
import * as fs3 from "fs/promises";
|
|
815
|
+
import * as path5 from "path";
|
|
816
|
+
var AGLIT_DIR = ".aglit";
|
|
817
|
+
async function exists(dirPath) {
|
|
818
|
+
try {
|
|
819
|
+
await fs3.access(dirPath);
|
|
820
|
+
return true;
|
|
821
|
+
} catch (error) {
|
|
822
|
+
if (error.code === "ENOENT") {
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async function findWorkspaceRoot(startDir) {
|
|
829
|
+
let current = path5.resolve(startDir);
|
|
830
|
+
while (true) {
|
|
831
|
+
if (await exists(path5.join(current, AGLIT_DIR))) {
|
|
832
|
+
return current;
|
|
833
|
+
}
|
|
834
|
+
const parent = path5.dirname(current);
|
|
835
|
+
if (parent === current) {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
current = parent;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function requireWorkspaceRoot(startDir) {
|
|
842
|
+
const root = await findWorkspaceRoot(startDir);
|
|
843
|
+
if (!root) {
|
|
844
|
+
throw new Error("AGLIT not initialized. Run `aglit init`.");
|
|
845
|
+
}
|
|
846
|
+
return root;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/commands/init.ts
|
|
850
|
+
var initFlags = {
|
|
851
|
+
prefix: {
|
|
852
|
+
kind: "parsed",
|
|
853
|
+
parse: String,
|
|
854
|
+
brief: "Issue key prefix",
|
|
855
|
+
optional: true
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
var initCommand = buildCommand({
|
|
859
|
+
func: async function(flags) {
|
|
860
|
+
const root = await findWorkspaceRoot(this.cwd) ?? this.cwd;
|
|
861
|
+
await ensureAglit(root);
|
|
862
|
+
if (flags.prefix) {
|
|
863
|
+
await setConfig(root, { issuePrefix: flags.prefix, schema: "aglit.config.v1" });
|
|
864
|
+
} else {
|
|
865
|
+
const config = await getConfig(root);
|
|
866
|
+
if (!config) {
|
|
867
|
+
throw new Error("Issue prefix required. Run `aglit init --prefix ABC`.");
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
this.process.stdout.write(`Initialized AGLIT at ${root}
|
|
871
|
+
`);
|
|
872
|
+
},
|
|
873
|
+
parameters: {
|
|
874
|
+
flags: initFlags
|
|
875
|
+
},
|
|
876
|
+
docs: {
|
|
877
|
+
brief: "Initialize AGLIT in this workspace"
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// src/commands/check.ts
|
|
882
|
+
import * as path7 from "path";
|
|
883
|
+
import { buildCommand as buildCommand2 } from "@stricli/core";
|
|
884
|
+
var checkCommand = buildCommand2({
|
|
885
|
+
func: async function() {
|
|
886
|
+
const root = await requireWorkspaceRoot(this.cwd);
|
|
887
|
+
const report = await checkWorkspace(root);
|
|
888
|
+
const lines = [];
|
|
889
|
+
lines.push(`issues: ${report.issues}`);
|
|
890
|
+
lines.push(`projects: ${report.projects}`);
|
|
891
|
+
lines.push(`problems: ${report.problems.length}`);
|
|
892
|
+
if (report.problems.length) {
|
|
893
|
+
lines.push("");
|
|
894
|
+
for (const problem of report.problems) {
|
|
895
|
+
const relative2 = path7.relative(root, problem.filePath) || problem.filePath;
|
|
896
|
+
lines.push(`- [${problem.level}] ${relative2}: ${problem.message}`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
this.process.stdout.write(`${lines.join(`
|
|
900
|
+
`)}
|
|
901
|
+
`);
|
|
902
|
+
},
|
|
903
|
+
parameters: {},
|
|
904
|
+
docs: {
|
|
905
|
+
brief: "Validate AGLIT state"
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// src/commands/list.ts
|
|
910
|
+
import { buildCommand as buildCommand3 } from "@stricli/core";
|
|
911
|
+
var statusParser = (input) => {
|
|
912
|
+
const value = input.toLowerCase();
|
|
913
|
+
if (ISSUE_STATUSES.includes(value)) {
|
|
914
|
+
return value;
|
|
915
|
+
}
|
|
916
|
+
throw new Error(`Invalid status: ${input}`);
|
|
917
|
+
};
|
|
918
|
+
var groupParser = (input) => {
|
|
919
|
+
const value = input.toLowerCase();
|
|
920
|
+
if (value === "status" || value === "none") {
|
|
921
|
+
return value;
|
|
922
|
+
}
|
|
923
|
+
throw new Error(`Invalid group: ${input}`);
|
|
924
|
+
};
|
|
925
|
+
var listFlags = {
|
|
926
|
+
status: {
|
|
927
|
+
kind: "parsed",
|
|
928
|
+
parse: statusParser,
|
|
929
|
+
brief: "Filter by status",
|
|
930
|
+
optional: true
|
|
931
|
+
},
|
|
932
|
+
project: {
|
|
933
|
+
kind: "parsed",
|
|
934
|
+
parse: String,
|
|
935
|
+
brief: "Filter by project slug",
|
|
936
|
+
optional: true
|
|
937
|
+
},
|
|
938
|
+
group: {
|
|
939
|
+
kind: "parsed",
|
|
940
|
+
parse: groupParser,
|
|
941
|
+
brief: "Group by status or none",
|
|
942
|
+
optional: true
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
var listCommand = buildCommand3({
|
|
946
|
+
func: async function(flags) {
|
|
947
|
+
const root = await requireWorkspaceRoot(this.cwd);
|
|
948
|
+
let projectId;
|
|
949
|
+
if (flags.project) {
|
|
950
|
+
const project = await getProjectBySlug(root, flags.project);
|
|
951
|
+
if (!project?.id) {
|
|
952
|
+
throw new Error(`Project not found or missing id: ${flags.project}`);
|
|
953
|
+
}
|
|
954
|
+
projectId = project.id;
|
|
955
|
+
}
|
|
956
|
+
const group = flags.group ?? "status";
|
|
957
|
+
if (group === "none") {
|
|
958
|
+
const headers = await listIssueHeaders(root, {
|
|
959
|
+
status: flags.status,
|
|
960
|
+
projectId
|
|
961
|
+
});
|
|
962
|
+
const summaries = headers.map((issue) => ({
|
|
963
|
+
key: issue.key,
|
|
964
|
+
title: issue.title,
|
|
965
|
+
status: issue.status,
|
|
966
|
+
priority: issue.priority,
|
|
967
|
+
projectId: issue.projectId
|
|
968
|
+
}));
|
|
969
|
+
this.process.stdout.write(`${renderList(summaries)}
|
|
970
|
+
`);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const view = await getBoardView(root, {
|
|
974
|
+
status: flags.status,
|
|
975
|
+
projectId
|
|
976
|
+
});
|
|
977
|
+
this.process.stdout.write(`${renderBoard(view)}
|
|
978
|
+
`);
|
|
979
|
+
},
|
|
980
|
+
parameters: {
|
|
981
|
+
flags: listFlags
|
|
982
|
+
},
|
|
983
|
+
docs: {
|
|
984
|
+
brief: "List issues"
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
// src/commands/new.ts
|
|
989
|
+
import { buildCommand as buildCommand4 } from "@stricli/core";
|
|
990
|
+
var statusParser2 = (input) => {
|
|
991
|
+
const value = input.toLowerCase();
|
|
992
|
+
if (ISSUE_STATUSES.includes(value)) {
|
|
993
|
+
return value;
|
|
994
|
+
}
|
|
995
|
+
throw new Error(`Invalid status: ${input}`);
|
|
996
|
+
};
|
|
997
|
+
var priorityParser = (input) => {
|
|
998
|
+
const value = input.toLowerCase();
|
|
999
|
+
if (PRIORITY_LEVELS.includes(value)) {
|
|
1000
|
+
return value;
|
|
1001
|
+
}
|
|
1002
|
+
throw new Error(`Invalid priority: ${input}`);
|
|
1003
|
+
};
|
|
1004
|
+
var newFlags = {
|
|
1005
|
+
status: {
|
|
1006
|
+
kind: "parsed",
|
|
1007
|
+
parse: statusParser2,
|
|
1008
|
+
brief: "Issue status",
|
|
1009
|
+
optional: true
|
|
1010
|
+
},
|
|
1011
|
+
priority: {
|
|
1012
|
+
kind: "parsed",
|
|
1013
|
+
parse: priorityParser,
|
|
1014
|
+
brief: "Priority (none|low|medium|high)",
|
|
1015
|
+
optional: true
|
|
1016
|
+
},
|
|
1017
|
+
project: {
|
|
1018
|
+
kind: "parsed",
|
|
1019
|
+
parse: String,
|
|
1020
|
+
brief: "Project slug",
|
|
1021
|
+
optional: true
|
|
1022
|
+
},
|
|
1023
|
+
prefix: {
|
|
1024
|
+
kind: "parsed",
|
|
1025
|
+
parse: String,
|
|
1026
|
+
brief: "Issue key prefix",
|
|
1027
|
+
optional: true
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
var newCommand = buildCommand4({
|
|
1031
|
+
func: async function(flags, title) {
|
|
1032
|
+
const root = await requireWorkspaceRoot(this.cwd);
|
|
1033
|
+
let projectId;
|
|
1034
|
+
if (flags.project) {
|
|
1035
|
+
const project = await getProjectBySlug(root, flags.project);
|
|
1036
|
+
if (!project?.id) {
|
|
1037
|
+
throw new Error(`Project not found or missing id: ${flags.project}`);
|
|
1038
|
+
}
|
|
1039
|
+
projectId = project.id;
|
|
1040
|
+
}
|
|
1041
|
+
const issue = await createIssueFile(root, {
|
|
1042
|
+
title,
|
|
1043
|
+
status: flags.status,
|
|
1044
|
+
priority: flags.priority,
|
|
1045
|
+
projectId,
|
|
1046
|
+
prefix: flags.prefix
|
|
1047
|
+
});
|
|
1048
|
+
this.process.stdout.write(`${issue.key} ${issue.path}
|
|
1049
|
+
`);
|
|
1050
|
+
},
|
|
1051
|
+
parameters: {
|
|
1052
|
+
flags: newFlags,
|
|
1053
|
+
positional: {
|
|
1054
|
+
kind: "tuple",
|
|
1055
|
+
parameters: [
|
|
1056
|
+
{
|
|
1057
|
+
brief: "Issue title",
|
|
1058
|
+
parse: String
|
|
1059
|
+
}
|
|
1060
|
+
]
|
|
1061
|
+
}
|
|
1062
|
+
},
|
|
1063
|
+
docs: {
|
|
1064
|
+
brief: "Create a new issue"
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
// src/commands/project-new.ts
|
|
1069
|
+
import { buildCommand as buildCommand5 } from "@stricli/core";
|
|
1070
|
+
var statusParser3 = (input) => {
|
|
1071
|
+
const value = input.toLowerCase();
|
|
1072
|
+
if (ISSUE_STATUSES.includes(value)) {
|
|
1073
|
+
return value;
|
|
1074
|
+
}
|
|
1075
|
+
throw new Error(`Invalid status: ${input}`);
|
|
1076
|
+
};
|
|
1077
|
+
var priorityParser2 = (input) => {
|
|
1078
|
+
const value = input.toLowerCase();
|
|
1079
|
+
if (PRIORITY_LEVELS.includes(value)) {
|
|
1080
|
+
return value;
|
|
1081
|
+
}
|
|
1082
|
+
throw new Error(`Invalid priority: ${input}`);
|
|
1083
|
+
};
|
|
1084
|
+
var projectNewFlags = {
|
|
1085
|
+
status: {
|
|
1086
|
+
kind: "parsed",
|
|
1087
|
+
parse: statusParser3,
|
|
1088
|
+
brief: "Project status",
|
|
1089
|
+
optional: true
|
|
1090
|
+
},
|
|
1091
|
+
priority: {
|
|
1092
|
+
kind: "parsed",
|
|
1093
|
+
parse: priorityParser2,
|
|
1094
|
+
brief: "Priority (none|low|medium|high)",
|
|
1095
|
+
optional: true
|
|
1096
|
+
},
|
|
1097
|
+
slug: {
|
|
1098
|
+
kind: "parsed",
|
|
1099
|
+
parse: String,
|
|
1100
|
+
brief: "Project slug (optional override)",
|
|
1101
|
+
optional: true
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
var projectNewCommand = buildCommand5({
|
|
1105
|
+
func: async function(flags, title) {
|
|
1106
|
+
const root = await requireWorkspaceRoot(this.cwd);
|
|
1107
|
+
const project = await createProjectFile(root, {
|
|
1108
|
+
title,
|
|
1109
|
+
status: flags.status,
|
|
1110
|
+
priority: flags.priority,
|
|
1111
|
+
slug: flags.slug
|
|
1112
|
+
});
|
|
1113
|
+
this.process.stdout.write(`${project.slug} ${project.path}
|
|
1114
|
+
`);
|
|
1115
|
+
},
|
|
1116
|
+
parameters: {
|
|
1117
|
+
flags: projectNewFlags,
|
|
1118
|
+
positional: {
|
|
1119
|
+
kind: "tuple",
|
|
1120
|
+
parameters: [
|
|
1121
|
+
{
|
|
1122
|
+
brief: "Project title",
|
|
1123
|
+
parse: String
|
|
1124
|
+
}
|
|
1125
|
+
]
|
|
1126
|
+
}
|
|
1127
|
+
},
|
|
1128
|
+
docs: {
|
|
1129
|
+
brief: "Create a new project"
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
// src/commands/projects.ts
|
|
1134
|
+
import { buildCommand as buildCommand6 } from "@stricli/core";
|
|
1135
|
+
var statusParser4 = (input) => {
|
|
1136
|
+
const value = input.toLowerCase();
|
|
1137
|
+
if (ISSUE_STATUSES.includes(value)) {
|
|
1138
|
+
return value;
|
|
1139
|
+
}
|
|
1140
|
+
throw new Error(`Invalid status: ${input}`);
|
|
1141
|
+
};
|
|
1142
|
+
var projectsFlags = {
|
|
1143
|
+
status: {
|
|
1144
|
+
kind: "parsed",
|
|
1145
|
+
parse: statusParser4,
|
|
1146
|
+
brief: "Filter by status",
|
|
1147
|
+
optional: true
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
var projectsCommand = buildCommand6({
|
|
1151
|
+
func: async function(flags) {
|
|
1152
|
+
const root = await requireWorkspaceRoot(this.cwd);
|
|
1153
|
+
const view = await getProjectsView(root, { status: flags.status });
|
|
1154
|
+
this.process.stdout.write(`${renderProjects(view)}
|
|
1155
|
+
`);
|
|
1156
|
+
},
|
|
1157
|
+
parameters: {
|
|
1158
|
+
flags: projectsFlags
|
|
1159
|
+
},
|
|
1160
|
+
docs: {
|
|
1161
|
+
brief: "List projects"
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
// src/routes.ts
|
|
1166
|
+
var projectRoutes = buildRouteMap({
|
|
1167
|
+
routes: {
|
|
1168
|
+
new: projectNewCommand
|
|
1169
|
+
},
|
|
1170
|
+
docs: {
|
|
1171
|
+
brief: "Project commands"
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
var routes = buildRouteMap({
|
|
1175
|
+
routes: {
|
|
1176
|
+
init: initCommand,
|
|
1177
|
+
new: newCommand,
|
|
1178
|
+
list: listCommand,
|
|
1179
|
+
projects: projectsCommand,
|
|
1180
|
+
project: projectRoutes,
|
|
1181
|
+
check: checkCommand
|
|
1182
|
+
},
|
|
1183
|
+
docs: {
|
|
1184
|
+
brief: "AGLIT CLI"
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
// src/app.ts
|
|
1189
|
+
var app = buildApplication(routes, {
|
|
1190
|
+
name: "aglit",
|
|
1191
|
+
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// src/index.ts
|
|
1195
|
+
async function main() {
|
|
1196
|
+
await run(app, process.argv.slice(2), {
|
|
1197
|
+
process,
|
|
1198
|
+
async forCommand() {
|
|
1199
|
+
return { process, cwd: process.cwd() };
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
main();
|
|
1204
|
+
|
|
1205
|
+
//# debugId=FD892239846FB21564756E2164756E21
|
|
1206
|
+
//# sourceMappingURL=index.js.map
|