@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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/dist/__tests__/integration/skill-board.test.d.ts +1 -0
  4. package/dist/__tests__/integration/skill-board.test.js +76 -0
  5. package/dist/__tests__/integration/skill-board.test.js.map +1 -0
  6. package/dist/__tests__/integration/tdd-planning.test.d.ts +1 -0
  7. package/dist/__tests__/integration/tdd-planning.test.js +41 -0
  8. package/dist/__tests__/integration/tdd-planning.test.js.map +1 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +115 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/commands/serve.d.ts +7 -0
  13. package/dist/commands/serve.js +113 -0
  14. package/dist/commands/serve.js.map +1 -0
  15. package/dist/commands/status.d.ts +2 -0
  16. package/dist/commands/status.js +177 -0
  17. package/dist/commands/status.js.map +1 -0
  18. package/dist/commands/update.d.ts +2 -0
  19. package/dist/commands/update.js +95 -0
  20. package/dist/commands/update.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +17 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/lib/__tests__/board.test.d.ts +1 -0
  25. package/dist/lib/__tests__/board.test.js +220 -0
  26. package/dist/lib/__tests__/board.test.js.map +1 -0
  27. package/dist/lib/__tests__/scaffold.test.d.ts +1 -0
  28. package/dist/lib/__tests__/scaffold.test.js +39 -0
  29. package/dist/lib/__tests__/scaffold.test.js.map +1 -0
  30. package/dist/lib/__tests__/serve.test.d.ts +1 -0
  31. package/dist/lib/__tests__/serve.test.js +57 -0
  32. package/dist/lib/__tests__/serve.test.js.map +1 -0
  33. package/dist/lib/__tests__/server.test.d.ts +1 -0
  34. package/dist/lib/__tests__/server.test.js +100 -0
  35. package/dist/lib/__tests__/server.test.js.map +1 -0
  36. package/dist/lib/__tests__/smoke.test.d.ts +1 -0
  37. package/dist/lib/__tests__/smoke.test.js +7 -0
  38. package/dist/lib/__tests__/smoke.test.js.map +1 -0
  39. package/dist/lib/board.d.ts +38 -0
  40. package/dist/lib/board.js +86 -0
  41. package/dist/lib/board.js.map +1 -0
  42. package/dist/lib/detect.d.ts +13 -0
  43. package/dist/lib/detect.js +60 -0
  44. package/dist/lib/detect.js.map +1 -0
  45. package/dist/lib/scaffold.d.ts +8 -0
  46. package/dist/lib/scaffold.js +105 -0
  47. package/dist/lib/scaffold.js.map +1 -0
  48. package/dist/lib/server.d.ts +5 -0
  49. package/dist/lib/server.js +125 -0
  50. package/dist/lib/server.js.map +1 -0
  51. package/dist/lib/version.d.ts +10 -0
  52. package/dist/lib/version.js +27 -0
  53. package/dist/lib/version.js.map +1 -0
  54. package/dist/ui/404.html +1 -0
  55. package/dist/ui/_next/static/XkK-IaWE1h2_WYJHhUKNa/_buildManifest.js +1 -0
  56. package/dist/ui/_next/static/XkK-IaWE1h2_WYJHhUKNa/_ssgManifest.js +1 -0
  57. package/dist/ui/_next/static/chunks/255-54d3085ce94738a4.js +1 -0
  58. package/dist/ui/_next/static/chunks/423-bb541b7ae2733575.js +1 -0
  59. package/dist/ui/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  60. package/dist/ui/_next/static/chunks/app/_not-found/page-d6bc774f7acb716e.js +1 -0
  61. package/dist/ui/_next/static/chunks/app/layout-e5fc8e78e1c8da95.js +1 -0
  62. package/dist/ui/_next/static/chunks/app/page-a1867b0e8c871ff8.js +1 -0
  63. package/dist/ui/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
  64. package/dist/ui/_next/static/chunks/main-49fd204fc9037ea3.js +1 -0
  65. package/dist/ui/_next/static/chunks/main-app-c46afa2f48f3aaef.js +1 -0
  66. package/dist/ui/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  67. package/dist/ui/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  68. package/dist/ui/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  69. package/dist/ui/_next/static/chunks/webpack-4a462cecab786e93.js +1 -0
  70. package/dist/ui/_next/static/css/721d4a8588775f36.css +1 -0
  71. package/dist/ui/index.html +1 -0
  72. package/dist/ui/index.txt +19 -0
  73. package/package.json +53 -0
  74. package/starters/generic.yaml +45 -0
  75. package/starters/go.yaml +47 -0
  76. package/starters/node-typescript.yaml +56 -0
  77. package/starters/python.yaml +50 -0
  78. package/templates/codeloop/board.json +5 -0
  79. package/templates/codeloop/gotchas.md +13 -0
  80. package/templates/codeloop/patterns.md +15 -0
  81. package/templates/codeloop/principles.md +49 -0
  82. package/templates/codeloop/rules.md +23 -0
  83. package/templates/commands/commit.md +245 -0
  84. package/templates/commands/manage.md +77 -0
  85. package/templates/commands/plan.md +83 -0
  86. package/templates/commands/reflect.md +93 -0
  87. 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":"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"}
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,IAAI,MAAc,CAAC;IACnB,IAAI,GAAwC,CAAC;IAE7C,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;QACzD,2BAA2B;QAC3B,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;QACjC,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAChC,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,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC5C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAE7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YACtF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE;gBAC1C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;aAC9D,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACtC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAEpC,2BAA2B;YAC3B,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,sBAAsB;YACtB,IAAI,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YAC1D,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAEzB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE;gBAChD,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;aAClE,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACnC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAExC,mBAAmB;YACnB,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE;gBAChD,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;aACxC,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,IAAI,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;YAC3D,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAEzB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE;gBAChD,MAAM,EAAE,QAAQ;aACjB,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE3B,2BAA2B;YAC3B,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAC1B,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;YAC5D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE;gBAChD,MAAM,EAAE,QAAQ;aACjB,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;QACnB,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ describe('test infrastructure', () => {
3
+ it('works', () => {
4
+ expect(true).toBe(true);
5
+ });
6
+ });
7
+ //# sourceMappingURL=smoke.test.js.map
@@ -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":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AA0BrC,MAAM,eAAe,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEhF,MAAM,UAAU,WAAW;IACzB,OAAO;QACL,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC,GAAG,eAAe,CAAC;QAC7B,KAAK,EAAE,EAAE;KACV,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAY;IACjC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IAE7C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACjD,OAAO,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC/B,CAAC,EAAE,CAAC,CAAC,CAAC;IAEN,OAAO,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACpD,CAAC;AAWD,MAAM,UAAU,OAAO,CAAC,KAAY,EAAE,KAAmB;IACvD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,IAAI,GAAS;QACjB,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC;QACjB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,SAAS;QACjC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;QAC1B,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;QAC1C,OAAO,EAAE,EAAE;QACX,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,SAAS,EAAE,GAAG;QACd,SAAS,EAAE,GAAG;KACf,CAAC;IAEF,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,EAAU,EAAE,KAA8C;IACjG,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAE1D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,OAAO,GAAS;QACpB,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;QACrB,GAAG,KAAK;QACR,SAAS,EAAE,GAAG;KACf,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/B,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC;IAEvB,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAY,EAAE,EAAU,EAAE,SAAyB;IAC1E,OAAO,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAY,EAAE,EAAU;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAE1D,OAAO;QACL,GAAG,KAAK;QACR,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAY,EAAE,EAAU;IAC9C,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAE1C,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IAEhD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAU,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,KAAY;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAElC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IACvE,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC"}
@@ -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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"}
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ export declare function createApp(projectDir: string, uiDir?: string): {
3
+ app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ broadcast: () => void;
5
+ };