@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dean Grover
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # codeloop
2
+
3
+ **Code agents that project manage themselves.**
4
+
5
+ Your AI agent plans the work, tracks its own tasks, and learns from every mistake — across sessions, across tools, without you babysitting it.
6
+
7
+ ![Codeloop Board](docs/board.png)
8
+
9
+ codeloop gives your project a memory that survives across sessions — and a live dashboard where you watch the agent think.
10
+
11
+ ## The Problem
12
+
13
+ AI coding tools (Claude Code, Cursor, Codex) are stateless. Every session starts from zero. You've explained that `doc.save()` has race conditions six times. You've caught `console.log` in production code on every PR. The agent never learns, because it can't remember.
14
+
15
+ Your team's hard-won knowledge — the gotchas, the patterns, the "don't do that, here's why" — lives in people's heads. Not where the AI can use it.
16
+
17
+ ## The Loop
18
+
19
+ codeloop creates a closed feedback loop between you and your AI agent:
20
+
21
+ ```
22
+ Plan → Build → Commit → Reflect
23
+ ↑ |
24
+ └─────────────────────────┘
25
+ lessons feed back in
26
+ ```
27
+
28
+ Four slash commands. That's the entire interface.
29
+
30
+ **`/plan`** reads your project's known gotchas before you start. "Last time we touched auth, we forgot to scope queries by workspace." The agent plans around landmines it hasn't personally stepped on yet.
31
+
32
+ **`/commit`** does a three-phase commit: first it **reviews** your diff against learned rubrics (not just lint rules — *your team's actual mistakes*), then it **reflects** on what happened this session ("we discovered that collection names are inconsistent — save this?"), then it commits. One command, three layers of quality.
33
+
34
+ **`/reflect`** is the deep version. End of a long session, multiple commits, hard-fought bugs. It scans everything that happened and proposes lessons to save. You pick what matters. It writes to your knowledge base.
35
+
36
+ **`/manage`** tracks the plan. Check off steps, add new ones, get a summary of where things stand.
37
+
38
+ ## How Knowledge Compounds
39
+
40
+ Here's where it gets interesting. Every gotcha has a frequency counter:
41
+
42
+ ```
43
+ Session 1: You discover that boolean query params need Transform decorators.
44
+ /commit saves it → gotchas.md [freq:1]
45
+ Next review: appears as a WARNING (non-blocking)
46
+
47
+ Session 4: It comes up again. /reflect increments → [freq:2]
48
+
49
+ Session 7: Third time. → [freq:3]
50
+ Now it's CRITICAL. /commit blocks until you confirm it's handled.
51
+
52
+ Session 20: [freq:10+]
53
+ codeloop status says: "promote this to rules.md?"
54
+ It graduates from gotcha to non-negotiable rule.
55
+ ```
56
+
57
+ **Frequency = severity.** The more something bites you, the harder the system fights to prevent it. No configuration needed — it emerges from use.
58
+
59
+ The review isn't one-size-fits-all either. Changed a backend file? It loads backend gotchas. Frontend only? It skips database warnings entirely. Scopes in your config file control what's relevant:
60
+
61
+ ```yaml
62
+ scopes:
63
+ backend:
64
+ paths: ["src/**", "lib/**"]
65
+ gotcha_sections: ["Backend", "Database"]
66
+ frontend:
67
+ paths: ["app/**", "components/**"]
68
+ gotcha_sections: ["Frontend", "React"]
69
+ ```
70
+
71
+ ## What Gets Created
72
+
73
+ ```bash
74
+ npm install -g codeloop
75
+ cd your-project
76
+ codeloop init
77
+ ```
78
+
79
+ It asks which AI tools you use, detects your tech stack, and scaffolds:
80
+
81
+ ```
82
+ .codeloop/
83
+ config.yaml ← Scopes, quality checks, diff scan rules
84
+ rules.md ← Non-negotiable rules (always CRITICAL)
85
+ gotchas.md ← Discovered gotchas with frequency tracking
86
+ patterns.md ← Proven patterns with confidence levels
87
+ principles.md ← Operating principles for the AI agent
88
+
89
+ .claude/commands/ ← Slash commands (Claude Code)
90
+ .cursor/commands/ ← Slash commands (Cursor)
91
+ .agents/skills/ ← Skills (Codex)
92
+
93
+ tasks/todo.md ← Current task plan
94
+ ```
95
+
96
+ The knowledge base (`.codeloop/`) is shared across all tools. Doesn't matter if you use Claude Code on Monday and Cursor on Tuesday — same gotchas, same patterns, same rules.
97
+
98
+ ## The Config
99
+
100
+ `.codeloop/config.yaml` is the brain. The AI reads it directly — no runtime, no server, just a YAML file the LLM parses.
101
+
102
+ ```yaml
103
+ project:
104
+ name: "my-api"
105
+
106
+ scopes:
107
+ backend:
108
+ paths: ["src/**"]
109
+ gotcha_sections: ["Backend", "Database", "API"]
110
+ pattern_sections: ["Backend", "Error Handling"]
111
+ tests:
112
+ paths: ["**/*.test.*", "**/*.spec.*"]
113
+ gotcha_sections: ["Testing"]
114
+
115
+ quality_checks:
116
+ backend:
117
+ - name: "Typecheck"
118
+ command: "npx tsc --noEmit 2>&1 | tail -20"
119
+
120
+ diff_scan:
121
+ - pattern: "console\\.log"
122
+ files: "*.ts,*.js"
123
+ exclude: "*.test.*"
124
+ severity: CRITICAL
125
+ message: "console.log in production code"
126
+
127
+ codeloop:
128
+ critical_frequency: 3
129
+ promote_frequency: 10
130
+ ```
131
+
132
+ **Scopes** connect file paths to knowledge sections — so `/commit` only loads relevant rubrics.
133
+
134
+ **Quality checks** run build/lint/type commands and report failures as CRITICAL.
135
+
136
+ **Diff scan** searches the actual diff for patterns you've banned (debug statements, .env files, explicit `any` types).
137
+
138
+ ## The Commit Flow
139
+
140
+ When you type `/commit`, this happens:
141
+
142
+ ```
143
+ Phase 1: Review
144
+ ├─ Map changed files → scopes (from config.yaml)
145
+ ├─ Load gotchas for those scopes (freq ≥ 3 = CRITICAL, 1-2 = WARNING)
146
+ ├─ Load patterns (HIGH confidence = expected, deviation = WARNING)
147
+ ├─ Load rules (always CRITICAL)
148
+ ├─ Run quality checks for active scopes
149
+ ├─ Scan diff for violations
150
+ └─ Verdict: CLEAN / WARNINGS / BLOCKED
151
+
152
+ Phase 2: Reflect (lightweight)
153
+ ├─ Scan session for new gotchas or patterns
154
+ ├─ Propose saves (you pick what to keep)
155
+ └─ Write to .codeloop/gotchas.md or patterns.md
156
+
157
+ Phase 3: Commit
158
+ ├─ Stage files
159
+ ├─ Generate conventional commit message
160
+ └─ Create commit
161
+ ```
162
+
163
+ If the review finds CRITICAL issues, it blocks. You can fix them, override, or abort. No silent failures.
164
+
165
+ ## Works With Everything
166
+
167
+ codeloop auto-detects your stack and your tools:
168
+
169
+ | Stack | Detected by | Starter config |
170
+ |-------|-------------|----------------|
171
+ | TypeScript | `tsconfig.json` | Typecheck, console.log scan, `any` warnings |
172
+ | Python | `pyproject.toml`, `setup.py` | mypy, ruff, print() detection, pdb scan |
173
+ | Go | `go.mod` | go vet, go build, fmt.Print detection |
174
+ | Generic | Fallback | Minimal — you configure |
175
+
176
+ | Tool | Commands go to | Format |
177
+ |------|---------------|--------|
178
+ | Claude Code | `.claude/commands/` | Markdown + frontmatter |
179
+ | Cursor | `.cursor/commands/` | Markdown + frontmatter |
180
+ | Codex | `.agents/skills/` | SKILL.md with YAML frontmatter |
181
+
182
+ ## CLI
183
+
184
+ ```bash
185
+ codeloop init # Interactive setup
186
+ codeloop init --tools claude,cursor # Skip tool prompt
187
+ codeloop init --starter python # Force specific stack
188
+
189
+ codeloop status # Knowledge stats, version check
190
+ codeloop update # Update skills (never touches knowledge)
191
+ codeloop update --dry-run # Preview what would change
192
+ ```
193
+
194
+ `init` never overwrites existing knowledge files. Your gotchas and patterns are sacred.
195
+
196
+ `update` refreshes the slash commands to the latest version (version-tagged with `<!-- codeloop-version: X.Y.Z -->`). Knowledge files are never touched.
197
+
198
+ ## The Knowledge Files
199
+
200
+ These are the files that make your project smarter:
201
+
202
+ **`rules.md`** — Non-negotiable. Always loaded as CRITICAL. Start with 4 universal rules, add yours.
203
+
204
+ **`gotchas.md`** — Discovered through work. Each entry has `[freq:N]`. The system auto-promotes severity as frequency climbs. Organized by sections that match your scopes.
205
+
206
+ **`patterns.md`** — What works well. HIGH-confidence patterns become expectations — deviations trigger warnings during review.
207
+
208
+ **`principles.md`** — How you want the AI to operate. Plan first? Verify before done? Subagents for research? Write it here once, it applies everywhere.
209
+
210
+ All of these are plain markdown. No lock-in, no proprietary format. If you stop using codeloop tomorrow, the knowledge stays in your repo as useful documentation.
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ const TEMPLATES_DIR = join(__dirname, '..', '..', '..', 'templates', 'commands');
8
+ function readTemplate(name) {
9
+ return readFileSync(join(TEMPLATES_DIR, `${name}.md`), 'utf-8');
10
+ }
11
+ describe('skill → board integration', () => {
12
+ describe('plan.md', () => {
13
+ it('references board.json update', () => {
14
+ const content = readTemplate('plan');
15
+ expect(content).toContain('board.json');
16
+ expect(content).toContain('.codeloop/board.json');
17
+ });
18
+ it('board update is conditional on file existence', () => {
19
+ const content = readTemplate('plan');
20
+ expect(content).toMatch(/if.*\.codeloop\/board\.json.*exists/i);
21
+ });
22
+ it('creates task with planned status', () => {
23
+ const content = readTemplate('plan');
24
+ expect(content).toContain('"planned"');
25
+ });
26
+ });
27
+ describe('manage.md', () => {
28
+ it('references board.json update', () => {
29
+ const content = readTemplate('manage');
30
+ expect(content).toContain('board.json');
31
+ expect(content).toContain('.codeloop/board.json');
32
+ });
33
+ it('board update is conditional on file existence', () => {
34
+ const content = readTemplate('manage');
35
+ expect(content).toMatch(/if.*\.codeloop\/board\.json.*exists/i);
36
+ });
37
+ it('updates step done status', () => {
38
+ const content = readTemplate('manage');
39
+ expect(content).toContain('done');
40
+ expect(content).toContain('step');
41
+ });
42
+ it('transitions to in_progress on first step', () => {
43
+ const content = readTemplate('manage');
44
+ expect(content).toContain('in_progress');
45
+ });
46
+ it('transitions to review when all steps done', () => {
47
+ const content = readTemplate('manage');
48
+ expect(content).toContain('review');
49
+ });
50
+ });
51
+ describe('commit.md', () => {
52
+ it('references board.json update', () => {
53
+ const content = readTemplate('commit');
54
+ expect(content).toContain('board.json');
55
+ expect(content).toContain('.codeloop/board.json');
56
+ });
57
+ it('board update is conditional on file existence', () => {
58
+ const content = readTemplate('commit');
59
+ expect(content).toMatch(/if.*\.codeloop\/board\.json.*exists/i);
60
+ });
61
+ it('appends commit SHA to task', () => {
62
+ const content = readTemplate('commit');
63
+ expect(content).toContain('commit SHA');
64
+ expect(content).toContain('commits');
65
+ });
66
+ });
67
+ describe('all templates', () => {
68
+ it('have version 0.2.0', () => {
69
+ for (const name of ['plan', 'manage', 'commit']) {
70
+ const content = readTemplate(name);
71
+ expect(content).toContain('codeloop-version: 0.2.0');
72
+ }
73
+ });
74
+ });
75
+ });
76
+ //# sourceMappingURL=skill-board.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-board.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/skill-board.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { readFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { createBoard, addTask } from '../../lib/board.js';
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const TEMPLATES_DIR = join(__dirname, '..', '..', '..', 'templates');
9
+ function readTemplate(path) {
10
+ return readFileSync(join(TEMPLATES_DIR, path), 'utf-8');
11
+ }
12
+ describe('TDD planning', () => {
13
+ it('plan.md template contains acceptance criteria instructions', () => {
14
+ const content = readTemplate('commands/plan.md');
15
+ expect(content.toLowerCase()).toContain('acceptance criteria');
16
+ });
17
+ it('plan.md template contains test-first instructions', () => {
18
+ const content = readTemplate('commands/plan.md');
19
+ // Should reference writing tests before implementation
20
+ expect(content.toLowerCase()).toMatch(/test.*before|red.*green|test.first/i);
21
+ });
22
+ it('principles.md contains TDD Default principle', () => {
23
+ const content = readTemplate('codeloop/principles.md');
24
+ expect(content).toContain('TDD');
25
+ });
26
+ it('board task model accepts acceptanceCriteria field', () => {
27
+ let board = createBoard();
28
+ board = addTask(board, {
29
+ title: 'TDD task',
30
+ acceptanceCriteria: ['Tests pass', 'Coverage > 80%'],
31
+ });
32
+ expect(board.tasks[0].acceptanceCriteria).toEqual(['Tests pass', 'Coverage > 80%']);
33
+ });
34
+ it('acceptanceCriteria is optional (backward compatible)', () => {
35
+ let board = createBoard();
36
+ board = addTask(board, { title: 'No criteria' });
37
+ // Should not be set (undefined) rather than empty array
38
+ expect(board.tasks[0].acceptanceCriteria).toBeUndefined();
39
+ });
40
+ });
41
+ //# sourceMappingURL=tdd-planning.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tdd-planning.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/tdd-planning.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAc,MAAM,oBAAoB,CAAC;AAEtE,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AACtC,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAErE,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAC1D,CAAC;AAED,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,OAAO,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAC;QACjD,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,OAAO,GAAG,YAAY,CAAC,kBAAkB,CAAC,CAAC;QACjD,uDAAuD;QACvD,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,OAAO,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC;QACvD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,IAAI,KAAK,GAAU,WAAW,EAAE,CAAC;QACjC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE;YACrB,KAAK,EAAE,UAAU;YACjB,kBAAkB,EAAE,CAAC,YAAY,EAAE,gBAAgB,CAAC;SACrD,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,IAAI,KAAK,GAAU,WAAW,EAAE,CAAC;QACjC,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QAEjD,wDAAwD;QACxD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
@@ -0,0 +1,115 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { createInterface } from 'readline';
4
+ import { detectStack } from '../lib/detect.js';
5
+ import { scaffold } from '../lib/scaffold.js';
6
+ import { detectTools } from '../lib/detect.js';
7
+ function prompt(question) {
8
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ return new Promise(resolve => {
10
+ rl.question(question, answer => {
11
+ rl.close();
12
+ resolve(answer.trim());
13
+ });
14
+ });
15
+ }
16
+ async function selectTools(projectDir) {
17
+ const detected = detectTools(projectDir);
18
+ const allTools = [
19
+ { id: 'claude', name: 'Claude Code', detected: detected.includes('claude') },
20
+ { id: 'cursor', name: 'Cursor', detected: detected.includes('cursor') },
21
+ { id: 'codex', name: 'Codex', detected: detected.includes('codex') },
22
+ ];
23
+ console.log();
24
+ console.log(chalk.bold(' Which AI coding tools do you use?'));
25
+ console.log();
26
+ for (let i = 0; i < allTools.length; i++) {
27
+ const tool = allTools[i];
28
+ const marker = tool.detected ? chalk.green('(detected)') : '';
29
+ console.log(` ${i + 1}. ${tool.name} ${marker}`);
30
+ }
31
+ console.log(` a. All`);
32
+ console.log();
33
+ const defaultSelection = detected.length > 0
34
+ ? detected.map(d => allTools.findIndex(t => t.id === d) + 1).join(',')
35
+ : '1';
36
+ const answer = await prompt(` Select tools (comma-separated) [${defaultSelection}]: `);
37
+ const input = answer || defaultSelection;
38
+ if (input.toLowerCase() === 'a') {
39
+ return allTools.map(t => t.id);
40
+ }
41
+ const indices = input.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
42
+ return indices
43
+ .filter(i => i >= 1 && i <= allTools.length)
44
+ .map(i => allTools[i - 1].id);
45
+ }
46
+ export const initCommand = new Command('init')
47
+ .description('Initialize codeloop in the current project')
48
+ .option('-s, --starter <name>', 'Use a specific starter (generic, node-typescript, python, go)')
49
+ .option('-t, --tools <tools>', 'Comma-separated tools: claude,cursor,codex (skip prompt)')
50
+ .action(async (options) => {
51
+ const projectDir = process.cwd();
52
+ // Detect or use specified starter
53
+ let stackId;
54
+ let stackDesc;
55
+ if (options.starter) {
56
+ stackId = options.starter;
57
+ stackDesc = options.starter;
58
+ }
59
+ else {
60
+ const detected = detectStack(projectDir);
61
+ stackId = detected.stack;
62
+ stackDesc = detected.description;
63
+ if (detected.matchedFile) {
64
+ console.log(chalk.dim(` Detected ${stackDesc} (found ${detected.matchedFile})`));
65
+ }
66
+ else {
67
+ console.log(chalk.dim(` No specific stack detected, using generic config`));
68
+ }
69
+ }
70
+ // Select tools — interactive prompt or flag
71
+ let tools;
72
+ if (options.tools) {
73
+ tools = options.tools.split(',').map(t => t.trim());
74
+ }
75
+ else {
76
+ tools = await selectTools(projectDir);
77
+ }
78
+ if (tools.length === 0) {
79
+ console.log(chalk.red('\n No tools selected. Aborting.\n'));
80
+ process.exit(1);
81
+ }
82
+ const starterFile = `${stackId}.yaml`;
83
+ console.log();
84
+ console.log(chalk.bold('Initializing codeloop...'));
85
+ console.log(chalk.dim(` Tools: ${tools.join(', ')} | Stack: ${stackDesc}`));
86
+ console.log();
87
+ const result = scaffold(projectDir, starterFile, tools);
88
+ // Print created files
89
+ if (result.created.length > 0) {
90
+ console.log(chalk.green(' Created:'));
91
+ for (const file of result.created) {
92
+ console.log(chalk.green(` + ${file}`));
93
+ }
94
+ }
95
+ // Print skipped files
96
+ if (result.skipped.length > 0) {
97
+ console.log(chalk.yellow(' Skipped (already exist):'));
98
+ for (const file of result.skipped) {
99
+ console.log(chalk.yellow(` ~ ${file}`));
100
+ }
101
+ }
102
+ console.log();
103
+ console.log(chalk.bold('Done.'));
104
+ console.log();
105
+ console.log(' Next steps:');
106
+ console.log(` 1. Edit ${chalk.cyan('.codeloop/config.yaml')} for your project`);
107
+ console.log(` 2. Add project-specific rules to ${chalk.cyan('.codeloop/rules.md')}`);
108
+ console.log(` 3. Use ${chalk.cyan('/plan')} to start your first task`);
109
+ console.log(` 4. Use ${chalk.cyan('/commit')} when ready to commit`);
110
+ console.log();
111
+ console.log(chalk.dim(' The loop gets smarter as you use it — gotchas and patterns'));
112
+ console.log(chalk.dim(' accumulate automatically through /commit and /reflect.'));
113
+ console.log();
114
+ });
115
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ export declare const serveCommand: Command;
3
+ export declare function getServeStatus(projectDir: string): {
4
+ running: boolean;
5
+ pid?: number;
6
+ port?: number;
7
+ };
@@ -0,0 +1,113 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { createApp } from '../lib/server.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ const PACKAGE_ROOT = join(__dirname, '..', '..');
10
+ const UI_DIR = join(PACKAGE_ROOT, 'dist', 'ui');
11
+ const DEFAULT_PORT = 4040;
12
+ const PID_FILE = '.codeloop/.serve.pid';
13
+ export const serveCommand = new Command('serve')
14
+ .description('Start the visual board server')
15
+ .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_PORT))
16
+ .option('--bg', 'Run in background')
17
+ .option('--stop', 'Stop background server')
18
+ .option('--open', 'Open browser after starting')
19
+ .action(async (options) => {
20
+ const projectDir = process.cwd();
21
+ const port = parseInt(options.port, 10);
22
+ // --stop: kill background server
23
+ if (options.stop) {
24
+ const pidPath = join(projectDir, PID_FILE);
25
+ if (!existsSync(pidPath)) {
26
+ console.log(chalk.dim(' Not running'));
27
+ return;
28
+ }
29
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
30
+ try {
31
+ process.kill(pid);
32
+ console.log(chalk.green(` Stopped server (PID ${pid})`));
33
+ }
34
+ catch {
35
+ console.log(chalk.yellow(` Process ${pid} not found (already stopped?)`));
36
+ }
37
+ unlinkSync(pidPath);
38
+ return;
39
+ }
40
+ // Check board exists
41
+ const boardPath = join(projectDir, '.codeloop', 'board.json');
42
+ if (!existsSync(boardPath)) {
43
+ console.log(chalk.red(' No board.json found. Run `codeloop init` first.'));
44
+ process.exit(1);
45
+ }
46
+ // --bg: fork as background process
47
+ if (options.bg) {
48
+ const { fork } = await import('child_process');
49
+ const child = fork(process.argv[1], ['serve', '--port', String(port)], {
50
+ detached: true,
51
+ stdio: 'ignore',
52
+ });
53
+ child.unref();
54
+ if (child.pid) {
55
+ const pidPath = join(projectDir, PID_FILE);
56
+ writeFileSync(pidPath, String(child.pid), 'utf-8');
57
+ console.log(chalk.green(` Board server started in background (PID ${child.pid})`));
58
+ console.log(` ${chalk.cyan(`http://localhost:${port}`)}`);
59
+ }
60
+ return;
61
+ }
62
+ // Foreground mode
63
+ const uiDir = existsSync(UI_DIR) ? UI_DIR : undefined;
64
+ const { app, broadcast } = createApp(projectDir, uiDir);
65
+ const { serve } = await import('@hono/node-server');
66
+ serve({ fetch: app.fetch, port }, () => {
67
+ console.log();
68
+ console.log(chalk.bold(` Codeloop board: ${chalk.cyan(`http://localhost:${port}`)}`));
69
+ console.log(chalk.dim(' Press Ctrl+C to stop'));
70
+ console.log();
71
+ // --open: open browser
72
+ if (options.open) {
73
+ import('child_process').then(({ exec }) => {
74
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
75
+ exec(`${cmd} http://localhost:${port}`);
76
+ });
77
+ }
78
+ });
79
+ // Watch board.json for external changes (skill writes) and push via SSE
80
+ const { watch } = await import('fs');
81
+ let debounceTimer = null;
82
+ try {
83
+ watch(boardPath, () => {
84
+ if (debounceTimer)
85
+ clearTimeout(debounceTimer);
86
+ debounceTimer = setTimeout(() => {
87
+ broadcast();
88
+ }, 100);
89
+ });
90
+ }
91
+ catch {
92
+ // board.json not created yet
93
+ }
94
+ });
95
+ export function getServeStatus(projectDir) {
96
+ const pidPath = join(projectDir, PID_FILE);
97
+ if (!existsSync(pidPath))
98
+ return { running: false };
99
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
100
+ try {
101
+ process.kill(pid, 0); // Test if process exists
102
+ return { running: true, pid };
103
+ }
104
+ catch {
105
+ // Stale PID file — clean up
106
+ try {
107
+ unlinkSync(pidPath);
108
+ }
109
+ catch { }
110
+ return { running: false };
111
+ }
112
+ }
113
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const statusCommand: Command;