@leejungkiin/awkit 1.1.7 → 1.2.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 (48) hide show
  1. package/README.md +36 -4
  2. package/bin/awk.js +2 -2
  3. package/package.json +6 -3
  4. package/skill-packs/neural-memory/skills/nm-memory-sync/SKILL.md +14 -1
  5. package/skills/gitnexus-intelligence/SKILL.md +224 -0
  6. package/skills/orchestrator/SKILL.md +9 -0
  7. package/skills/symphony-orchestrator/SKILL.md +9 -7
  8. package/workflows/gitnexus.md +123 -0
  9. package/symphony/LICENSE +0 -21
  10. package/symphony/README.md +0 -178
  11. package/symphony/app/api/agents/route.js +0 -152
  12. package/symphony/app/api/events/route.js +0 -22
  13. package/symphony/app/api/knowledge/route.js +0 -253
  14. package/symphony/app/api/locks/route.js +0 -29
  15. package/symphony/app/api/notes/route.js +0 -125
  16. package/symphony/app/api/preflight/route.js +0 -23
  17. package/symphony/app/api/projects/route.js +0 -116
  18. package/symphony/app/api/roles/route.js +0 -134
  19. package/symphony/app/api/skills/route.js +0 -82
  20. package/symphony/app/api/status/route.js +0 -18
  21. package/symphony/app/api/tasks/route.js +0 -157
  22. package/symphony/app/api/workflows/route.js +0 -61
  23. package/symphony/app/api/workspaces/route.js +0 -15
  24. package/symphony/app/globals.css +0 -2605
  25. package/symphony/app/layout.js +0 -20
  26. package/symphony/app/page.js +0 -2122
  27. package/symphony/cli/index.js +0 -1060
  28. package/symphony/core/agent-manager.js +0 -357
  29. package/symphony/core/context-bus.js +0 -100
  30. package/symphony/core/db.js +0 -223
  31. package/symphony/core/file-lock-manager.js +0 -154
  32. package/symphony/core/merge-pipeline.js +0 -234
  33. package/symphony/core/orchestrator.js +0 -236
  34. package/symphony/core/task-manager.js +0 -335
  35. package/symphony/core/workspace-manager.js +0 -168
  36. package/symphony/jsconfig.json +0 -7
  37. package/symphony/lib/core.mjs +0 -1034
  38. package/symphony/mcp/index.js +0 -29
  39. package/symphony/mcp/server.js +0 -110
  40. package/symphony/mcp/tools/context.js +0 -80
  41. package/symphony/mcp/tools/locks.js +0 -99
  42. package/symphony/mcp/tools/status.js +0 -82
  43. package/symphony/mcp/tools/tasks.js +0 -216
  44. package/symphony/mcp/tools/workspace.js +0 -143
  45. package/symphony/next.config.mjs +0 -7
  46. package/symphony/package.json +0 -53
  47. package/symphony/scripts/postinstall.js +0 -49
  48. package/symphony/symphony.config.js +0 -41
