@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
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
|
+

|
|
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":"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;AAEpC,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,EAAE,UAAU,CAAC,CAAC;AAEjF,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,IAAI,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAClE,CAAC;AAED,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAClC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;YACvD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;YACxC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;YAC5B,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAChD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;gBACnC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;YACvD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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,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,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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"}
|