Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .claude/skills/feeds-sprint/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: feeds-sprint
description: innate-feeds Sprint 工作流:按 tasks/sprint-N 的文档驱动方式认领、执行、回写任务状态。当用户要求"执行 sprint 任务"、"继续 sprint-1"、"按 feeds-sprint 工作流处理 issue"或创建/更新 sprint 任务时使用。
---

# feeds-sprint:文档驱动的多 Agent Sprint 工作流

本仓库用 `tasks/sprint-N/` 目录管理一个 Sprint。每个 Sprint 的执行原则:**先计划、任务独立、状态可查、可被任何 Agent 接手**。

## 目录结构(如何阅读)

```
tasks/sprint-N/
├── context.md # Sprint 背景、目标、任务索引、依赖关系、工作流约定 —— 先读这个
├── status.md # 任务状态总表(单一事实来源):todo/in_progress/done/blocked
├── research/ # 调研产出物(方案对比、验证 SQL、推荐结论)
├── task-XX-*.md # 每个独立任务一份文档:描述 + 依赖 + Issue 链接 + 验收记录
└── task-rX-*-research.md # 调研类任务同样有 task 文档,产出物放 research/
```

阅读顺序:`context.md`(全局)→ `status.md`(找活)→ 对应 `task-XX.md`(怎么做、怎么算完成)。

## 执行规则

1. **认领前先查状态**:读 `status.md`,目标 task 已是 `done` → 直接跳到下一个无依赖或依赖已满足的 task;`in_progress` 且非本人 → 不要碰。
2. **认领**:把 `status.md` 对应行改为 `in_progress` + 写 Agent 名。
3. **执行**:实现改动进代码;调研产出写 `research/`;无论哪类,回填 task 文档的「验收记录」复选框。
4. **完成**:`status.md` 置 `done` + 提交 SHA;有 GitHub Issue 的用 `gh issue close` 关闭;在「执行日志」追加一行。
5. **阻塞**:状态改 `blocked` 并在备注写原因,换下一个可做的 task,不要空转。
6. **新增任务**:新建 task 文档 + `gh issue create --label feeds-sprint-1` + 更新 `context.md` 任务表与 `status.md`。新任务必须独立;不独立则声明依赖与优先级(参考 context.md 的分组说明)。

## 本仓快速命令

```bash
bun run typecheck # backend + frontend 类型检查
bun run dev:backend # 后端 :4000(默认启用 /admin 与 /api/admin/db/*)
bun run dev:frontend # 前端 :3000
cd backend && bun run data:sync:trending && bun run data:sync:starred # 数据同步
gh issue create -R variableway/innate-feeds -l feeds-sprint-1 ...
```

## 提交约定

- 分支:`qdriven/issue-<n>`;提交信息用 conventional commits(`feat:` / `docs:` / `chore:`)。
- Sprint 文档与代码改动同一 PR 提交,PR 描述列出完成的 task。
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ jobs:

- name: Install root dependencies
working-directory: innate-feeds
run: bun install
# pnpm, not bun install: CI republishes @innate/* @0.1.0 tarballs to a
# fresh Verdaccio on every run, so bun.lock's pinned integrity hashes
# can never match (IntegrityCheckFailed). pnpm resolves fresh — same
# as local setup (innate-fe-cli install drives pnpm for frontend/).
run: pnpm install

- name: Run tests
working-directory: innate-feeds
Expand Down Expand Up @@ -100,7 +104,11 @@ jobs:

- name: Install root dependencies
working-directory: innate-feeds
run: bun install
# pnpm, not bun install: CI republishes @innate/* @0.1.0 tarballs to a
# fresh Verdaccio on every run, so bun.lock's pinned integrity hashes
# can never match (IntegrityCheckFailed). pnpm resolves fresh — same
# as local setup (innate-fe-cli install drives pnpm for frontend/).
run: pnpm install

- name: Build frontend
working-directory: innate-feeds
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ jobs:
--root "$GITHUB_WORKSPACE/innate-fe" \
--dir frontend
cp frontend/.npmrc .npmrc
bun install
bun run install:all
# pnpm, not bun install: fresh @innate/* tarballs never match
# bun.lock integrity hashes (see ci.yml note).
pnpm install