@@ -1,178 +0,0 @@
1
- # 🎼 AWKit Symphony
2
-
3
- > Multi-Agent Orchestration for AI Coding Assistants
4
-
5
- Symphony coordinates multiple AI agents working on the same codebase — managing tasks, preventing file conflicts, and providing real-time visibility through a dashboard.
6
-
7
- ## ✨ Features
8
-
9
- | Feature | Description |
10
- |---------|-------------|
11
- | **Task Management** | Create, assign, track tasks with priority & acceptance criteria |
12
- | **Git Isolation** | Auto worktree/clone per task — agents work on separate branches |
13
- | **File Locking** | Pessimistic locking prevents two agents from editing the same file |
14
- | **Merge Pipeline** | Auto-rebase + fast-forward merge when tasks complete |
15
- | **MCP Server** | 14 tools for IDE integration via Model Context Protocol |
16
- | **Dashboard** | Real-time Kanban board, agent status, events feed |
17
- | **Context Bus** | Event pub/sub — agents notify each other of changes |
18
-
19
- ## 🚀 Quick Start
20
-
21
- ```bash
22
- # Install
23
- cd symphony
24
- npm install
25
-
26
- # Start dashboard (port 3100)
27
- npm run dev
28
-
29
- # Or use CLI
30
- node cli/index.js status
31
- node cli/index.js task list
32
- ```
33
-
34
- ## 📋 CLI Commands
35
-
36
- ```bash
37
- # System
38
- symphony status # Show system status
39
- symphony start # Start dashboard server
40
- symphony dashboard # Open dashboard in browser
41
-
42
- # Tasks
43
- symphony task list [-s ready] # List tasks (filter by status)
44
- symphony task create "Feature X" -p 1 # Create task (priority 1-3)
45
- symphony task show <id> # Show task details
46
-
47
- # Workspaces
48
- symphony workspace list # List active workspaces
49
- symphony workspace create <task-id> # Create workspace for task
50
- symphony workspace merge <task-id> # Auto-merge completed task
51
- symphony workspace clean # Remove merged workspaces
52
-
53
- # File Locks
54
- symphony lock list # Show active locks
55
- symphony lock release <file> # Force-release stuck lock
56
-
57
- # MCP Server
58
- symphony mcp-serve [-n "Agent Name"] # Start MCP server (stdio)
59
- ```
60
-
61
- ## 🔌 MCP Integration
62
-
63
- Add to your IDE's MCP config:
64
-
65
- ```json
66
- {
67
- "mcpServers": {
68
- "symphony": {
69
- "command": "node",
70
- "args": ["/path/to/symphony/mcp/server.js"],
71
- "env": {
72
- "SYMPHONY_AGENT_NAME": "my-agent"
73
- }
74
- }
75
- }
76
- }
77
- ```
78
-
79
- ### Available MCP Tools (14)
80
-
81
- | Tool | Description |
82
- |------|-------------|
83
- | `symphony_available_tasks` | List available tasks |
84
- | `symphony_claim_task` | Claim a task (get workspace + branch) |
85
- | `symphony_report_progress` | Report progress (0-100%) |
86
- | `symphony_complete_task` | Complete task + trigger merge |
87
- | `symphony_abandon_task` | Abandon a task |
88
- | `symphony_check_files` | Check file lock status |
89
- | `symphony_lock_files` | Lock files for editing |
90
- | `symphony_unlock_files` | Release file locks |
91
- | `symphony_broadcast` | Broadcast event to other agents |
92
- | `symphony_events` | Query context bus events |
93
- | `symphony_status` | Get system status |
94
- | `symphony_create_task` | Create a new task |
95
- | `symphony_workspace_status` | Get workspace info + diff stats |
96
- | `symphony_merge_task` | Run auto-merge pipeline |
97
-
98
- ## 🏗️ Architecture
99
-
100
- ```
101
- symphony/
102
- ├── core/ # Engine
103
- │ ├── db.js # SQLite (WAL mode)
104
- │ ├── task-manager.js # Task CRUD + state machine
105
- │ ├── workspace-manager.js # Git worktree lifecycle
106
- │ ├── merge-pipeline.js # Auto-rebase + merge
107
- │ ├── file-lock-manager.js # Pessimistic file locking
108
- │ ├── context-bus.js # Event pub/sub
109
- │ └── orchestrator.js # Agent dispatch + state
110
- ├── mcp/ # MCP Server
111
- │ ├── server.js # stdio transport
112
- │ ├── index.js # Tool registry (14 tools)
113
- │ └── tools/ # Tool implementations
114
- ├── cli/ # CLI
115
- │ └── index.js # Commander.js commands
116
- ├── app/ # Dashboard (Next.js)
117
- │ ├── page.js # Kanban + status + events
118
- │ └── api/ # REST API routes
119
- └── lib/
120
- └── core.mjs # ESM bridge for Turbopack
121
- ```
122
-
123
- ## ⚙️ Configuration
124
-
125
- Edit `symphony.config.js`:
126
-
127
- ```js
128
- module.exports = {
129
- port: 3100,
130
- maxAgents: 3,
131
- workspace: {
132
- type: 'hybrid', // 'worktree' | 'clone' | 'hybrid'
133
- cloneThreshold: 30, // files > 30 → full clone
134
- },
135
- git: {
136
- autoMerge: true,
137
- targetBranch: 'main',
138
- branchPrefix: 'symphony/',
139
- },
140
- locks: {
141
- strategy: 'pessimistic',
142
- autoRelease: 3600, // seconds
143
- },
144
- };
145
- ```
146
-
147
- ## 📊 Dashboard
148
-
149
- The dashboard runs on `http://localhost:3100` and provides:
150
-
151
- - **Kanban Board** — Tasks by status (Ready → In Progress → Review → Done)
152
- - **Agent Panel** — Connected agents with live status
153
- - **Events Feed** — Real-time context bus events
154
- - **Lock Panel** — Active file locks with force-release
155
- - **Stats Bar** — Task counts and system health
156
-
157
- ## 🔄 Task Lifecycle
158
-
159
- ```
160
- Ready → Claimed → In Progress → Review → Done
161
- │ │
162
- └─── Abandoned ◀────────────────────────┘
163
- ```
164
-
165
- 1. **Create** task via CLI or dashboard
166
- 2. **Agent claims** via MCP → gets isolated workspace + branch
167
- 3. **Agent works** → reports progress, locks files, broadcasts events
168
- 4. **Agent completes** → triggers auto-merge pipeline
169
- 5. **Merge** → rebase onto main, fast-forward, cleanup workspace
170
-
171
- ## 📦 Tech Stack
172
-
173
- - **Runtime:** Node.js
174
- - **Database:** SQLite (via better-sqlite3, WAL mode)
175
- - **Dashboard:** Next.js 16 + React 19
176
- - **MCP:** @modelcontextprotocol/sdk
177
- - **CLI:** Commander.js
178
- - **Git:** Native git commands (worktree, rebase, merge)
@@ -1,152 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import {
3
- listAllAgents, registerAgent, updateAgentProfile, removeAgent, dispatchTask,
4
- listProjectAgents, getProjectAgent, createProjectAgent, updateProjectAgent,
5
- removeProjectAgent, attachProjectAgentSession, detachProjectAgentSession,
6
- } from '../../../lib/core.mjs';
7
-
8
- // GET /api/agents — List agents (supports ?project=X for project-scoped filtering)
9
- export async function GET(request) {
10
- try {
11
- const { searchParams } = new URL(request.url);
12
- const project = searchParams.get('project');
13
- const type = searchParams.get('type') || 'project'; // 'project' (default) or 'legacy'
14
-
15
- if (type === 'legacy') {
16
- const agents = listAllAgents();
17
- return NextResponse.json({ agents, type: 'legacy' });
18
- }
19
-
20
- const agents = listProjectAgents(project || undefined);
21
- return NextResponse.json({ agents, type: 'project' });
22
- } catch (error) {
23
- return NextResponse.json(
24
- { error: error.message },
25
- { status: 500 }
26
- );
27
- }
28
- }
29
-
30
- // POST /api/agents — Create agent (project-scoped or legacy)
31
- export async function POST(request) {
32
- try {
33
- const body = await request.json();
34
- if (!body.id) {
35
- return NextResponse.json({ error: 'Agent ID is required' }, { status: 400 });
36
- }
37
-
38
- // Project-scoped agent (new)
39
- if (body.projectId || body.project_id) {
40
- const agent = createProjectAgent({
41
- id: body.id,
42
- projectId: body.projectId || body.project_id,
43
- name: body.name || body.id,
44
- skills: body.skills || [],
45
- icon: body.icon,
46
- color: body.color,
47
- });
48
- return NextResponse.json({ agent, type: 'project' }, { status: 201 });
49
- }
50
-
51
- // Legacy agent
52
- const agent = registerAgent(body.id, body.name);
53
- if (body.specialties || body.color || body.max_concurrent) {
54
- updateAgentProfile(body.id, {
55
- specialties: body.specialties,
56
- color: body.color,
57
- max_concurrent: body.max_concurrent,
58
- });
59
- }
60
- const updated = listAllAgents().find(a => a.id === body.id);
61
- return NextResponse.json({ agent: updated, type: 'legacy' }, { status: 201 });
62
- } catch (error) {
63
- return NextResponse.json(
64
- { error: error.message },
65
- { status: 500 }
66
- );
67
- }
68
- }
69
-
70
- // PATCH /api/agents — Update agent, assign task, attach, or detach
71
- export async function PATCH(request) {
72
- try {
73
- const body = await request.json();
74
- if (!body.id) {
75
- return NextResponse.json({ error: 'Agent ID is required' }, { status: 400 });
76
- }
77
-
78
- // Check if this is a project agent
79
- const projectAgent = getProjectAgent(body.id);
80
-
81
- if (projectAgent) {
82
- // Project agent actions
83
- if (body.action === 'attach' && body.sessionId) {
84
- const agent = attachProjectAgentSession(body.id, body.sessionId);
85
- return NextResponse.json({ agent, action: 'attached' });
86
- }
87
- if (body.action === 'detach') {
88
- const agent = detachProjectAgentSession(body.id);
89
- return NextResponse.json({ agent, action: 'detached' });
90
- }
91
- if (body.action === 'assign' && body.taskId) {
92
- const task = dispatchTask(body.id, body.taskId);
93
- return NextResponse.json({ task, action: 'assigned' });
94
- }
95
-
96
- // Update project agent fields
97
- const agent = updateProjectAgent(body.id, {
98
- name: body.name,
99
- skills: body.skills,
100
- icon: body.icon,
101
- color: body.color,
102
- });
103
- return NextResponse.json({ agent, type: 'project' });
104
- }
105
-
106
- // Legacy agent fallback
107
- if (body.action === 'assign' && body.taskId) {
108
- const task = dispatchTask(body.id, body.taskId);
109
- return NextResponse.json({ task });
110
- }
111
-
112
- const agent = updateAgentProfile(body.id, {
113
- name: body.name,
114
- specialties: body.specialties,
115
- color: body.color,
116
- max_concurrent: body.max_concurrent,
117
- });
118
- const updated = listAllAgents().find(a => a.id === body.id);
119
- return NextResponse.json({ agent: updated, type: 'legacy' });
120
- } catch (error) {
121
- return NextResponse.json(
122
- { error: error.message },
123
- { status: 500 }
124
- );
125
- }
126
- }
127
-
128
- // DELETE /api/agents — Remove agent
129
- export async function DELETE(request) {
130
- try {
131
- const body = await request.json();
132
- if (!body.id) {
133
- return NextResponse.json({ error: 'Agent ID is required' }, { status: 400 });
134
- }
135
-
136
- // Try project agent first
137
- const projectAgent = getProjectAgent(body.id);
138
- if (projectAgent) {
139
- removeProjectAgent(body.id);
140
- return NextResponse.json({ success: true, type: 'project' });
141
- }
142
-
143
- // Legacy fallback
144
- removeAgent(body.id);
145
- return NextResponse.json({ success: true, type: 'legacy' });
146
- } catch (error) {
147
- return NextResponse.json(
148
- { error: error.message },
149
- { status: 500 }
150
- );
151
- }
152
- }
@@ -1,22 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { queryEvents } from '../../../lib/core.mjs';
3
-
4
- // GET /api/events — List recent events (scoped by project)
5
- export async function GET(request) {
6
- try {
7
- const { searchParams } = new URL(request.url);
8
- const since = searchParams.get('since') || undefined;
9
- const eventType = searchParams.get('type') || undefined;
10
- const project = searchParams.get('project') || undefined;
11
- const limit = parseInt(searchParams.get('limit') || '30');
12
-
13
- const events = queryEvents({ since, eventType, project, limit });
14
-
15
- return NextResponse.json({ events });
16
- } catch (error) {
17
- return NextResponse.json(
18
- { error: error.message },
19
- { status: 500 }
20
- );
21
- }
22
- }
@@ -1,253 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import fs from 'fs';
3
- import path from 'path';
4
-
5
- const KNOWLEDGE_DIR = path.join(
6
- process.env.HOME || process.env.USERPROFILE,
7
- '.gemini', 'antigravity', 'knowledge'
8
- );
9
-
10
- /**
11
- * GET /api/knowledge
12
- *
13
- * Query params:
14
- * - (none) → List all KI summaries
15
- * - id=<folder> → Get KI detail + artifact list
16
- * - id=<folder>&file=<path> → Read artifact file content
17
- */
18
- export async function GET(request) {
19
- try {
20
- const { searchParams } = new URL(request.url);
21
- const id = searchParams.get('id');
22
- const file = searchParams.get('file');
23
-
24
- // Read a specific artifact file
25
- if (id && file) {
26
- const filePath = path.join(KNOWLEDGE_DIR, id, file);
27
- if (!filePath.startsWith(KNOWLEDGE_DIR)) {
28
- return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
29
- }
30
- if (!fs.existsSync(filePath)) {
31
- return NextResponse.json({ error: 'File not found' }, { status: 404 });
32
- }
33
- const content = fs.readFileSync(filePath, 'utf-8');
34
- return NextResponse.json({ content, path: file });
35
- }
36
-
37
- // Get detail of a specific KI
38
- if (id) {
39
- const kiDir = path.join(KNOWLEDGE_DIR, id);
40
- if (!fs.existsSync(kiDir)) {
41
- return NextResponse.json({ error: 'KI not found' }, { status: 404 });
42
- }
43
- const metadata = readMetadata(kiDir);
44
- const artifacts = listArtifacts(kiDir);
45
- return NextResponse.json({ item: { id, ...metadata, artifacts } });
46
- }
47
-
48
- // List all KIs
49
- if (!fs.existsSync(KNOWLEDGE_DIR)) {
50
- return NextResponse.json({ items: [] });
51
- }
52
- const entries = fs.readdirSync(KNOWLEDGE_DIR, { withFileTypes: true });
53
- const items = [];
54
- for (const entry of entries) {
55
- if (!entry.isDirectory()) continue;
56
- const kiDir = path.join(KNOWLEDGE_DIR, entry.name);
57
- const metaPath = path.join(kiDir, 'metadata.json');
58
- if (!fs.existsSync(metaPath)) continue;
59
- const metadata = readMetadata(kiDir);
60
- const artifactCount = countArtifacts(kiDir);
61
- const stat = fs.statSync(metaPath);
62
- items.push({
63
- id: entry.name,
64
- title: metadata.title || entry.name,
65
- summary: metadata.summary || '',
66
- referenceCount: (metadata.references || []).length,
67
- artifactCount,
68
- updatedAt: stat.mtime.toISOString(),
69
- });
70
- }
71
- // Sort by updatedAt desc
72
- items.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
73
- return NextResponse.json({ items });
74
- } catch (error) {
75
- return NextResponse.json({ error: error.message }, { status: 500 });
76
- }
77
- }
78
-
79
- /**
80
- * POST /api/knowledge — Create a new KI
81
- * Body: { id, title, summary, references? }
82
- */
83
- export async function POST(request) {
84
- try {
85
- const body = await request.json();
86
- if (!body.id || !body.title) {
87
- return NextResponse.json({ error: 'id and title are required' }, { status: 400 });
88
- }
89
- // Sanitize id
90
- const safeId = body.id.replace(/[^a-z0-9_-]/gi, '_').toLowerCase();
91
- const kiDir = path.join(KNOWLEDGE_DIR, safeId);
92
- if (fs.existsSync(kiDir)) {
93
- return NextResponse.json({ error: 'KI already exists' }, { status: 409 });
94
- }
95
- const artifactsDir = path.join(kiDir, 'artifacts');
96
- fs.mkdirSync(artifactsDir, { recursive: true });
97
-
98
- const metadata = {
99
- title: body.title,
100
- summary: body.summary || '',
101
- references: body.references || [],
102
- };
103
- fs.writeFileSync(
104
- path.join(kiDir, 'metadata.json'),
105
- JSON.stringify(metadata, null, 2),
106
- 'utf-8'
107
- );
108
- // Create overview.md stub
109
- fs.writeFileSync(
110
- path.join(artifactsDir, 'overview.md'),
111
- `# ${body.title}\n\n${body.summary || 'TODO: Add content'}\n`,
112
- 'utf-8'
113
- );
114
- return NextResponse.json({
115
- item: { id: safeId, ...metadata, artifacts: ['artifacts/overview.md'] }
116
- }, { status: 201 });
117
- } catch (error) {
118
- return NextResponse.json({ error: error.message }, { status: 500 });
119
- }
120
- }
121
-
122
- /**
123
- * PATCH /api/knowledge — Update metadata or file content
124
- * Body: { id, file?, content?, metadata? }
125
- */
126
- export async function PATCH(request) {
127
- try {
128
- const body = await request.json();
129
- if (!body.id) {
130
- return NextResponse.json({ error: 'id is required' }, { status: 400 });
131
- }
132
- const kiDir = path.join(KNOWLEDGE_DIR, body.id);
133
- if (!fs.existsSync(kiDir)) {
134
- return NextResponse.json({ error: 'KI not found' }, { status: 404 });
135
- }
136
-
137
- // Update file content
138
- if (body.file && body.content !== undefined) {
139
- const filePath = path.join(kiDir, body.file);
140
- if (!filePath.startsWith(KNOWLEDGE_DIR)) {
141
- return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
142
- }
143
- // Ensure parent dir exists
144
- const parentDir = path.dirname(filePath);
145
- if (!fs.existsSync(parentDir)) {
146
- fs.mkdirSync(parentDir, { recursive: true });
147
- }
148
- fs.writeFileSync(filePath, body.content, 'utf-8');
149
- return NextResponse.json({ success: true, file: body.file });
150
- }
151
-
152
- // Update metadata
153
- if (body.metadata) {
154
- const metaPath = path.join(kiDir, 'metadata.json');
155
- const existing = readMetadata(kiDir);
156
- const updated = {
157
- ...existing,
158
- ...body.metadata,
159
- };
160
- fs.writeFileSync(metaPath, JSON.stringify(updated, null, 2), 'utf-8');
161
- return NextResponse.json({ success: true, metadata: updated });
162
- }
163
-
164
- return NextResponse.json({ error: 'No update specified (need file+content or metadata)' }, { status: 400 });
165
- } catch (error) {
166
- return NextResponse.json({ error: error.message }, { status: 500 });
167
- }
168
- }
169
-
170
- /**
171
- * DELETE /api/knowledge — Delete an artifact file
172
- * Body: { id, file }
173
- */
174
- export async function DELETE(request) {
175
- try {
176
- const body = await request.json();
177
- if (!body.id || !body.file) {
178
- return NextResponse.json({ error: 'id and file are required' }, { status: 400 });
179
- }
180
- // Prevent deleting metadata.json
181
- if (body.file === 'metadata.json') {
182
- return NextResponse.json({ error: 'Cannot delete metadata.json' }, { status: 400 });
183
- }
184
- const filePath = path.join(KNOWLEDGE_DIR, body.id, body.file);
185
- if (!filePath.startsWith(KNOWLEDGE_DIR)) {
186
- return NextResponse.json({ error: 'Invalid path' }, { status: 400 });
187
- }
188
- if (!fs.existsSync(filePath)) {
189
- return NextResponse.json({ error: 'File not found' }, { status: 404 });
190
- }
191
- fs.unlinkSync(filePath);
192
- return NextResponse.json({ success: true });
193
- } catch (error) {
194
- return NextResponse.json({ error: error.message }, { status: 500 });
195
- }
196
- }
197
-
198
- // ─── Helpers ────────────────────────────────────────────────────────────────
199
-
200
- function readMetadata(kiDir) {
201
- const metaPath = path.join(kiDir, 'metadata.json');
202
- if (!fs.existsSync(metaPath)) return { title: '', summary: '', references: [] };
203
- try {
204
- return JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
205
- } catch {
206
- return { title: '', summary: '', references: [] };
207
- }
208
- }
209
-
210
- function listArtifacts(kiDir) {
211
- const results = [];
212
- function walk(dir, prefix) {
213
- if (!fs.existsSync(dir)) return;
214
- const entries = fs.readdirSync(dir, { withFileTypes: true });
215
- for (const entry of entries) {
216
- const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
217
- if (entry.name === 'metadata.json') continue;
218
- if (entry.name === '.DS_Store') continue;
219
- if (entry.isDirectory()) {
220
- walk(path.join(dir, entry.name), relPath);
221
- } else {
222
- const stat = fs.statSync(path.join(dir, entry.name));
223
- results.push({
224
- path: relPath,
225
- name: entry.name,
226
- size: stat.size,
227
- updatedAt: stat.mtime.toISOString(),
228
- });
229
- }
230
- }
231
- }
232
- walk(kiDir, '');
233
- return results;
234
- }
235
-
236
- function countArtifacts(kiDir) {
237
- const artifactsDir = path.join(kiDir, 'artifacts');
238
- if (!fs.existsSync(artifactsDir)) return 0;
239
- let count = 0;
240
- function walk(dir) {
241
- const entries = fs.readdirSync(dir, { withFileTypes: true });
242
- for (const entry of entries) {
243
- if (entry.name === '.DS_Store') continue;
244
- if (entry.isDirectory()) {
245
- walk(path.join(dir, entry.name));
246
- } else {
247
- count++;
248
- }
249
- }
250
- }
251
- walk(artifactsDir);
252
- return count;
253
- }
@@ -1,29 +0,0 @@
1
- import { NextResponse } from 'next/server';
2
- import { forceReleaseLock } from '../../../lib/core.mjs';
3
-
4
- // DELETE /api/locks — Force-release a file lock
5
- export async function DELETE(request) {
6
- try {
7
- const body = await request.json();
8
-
9
- if (!body.file) {
10
- return NextResponse.json(
11
- { error: 'File path is required' },
12
- { status: 400 }
13
- );
14
- }
15
-
16
- const released = forceReleaseLock(body.file);
17
-
18
- return NextResponse.json({
19
- released,
20
- file: body.file,
21
- message: released ? 'Lock released' : 'No lock found',
22
- });
23
- } catch (error) {
24
- return NextResponse.json(
25
- { error: error.message },
26
- { status: 500 }
27
- );
28
- }
29
- }