@sixfactors-ai/codeloop 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 +214 -0
- package/dist/__tests__/integration/skill-board.test.d.ts +1 -0
- package/dist/__tests__/integration/skill-board.test.js +76 -0
- package/dist/__tests__/integration/skill-board.test.js.map +1 -0
- package/dist/__tests__/integration/tdd-planning.test.d.ts +1 -0
- package/dist/__tests__/integration/tdd-planning.test.js +41 -0
- package/dist/__tests__/integration/tdd-planning.test.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +115 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/serve.d.ts +7 -0
- package/dist/commands/serve.js +113 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +177 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +95 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/__tests__/board.test.d.ts +1 -0
- package/dist/lib/__tests__/board.test.js +220 -0
- package/dist/lib/__tests__/board.test.js.map +1 -0
- package/dist/lib/__tests__/scaffold.test.d.ts +1 -0
- package/dist/lib/__tests__/scaffold.test.js +39 -0
- package/dist/lib/__tests__/scaffold.test.js.map +1 -0
- package/dist/lib/__tests__/serve.test.d.ts +1 -0
- package/dist/lib/__tests__/serve.test.js +57 -0
- package/dist/lib/__tests__/serve.test.js.map +1 -0
- package/dist/lib/__tests__/server.test.d.ts +1 -0
- package/dist/lib/__tests__/server.test.js +100 -0
- package/dist/lib/__tests__/server.test.js.map +1 -0
- package/dist/lib/__tests__/smoke.test.d.ts +1 -0
- package/dist/lib/__tests__/smoke.test.js +7 -0
- package/dist/lib/__tests__/smoke.test.js.map +1 -0
- package/dist/lib/board.d.ts +38 -0
- package/dist/lib/board.js +86 -0
- package/dist/lib/board.js.map +1 -0
- package/dist/lib/detect.d.ts +13 -0
- package/dist/lib/detect.js +60 -0
- package/dist/lib/detect.js.map +1 -0
- package/dist/lib/scaffold.d.ts +8 -0
- package/dist/lib/scaffold.js +105 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/dist/lib/server.d.ts +5 -0
- package/dist/lib/server.js +125 -0
- package/dist/lib/server.js.map +1 -0
- package/dist/lib/version.d.ts +10 -0
- package/dist/lib/version.js +27 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/ui/404.html +1 -0
- package/dist/ui/_next/static/XkK-IaWE1h2_WYJHhUKNa/_buildManifest.js +1 -0
- package/dist/ui/_next/static/XkK-IaWE1h2_WYJHhUKNa/_ssgManifest.js +1 -0
- package/dist/ui/_next/static/chunks/255-54d3085ce94738a4.js +1 -0
- package/dist/ui/_next/static/chunks/423-bb541b7ae2733575.js +1 -0
- package/dist/ui/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dist/ui/_next/static/chunks/app/_not-found/page-d6bc774f7acb716e.js +1 -0
- package/dist/ui/_next/static/chunks/app/layout-e5fc8e78e1c8da95.js +1 -0
- package/dist/ui/_next/static/chunks/app/page-a1867b0e8c871ff8.js +1 -0
- package/dist/ui/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
- package/dist/ui/_next/static/chunks/main-49fd204fc9037ea3.js +1 -0
- package/dist/ui/_next/static/chunks/main-app-c46afa2f48f3aaef.js +1 -0
- package/dist/ui/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dist/ui/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dist/ui/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/ui/_next/static/chunks/webpack-4a462cecab786e93.js +1 -0
- package/dist/ui/_next/static/css/721d4a8588775f36.css +1 -0
- package/dist/ui/index.html +1 -0
- package/dist/ui/index.txt +19 -0
- package/package.json +53 -0
- package/starters/generic.yaml +45 -0
- package/starters/go.yaml +47 -0
- package/starters/node-typescript.yaml +56 -0
- package/starters/python.yaml +50 -0
- package/templates/codeloop/board.json +5 -0
- package/templates/codeloop/gotchas.md +13 -0
- package/templates/codeloop/patterns.md +15 -0
- package/templates/codeloop/principles.md +49 -0
- package/templates/codeloop/rules.md +23 -0
- package/templates/commands/commit.md +245 -0
- package/templates/commands/manage.md +77 -0
- package/templates/commands/plan.md +83 -0
- package/templates/commands/reflect.md +93 -0
- package/templates/tasks/todo.md +3 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { getServeStatus } from '../../commands/serve.js';
|
|
6
|
+
describe('serve background mode', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'codeloop-serve-'));
|
|
10
|
+
mkdirSync(join(tmpDir, '.codeloop'), { recursive: true });
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
it('--bg writes PID file', () => {
|
|
16
|
+
// Simulate what --bg does: write PID to file
|
|
17
|
+
const pidPath = join(tmpDir, '.codeloop', '.serve.pid');
|
|
18
|
+
writeFileSync(pidPath, '12345', 'utf-8');
|
|
19
|
+
expect(existsSync(pidPath)).toBe(true);
|
|
20
|
+
expect(readFileSync(pidPath, 'utf-8').trim()).toBe('12345');
|
|
21
|
+
});
|
|
22
|
+
it('--stop kills process and cleans PID file', () => {
|
|
23
|
+
// Simulate: PID file exists with our own PID (so kill(0) works)
|
|
24
|
+
const pidPath = join(tmpDir, '.codeloop', '.serve.pid');
|
|
25
|
+
writeFileSync(pidPath, String(process.pid), 'utf-8');
|
|
26
|
+
expect(existsSync(pidPath)).toBe(true);
|
|
27
|
+
// getServeStatus should detect it as running
|
|
28
|
+
// We test with the actual function by temporarily using our PID
|
|
29
|
+
// (We can't actually kill ourselves, but we can test the PID file logic)
|
|
30
|
+
});
|
|
31
|
+
it('--stop when not running does not throw', () => {
|
|
32
|
+
// No PID file exists
|
|
33
|
+
const status = getServeStatus(tmpDir);
|
|
34
|
+
expect(status.running).toBe(false);
|
|
35
|
+
expect(status.pid).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
it('status reports running state from PID file', () => {
|
|
38
|
+
// No PID file = not running
|
|
39
|
+
expect(getServeStatus(tmpDir).running).toBe(false);
|
|
40
|
+
// Write PID file with current process PID (it exists, so kill(pid, 0) succeeds)
|
|
41
|
+
const pidPath = join(tmpDir, '.codeloop', '.serve.pid');
|
|
42
|
+
writeFileSync(pidPath, String(process.pid), 'utf-8');
|
|
43
|
+
const status = getServeStatus(tmpDir);
|
|
44
|
+
expect(status.running).toBe(true);
|
|
45
|
+
expect(status.pid).toBe(process.pid);
|
|
46
|
+
});
|
|
47
|
+
it('cleans up stale PID files', () => {
|
|
48
|
+
// Write a PID file with a PID that definitely doesn't exist
|
|
49
|
+
const pidPath = join(tmpDir, '.codeloop', '.serve.pid');
|
|
50
|
+
writeFileSync(pidPath, '999999999', 'utf-8');
|
|
51
|
+
const status = getServeStatus(tmpDir);
|
|
52
|
+
expect(status.running).toBe(false);
|
|
53
|
+
// Stale PID file should be cleaned up
|
|
54
|
+
expect(existsSync(pidPath)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=serve.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/serve.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,IAAI,MAAc,CAAC;IAEnB,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;QACxD,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,6CAA6C;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACxD,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAEzC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,gEAAgE;QAChE,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACxD,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QAErD,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEvC,6CAA6C;QAC7C,gEAAgE;QAChE,yEAAyE;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,qBAAqB;QACrB,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,4BAA4B;QAC5B,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEnD,gFAAgF;QAChF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACxD,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QAErD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,4DAA4D;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACxD,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QAE7C,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,sCAAsC;QACtC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { createBoard, addTask, saveBoard, loadBoard } from '../board.js';
|
|
6
|
+
import { createApp } from '../server.js';
|
|
7
|
+
describe('server', () => {
|
|
8
|
+
let tmpDir;
|
|
9
|
+
let app;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'codeloop-server-'));
|
|
12
|
+
// Seed with an empty board
|
|
13
|
+
saveBoard(tmpDir, createBoard());
|
|
14
|
+
({ app } = createApp(tmpDir));
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
describe('REST API', () => {
|
|
20
|
+
it('GET /api/board returns board contents', async () => {
|
|
21
|
+
const res = await app.request('/api/board');
|
|
22
|
+
expect(res.status).toBe(200);
|
|
23
|
+
const body = await res.json();
|
|
24
|
+
expect(body.version).toBe(1);
|
|
25
|
+
expect(body.columns).toEqual(['backlog', 'planned', 'in_progress', 'review', 'done']);
|
|
26
|
+
expect(body.tasks).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
it('POST /api/tasks creates task and persists', async () => {
|
|
29
|
+
const res = await app.request('/api/tasks', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({ title: 'New task', labels: ['feat'] }),
|
|
33
|
+
});
|
|
34
|
+
expect(res.status).toBe(201);
|
|
35
|
+
const task = await res.json();
|
|
36
|
+
expect(task.id).toBe('t-001');
|
|
37
|
+
expect(task.title).toBe('New task');
|
|
38
|
+
expect(task.labels).toEqual(['feat']);
|
|
39
|
+
expect(task.status).toBe('backlog');
|
|
40
|
+
// Verify persisted to disk
|
|
41
|
+
const board = loadBoard(tmpDir);
|
|
42
|
+
expect(board.tasks).toHaveLength(1);
|
|
43
|
+
expect(board.tasks[0].title).toBe('New task');
|
|
44
|
+
});
|
|
45
|
+
it('PATCH /api/tasks/:id updates task', async () => {
|
|
46
|
+
// Create a task first
|
|
47
|
+
let board = addTask(createBoard(), { title: 'Original' });
|
|
48
|
+
saveBoard(tmpDir, board);
|
|
49
|
+
const res = await app.request('/api/tasks/t-001', {
|
|
50
|
+
method: 'PATCH',
|
|
51
|
+
headers: { 'Content-Type': 'application/json' },
|
|
52
|
+
body: JSON.stringify({ title: 'Updated', status: 'in_progress' }),
|
|
53
|
+
});
|
|
54
|
+
expect(res.status).toBe(200);
|
|
55
|
+
const task = await res.json();
|
|
56
|
+
expect(task.title).toBe('Updated');
|
|
57
|
+
expect(task.status).toBe('in_progress');
|
|
58
|
+
// Verify persisted
|
|
59
|
+
board = loadBoard(tmpDir);
|
|
60
|
+
expect(board.tasks[0].title).toBe('Updated');
|
|
61
|
+
});
|
|
62
|
+
it('PATCH /api/tasks/:id returns 404 for bad ID', async () => {
|
|
63
|
+
const res = await app.request('/api/tasks/t-999', {
|
|
64
|
+
method: 'PATCH',
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({ title: 'Nope' }),
|
|
67
|
+
});
|
|
68
|
+
expect(res.status).toBe(404);
|
|
69
|
+
const body = await res.json();
|
|
70
|
+
expect(body.error).toContain('not found');
|
|
71
|
+
});
|
|
72
|
+
it('DELETE /api/tasks/:id removes task', async () => {
|
|
73
|
+
let board = addTask(createBoard(), { title: 'To delete' });
|
|
74
|
+
saveBoard(tmpDir, board);
|
|
75
|
+
const res = await app.request('/api/tasks/t-001', {
|
|
76
|
+
method: 'DELETE',
|
|
77
|
+
});
|
|
78
|
+
expect(res.status).toBe(200);
|
|
79
|
+
const body = await res.json();
|
|
80
|
+
expect(body.ok).toBe(true);
|
|
81
|
+
// Verify removed from disk
|
|
82
|
+
board = loadBoard(tmpDir);
|
|
83
|
+
expect(board.tasks).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
it('DELETE /api/tasks/:id returns 404 for bad ID', async () => {
|
|
86
|
+
const res = await app.request('/api/tasks/t-999', {
|
|
87
|
+
method: 'DELETE',
|
|
88
|
+
});
|
|
89
|
+
expect(res.status).toBe(404);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('SSE', () => {
|
|
93
|
+
it('GET /api/events returns event stream', async () => {
|
|
94
|
+
const res = await app.request('/api/events');
|
|
95
|
+
expect(res.status).toBe(200);
|
|
96
|
+
expect(res.headers.get('content-type')).toContain('text/event-stream');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=server.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/server.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACf,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface TaskStep {
|
|
2
|
+
text: string;
|
|
3
|
+
done: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface Task {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
status: 'backlog' | 'planned' | 'in_progress' | 'review' | 'done';
|
|
10
|
+
labels: string[];
|
|
11
|
+
steps: TaskStep[];
|
|
12
|
+
commits: string[];
|
|
13
|
+
acceptanceCriteria?: string[];
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
export interface Board {
|
|
18
|
+
version: 1;
|
|
19
|
+
columns: string[];
|
|
20
|
+
tasks: Task[];
|
|
21
|
+
}
|
|
22
|
+
export declare function createBoard(): Board;
|
|
23
|
+
export declare function nextId(board: Board): string;
|
|
24
|
+
export interface AddTaskInput {
|
|
25
|
+
title: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
status?: Task['status'];
|
|
28
|
+
labels?: string[];
|
|
29
|
+
steps?: TaskStep[];
|
|
30
|
+
acceptanceCriteria?: string[];
|
|
31
|
+
}
|
|
32
|
+
export declare function addTask(board: Board, input: AddTaskInput): Board;
|
|
33
|
+
export declare function updateTask(board: Board, id: string, patch: Partial<Omit<Task, 'id' | 'createdAt'>>): Board;
|
|
34
|
+
export declare function moveTask(board: Board, id: string, newStatus: Task['status']): Board;
|
|
35
|
+
export declare function deleteTask(board: Board, id: string): Board;
|
|
36
|
+
export declare function getTask(board: Board, id: string): Task | undefined;
|
|
37
|
+
export declare function loadBoard(dir: string): Board;
|
|
38
|
+
export declare function saveBoard(dir: string, board: Board): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
const DEFAULT_COLUMNS = ['backlog', 'planned', 'in_progress', 'review', 'done'];
|
|
4
|
+
export function createBoard() {
|
|
5
|
+
return {
|
|
6
|
+
version: 1,
|
|
7
|
+
columns: [...DEFAULT_COLUMNS],
|
|
8
|
+
tasks: [],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function nextId(board) {
|
|
12
|
+
if (board.tasks.length === 0)
|
|
13
|
+
return 't-001';
|
|
14
|
+
const maxNum = board.tasks.reduce((max, t) => {
|
|
15
|
+
const num = parseInt(t.id.replace('t-', ''), 10);
|
|
16
|
+
return num > max ? num : max;
|
|
17
|
+
}, 0);
|
|
18
|
+
return `t-${String(maxNum + 1).padStart(3, '0')}`;
|
|
19
|
+
}
|
|
20
|
+
export function addTask(board, input) {
|
|
21
|
+
const now = new Date().toISOString();
|
|
22
|
+
const task = {
|
|
23
|
+
id: nextId(board),
|
|
24
|
+
title: input.title,
|
|
25
|
+
description: input.description,
|
|
26
|
+
status: input.status ?? 'backlog',
|
|
27
|
+
labels: input.labels ?? [],
|
|
28
|
+
steps: input.steps ? [...input.steps] : [],
|
|
29
|
+
commits: [],
|
|
30
|
+
acceptanceCriteria: input.acceptanceCriteria,
|
|
31
|
+
createdAt: now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
...board,
|
|
36
|
+
tasks: [...board.tasks, task],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function updateTask(board, id, patch) {
|
|
40
|
+
const index = board.tasks.findIndex(t => t.id === id);
|
|
41
|
+
if (index === -1)
|
|
42
|
+
throw new Error(`Task ${id} not found`);
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
const updated = {
|
|
45
|
+
...board.tasks[index],
|
|
46
|
+
...patch,
|
|
47
|
+
updatedAt: now,
|
|
48
|
+
};
|
|
49
|
+
const tasks = [...board.tasks];
|
|
50
|
+
tasks[index] = updated;
|
|
51
|
+
return { ...board, tasks };
|
|
52
|
+
}
|
|
53
|
+
export function moveTask(board, id, newStatus) {
|
|
54
|
+
return updateTask(board, id, { status: newStatus });
|
|
55
|
+
}
|
|
56
|
+
export function deleteTask(board, id) {
|
|
57
|
+
const index = board.tasks.findIndex(t => t.id === id);
|
|
58
|
+
if (index === -1)
|
|
59
|
+
throw new Error(`Task ${id} not found`);
|
|
60
|
+
return {
|
|
61
|
+
...board,
|
|
62
|
+
tasks: board.tasks.filter(t => t.id !== id),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function getTask(board, id) {
|
|
66
|
+
return board.tasks.find(t => t.id === id);
|
|
67
|
+
}
|
|
68
|
+
const BOARD_PATH = '.codeloop/board.json';
|
|
69
|
+
export function loadBoard(dir) {
|
|
70
|
+
const filePath = join(dir, BOARD_PATH);
|
|
71
|
+
if (!existsSync(filePath))
|
|
72
|
+
return createBoard();
|
|
73
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
74
|
+
return JSON.parse(content);
|
|
75
|
+
}
|
|
76
|
+
export function saveBoard(dir, board) {
|
|
77
|
+
const filePath = join(dir, BOARD_PATH);
|
|
78
|
+
const dirPath = dirname(filePath);
|
|
79
|
+
if (!existsSync(dirPath)) {
|
|
80
|
+
mkdirSync(dirPath, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
const tmpPath = filePath + '.tmp';
|
|
83
|
+
writeFileSync(tmpPath, JSON.stringify(board, null, 2) + '\n', 'utf-8');
|
|
84
|
+
renameSync(tmpPath, filePath);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=board.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/lib/board.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type StackId = 'node-typescript' | 'python' | 'go' | 'generic';
|
|
2
|
+
export type ToolId = 'claude' | 'cursor' | 'codex';
|
|
3
|
+
export interface DetectionResult {
|
|
4
|
+
stack: StackId;
|
|
5
|
+
description: string;
|
|
6
|
+
matchedFile: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function detectStack(projectDir: string): DetectionResult;
|
|
9
|
+
/**
|
|
10
|
+
* Detect which AI coding tools are present in the project.
|
|
11
|
+
* Looks for tool-specific directories.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectTools(projectDir: string): ToolId[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
const rules = [
|
|
4
|
+
{
|
|
5
|
+
stack: 'node-typescript',
|
|
6
|
+
files: ['tsconfig.json'],
|
|
7
|
+
description: 'TypeScript (Node.js)',
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
stack: 'python',
|
|
11
|
+
files: ['pyproject.toml', 'setup.py', 'requirements.txt', 'Pipfile'],
|
|
12
|
+
description: 'Python',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
stack: 'go',
|
|
16
|
+
files: ['go.mod'],
|
|
17
|
+
description: 'Go',
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
export function detectStack(projectDir) {
|
|
21
|
+
for (const rule of rules) {
|
|
22
|
+
for (const file of rule.files) {
|
|
23
|
+
if (existsSync(join(projectDir, file))) {
|
|
24
|
+
return {
|
|
25
|
+
stack: rule.stack,
|
|
26
|
+
description: rule.description,
|
|
27
|
+
matchedFile: file,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
stack: 'generic',
|
|
34
|
+
description: 'Generic project',
|
|
35
|
+
matchedFile: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detect which AI coding tools are present in the project.
|
|
40
|
+
* Looks for tool-specific directories.
|
|
41
|
+
*/
|
|
42
|
+
export function detectTools(projectDir) {
|
|
43
|
+
const detected = [];
|
|
44
|
+
// Claude Code: .claude/ directory or CLAUDE.md
|
|
45
|
+
if (existsSync(join(projectDir, '.claude')) || existsSync(join(projectDir, 'CLAUDE.md'))) {
|
|
46
|
+
detected.push('claude');
|
|
47
|
+
}
|
|
48
|
+
// Cursor: .cursor/ directory or .cursorrules
|
|
49
|
+
if (existsSync(join(projectDir, '.cursor')) || existsSync(join(projectDir, '.cursorrules'))) {
|
|
50
|
+
detected.push('cursor');
|
|
51
|
+
}
|
|
52
|
+
// Codex: .codex/ directory or AGENTS.md or .agents/
|
|
53
|
+
if (existsSync(join(projectDir, '.codex')) ||
|
|
54
|
+
existsSync(join(projectDir, 'AGENTS.md')) ||
|
|
55
|
+
existsSync(join(projectDir, '.agents'))) {
|
|
56
|
+
detected.push('codex');
|
|
57
|
+
}
|
|
58
|
+
return detected;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../../src/lib/detect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,MAAM,KAAK,GAAoB;IAC7B;QACE,KAAK,EAAE,iBAAiB;QACxB,KAAK,EAAE,CAAC,eAAe,CAAC;QACxB,WAAW,EAAE,sBAAsB;KACpC;IACD;QACE,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,CAAC,gBAAgB,EAAE,UAAU,EAAE,kBAAkB,EAAE,SAAS,CAAC;QACpE,WAAW,EAAE,QAAQ;KACtB;IACD;QACE,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,CAAC,QAAQ,CAAC;QACjB,WAAW,EAAE,IAAI;KAClB;CACF,CAAC;AAQF,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBACvC,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,WAAW,EAAE,IAAI;iBAClB,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,iBAAiB;QAC9B,WAAW,EAAE,IAAI;KAClB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,+CAA+C;IAC/C,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QACzF,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,6CAA6C;IAC7C,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QAC5F,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,oDAAoD;IACpD,IACE,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACtC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACzC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,EACvC,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ToolId = 'claude' | 'cursor' | 'codex';
|
|
2
|
+
export interface ScaffoldResult {
|
|
3
|
+
created: string[];
|
|
4
|
+
skipped: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function scaffold(projectDir: string, starterFile: string, tools: ToolId[]): ScaffoldResult;
|
|
7
|
+
export declare function getTemplateVersion(templatePath: string): string | null;
|
|
8
|
+
export declare function getInstalledVersion(projectDir: string, filePath: string): string | null;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fse from 'fs-extra';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const { copySync, ensureDirSync, existsSync, readFileSync } = fse;
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
// Walk up from dist/lib/ to package root
|
|
8
|
+
const PACKAGE_ROOT = join(__dirname, '..', '..');
|
|
9
|
+
/**
|
|
10
|
+
* Map template commands to tool-specific destinations.
|
|
11
|
+
*
|
|
12
|
+
* Claude Code: .claude/commands/*.md (markdown with frontmatter)
|
|
13
|
+
* Cursor: .cursor/commands/*.md (same format — Cursor supports this natively)
|
|
14
|
+
* Codex: .agents/skills/<name>/SKILL.md (YAML frontmatter with name/description)
|
|
15
|
+
*/
|
|
16
|
+
function getCommandDestinations(tools) {
|
|
17
|
+
const commands = ['plan', 'manage', 'commit', 'reflect'];
|
|
18
|
+
const files = [];
|
|
19
|
+
for (const cmd of commands) {
|
|
20
|
+
const source = `templates/commands/${cmd}.md`;
|
|
21
|
+
if (tools.includes('claude')) {
|
|
22
|
+
files.push({ source, destination: `.claude/commands/${cmd}.md`, overwrite: false });
|
|
23
|
+
}
|
|
24
|
+
if (tools.includes('cursor')) {
|
|
25
|
+
files.push({ source, destination: `.cursor/commands/${cmd}.md`, overwrite: false });
|
|
26
|
+
}
|
|
27
|
+
if (tools.includes('codex')) {
|
|
28
|
+
files.push({ source, destination: `.agents/skills/${cmd}/SKILL.md`, overwrite: false });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
function getKnowledgeFiles() {
|
|
34
|
+
return [
|
|
35
|
+
{ source: 'templates/codeloop/rules.md', destination: '.codeloop/rules.md', overwrite: false },
|
|
36
|
+
{ source: 'templates/codeloop/gotchas.md', destination: '.codeloop/gotchas.md', overwrite: false },
|
|
37
|
+
{ source: 'templates/codeloop/patterns.md', destination: '.codeloop/patterns.md', overwrite: false },
|
|
38
|
+
{ source: 'templates/codeloop/principles.md', destination: '.codeloop/principles.md', overwrite: false },
|
|
39
|
+
{ source: 'templates/codeloop/board.json', destination: '.codeloop/board.json', overwrite: false },
|
|
40
|
+
{ source: 'templates/tasks/todo.md', destination: 'tasks/todo.md', overwrite: false },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
export function scaffold(projectDir, starterFile, tools) {
|
|
44
|
+
const result = { created: [], skipped: [] };
|
|
45
|
+
// 1. Copy command files to tool-specific directories
|
|
46
|
+
const commandFiles = getCommandDestinations(tools);
|
|
47
|
+
for (const file of commandFiles) {
|
|
48
|
+
const destPath = join(projectDir, file.destination);
|
|
49
|
+
const srcPath = join(PACKAGE_ROOT, file.source);
|
|
50
|
+
if (!existsSync(srcPath))
|
|
51
|
+
continue;
|
|
52
|
+
if (existsSync(destPath) && !file.overwrite) {
|
|
53
|
+
result.skipped.push(file.destination);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
ensureDirSync(dirname(destPath));
|
|
57
|
+
copySync(srcPath, destPath);
|
|
58
|
+
result.created.push(file.destination);
|
|
59
|
+
}
|
|
60
|
+
// 2. Copy knowledge files → .codeloop/
|
|
61
|
+
for (const file of getKnowledgeFiles()) {
|
|
62
|
+
const destPath = join(projectDir, file.destination);
|
|
63
|
+
const srcPath = join(PACKAGE_ROOT, file.source);
|
|
64
|
+
if (!existsSync(srcPath))
|
|
65
|
+
continue;
|
|
66
|
+
if (existsSync(destPath) && !file.overwrite) {
|
|
67
|
+
result.skipped.push(file.destination);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
ensureDirSync(dirname(destPath));
|
|
71
|
+
copySync(srcPath, destPath);
|
|
72
|
+
result.created.push(file.destination);
|
|
73
|
+
}
|
|
74
|
+
// 3. Copy starter config → .codeloop/config.yaml
|
|
75
|
+
const configDest = join(projectDir, '.codeloop/config.yaml');
|
|
76
|
+
if (!existsSync(configDest)) {
|
|
77
|
+
const starterPath = join(PACKAGE_ROOT, 'starters', starterFile);
|
|
78
|
+
if (existsSync(starterPath)) {
|
|
79
|
+
ensureDirSync(dirname(configDest));
|
|
80
|
+
copySync(starterPath, configDest);
|
|
81
|
+
result.created.push('.codeloop/config.yaml');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
result.skipped.push('.codeloop/config.yaml');
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
export function getTemplateVersion(templatePath) {
|
|
90
|
+
const fullPath = join(PACKAGE_ROOT, templatePath);
|
|
91
|
+
if (!existsSync(fullPath))
|
|
92
|
+
return null;
|
|
93
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
94
|
+
const match = content.match(/<!--\s*codeloop-version:\s*([\d.]+)\s*-->/);
|
|
95
|
+
return match ? match[1] : null;
|
|
96
|
+
}
|
|
97
|
+
export function getInstalledVersion(projectDir, filePath) {
|
|
98
|
+
const fullPath = join(projectDir, filePath);
|
|
99
|
+
if (!existsSync(fullPath))
|
|
100
|
+
return null;
|
|
101
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
102
|
+
const match = content.match(/<!--\s*codeloop-version:\s*([\d.]+)\s*-->/);
|
|
103
|
+
return match ? match[1] : null;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/lib/scaffold.ts"],"names":[],"mappings":"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"}
|