- name: Restore README snapshot cache
uses: actions/cache@v4
Expand Down
24 changes: 19 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,13 @@ All commands assume you are in the project root unless noted.

### Install dependencies

The repo is a **bun workspace** (`workspaces: backend, frontend`). `@innate/ui` / `@innate/web-domain` come from the innate-fe base repo via a private registry — run `innate-fe-cli install` first (see [docs/arch-design/innate-fe-plugin-migration.md](docs/arch-design/innate-fe-plugin-migration.md)):
The repo is a workspace (`workspaces: backend, frontend`, `pnpm-workspace.yaml`). `@innate/ui` / `@innate/web-domain` come from the innate-fe base repo via a private registry — run `innate-fe-cli install` first (see [docs/arch-design/innate-fe-plugin-migration.md](docs/arch-design/innate-fe-plugin-migration.md)). Installs are **pnpm**-based (fresh @innate/* tarballs never match `bun.lock` integrity hashes, so don't use `bun install` at the root):

```bash
export INNATE_FE_ROOT=/path/to/innate-fe
bun run setup:innate # publish @innate/* → .npmrc → rewrite workspace:* → pnpm install
bun install # = bun run install:all
bun run setup:innate # publish @innate/* → write frontend/.npmrc → pnpm install
cp frontend/.npmrc .npmrc # root .npmrc is required for @innate scope resolution
pnpm install # = bun run install:all
```

### Start development
Expand Down Expand Up @@ -338,6 +339,17 @@ Interactive docs: **`/docs`** (Swagger UI) · spec: **`/api/openapi.json`** (`ba
| GET | `/api/auth/status` | gh CLI + PAT presence (never returns the token). |
| PUT | `/api/auth/pat` | Store GitHub PAT encrypted at rest. Body: `{ token }`. |
| DELETE | `/api/auth/pat` | Remove the stored PAT. |
| GET | `/api/admin/db/tables` | All tables + row counts (admin workspace, API mode only). |
| GET | `/api/admin/db/tables/:name/schema` | Columns / indexes / DDL for one table. |
| POST | `/api/admin/db/query` | Execute a single read-only SELECT/WITH on a dedicated readonly connection; results capped at 500 rows. Body: `{ sql }`. |
| GET / POST | `/api/admin/db/queries` | Saved SQL list / create (seeded with example queries). |
| PUT / DELETE | `/api/admin/db/queries/:id` | Update / delete a saved SQL. |
| POST | `/api/admin/db/json/scan` | Scan JSON dir (default `~/.innate`, override `ADMIN_JSON_DIR`) into the temp `json_documents` registry. |
| GET | `/api/admin/db/json/files` | Registered JSON files. |

### Admin workspace (`/admin`)

Simplified SQLite workbench served by the backend in API mode (not available in static/Pages mode): left = tables with row counts, center = CodeMirror SQL editor (highlighting, Ctrl/Cmd+Enter) + result table, right = saved SQL. On by default — **disable with `--no-admin` or `ADMIN_DB_UI=off`** (convenience script `bun run dev:no-admin`). Query logic lives in `backend/src/app/admin-db.ts`; UI in `frontend/src/pages/admin/page.tsx`.

### Sync pipeline

Expand Down Expand Up @@ -367,7 +379,7 @@ Schema is defined in `backend/src/db/schema.sql`:
- **ES modules** everywhere (`"type": "module"` in both `package.json` files).
- **Strict TypeScript** is enabled (`strict: true`).
- **Path alias**: `@/*` maps to `src/*` in the frontend. In the backend, local imports use relative paths with explicit `.js` extensions (e.g., `import { … } from "./db/index.js"`).
- **No test suite** is currently present.
- **Tests** run with `bun run test` (= `bun test`; vitest is not used because its bundler cannot parse bun-only `with { type: "text" }` imports). Test files: `backend/src/**/*.test.ts`.

### Backend

Expand Down Expand Up @@ -406,6 +418,8 @@ Schema is defined in `backend/src/db/schema.sql`:
| If you are… | Go to… |
|---|---|
| Adding or changing API endpoints | `backend/src/app/server.ts` |
| Changing the admin DB workspace API | `backend/src/app/admin-db.ts`, `backend/src/data/admin-saved-queries.ts` |
| Changing the admin workspace UI | `frontend/src/pages/admin/page.tsx`, `frontend/src/services/admin-db.ts` |
| Changing how repos are fetched from GitHub | `backend/src/collector/github.ts` |
| Changing trending/starred/window sync | `backend/src/collector/sync.ts`, `sync-window.ts`, `firecrawl.ts` |
| Changing the 90-day window / README prefetch | `backend/src/collector/sync-window.ts` |
Expand Down Expand Up @@ -438,7 +452,7 @@ Schema is defined in `backend/src/db/schema.sql`:
- POST `/api/feeds/sync` validates request body with Zod schema before processing.
- All GET API routes have try/catch error handling and return structured JSON errors.
- The SQLite database path is controlled by the `DB_PATH` environment variable or defaults to `~/.innate/feeds.db`. Ensure the database file is not served or committed.
- Environment variables: `PORT` (backend port, default 4000), `DB_PATH` (SQLite path), `INNATE_HOME` (data directory, default `~/.innate`), `VITE_STATIC_MODE` (frontend static mode), `VITE_BASE_PATH` (base URL for GitHub Pages).
- Environment variables: `PORT` (backend port, default 4000), `DB_PATH` (SQLite path), `INNATE_HOME` (data directory, default `~/.innate`), `VITE_STATIC_MODE` (frontend static mode), `VITE_BASE_PATH` (base URL for GitHub Pages), `ADMIN_DB_UI` (`off` disables the admin workspace; also `--no-admin`), `ADMIN_JSON_DIR` (JSON scan root for the admin workspace, default `~/.innate`).

## Deployment notes

Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "bun --watch src/app/server.ts",
"dev:no-admin": "bun --watch src/app/server.ts --no-admin",
"build": "bun build src/app/server.ts --target=bun --minify --outdir=dist && bun build src/app/cli.ts --target=bun --minify --outfile=dist/cli.js",
"start": "bun dist/server.js",
"sync:daily": "bun src/app/cli.ts sync trending daily",
Expand Down
57 changes: 57 additions & 0 deletions backend/src/app/admin-db.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import { validateReadOnlySql } from "./admin-db.js";

describe("validateReadOnlySql", () => {
it("accepts plain SELECT", () => {
expect(validateReadOnlySql("SELECT * FROM starred_repos")).toBeNull();
expect(
validateReadOnlySql(" select full_name, stars\nfrom starred_repos;\n"),
).toBeNull();
});

it("accepts WITH (CTE) queries", () => {
expect(
validateReadOnlySql("WITH t AS (SELECT 1 AS x) SELECT x FROM t"),
).toBeNull();
});

it("accepts commented SQL with trailing semicolon", () => {
expect(
validateReadOnlySql(
"-- get everything\nSELECT * FROM trending_repos -- row; hint\n;",
),
).toBeNull();
});

it("rejects empty / comment-only input", () => {
expect(validateReadOnlySql("")).not.toBeNull();
expect(validateReadOnlySql(" ")).not.toBeNull();
expect(validateReadOnlySql("-- nothing here")).not.toBeNull();
});

it("rejects multi-statement input", () => {
expect(
validateReadOnlySql("SELECT 1; DROP TABLE starred_repos"),
).not.toBeNull();
});

it("rejects write and schema statements", () => {
for (const sql of [
"UPDATE starred_repos SET stars = 0",
"DELETE FROM starred_repos",
"INSERT INTO starred_repos VALUES (1)",
"DROP TABLE starred_repos",
"CREATE TABLE x (id INTEGER)",
"ATTACH DATABASE 'x.db' AS x",
"PRAGMA journal_mode = DELETE",
"VACUUM",
"REPLACE INTO starred_repos SELECT * FROM starred_repos",
]) {
expect(validateReadOnlySql(sql), sql).not.toBeNull();
}
});

it("rejects non-SELECT openers even without forbidden keywords", () => {
expect(validateReadOnlySql("EXPLAIN SELECT 1")).not.toBeNull();
});
});
Loading
Loading