- commit
- cf510d3
- parent
- 4aea341
- author
- codex@macbookpro
- date
- 2026-04-01 20:05:10 +0800 CST
feat: remove stagit repo route
8 files changed,
+219,
-143
1@@ -52,7 +52,7 @@
2 - `/artifact/` 静态文件服务
3 - `/v1/messages`、`/v1/executions`、`/v1/sessions`、`/v1/sessions/latest`
4 - `/describe` 返回 `recent_sessions_url`
5-- stagit 仓库静态页面
6+- repo 静态页浏览能力后来已迁到 `baa-pgit`,当前 `conductor` 不再提供 `/artifact/repo/*`
7
8 因此,`ARTIFACT_STATIC_SERVICE.md` 现在更适合作为已完成主线的实现参考,而不是当前活跃任务入口。
9
10@@ -80,11 +80,10 @@
11
12 ## 当前 open bug / 风险
13
14-### BUG-026
15+### BUG-026(历史背景)
16
17-- `/artifact/repo/:repo_name` 根路径不会落到默认 `log.html`
18-- 显式 `/artifact/repo/<repo>/log.html` 可用
19-- 影响 repo 首页入口和 URL 语义一致性
20+- 该问题对应的 repo 静态页路由已在后续任务中整体删除
21+- `/artifact/repo/*` 不再是当前仓库能力;相关描述只保留为历史排障背景
22
23 ### BUG-027
24
+6,
-12
1@@ -7846,10 +7846,6 @@ test("handleConductorHttpRequest serves artifact files and robots.txt", async ()
2 const { repository, snapshot } = await createLocalApiFixture();
3
4 await withArtifactStoreFixture(async ({ artifactStore }) => {
5- const repoDir = join(artifactStore.getArtifactsDir(), "repo", "demo-repo");
6- mkdirSync(repoDir, { recursive: true });
7- writeFileSync(join(repoDir, "log.html"), "<html><body>demo repo home</body></html>\n");
8-
9 await artifactStore.insertMessage({
10 conversationId: "conv_artifact",
11 id: "msg_artifact",
12@@ -7935,27 +7931,25 @@ test("handleConductorHttpRequest serves artifact files and robots.txt", async ()
13 assert.equal(sessionLatestResponse.status, 200);
14 assert.match(Buffer.from(sessionLatestResponse.body).toString("utf8"), /session_artifact/u);
15
16- const repoIndexResponse = await handleConductorHttpRequest(
17+ const removedRepoRootResponse = await handleConductorHttpRequest(
18 {
19 method: "GET",
20 path: "/artifact/repo/demo-repo"
21 },
22 context
23 );
24- assert.equal(repoIndexResponse.status, 200);
25- assert.match(repoIndexResponse.headers["content-type"], /^text\/html\b/u);
26- assert.match(Buffer.from(repoIndexResponse.body).toString("utf8"), /demo repo home/u);
27+ assert.equal(removedRepoRootResponse.status, 404);
28+ assert.equal(parseJsonBody(removedRepoRootResponse).error, "not_found");
29
30- const repoLogResponse = await handleConductorHttpRequest(
31+ const removedRepoFileResponse = await handleConductorHttpRequest(
32 {
33 method: "GET",
34 path: "/artifact/repo/demo-repo/log.html"
35 },
36 context
37 );
38- assert.equal(repoLogResponse.status, 200);
39- assert.match(repoLogResponse.headers["content-type"], /^text\/html\b/u);
40- assert.match(Buffer.from(repoLogResponse.body).toString("utf8"), /demo repo home/u);
41+ assert.equal(removedRepoFileResponse.status, 404);
42+ assert.equal(parseJsonBody(removedRepoFileResponse).error, "not_found");
43
44 const missingResponse = await handleConductorHttpRequest(
45 {
+0,
-73
1@@ -437,16 +437,6 @@ const LOCAL_API_ROUTES: LocalApiRouteDefinition[] = [
2 pathPattern: "/robots.txt",
3 summary: "返回允许 AI 访问 /artifact/ 的 robots.txt"
4 },
5- // Keep the repo route ahead of the generic artifact route so repo root URLs
6- // can fall back to log.html instead of being claimed by the generic matcher.
7- {
8- id: "service.artifact.repo",
9- exposeInDescribe: false,
10- kind: "read",
11- method: "GET",
12- pathPattern: "/artifact/repo/:repo_name/*",
13- summary: "读取 stagit 生成的 git 仓库静态页面"
14- },
15 {
16 id: "service.artifact.read",
17 exposeInDescribe: false,
18@@ -6576,67 +6566,6 @@ async function handleArtifactRead(context: LocalApiRequestContext): Promise<Cond
19 }
20 }
21
22-const REPO_STATIC_CONTENT_TYPES: Record<string, string> = {
23- ".html": "text/html; charset=utf-8",
24- ".css": "text/css; charset=utf-8",
25- ".xml": "application/xml",
26- ".atom": "application/atom+xml",
27- ".json": "application/json",
28- ".txt": "text/plain; charset=utf-8",
29- ".png": "image/png",
30- ".ico": "image/x-icon",
31- ".svg": "image/svg+xml"
32-};
33-
34-function getRepoStaticContentType(filePath: string): string {
35- const dot = filePath.lastIndexOf(".");
36-
37- if (dot !== -1) {
38- const ext = filePath.slice(dot).toLowerCase();
39- const ct = REPO_STATIC_CONTENT_TYPES[ext];
40-
41- if (ct) {
42- return ct;
43- }
44- }
45-
46- return "text/plain; charset=utf-8";
47-}
48-
49-const REPO_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
50-
51-async function handleArtifactRepoRead(context: LocalApiRequestContext): Promise<ConductorHttpResponse> {
52- const artifactStore = requireArtifactStore(context.artifactStore);
53- const repoName = context.params.repo_name;
54- const wildcard = context.params["*"] || "log.html";
55-
56- if (!repoName || !REPO_NAME_PATTERN.test(repoName) || wildcard.includes("..")) {
57- throw new LocalApiHttpError(
58- 404,
59- "not_found",
60- `No conductor route matches "${normalizePathname(context.url.pathname)}".`
61- );
62- }
63-
64- const filePath = join(artifactStore.getArtifactsDir(), "repo", repoName, wildcard);
65-
66- try {
67- return binaryResponse(200, readFileSync(filePath), {
68- "content-type": getRepoStaticContentType(wildcard)
69- });
70- } catch (error) {
71- if (isMissingFileError(error)) {
72- throw new LocalApiHttpError(
73- 404,
74- "not_found",
75- `Artifact "${normalizePathname(context.url.pathname)}" was not found.`
76- );
77- }
78-
79- throw error;
80- }
81-}
82-
83 function buildPlainTextBinaryResponse(status: number, body: string): ConductorHttpResponse {
84 return binaryResponse(status, Buffer.from(body, "utf8"), {
85 "content-type": CODE_ROUTE_CONTENT_TYPE
86@@ -7754,8 +7683,6 @@ async function dispatchBusinessRoute(
87 return handleRobotsRead();
88 case "service.artifact.read":
89 return handleArtifactRead(context);
90- case "service.artifact.repo":
91- return handleArtifactRepoRead(context);
92 case "service.code.read":
93 return handleCodeRead(context);
94 case "service.health":
+1,
-1
1@@ -379,7 +379,7 @@ D1 同步细节:
2 7. **D1 异步适配器**(TypeScript async 重写)
3 8. **D1 同步队列**(后台推送 + 重试)
4 9. **会话索引自动更新**(事件触发)
5-10. **stagit 集成**(后续)
6+10. **repo 静态页浏览能力迁出**(当前仓库不做)
7
8 ## 11. 不做的事
9
+2,
-1
1@@ -17,7 +17,7 @@
2 - 本地 SQLite + D1 异步同步
3 - `/artifact/` HTTP serve
4 - recent sessions 入口
5- - stagit 仓库浏览
6+ - repo 静态页浏览能力已迁到 `baa-pgit`;当前 `conductor` 不再提供 `/artifact/repo/*`
7 - 插件诊断日志链路已经完成:
8 - 插件 → WS → conductor → `logs/baa-plugin/YYYY-MM-DD.jsonl`
9 - `browser.final_message` ingest → `logs/baa-ingest/YYYY-MM-DD.jsonl`
10@@ -72,6 +72,7 @@
11 - `OPT-002`、`OPT-007` 已分别随 `889f746`、`b8d69c8` 合入 `main`,旧汇总中的 open 状态已改正
12 - `T-BUG-029` / `T-BUG-031` 的任务卡已完成,但旧汇总文档仍把它们写成 pending manual verification;现统一改为“建议补做浏览器复核”
13 - Artifact 静态服务已经完成,不再把它写成“下一阶段主线”
14+- `T-S050` 曾引入 stagit repo 静态页,但该能力已随 `T-S070` 迁出到 `baa-pgit`;旧汇总里“当前主线仍提供 /artifact/repo/*”的口径已清理
15
16 ## 当前最高优先级
17
+0,
-49
1@@ -1,49 +0,0 @@
2-#!/usr/bin/env bash
3-# git-snapshot.sh — generate static HTML for a git repo using stagit.
4-# Usage: git-snapshot.sh <repo_path> <output_dir>
5-#
6-# Supports incremental generation via stagit -c (cachefile).
7-
8-set -euo pipefail
9-
10-REPO_PATH="${1:?Usage: git-snapshot.sh <repo_path> <output_dir>}"
11-OUTPUT_DIR="${2:?Usage: git-snapshot.sh <repo_path> <output_dir>}"
12-
13-# Resolve to absolute paths.
14-REPO_PATH="$(cd "$REPO_PATH" && pwd)"
15-
16-# Ensure output directory exists.
17-mkdir -p "$OUTPUT_DIR"
18-OUTPUT_DIR="$(cd "$OUTPUT_DIR" && pwd)"
19-
20-CACHEFILE="$OUTPUT_DIR/.stagit-cache"
21-
22-# stagit must be run from the output directory.
23-cd "$OUTPUT_DIR"
24-
25-# Generate static HTML (with incremental cache).
26-stagit -c "$CACHEFILE" "$REPO_PATH"
27-
28-# Provide a minimal style.css if one doesn't exist yet.
29-if [ ! -f "$OUTPUT_DIR/style.css" ]; then
30- cat > "$OUTPUT_DIR/style.css" <<'CSSEOF'
31-body { font-family: monospace; margin: 1em; background: #fff; color: #222; }
32-table { border-collapse: collapse; }
33-td, th { padding: 2px 6px; }
34-a { color: #005f87; }
35-pre { overflow-x: auto; }
36-hr { border: 0; border-top: 1px solid #ccc; }
37-#content { overflow-x: auto; }
38-.num { text-align: right; }
39-CSSEOF
40-fi
41-
42-# Create a 1x1 transparent PNG for logo/favicon if missing.
43-if [ ! -f "$OUTPUT_DIR/logo.png" ]; then
44- printf '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n\xb4\x00\x00\x00\x00IEND\xaeB`\x82' > "$OUTPUT_DIR/logo.png"
45-fi
46-if [ ! -f "$OUTPUT_DIR/favicon.png" ]; then
47- cp "$OUTPUT_DIR/logo.png" "$OUTPUT_DIR/favicon.png"
48-fi
49-
50-echo "stagit: generated static HTML in $OUTPUT_DIR"
+199,
-0
1@@ -0,0 +1,199 @@
2+# Task T-S070:移除 conductor 内的 stagit 仓库静态页能力
3+
4+## 状态
5+
6+- 当前状态:`已完成`
7+- 规模预估:`M`
8+- 依赖任务:无
9+- 建议执行者:`Codex`
10+
11+## 直接给对话的提示词
12+
13+读 `/Users/george/code/baa-conductor/tasks/T-S070.md` 任务文档,完成开发任务。
14+
15+如需补背景,再读:
16+
17+- `/Users/george/code/baa-conductor/tasks/TASK_OVERVIEW.md`
18+- `/Users/george/code/baa-conductor/plans/STATUS_SUMMARY.md`
19+- `/Users/george/code/baa-conductor/tasks/T-S050.md`
20+
21+## 当前基线
22+
23+- 仓库:`/Users/george/code/baa-conductor`
24+- 分支基线:`main`
25+- 提交:`4aea341`
26+
27+## 分支与 worktree(强制)
28+
29+- 分支名:`feat/remove-stagit-repo-route`
30+- worktree 路径:`/Users/george/code/baa-conductor-remove-stagit-repo-route`
31+
32+开工步骤:
33+
34+1. `cd /Users/george/code/baa-conductor`
35+2. `git worktree add ../baa-conductor-remove-stagit-repo-route -b feat/remove-stagit-repo-route main`
36+3. `cd ../baa-conductor-remove-stagit-repo-route`
37+4. 在这个 worktree 目录里开发,不要回到主仓库目录
38+
39+完成后提交与推送:
40+
41+1. 在 worktree 里提交所有变更(包括更新后的任务文档)
42+2. `git push -u origin feat/remove-stagit-repo-route`
43+
44+合并步骤(由合并者执行):
45+
46+1. `cd /Users/george/code/baa-conductor`
47+2. `git fetch origin`
48+3. `git merge origin/feat/remove-stagit-repo-route`
49+4. `git push`
50+5. `git worktree remove ../baa-conductor-remove-stagit-repo-route`
51+
52+合并冲突处理:
53+
54+1. 如果 `git merge` 报冲突,先 `git diff` 查看冲突文件
55+2. 手动解决冲突后 `git add` 冲突文件
56+3. `git merge --continue` 完成合并
57+4. 不要用 `git merge --abort` 然后 force 覆盖
58+
59+## 目标
60+
61+删除 `conductor` 仓库内的 stagit 生成与 `/artifact/repo/*` 静态服务能力,不做兼容跳转。
62+
63+## 背景
64+
65+静态 repo 页面功能已经移到 `baa-pgit` 仓库,这个仓库内保留的 stagit 路由、脚本和测试已经不再属于当前主线。继续保留只会让 `conductor` 同时承担两套 repo 浏览方案,增加维护成本和文档歧义。
66+
67+当前实现边界很清楚:
68+
69+- `apps/conductor-daemon/src/local-api.ts` 内有 `/artifact/repo/:repo_name/*` 路由与 handler
70+- `scripts/git-snapshot.sh` 负责调用 stagit 生成静态页
71+- `apps/conductor-daemon/src/index.test.js` 有对应 repo 路由测试
72+- 多份文档仍把 stagit 仓库浏览写成当前能力
73+
74+本任务要求直接删除,不保留兼容跳转。
75+
76+## 涉及仓库
77+
78+- `/Users/george/code/baa-conductor`
79+
80+## 范围
81+
82+- 删除 conductor 内的 stagit repo 静态页路由与读取逻辑
83+- 删除 stagit 生成脚本和对应测试
84+- 更新总览、状态、实现说明文档,移除“当前主线仍提供 /artifact/repo/*”的口径
85+
86+## 路径约束
87+
88+- 不在本任务里接入 `baa-pgit` 跳转或新域名代理
89+- 不改动普通 `/artifact/:artifact_scope/:artifact_file` 读路径
90+- 不改动 `artifact-db`、D1 同步、recent sessions 或其他 artifact 主链
91+- 历史任务卡和 archive 文档可以保留历史描述,不要求批量清洗
92+
93+## 推荐实现边界
94+
95+建议删除或收缩:
96+
97+- `service.artifact.repo` 路由与 `handleArtifactRepoRead`
98+- `scripts/git-snapshot.sh`
99+- repo 静态页测试
100+
101+建议更新:
102+
103+- `/Users/george/code/baa-conductor/tasks/TASK_OVERVIEW.md`
104+- `/Users/george/code/baa-conductor/plans/STATUS_SUMMARY.md`
105+- 如有必要,更新主 README 或相关 API/plan 文档
106+
107+## 允许修改的目录
108+
109+- `/Users/george/code/baa-conductor/apps/conductor-daemon/src/`
110+- `/Users/george/code/baa-conductor/scripts/`
111+- `/Users/george/code/baa-conductor/tasks/`
112+- `/Users/george/code/baa-conductor/plans/`
113+- `/Users/george/code/baa-conductor/docs/`
114+
115+## 尽量不要修改
116+
117+- `/Users/george/code/baa-conductor/packages/`
118+- `/Users/george/code/baa-conductor/plugins/`
119+- `/Users/george/code/baa-conductor/ops/sql/`
120+
121+## 必须完成
122+
123+### 1. 删除 repo 静态页路由
124+
125+- 删除 `/artifact/repo/:repo_name/*` 路由注册
126+- 删除对应 handler、content-type 辅助逻辑和只为 repo 静态页服务的辅助代码
127+- 确保普通 `/artifact/` 路由不受影响
128+
129+### 2. 删除 stagit 生成脚本与测试
130+
131+- 删除 `scripts/git-snapshot.sh`
132+- 删除或改写只覆盖 repo 静态页的测试
133+- 确保删除后测试套仍通过
134+
135+### 3. 更新文档口径
136+
137+- 从总览和状态文档中移除“stagit 仓库浏览是当前能力”的描述
138+- 明确说明 repo 静态页能力已迁出当前仓库
139+- 更新任务文档状态与执行记录
140+
141+## 需要特别注意
142+
143+- 本任务是功能删除,不是兼容迁移;不要额外加入跳转逻辑
144+- 不要误删普通 artifact 静态读接口
145+- 不要顺手改动 `baa-pgit` 或其他仓库
146+- 所有开发必须在 worktree 中进行,不要在主仓库目录修改代码
147+
148+## 验收标准
149+
150+- `conductor` 代码中不再保留 `/artifact/repo/:repo_name/*` 路由
151+- 仓库内不再保留 `scripts/git-snapshot.sh`
152+- 文档不再把 stagit 仓库浏览写成当前主线能力
153+- 相关测试通过,且普通 artifact 路由不回退
154+
155+## 推荐验证命令
156+
157+- `pnpm -C /Users/george/code/baa-conductor/apps/conductor-daemon test`
158+- `pnpm -C /Users/george/code/baa-conductor/apps/conductor-daemon build`
159+- `rg -n "artifact/repo|stagit|git-snapshot\\.sh" /Users/george/code/baa-conductor`
160+
161+## 执行记录
162+
163+> 以下内容由执行任务的 AI 填写,创建任务时留空。
164+
165+### 开始执行
166+
167+- 执行者:`Codex`
168+- 开始时间:`2026-04-01 19:57:00 CST`
169+- 状态变更:`待开始` -> `进行中`
170+
171+### 完成摘要
172+
173+- 完成时间:`2026-04-01 20:04:52 CST`
174+- 状态变更:`进行中` -> `已完成`
175+- 修改了哪些文件:
176+ - `apps/conductor-daemon/src/local-api.ts`
177+ - `apps/conductor-daemon/src/index.test.js`
178+ - `scripts/git-snapshot.sh`
179+ - `tasks/TASK_OVERVIEW.md`
180+ - `plans/STATUS_SUMMARY.md`
181+ - `plans/ARTIFACT_STATIC_SERVICE.md`
182+ - `PROGRESS/2026-03-29-current-code-progress.md`
183+ - `tasks/T-S070.md`
184+- 核心实现思路:
185+ - 直接删除 `service.artifact.repo` 路由、`handleArtifactRepoRead` 和 repo 静态页专用 content-type/路径校验逻辑,不保留跳转或兼容层
186+ - 把 repo 静态页测试改成负向断言,确认 `/artifact/repo/*` 现在返回 `404`,同时保留普通 artifact 路径回归覆盖
187+ - 删除 `scripts/git-snapshot.sh`,并把当前文档口径统一改成“repo 静态页能力已迁到 `baa-pgit`”
188+- 跑了哪些测试:
189+ - `pnpm -C /Users/george/code/baa-conductor-remove-stagit-repo-route/apps/conductor-daemon test`
190+ - `pnpm -C /Users/george/code/baa-conductor-remove-stagit-repo-route/apps/conductor-daemon build`
191+ - `rg -n --glob '!tasks/archive/**' --glob '!plans/archive/**' --glob '!bugs/archive/**' --glob '!node_modules/**' "artifact/repo|stagit|git-snapshot\\.sh" /Users/george/code/baa-conductor-remove-stagit-repo-route`
192+
193+### 执行过程中遇到的问题
194+
195+- 主仓库 worktree 里已有未提交的文档改动,因此先确认 `main` 与 `origin/main` 一致,再从 `main` 新建独立 worktree,避免污染主仓库工作区
196+- 新建 worktree 默认没有 `node_modules`,首次执行 `pnpm test` 时 `pnpm exec tsc` 缺失;在 worktree 根目录执行一次 `pnpm install` 后恢复正常
197+
198+### 剩余风险
199+
200+- 历史任务卡、archive 文档和历史需求文档仍保留 stagit 描述;这是按任务约束刻意保留的历史记录,不代表当前主线能力
+7,
-2
1@@ -16,7 +16,7 @@
2 - `artifact-db` 持久化 messages / executions / sessions
3 - `conductor` HTTP serve `/artifact/`
4 - D1 异步同步队列
5- - stagit 仓库浏览:`/artifact/repo/baa-conductor/log.html`
6+ - repo 静态页浏览能力已迁到 `baa-pgit`;当前 `conductor` 不再提供 `/artifact/repo/*`
7 - Firefox 插件诊断日志链路已落地:
8 - `page-interceptor -> content-script -> controller -> WS -> conductor`
9 - conductor 写 `logs/baa-plugin/YYYY-MM-DD.jsonl`
10@@ -72,6 +72,7 @@
11 - `OPT-002`、`OPT-007` 已分别随 `889f746`、`b8d69c8` 合入 `main`,旧总览中的 open 状态已改正
12 - `T-BUG-029`、`T-BUG-031` 的任务卡已是 `已完成`,但旧文档仍把它们写成 pending manual verification;现统一改为“建议补做浏览器复核”
13 - Artifact 静态服务已经完成,不再把 `T-S039`~`T-S045` 写成“当前活跃主线”
14+- `T-S050` 曾引入 stagit repo 静态页,但该能力已随 `T-S070` 迁出到 `baa-pgit`;旧文档里“当前主线仍提供 /artifact/repo/*”的口径已清理
15
16 ## 当前活跃任务与优先级
17
18@@ -94,6 +95,7 @@
19 | [`T-S068`](./T-S068.md) | ChatGPT proxy send 冷启动降级保护 | S | 无 | Codex | 已完成 |
20 | [`T-S069`](./T-S069.md) | proxy_delivery 成功语义增强 | L | T-S060 | Codex | 已完成 |
21 | [`T-S065`](./T-S065.md) | policy 配置化 | M | 无 | Codex | 已完成 |
22+| [`T-S070`](./T-S070.md) | 移除 conductor 内的 stagit 仓库静态页能力 | M | 无 | Codex | 已完成 |
23
24 ### 当前下一波任务
25
26@@ -123,6 +125,7 @@
27 | T-S048 | Gemini 投递适配器 | ✅ |
28 | T-S049 | 开放 chatgpt/gemini target | ✅ |
29 | T-S050 | stagit git 静态页面 | ✅ |
30+| T-S070 | 移除 conductor 内的 stagit 仓库静态页能力 | ✅ |
31 | T-S052 | D1 数据库初始化 | ✅ |
32 | T-S053 | 插件诊断日志 | ✅ |
33 | T-S054 | 插件日志 WS 转发 | ✅ |
34@@ -181,7 +184,9 @@
35
36 ## 当前主线判断
37
38-Phase 1(浏览器主链)、Artifact 静态服务,以及 timed-jobs + 续命主线都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069` 已全部落地,当前主线已经没有 open bug blocker,也没有 open opt。
39+Phase 1(浏览器主链)、Artifact 静态服务,以及 timed-jobs + 续命主线都已完成收口。`T-S060`、`T-S061`、`T-S062`、`T-S063`、`T-S064`、`T-S065`、`T-S066`、`T-S067`、`T-S068`、`T-S069`、`T-S070` 已全部落地,当前主线已经没有 open bug blocker,也没有 open opt。
40+
41+repo 静态页浏览能力已随 `T-S070` 迁出到 `baa-pgit`;当前仓库只保留普通 artifact 静态读面,不再提供 `/artifact/repo/*`。
42
43 如果继续推进,建议:
44