-
FR-5a: a new, standalone four-stage Org meta-graph (renderOrgMetaGraph and friends) rather than extending the existing 3-node renderOrgGraphTopology (left untouched, it's pinned by test_graph_console_smoke.js). Each stage expands in place: planner/coder/validator reuse renderGraphVisualizerPanel unmodified against that stage's latest run (identified via org_nodes_involved, Phase 2c's replacement for the removed single-value org_node column); planner_reviewer has no self-planned work_graph_run, so it shows its planner_review_verdict execution_event history instead (new task_query.py::_planner_review_events_for_task(), mirroring the existing _loop_back_events_for_run() pattern). FR-5b: an attempt_number-ordered, click-to-reveal feedback timeline built from loop_back_events, which was already being fetched but never rendered. New frontend/test_org_meta_graph_smoke.js (35 assertions, same vm.Context sandbox convention as the existing 4 smoke tests). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
A merged graph's one run genuinely spans several org_nodes now (Planner/ Coder/Validator all append their own nodes into the SAME run) -- a single run-level org_node column was never accurate once Phase 2b/2c landed, and real per-node ownership already lives in work_node_execution.subagent_type. Schema: work_graph_run.org_node column removed; ux_work_graph_run_active narrowed from UNIQUE(task_id, org_node) to UNIQUE(task_id) WHERE status NOT IN ('done', 'failed') -- 'blocked' now counts as active on purpose, since resuming a blocked run always appends into the same row rather than inserting a fresh one. New migrate_drop_work_graph_run_org_node() handles already-populated databases, wired into backend_server.py's startup path. Call sites updated: work_graph_plan.py/work_graph_fork.py/ root_cause_trace.py's INSERTs; work_graph_run_query.py/task_query.py now return "org_nodes_involved" (derived from work_node_execution.subagent_type) instead of the removed single-value "org_node"; frontend/app.js's org-strip and run-list rendering updated to the list-based shape. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
v_task_board's task_id cell now links to #/graph?task_id=<id> for quick navigation to a task's execution graph. v_graph_console reads that param to preselect the task, and syncs the URL (via history.replaceState) on every task load -- including the initial auto-preselect -- so the address bar always reflects the task actually showing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…76ae-main-checkout-drift
-
Implements docs/todo/work-graph-time-travel-debugging-design.md, backend and console together (user confirmed 2026-08-06). Key finding that shrank the v1 scope: ux_work_graph_run_active is a PARTIAL unique index (only covers non-terminal statuses), not a blanket one -- forking from a parent run that's already reached a terminal status never collides with it at all. v1 restricts forking to exactly that case, so no index/constraint change is needed, only two new nullable columns (parent_run_id, fork_of_node_exec_id). Resolved the requirements doc's FR-1 ambiguity ("override input_payload, re-trigger execution") as OUTPUT SUBSTITUTION instead: the fork point's own output_payload is replaced with the human-supplied content directly, it is never re-executed via a new claude -p call -- only the strict downstream subgraph actually re-runs. This sidesteps reconciling the different build_prompt/input_payload conventions each caller uses (Planner's artifact_type lookup, E's generic task_prompt field, F's jury) -- work_graph_fork.py only needs the persisted graph_manifest and each node's output_payload. Implementation needs ZERO changes to the execution engine: pre-seeding every non-downstream node as a 'done' work_node_execution row before calling the unmodified run_graph() on the unmodified graph_manifest is enough -- run_work_node()'s existing REQ-06 breakpoint-recovery short-circuit (already-'done' nodes return their persisted output without calling run_fn) does all the "reuse without re-executing" work for free. _reconstruct_build_prompt supports two graph shapes generically from persisted state alone (Planner's canonical doc chain via e2e_pipeline._planner_prompt_builder, and E's generic task_prompt field); anything else raises ForkGraphShapeUnsupportedError rather than producing broken prompts. New: src/work_graph_fork.py (create_fork/execute_fork/fork_work_graph_run/ start_fork_in_background, the last splitting fast DB-only setup from slow real execution so an HTTP caller gets run_id back immediately, same pattern task_execution.start_task_execution_in_background already uses). work_graph_run gets parent_run_id/fork_of_node_exec_id (SCHEMA_SQL + migrate_add_work_graph_fork_columns for existing databases). work_graph_run_query.py/task_query.py surface both fields. New endpoint POST /api/work-nodes/:node_exec_id/fork. Console: a "从这里分叉" form on each 'done' node in v_run_detail, a fork badge linking back to the parent run, and a 🔀 marker on v_graph_console's run list -- no diff/merge UI (non-goal #3), read-only branch visibility only. Verified end-to-end for real: started the actual backend, sent a real HTTP fork request, confirmed the new run persisted with correct parent_run_id/fork_of_node_exec_id lineage. Tests cover requirements doc acceptance standards 1-3 (PRD swapped -> SDD/Benchmark genuinely re-execute, BRD untouched; original run completely unaffected; the same node forked twice produces independent branches) plus schema migration idempotency, query-layer field surfacing, the HTTP route, and 3 frontend smoke-test assertions. Full src suite (608 tests) + all 3 frontend smoke tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
…10c5-main-checkout-drift
-
Users had no way to actually start a queued goal from the console -- task.status just sits at 'queued' forever. Traced the root cause through 03-C: c_task_create's own contract ("若 max_concurrent_goals 已达上限, 任务落 queued 但不触发调度") implies task creation auto-triggers scheduling otherwise, and 03-C/04-P repeatedly assume an always-on Org Graph scheduler that watches the queue -- src/scheduler.py builds the mechanism but nothing ever spins it up as a running process, so no "start" button was ever designed because none was supposed to be needed. Design (confirmed with user, both recommended options chosen): - Pause/Stop are a soft stop, not a real subprocess kill -- the current in-flight `claude` call finishes naturally, only the next phase is withheld. Hard-killing would need Popen + PID tracking, out of scope. - No new task.status enum value -- reuses blocked_pending_human with blocked_reason='user_paused'/'user_stopped', riding the existing manual_resume mechanism instead of a schema change. New src/task_execution.py: run_or_resume_task_lifecycle() resumes from whichever phase actually hasn't run yet (queued/planning_brd_prd_sdd/ coding), start_task_execution_in_background() runs it to completion on its own daemon thread + sqlite connection, request_pause() blocks an in-flight task. Extended /route's manual_resume handling so resuming from a user-initiated pause/stop actually re-continues execution in the background, not just flips the status field back. Real gap found while building this: backend_server.create_server() never stored db_path on the server object -- needed for the new background thread to open its own connection to the same db. 22 new tests (14 src/test_task_execution.py + 8 TaskExecutionRouteTest in test_backend_server.py, HTTP wiring mocked around the already-unit-tested background thread mechanics). Verified against a real running server via curl: /execute 409s a non-queued task, /pause 409s a queued task and a double-pause, succeeds on a coding task. src/ suite: 496 tests, all green. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> -
Two new console pages requested directly by the user, beyond 02-V's original declared non-goal ("不做完整多项目管理 UI(项目列表/切换)"): - v_project_list: full project list + rename + archive/reactivate. "Import an existing directory with real code" needs no new code -- project_scaffold.create_project() already only ever adds .graphlemon/ inside the target folder. New: src/project_query.py (list/get/update), GET/PUT/DELETE /api/projects[/:id]. DELETE archives (status='archived'), never a physical row delete -- project_id is FK-referenced from task/ artifact/bug_record/etc., and 01-M's own active/archived enum already models this as soft-delete. - v_goal_list: extends the existing v_task_board/v_task_detail rather than duplicating them. Added sort/order to task_query.list_tasks() (column whitelist, no injection surface) and edit/delete on v_task_detail, both restricted to status='queued' (task_query.TaskNotEditableError, HTTP 409) -- editing or deleting a goal after real work has referenced it would leave that history pointing at something that no longer matches reality. Implemented directly rather than through graphlemon's own self-hosting loop -- confirmed with the user first that implement_code() always uses a hardcoded single-node manifest (no plan_work_graph() call for Coder), so a multi-page/multi-endpoint feature like this would've been one unreviewable Coder session with no way to decompose it. Real bug found on first verification pass (not caught by the existing _insert_task test helper, which never writes execution_event): a real task_create.create_task() always writes an execution_event row in the same transaction (01-M's FK, no ON DELETE CASCADE) -- even a never-touched queued task already has one, so DELETE FROM task raised sqlite3.IntegrityError. Fixed by clearing the task's own execution_event rows first; new regression test goes through the real create_task() path. 474 tests green. Verified end-to-end against a real running server (every new endpoint exercised via curl) since the Chrome extension is still unavailable this session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
Adds src/deployment_query.py (list_deployments/get_current_deployment/ get_deployment -- no project_id filter needed, deployment has no project_id column since there's exactly one graphlemon self-target) + three new backend_server.py routes (GET /api/deployments, /current, /:id, the last with recent deploy_health_signal history) + a new v_deployment_status page in frontend/app.js polling every 5s (deployment.status changes don't go through c_ws_subscribe -- Deploy Supervisor writes deployment/ deploy_health_signal directly, no execution_event of its own). No 02-V spec exists for this view (PH5 was added after 02-V was written) -- DOM/class conventions follow the existing pages' own patterns. Verified against a real running server via urllib (browser extension unavailable this session), plus 9 deployment_query unit tests and 5 real HTTP route tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
U-031: closes a structural integration gap found during the REQ-01~14 regression audit -- plan_work_graph/execute_work_graph/run_work_node were each individually tested but never actually called together in production code (e2e_pipeline.py's only validated closed loop bypassed all three via flat org_node_trigger calls). Adds work_graph_orchestrate.py (topological sort over graph_manifest + one one-node dynamic-workflow agent() call per Work Node, needed for REQ-06's per-node breakpoint recovery), rewires e2e_pipeline.py's plan_documents/ implement_code onto the real chain, and fixes two bugs a real API run surfaced: graph_manifest's id/node_id/depends_on normalization (work_graph_plan._normalize_graph_manifest) and a missing repo_path in run_work_node's cwd resolution. work_node_run.py also now marks a node 'blocked' instead of leaving it stuck 'running' on any unretryable run_fn exception. U-024.5: the regression audit also found REQ-12's reconnect boundary case (refetch a snapshot after a dropped WS reconnects) was never implemented -- only the dashboard reacted to onStatusChange, and even it never refetched. Fixes mountDashboard/mountTaskBoard/mountTaskDetail (which embeds v_graph_visualizer) to refetch their current snapshot on reconnect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
三个都是 config_id='default' 单例配置,首次使用即创建、之后原地更新 (upsert),不需要单独的初始化流程: - c_concurrency_config_update:新增 src/settings_config.py( org_concurrency_config 之前完全没人碰过),校验 max_concurrent_goals 落在 1-50。 - c_budget_config_update:budget_guardrail.py 新增 upsert_budget_config() ——跟 U-019 已有的 update_budget_limit_and_resume()(只更新已存在的 行)不是一回事,这个是真正完整的设置页控制器,支持首次创建,沿用 同一套"额度调高后自动恢复 budget_exceeded 阻塞任务"副作用(提取成 _resume_budget_blocked_tasks() 共享)。 - c_log_retention_config_update:同样在 settings_config.py 里,校验 retention_days > 0。 三处校验都在业务层做完整检查,HTTP 层额外兜了 except (InvalidParamError, TypeError)——payload 数值字段缺失时 None<=0 这类比较会抛 TypeError 而 不是业务异常,两个都要接住才能保证返回干净的 400 而不是裸 500。 backend_server.py 第一次加了 do_PUT。 前端三个页面共享一个 renderSettingsSubnav() 小组件模拟 02-V 原型里的 设置下拉菜单。真实服务器端到端验证过三个 PUT/GET 的完整读写循环。 PH3(人机交互控制台)至此全部完成:U-022~U-030。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
c_skill_diff 和 U-028 的 c_artifact_get 一样,只存在于机器可读的 sdd-mvcp-contract.json,03-C 正文从未写出来,已订正补全。 c_skill_list/c_skill_get 两份文档都没定义,是 v_skill_library/ v_skill_audit_log 自己的展示需求倒逼出来的缺口。 新增 src/skill_query.py:list_skills()、get_skill_audit_log()、 get_diff()(git diff <before> <after>——skill_update.py 的 _write_and_commit 把每次迭代做成原子单文件 commit,整仓库范围的 diff 天然只涉及那一个 Skill 文件)。 关键安全考量:Skill 库活在 graphlemon 自己的仓库里,c_skill_rollback 会真的在这个仓库里跑 git commit——如果 HTTP 层写死指向真实仓库根目 录,任何针对这条路由的测试都会在正在跑的这个 graphlemon 仓库里留下 真实提交。参照 db_path 已有的做法,给 create_server() 加了 skill_repo_path 参数(默认 graphlemon 真实仓库根,测试传临时仓库), 不通过 HTTP 暴露这个参数,避免变成任意写入的口子。记录在 05 §5.4.1.4。 真实服务器 + 临时仓库端到端验证过完整的 update→diff→rollback 闭环, 确认过跑完之后 graphlemon 自己仓库的 git log 完全没变化。 v_skill_library/v_skill_audit_log 不检查"当前项目"——Skill 是平台级 资产,跟项目无关。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
c_artifact_get 在机器可读的 sdd-mvcp-contract.json 里其实定义过 (GET /api/artifacts/:artifact_id),只是 03-C 正文一直没写出来,已订正 补全。c_artifact_list 连 MVCP JSON 里都没有 operation 定义,纯粹是 v_artifact_browser 自己的过滤字段倒逼出来的缺口,跟 U-025 补 c_task_list 是同一类问题。 新增 src/artifact_query.py:list_artifacts() 返回裸数组(跟已有的 list_tasks() 保持同款风格);get_artifact() 把血缘链(按 depends_on_artifact_ids 往回走,只走第一个依赖——这条流水线的实际产出 只有线性 BRD→PRD→SDD 链)和文件内容(artifact 表本身没有 content 列, 走 project.target_folder_path + artifact.file_path 现读磁盘)一起返回, 预览面板一次请求就够。 前端预览统一用 <pre> 纯文本展示,不做 Markdown 渲染/代码高亮——02-V 原文想要那个效果,但需要引入第一个前端第三方库,跟这个控制台一贯的纯 vanilla JS、无构建步骤做法冲突,是有意简化。顺手给 index.html 常驻导航 栏加上了"产物"链接。 真实服务器验证过三级血缘链(BRD→PRD→SDD)返回顺序正确。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
execution_log_file 的行生命周期 U-013 早就管好了,但从来没有代码真的往 日志文件里写过内容——src/execution_log.py 新增 append_log() 补上这条 缺失的写路径,设计成 run_fn 执行过程中随时可调用,不关心节点在做什么。 get_log() 按 offset/limit 分页读,download=true 时走原始文件流。 c_ws_log_tail(WS /api/ws/work-nodes/:node_exec_id/log)复用 U-024 手写 的 RFC 6455 机制,轮询 execution_event(log_appended);节点一旦转终态 (done/blocked/failed)服务端主动发关闭帧断开,不让客户端一直轮询。 p_log_retention_cleanup 实现成纯函数 run_log_retention_cleanup():过期 扫描 + max_total_size_mb 超限按最旧优先追加候选 + 单个文件删除失败只 记录不阻塞其余项,没有配调度器(不在本 Unit 覆盖范围),留给未来的 调度进程周期调用。 过程中意外抓到一个真的并发 bug:_Server.server_close() 关闭共享 sqlite 连接时原来没拿 conn_lock,如果这时候正好有个 WS 轮询线程在 另一边持锁执行 SQL,两个线程无同步地并发操作同一个 C 级 sqlite3 连接 对象,实测直接把解释器崩了(SIGSEGV)——U-024 就已经有这个隐患,这次 高频 WS 测试把时序窗口撞开了。修好后连续跑几十次全部稳定通过,记录 在 05 §5.4.1.3。 frontend/app.js 的 v_log_viewer 挂在 v_task_detail 每个 to-do 节点的 展开详情里,首次展开才建连接(懒加载),运行中态走 c_ws_log_tail 实时 滚动、终态走 c_execution_log_get 静态拉取,活跃连接在任务级刷新/离开 页面时统一关闭。真实服务器 + Node 原生 WebSocket/fetch 端到端验证过 实时追加与终态自动断开的完整链路。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
纯前端 Unit,数据已经在 U-025 的 c_task_get 里,只是 task_query.py 的 _work_nodes_for_run 补上了 hover tooltip 需要但当时没取的字段 (input_schema_ref/output_schema_ref/quota_limit_type/ quota_wait_started_at)。 frontend/app.js 新增 computeGraphLayers():按 graph_manifest 节点的 depends_on 边做拓扑分层,渲染成按执行层级排布的行,fan-out/fan-in 都能正确分层——用一个 n1→[n2,n3]→n4 的真实 graph_manifest 验证过, 直接从 app.js 里取出 computeGraphLayers 本体(不是重新实现一份逻辑) 跑真实服务器返回的 JSON,分层结果完全正确。节点按 03-C 3.6 节六态 配色,waiting_for_quota 态 hover 展示 quota_limit_type + 已等待时长。 02-V §2.7 原描述还包含一份 Org Graph 层,但 v_task_detail 页面顶部 已有等价的 org-strip 面板,这里判断重复渲染没有意义,只做了 Work Graph DAG 层,Org Graph 复用页面已有的那个。 嵌入位置:v_task_detail 里原来"随 U-026 补上"的占位提示换成了真实 的 DAG 面板。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
三个页面需要的读接口此前都不存在,本 Unit 一并建了: - c_task_list/c_task_get(03-C §3.2,此前无 Unit 认领):新增 src/task_query.py + GET /api/tasks、GET /api/tasks/:task_id。 c_task_get 的返回比 03-C 字面契约更完整——额外带上 work_graph_run(含解析后的 graph_manifest)+ 每个 run 的 work_node_execution 列表,因为 v_task_detail 的 to-do 树需要这份数据, 03-C 已订正补全。 - c_org_loop_route(U-016 已实现 route_org_loop(),从未接 HTTP)第一次 接上 POST /api/tasks/:task_id/route,v_task_detail 的人工干预 (manual_resume)按钮需要它。 - 预算护栏此前没有任何读接口(03-C 只写了 PUT),新增 budget_guardrail.list_budget_configs() + GET /api/settings/budget 给仪表盘预算条用。 frontend/app.js 补上真实的 #/(org graph 三节点卡片、当前任务概要、 预算条、事件时间线,全部通过 c_ws_subscribe 实时刷新)、#/tasks (关键字/状态/org_node 过滤)、#/tasks/:task_id(task 字段、to-do 树、 关联 artifact/bug_record、blocked_pending_human 告警条 + manual_resume 按钮)。路由改成对象形式支持挂载态清理,切页面时关掉上一个页面的 WebSocket 连接。v_graph_visualizer/v_log_viewer 子模块明确标成占位 提示(随 U-026/U-027 补上),不是假造内容。 顺带把 U-023 里因 c_task_list 当时不存在而简化的 related_task_id 换回真下拉,goal_composer/bug_feedback 提交成功后也改回跳转真实的 v_task_detail。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
c_task_create(U-009 已实现的 task_create.create_task())第一次接上 POST /api/tasks。c_bug_report_create 此前完全没有实现,新增 bug_record.create_bug_report() + POST /api/bug-reports。 发现并订正 03-C 一处输入契约遗漏:c_bug_report_create 的 input_contract 没写 project_id,但 execution_event.project_id 是 NOT NULL、bug_record 表自己没有 project_id 列可以在 related_task_id 留空时兜底推导,字面上无法满足约束——按 c_task_create 同款处理方式把 project_id 变成必填,03-C 已订正。 frontend/app.js 补上 #/goals/new、#/bugs/new 真实交互;控制台还没有 "当前项目"状态的正式载体(v_dashboard 是 U-025 才做),project_id 暂存 localStorage,v_project_setup 创建成功时写入。related_task_id 因为 c_task_list 未被任何 Unit 认领,简化成文本输入而非下拉,等 U-025 补上 任务列表后再换。提交成功后原型写的是跳转 v_task_detail,该页面也是 U-025 才建,暂时改成原页面内展示确认信息。 端到端冒烟时额外发现一个真 bug:create_bug_report() 原本没校验 project_id/related_task_id 是否存在,坏 project_id 会在 execution_event 插入时触发未捕获的 IntegrityError,请求直接 500——补了存在性校验, 改为干净的 INVALID_PARAM/400。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
c_pick_folder 补上 Windows(PowerShell FolderBrowserDialog)和 Linux (zenity 优先、回退 kdialog)实现,三平台都覆盖到。c_project_create (U-002 已实现的 project_scaffold.create_project())第一次接上 HTTP 路由 POST /api/projects——backend_server.py 因此第一次需要持有 sqlite 连接,schema.connect() 加 check_same_thread=False 参数,配合 server.conn_lock 序列化 ThreadingHTTPServer 各请求线程的访问。 frontend/app.js 补上 #/setup 真实交互(选文件夹→校验 Git 仓库→非 Git 仓库二次确认→创建→跳转仪表盘),DOM class 命名对齐 docs/wireframes/v_project_setup.html。 REQ-11 完整 happy 用例(含创建 task)还需要 U-023 的 v_goal_composer/c_task_create,本 Unit 只完成 v_project_setup/ c_pick_folder/c_project_create 部分,development-plan.md 已记录。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
-
frontend/{index.html,app.js,style.css}: no build step, no npm, vanilla JS hash router (05 §5.7). Real page content is PH3 (U-022+, starting from docs/wireframes/); this is only the empty shell + working navigation the Done criterion asks for. backend_server.py serves the 3 files via an exact-path whitelist (not a generic static-file/directory server) -- no path-traversal surface to reason about for 3 fixed files. Verified in an actual browser this session (navigated to localhost, clicked all 3 nav links, screenshotted each) rather than only trusting stdlib tests -- a Python-only test can't exercise real hashchange/DOM behavior without a JS engine. Nav highlighting and content swap both confirmed working, no page reload, no build step. 7 new stdlib unittest cases covering what the server is actually responsible for (static files served with correct content-type, 404 on unknown GET path). 66 total across U-001-008, all passing. This closes out PH0 (U-001~U-008) -- all three Phase-acceptance criteria now hold for real: target-project directory scaffold, 15-table schema with constraint tests, and a real claude --agent round-trip. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>