feinai 0.6.5 → 0.6.6
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/CHANGELOG.md +14 -14
- package/README.md +6 -6
- package/package.json +1 -1
- package/skills/feinai-dispatch/SKILL.md +2 -2
- package/skills/feinai-implement/SKILL.md +6 -6
- package/skills/feinai-sdd/SKILL.md +3 -3
- package/skills/feinai-write-spec/SKILL.md +4 -4
- package/skills/feinai-write-tasks/SKILL.md +1 -1
- package/src/cli.ts +9 -9
- package/src/dashboard.html +8 -8
- package/src/db.ts +5 -44
- package/src/format.ts +3 -3
- package/src/server.ts +8 -8
- package/src/sqlite-adapter.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,23 +12,23 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
12
12
|
## [0.5.0] - 2026-06-10
|
|
13
13
|
|
|
14
14
|
### Added
|
|
15
|
-
- `
|
|
16
|
-
- `opengit` shipped as a binary alongside `
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
19
|
-
- Live Agents Monitor: working directory, repo name, and `.
|
|
15
|
+
- `feinai git <cmd>` — safe git wrapper subcommand. Passes through to `opengit`, enforcing a worktree-only whitelist (blocks branch, merge, rebase, checkout, etc.)
|
|
16
|
+
- `opengit` shipped as a binary alongside `feinai` — `bun install -g feinai` now installs both
|
|
17
|
+
- `feinai unblock <TASK-ID> --dep <TASK-ID>` — remove a specific dependency from a task
|
|
18
|
+
- `feinai edit --clear-blocked-by` — clear all dependencies from a task
|
|
19
|
+
- Live Agents Monitor: working directory, repo name, and `.feinai` path shown per agent card
|
|
20
20
|
- Live Agents Monitor: presence indicator — gray dot when idle, green dot with ripple animation when agents are active
|
|
21
21
|
- Live Agents Monitor: elapsed time and spec ID per card
|
|
22
|
-
- `
|
|
23
|
-
- Skills: `
|
|
22
|
+
- `feinai init` now auto-adds `.feinai/` to `.gitignore` if in a git repo
|
|
23
|
+
- Skills: `feinai-implement` — claim one pending task, implement in isolated worktree, run quality gates, push to main
|
|
24
24
|
|
|
25
25
|
### Changed
|
|
26
26
|
- Dashboard is now English-only — removed i18n/language selector
|
|
27
|
-
- `
|
|
27
|
+
- `feinai-implement` SKILL.md updated to use `feinai git` instead of `opengit` directly
|
|
28
28
|
- `package.json`: added `repository`, `homepage`, `bugs` fields
|
|
29
29
|
|
|
30
30
|
### Skills included
|
|
31
|
-
`
|
|
31
|
+
`feinai-sdd` · `feinai-write-spec` · `feinai-write-tasks` · `feinai-dispatch` · `feinai-implement`
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
@@ -37,11 +37,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
37
37
|
Initial public release. Core CLI with specs, plans, tasks, HTTP dashboard, and SDD skills.
|
|
38
38
|
|
|
39
39
|
### Features
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
42
|
-
- `
|
|
43
|
-
- `
|
|
40
|
+
- `feinai init / status / list / add / show / take / done / fail / block / release / reopen / edit`
|
|
41
|
+
- `feinai spec` — spec lifecycle (add, start, done, archive, set-content, edit)
|
|
42
|
+
- `feinai plan` — plan revisions per spec
|
|
43
|
+
- `feinai server` — HTTP dashboard + REST API + SSE live updates
|
|
44
44
|
- Live Agents Monitor — real-time view of in-progress tasks with worktree state
|
|
45
45
|
- Atomic `take` — SQL-level concurrency safety for parallel agents
|
|
46
46
|
- Append-only events audit log
|
|
47
|
-
- Skills: `
|
|
47
|
+
- Skills: `feinai-sdd`, `feinai-write-spec`, `feinai-write-tasks`, `feinai-dispatch`
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ State lives in a local SQLite file. No cloud, no external service, no account.
|
|
|
73
73
|
- Installs two binaries:
|
|
74
74
|
- `feinai` – main CLI + embedded HTTP server and dashboard.
|
|
75
75
|
- `opengit` – safe git wrapper for parallel worktrees.
|
|
76
|
-
- Stores all state in a single SQLite file: `.
|
|
76
|
+
- Stores all state in a single SQLite file: `.feinai/feinai.db`, discovered by walking up from the current directory (like `.git`).
|
|
77
77
|
|
|
78
78
|
No background services are required beyond the optional dashboard server.
|
|
79
79
|
|
|
@@ -83,7 +83,7 @@ No background services are required beyond the optional dashboard server.
|
|
|
83
83
|
|
|
84
84
|
feinai is designed to be "boring" infrastructure:
|
|
85
85
|
|
|
86
|
-
- **Local‑only state.** All coordination lives in `.
|
|
86
|
+
- **Local‑only state.** All coordination lives in `.feinai/feinai.db` in your repo. There is no remote backend.
|
|
87
87
|
- **Explicit operations.** Every change to specs, plans, tasks and worktrees goes through the CLI or HTTP API and is logged in `events`.
|
|
88
88
|
- **Safe git workflow.** `feinai git` wraps git and blocks operations that can corrupt parallel worktrees (branch, checkout, merge, rebase, reset, fetch, pull, clone).
|
|
89
89
|
- **Auditability.** Every task has a clear chain: spec → plan → task → worktree → events. Every agent identity can be traced (`$FEINAI_USER` override supported).
|
|
@@ -111,7 +111,7 @@ Agents don't parse `QUEUE.md`. They talk to a small local coordination service i
|
|
|
111
111
|
|
|
112
112
|
## Claude Code skills
|
|
113
113
|
|
|
114
|
-
feinai ships five Claude Code skills covering the full development loop. They activate automatically when `.
|
|
114
|
+
feinai ships five Claude Code skills covering the full development loop. They activate automatically when `.feinai/feinai.db` is present:
|
|
115
115
|
|
|
116
116
|
| Skill | Purpose |
|
|
117
117
|
|---------------------|--------------------------------------------------------|
|
|
@@ -169,7 +169,7 @@ sudo ln -sf ~/.bun/bin/bun /usr/local/bin/bun
|
|
|
169
169
|
cd my-project
|
|
170
170
|
|
|
171
171
|
# Initialize local state
|
|
172
|
-
feinai init # creates .
|
|
172
|
+
feinai init # creates .feinai/feinai.db, adds to .gitignore
|
|
173
173
|
|
|
174
174
|
# Add a spec and plan
|
|
175
175
|
feinai spec add SPEC-001 "User authentication" --content "..."
|
|
@@ -197,7 +197,7 @@ feinai server # live dashboard at http://127.0.0.1:8272
|
|
|
197
197
|
High‑level CLI:
|
|
198
198
|
|
|
199
199
|
```text
|
|
200
|
-
feinai init Create .
|
|
200
|
+
feinai init Create .feinai/feinai.db
|
|
201
201
|
feinai status Show counts: pending / in_progress / completed
|
|
202
202
|
feinai list [--pending] [--spec X] List tasks
|
|
203
203
|
feinai add ID "subject" Create task
|
|
@@ -267,7 +267,7 @@ Use your normal git tooling inside each worktree; use `feinai git` when operatin
|
|
|
267
267
|
## Architecture
|
|
268
268
|
|
|
269
269
|
```text
|
|
270
|
-
<project>/.
|
|
270
|
+
<project>/.feinai/feinai.db # local SQLite database (discovered like .git)
|
|
271
271
|
|
|
272
272
|
specs — what to build
|
|
273
273
|
plans — how to build it (versioned)
|
package/package.json
CHANGED
|
@@ -66,7 +66,7 @@ PATH issue in non-interactive SSH session. `~/.bun/bin` is not loaded. Tell the
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
-
### Failure: `feinai status` exits with code 2 (`no .
|
|
69
|
+
### Failure: `feinai status` exits with code 2 (`no .feinai/feinai.db found`)
|
|
70
70
|
|
|
71
71
|
feinai is installed but no DB in this project. Ask the user:
|
|
72
72
|
|
|
@@ -206,7 +206,7 @@ Spawn a subagent with this prompt template:
|
|
|
206
206
|
|
|
207
207
|
### Step E — Wait for results
|
|
208
208
|
|
|
209
|
-
Each subagent returns `done` or `fail`. The
|
|
209
|
+
Each subagent returns `done` or `fail`. The feinai DB is the source of truth — re-query it:
|
|
210
210
|
|
|
211
211
|
```bash
|
|
212
212
|
feinai show TASK-X --json
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feinai-implement
|
|
3
|
-
description: Use when a feinai task needs to be executed. Claims one pending task from
|
|
3
|
+
description: Use when a feinai task needs to be executed. Claims one pending task from feinai, implements it in an isolated worktree, runs quality gates, and pushes to main. Designed to be dispatched by feinai-dispatch or run standalone for a single task.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# feinai-implement
|
|
7
7
|
|
|
8
|
-
Claim one pending task from
|
|
8
|
+
Claim one pending task from feinai, implement it in an isolated worktree, run quality gates, push to main.
|
|
9
9
|
|
|
10
10
|
## Preconditions
|
|
11
11
|
|
|
12
12
|
1. `feinai status` succeeds and there is at least one pending task
|
|
13
13
|
2. Current working directory is a clean git repo
|
|
14
|
-
3. `feinai git status` works (feinai git is bundled with
|
|
14
|
+
3. `feinai git status` works (feinai git is bundled with feinai — no separate setup needed)
|
|
15
15
|
|
|
16
16
|
If any fails: stop and report. Do not improvise.
|
|
17
17
|
|
|
@@ -79,7 +79,7 @@ Gates fallan → seguí "Si algo falla".
|
|
|
79
79
|
**Done = 3 hechos observables:**
|
|
80
80
|
1. Quality gates pasan sin errores
|
|
81
81
|
2. Los archivos de la tarea existen con contenido correcto
|
|
82
|
-
3. Commit limpio en `main` y tarea en estado `completed` en
|
|
82
|
+
3. Commit limpio en `main` y tarea en estado `completed` en feinai
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
@@ -102,7 +102,7 @@ Gates fallan, push falla, o error en cualquier paso:
|
|
|
102
102
|
|
|
103
103
|
## Git — `feinai git` exclusivamente
|
|
104
104
|
|
|
105
|
-
`git` y `gh` están bloqueados. Usá `feinai git` para todo — es opengit bundleado con
|
|
105
|
+
`git` y `gh` están bloqueados. Usá `feinai git` para todo — es opengit bundleado con feinai.
|
|
106
106
|
|
|
107
107
|
**Permitido:**
|
|
108
108
|
- `feinai git worktree add/list/lock/unlock`
|
|
@@ -125,7 +125,7 @@ Si `feinai git` falla → **STOP**. No reintentes, no uses `git`. Reportá al us
|
|
|
125
125
|
**No modifiques:**
|
|
126
126
|
- `AGENTS.md`, `CLAUDE.md`
|
|
127
127
|
- Archivos de configuración de CI/CD, infra, o secretos (`.env`, `.env.*`)
|
|
128
|
-
- La DB de
|
|
128
|
+
- La DB de feinai directamente
|
|
129
129
|
|
|
130
130
|
**Código:**
|
|
131
131
|
- Sin `any` sin comentario justificado en la misma línea
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feinai-sdd
|
|
3
|
-
description: Use when working in spec-driven development (SDD) workflows that involve brainstorming, writing-plans, or subagent-driven-development AND the project has a `\.feinai/feinai.db` file. Replaces creating markdown files in `docs/superpowers/specs/` and `docs/superpowers/plans/` with atomic `feinai` CLI calls, keeping state queryable, race-free, and audit-logged. Also use when the user asks to create a spec, add a task, claim work, or mark progress
|
|
3
|
+
description: Use when working in spec-driven development (SDD) workflows that involve brainstorming, writing-plans, or subagent-driven-development AND the project has a `\.feinai/feinai.db` file. Replaces creating markdown files in `docs/superpowers/specs/` and `docs/superpowers/plans/` with atomic `feinai` CLI calls, keeping state queryable, race-free, and audit-logged. Also use when the user asks to create a spec, add a task, claim work, or mark progress.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# feinai SDD integration
|
|
@@ -21,7 +21,7 @@ Invoke when **all** of the following are true:
|
|
|
21
21
|
|
|
22
22
|
1. The project has a `\.feinai/feinai.db` file (walk up the directory tree from
|
|
23
23
|
the current working directory; feinai uses the same discovery pattern as git).
|
|
24
|
-
Check with: `feinai status` (exit code 0 =
|
|
24
|
+
Check with: `feinai status` (exit code 0 = feinai is set up).
|
|
25
25
|
2. You are about to:
|
|
26
26
|
- Write a spec via the `brainstorming` skill, OR
|
|
27
27
|
- Write a plan via the `writing-plans` skill, OR
|
|
@@ -274,7 +274,7 @@ field will read `bun:<pid>:<user>` for everyone.
|
|
|
274
274
|
|
|
275
275
|
| Need | Command |
|
|
276
276
|
|---|---|
|
|
277
|
-
| Is
|
|
277
|
+
| Is feinai set up here? | `feinai status` |
|
|
278
278
|
| Next pending task | `feinai list --pending --json` |
|
|
279
279
|
| Claim a task atomically | `feinai take TASK-X` |
|
|
280
280
|
| Read a spec | `feinai spec content SPEC-X` |
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feinai-write-spec
|
|
3
|
-
description: Use when the user wants to design a new feature, refactor, or change in a project that has `.
|
|
3
|
+
description: Use when the user wants to design a new feature, refactor, or change in a project that has `.feinai/feinai.db`. Writes a complete spec + plan into feinai in a single session. Replaces `brainstorming` + `writing-plans` from superpowers when feinai is active. Reads project context (CLAUDE.md, ARCHITECTURE.md, README.md), asks clarifying questions only when context has gaps, and produces spec+plan atomically with one `feinai spec add` + one `feinai plan add`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# feinai-write-spec
|
|
7
7
|
|
|
8
|
-
Write a spec + plan for a feature in one session. Output goes to
|
|
8
|
+
Write a spec + plan for a feature in one session. Output goes to feinai, not markdown files.
|
|
9
9
|
|
|
10
10
|
## Preconditions
|
|
11
11
|
|
|
12
|
-
Run `feinai status` (exit 0 =
|
|
12
|
+
Run `feinai status` (exit 0 = feinai is active). If not active, stop and ask the
|
|
13
13
|
user if they want `feinai init` or to fall back to vanilla superpowers.
|
|
14
14
|
|
|
15
15
|
## The flow — three entry modes
|
|
@@ -163,7 +163,7 @@ These come up mid-session. Ask them inline, one at a time, brief:
|
|
|
163
163
|
|
|
164
164
|
If you delegate the spec drafting to a subagent (e.g. for parallel research),
|
|
165
165
|
the subagent must NOT use this skill — it has no context. Hand it concrete
|
|
166
|
-
findings and let it return text; you write to
|
|
166
|
+
findings and let it return text; you write to feinai yourself.
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
@@ -5,7 +5,7 @@ description: Use when a spec + plan already exist in feinai (written by `feinai-
|
|
|
5
5
|
|
|
6
6
|
# feinai-write-tasks
|
|
7
7
|
|
|
8
|
-
Read an existing spec + plan from
|
|
8
|
+
Read an existing spec + plan from feinai, produce the executable task set.
|
|
9
9
|
|
|
10
10
|
## Preconditions
|
|
11
11
|
|
package/src/cli.ts
CHANGED
|
@@ -381,9 +381,9 @@ async function cmdDestroy(args: ParsedArgs): Promise<void> {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
const
|
|
385
|
-
rmSync(
|
|
386
|
-
console.log(`Destroyed ${
|
|
384
|
+
const feinaiDir = dbPath.replace(/\/feinai\.db$/, "");
|
|
385
|
+
rmSync(feinaiDir, { recursive: true, force: true });
|
|
386
|
+
console.log(`Destroyed ${feinaiDir}`);
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
function cmdInit(args: ParsedArgs): void {
|
|
@@ -770,7 +770,7 @@ function cmdSpec(rest: string[], args: ParsedArgs, format: OutputFormat): void {
|
|
|
770
770
|
async function cmdServer(args: ParsedArgs): Promise<void> {
|
|
771
771
|
const port = Number(args.options.port ?? "8272");
|
|
772
772
|
|
|
773
|
-
// --down: kill whatever is listening on the
|
|
773
|
+
// --down: kill whatever is listening on the feinai port
|
|
774
774
|
if (args.flags["down"]) {
|
|
775
775
|
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
776
776
|
console.error("Error: --port must be a valid port number");
|
|
@@ -785,7 +785,7 @@ async function cmdServer(args: ParsedArgs): Promise<void> {
|
|
|
785
785
|
for (const pid of pids) {
|
|
786
786
|
try {
|
|
787
787
|
process.kill(Number(pid), "SIGTERM");
|
|
788
|
-
console.log(`Stopped
|
|
788
|
+
console.log(`Stopped feinai server (PID ${pid}).`);
|
|
789
789
|
} catch {
|
|
790
790
|
console.error(`Failed to kill PID ${pid}.`);
|
|
791
791
|
}
|
|
@@ -812,8 +812,8 @@ async function cmdServer(args: ParsedArgs): Promise<void> {
|
|
|
812
812
|
: [process.execPath, ...process.argv.slice(1).filter(noDaemon)];
|
|
813
813
|
const child = Bun.spawn(childArgs, { detached: true, stdio: ["ignore", "ignore", "ignore"] });
|
|
814
814
|
child.unref();
|
|
815
|
-
console.log(`
|
|
816
|
-
console.log(`Stop with:
|
|
815
|
+
console.log(`feinai dashboard → http://${host}:${port}`);
|
|
816
|
+
console.log(`Stop with: feinai server --down`);
|
|
817
817
|
return;
|
|
818
818
|
}
|
|
819
819
|
|
|
@@ -821,8 +821,8 @@ async function cmdServer(args: ParsedArgs): Promise<void> {
|
|
|
821
821
|
const { startServer } = await import("./server");
|
|
822
822
|
const server = startServer({ port, host });
|
|
823
823
|
|
|
824
|
-
console.log(`
|
|
825
|
-
console.log(`Stop with:
|
|
824
|
+
console.log(`feinai dashboard listening at ${server.url}`);
|
|
825
|
+
console.log(`Stop with: feinai server --down`);
|
|
826
826
|
|
|
827
827
|
// Keep process alive until SIGINT
|
|
828
828
|
process.on("SIGINT", () => {
|
package/src/dashboard.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>
|
|
6
|
+
<title>feinai dashboard</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
--bg: #0f1117;
|
|
@@ -672,7 +672,7 @@
|
|
|
672
672
|
<header>
|
|
673
673
|
<h1>
|
|
674
674
|
<span class="live-indicator" id="live"></span>
|
|
675
|
-
|
|
675
|
+
feinai
|
|
676
676
|
</h1>
|
|
677
677
|
<div class="stats" id="stats"></div>
|
|
678
678
|
<div class="header-actions">
|
|
@@ -723,7 +723,7 @@
|
|
|
723
723
|
</section>
|
|
724
724
|
</main>
|
|
725
725
|
|
|
726
|
-
<footer>
|
|
726
|
+
<footer>feinai · streaming live · click any item for details & actions · <span id="dash-version">v—</span></footer>
|
|
727
727
|
|
|
728
728
|
<div class="modal-bg" id="modal-bg">
|
|
729
729
|
<div class="modal" id="modal">
|
|
@@ -784,7 +784,7 @@
|
|
|
784
784
|
// -------- Confirm modal --------
|
|
785
785
|
function confirm(message, skipKey) {
|
|
786
786
|
return new Promise((resolve) => {
|
|
787
|
-
if (localStorage.getItem("
|
|
787
|
+
if (localStorage.getItem("feinai-skip-" + skipKey) === "1") { resolve(true); return; }
|
|
788
788
|
const overlay = document.createElement("div");
|
|
789
789
|
overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:9999;display:flex;align-items:center;justify-content:center;";
|
|
790
790
|
overlay.innerHTML = `
|
|
@@ -800,7 +800,7 @@
|
|
|
800
800
|
</div>`;
|
|
801
801
|
document.body.appendChild(overlay);
|
|
802
802
|
overlay.querySelector("#confirm-ok").onclick = () => {
|
|
803
|
-
if (overlay.querySelector("#skip-cb").checked) localStorage.setItem("
|
|
803
|
+
if (overlay.querySelector("#skip-cb").checked) localStorage.setItem("feinai-skip-" + skipKey, "1");
|
|
804
804
|
document.body.removeChild(overlay); resolve(true);
|
|
805
805
|
};
|
|
806
806
|
overlay.querySelector("#confirm-cancel").onclick = () => { document.body.removeChild(overlay); resolve(false); };
|
|
@@ -827,7 +827,7 @@
|
|
|
827
827
|
async function api(method, path, body) {
|
|
828
828
|
const res = await fetch(path, {
|
|
829
829
|
method,
|
|
830
|
-
headers: body ? { "Content-Type": "application/json", "X-
|
|
830
|
+
headers: body ? { "Content-Type": "application/json", "X-Feinai-Actor": "dashboard" } : { "X-Feinai-Actor": "dashboard" },
|
|
831
831
|
body: body ? JSON.stringify(body) : undefined,
|
|
832
832
|
});
|
|
833
833
|
if (!res.ok) {
|
|
@@ -1456,7 +1456,7 @@
|
|
|
1456
1456
|
: '';
|
|
1457
1457
|
const workingDir = task.worktree || appContext.repoRoot || "—";
|
|
1458
1458
|
const repoName = appContext.repoName || "—";
|
|
1459
|
-
const
|
|
1459
|
+
const feinaiLoc = appContext.feinaiPath || "—";
|
|
1460
1460
|
const card = document.createElement('div');
|
|
1461
1461
|
card.className = 'worktree-card';
|
|
1462
1462
|
card.setAttribute('data-worktree-task', task.id);
|
|
@@ -1469,7 +1469,7 @@
|
|
|
1469
1469
|
<div class="worktree-context">
|
|
1470
1470
|
<div class="context-row"><span class="context-label">Working directory</span><span class="context-value">${escapeHtml(workingDir)}</span></div>
|
|
1471
1471
|
<div class="context-row"><span class="context-label">Repo</span><span class="context-value">${escapeHtml(repoName)}</span></div>
|
|
1472
|
-
<div class="context-row"><span class="context-label">.feinai</span><span class="context-value">${escapeHtml(
|
|
1472
|
+
<div class="context-row"><span class="context-label">.feinai</span><span class="context-value">${escapeHtml(feinaiLoc)}</span></div>
|
|
1473
1473
|
</div>
|
|
1474
1474
|
<span class="elapsed-time" data-elapsed="${escapeHtml(task.taken_at ?? '')}"></span>
|
|
1475
1475
|
</div>
|
package/src/db.ts
CHANGED
|
@@ -1,47 +1,10 @@
|
|
|
1
|
-
import { existsSync, mkdirSync
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { openSqlite, type DbAdapter } from "./sqlite-adapter";
|
|
4
4
|
|
|
5
5
|
const DB_DIR = ".feinai";
|
|
6
6
|
const DB_FILE = "feinai.db";
|
|
7
7
|
|
|
8
|
-
// v0.5.x migration: rename .tasca/tasca.db → .feinai/feinai.db
|
|
9
|
-
// TODO: remove in v0.7
|
|
10
|
-
function migrateLegacyDir(dir: string): void {
|
|
11
|
-
const legacyDir = join(dir, ".tasca");
|
|
12
|
-
const legacyDb = join(legacyDir, "tasca.db");
|
|
13
|
-
const newDir = join(dir, ".feinai");
|
|
14
|
-
const newDb = join(newDir, "feinai.db");
|
|
15
|
-
if (!existsSync(legacyDb) || existsSync(newDb)) return;
|
|
16
|
-
|
|
17
|
-
// Prompt user — only works in TTY contexts
|
|
18
|
-
if (process.stdout.isTTY) {
|
|
19
|
-
process.stdout.write(
|
|
20
|
-
`\n⚠ Found legacy .tasca/tasca.db at ${legacyDir}\n` +
|
|
21
|
-
` Rename to .feinai/feinai.db? [Y/n] `
|
|
22
|
-
);
|
|
23
|
-
const buf = Buffer.alloc(4);
|
|
24
|
-
let answer = "y";
|
|
25
|
-
try {
|
|
26
|
-
const n = require("node:fs").readSync(0, buf, 0, 4, null);
|
|
27
|
-
answer = buf.slice(0, n).toString().trim().toLowerCase() || "y";
|
|
28
|
-
} catch {}
|
|
29
|
-
if (answer !== "y" && answer !== "") {
|
|
30
|
-
process.stdout.write("Skipped. Run 'feinai init' to create a new DB.\n\n");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
// Non-interactive (agent/CI): migrate silently
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!existsSync(newDir)) mkdirSync(newDir, { recursive: true });
|
|
38
|
-
renameSync(legacyDb, newDb);
|
|
39
|
-
try { require("node:fs").rmdirSync(legacyDir); } catch {}
|
|
40
|
-
if (process.stdout.isTTY) {
|
|
41
|
-
process.stdout.write(`✓ Migrated to .feinai/feinai.db\n\n`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
8
|
export type DbInstance = DbAdapter;
|
|
46
9
|
|
|
47
10
|
/**
|
|
@@ -189,8 +152,6 @@ function applySchema(db: DbInstance): void {
|
|
|
189
152
|
export function findDbPath(startDir: string = process.cwd()): string | null {
|
|
190
153
|
let current = resolve(startDir);
|
|
191
154
|
while (true) {
|
|
192
|
-
// migrate legacy .tasca/tasca.db → .feinai/feinai.db if found
|
|
193
|
-
migrateLegacyDir(current);
|
|
194
155
|
const candidate = join(current, DB_DIR, DB_FILE);
|
|
195
156
|
if (existsSync(candidate)) return candidate;
|
|
196
157
|
const parent = dirname(current);
|
|
@@ -204,11 +165,11 @@ export function findDbPath(startDir: string = process.cwd()): string | null {
|
|
|
204
165
|
* Returns the absolute path of the created DB file.
|
|
205
166
|
*/
|
|
206
167
|
export function initDb(dir: string = process.cwd()): string {
|
|
207
|
-
const
|
|
208
|
-
const dbPath = join(
|
|
168
|
+
const feinaiDir = join(dir, DB_DIR);
|
|
169
|
+
const dbPath = join(feinaiDir, DB_FILE);
|
|
209
170
|
|
|
210
|
-
if (!existsSync(
|
|
211
|
-
mkdirSync(
|
|
171
|
+
if (!existsSync(feinaiDir)) {
|
|
172
|
+
mkdirSync(feinaiDir, { recursive: true });
|
|
212
173
|
}
|
|
213
174
|
|
|
214
175
|
const db = openSqlite(dbPath, { create: true });
|
package/src/format.ts
CHANGED
|
@@ -96,7 +96,7 @@ export function formatSpec(
|
|
|
96
96
|
if (opts.includeContent) {
|
|
97
97
|
for (const line of spec.content.split("\n")) lines.push(` ${line}`);
|
|
98
98
|
} else {
|
|
99
|
-
lines.push(` ${c(format, "dim", "(use --full to view;
|
|
99
|
+
lines.push(` ${c(format, "dim", "(use --full to view; feinai spec content " + spec.id + " to export)")}`);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
if (opts.plan) {
|
|
@@ -104,7 +104,7 @@ export function formatSpec(
|
|
|
104
104
|
if (opts.includeContent) {
|
|
105
105
|
for (const line of opts.plan.content.split("\n")) lines.push(` ${line}`);
|
|
106
106
|
} else {
|
|
107
|
-
lines.push(` ${c(format, "dim", "(use --full to view;
|
|
107
|
+
lines.push(` ${c(format, "dim", "(use --full to view; feinai plan show " + spec.id + " to export)")}`);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
return lines.join("\n");
|
|
@@ -154,7 +154,7 @@ export function formatStatus(
|
|
|
154
154
|
if (format === "json") return JSON.stringify(stats, null, 2);
|
|
155
155
|
const serverLine = stats.serverRunning
|
|
156
156
|
? c(format, "green", `server: running → http://127.0.0.1:${stats.serverPort}`)
|
|
157
|
-
: c(format, "dim", `server: stopped (
|
|
157
|
+
: c(format, "dim", `server: stopped (feinai server -d to start)`);
|
|
158
158
|
return [
|
|
159
159
|
`${c(format, "yellow", `pending: ${stats.pending}`)}`,
|
|
160
160
|
`${c(format, "blue", `in_progress: ${stats.in_progress}`)}`,
|
package/src/server.ts
CHANGED
|
@@ -250,7 +250,7 @@ function serverError(err: unknown): Response {
|
|
|
250
250
|
|
|
251
251
|
/**
|
|
252
252
|
* Determine actor for an inbound HTTP mutation. Priority:
|
|
253
|
-
* 1. X-
|
|
253
|
+
* 1. X-Feinai-Actor header (explicit)
|
|
254
254
|
* 2. derived from request: "dashboard:<host>" or "api:<user-agent>"
|
|
255
255
|
*/
|
|
256
256
|
function actorFromRequest(req: Request): string {
|
|
@@ -258,7 +258,7 @@ function actorFromRequest(req: Request): string {
|
|
|
258
258
|
// iterate manually to be safe.
|
|
259
259
|
let explicit: string | undefined;
|
|
260
260
|
for (const [k, v] of req.headers.entries()) {
|
|
261
|
-
if (k.toLowerCase() === "x-
|
|
261
|
+
if (k.toLowerCase() === "x-feinai-actor") {
|
|
262
262
|
explicit = v;
|
|
263
263
|
break;
|
|
264
264
|
}
|
|
@@ -294,9 +294,9 @@ async function resolveDashboardVersion(): Promise<string> {
|
|
|
294
294
|
const root = findRepoRoot();
|
|
295
295
|
if (!root) return "";
|
|
296
296
|
try {
|
|
297
|
-
const countResult = await Bun.$`git log --oneline --
|
|
297
|
+
const countResult = await Bun.$`git log --oneline -- src/dashboard.html | wc -l`.cwd(root).text();
|
|
298
298
|
const count = parseInt(countResult.trim(), 10);
|
|
299
|
-
const hashResult = await Bun.$`git log -1 --format=%h --
|
|
299
|
+
const hashResult = await Bun.$`git log -1 --format=%h -- src/dashboard.html`.cwd(root).text();
|
|
300
300
|
const hash = hashResult.trim();
|
|
301
301
|
cachedDashboardVersion = `dashboard-v${count} (${hash})`;
|
|
302
302
|
} catch {
|
|
@@ -325,7 +325,7 @@ export function startServer(opts: ServerOptions): { url: string; stop: () => voi
|
|
|
325
325
|
headers: {
|
|
326
326
|
"Access-Control-Allow-Origin": "*",
|
|
327
327
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, PATCH, OPTIONS",
|
|
328
|
-
"Access-Control-Allow-Headers": "Content-Type, X-
|
|
328
|
+
"Access-Control-Allow-Headers": "Content-Type, X-Feinai-Actor",
|
|
329
329
|
},
|
|
330
330
|
});
|
|
331
331
|
}
|
|
@@ -441,18 +441,18 @@ export function startServer(opts: ServerOptions): { url: string; stop: () => voi
|
|
|
441
441
|
return json(agents);
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
// GET /api/context — repo and
|
|
444
|
+
// GET /api/context — repo and feinai DB context info for dashboard
|
|
445
445
|
if (path === "/api/context" && method === "GET") {
|
|
446
446
|
const repoRoot = findRepoRoot();
|
|
447
447
|
const dbPath = findDbPath();
|
|
448
448
|
const home = homedir();
|
|
449
|
-
const
|
|
449
|
+
const feinaiPath = dbPath?.startsWith(home)
|
|
450
450
|
? `~${dbPath.slice(home.length)}`
|
|
451
451
|
: (dbPath ?? null);
|
|
452
452
|
return json({
|
|
453
453
|
repoRoot: repoRoot ?? null,
|
|
454
454
|
repoName: repoRoot ? basename(repoRoot) : null,
|
|
455
|
-
|
|
455
|
+
feinaiPath,
|
|
456
456
|
});
|
|
457
457
|
}
|
|
458
458
|
|
package/src/sqlite-adapter.ts
CHANGED
|
@@ -114,7 +114,7 @@ export function getSqliteConstructor(): DbConstructor {
|
|
|
114
114
|
|
|
115
115
|
if (!_constructor) {
|
|
116
116
|
console.error(`
|
|
117
|
-
Error: No SQLite backend found.
|
|
117
|
+
Error: No SQLite backend found. feinai requires one of:
|
|
118
118
|
• Bun 1.0+ (current runtime)
|
|
119
119
|
• Node.js 22.5+ (built-in node:sqlite)
|
|
120
120
|
• better-sqlite3 (npm install -g better-sqlite3)
|