• 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »

  • 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>
    bboonnee authored
     
    Browse File »
  • Adds supervisor/deploy_health_check.py: run_health_check() runs the three
    pre-cutover checks in order, stopping at the first failure (03-C: "任一检查项
    fail 立即整体判定 health_check_failed,不等待其余检查项跑完") and persisting
    a deploy_health_signal row for every check that actually ran.
    
    - process_alive: polls deploy_supervisor.is_alive() up to HEALTH_CHECK_STARTUP_TIMEOUT.
    - http_health: hits the candidate's /api/health -- added to backend_server.py
      (U-037 deliberately left this for U-038). Not just "process exists" --
      runs a real SELECT 1 to catch a wedged sqlite connection a live-but-stuck
      process would otherwise pass.
    - canary_task: a real (not mocked) project+task creation round-trip against
      the candidate over HTTP -- no real Org Graph/claude calls, since
      c_task_create's synchronous 'queued' landing is already a deterministic,
      always-should-pass result, which is what a canary needs.
    
    Timeouts are the MVP-fixed drafts from 05 §5.9, explicitly flagged there as
    needing real calibration later, not hardcoded as authoritative.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse File »
  • Adds supervisor/deploy_stage.py: stage_deployment() creates a deployment row,
    git-worktree-checks-out the target commit into a fresh directory (--detach,
    never in-place git pull), copies the production DB to a one-time candidate
    copy, and Popens a real candidate backend process on a free port.
    
    Return value follows 04-P §4.6's state-machine table (status after the call
    is 'candidate_starting') rather than 03-C's one-line 'staged' summary --
    04-P's own trigger text ("c_deploy_supervisor_stage 完成" moves the row to
    candidate_starting) is the more detailed/authoritative source when the two
    disagree, same precedent used throughout this project.
    
    Found and closed two real gaps along the way:
    - deployment's own field list never included where a candidate's checkout
      directory lives -- added candidate_instance_dir to schema.py (also what
      the NEXT staging call's previous_instance_dir bootstraps from once a
      deployment reaches 'finalized').
    - backend_server.py had no CLI entry point at all since U-007 -- create_server()
      was only ever instantiated in-process by tests. Added main() (argparse +
      serve_forever + SIGTERM->shutdown() from a separate thread, per
      http.server's own same-thread-deadlock warning).
    
    RealCandidateLaunchTest spawns and HTTP-probes a genuine
    `python3 -m src.backend_server` subprocess against a real git-worktree
    checkout of this repo (not mocked) -- caught a real ordering issue doing so:
    git worktree only sees committed content, so testing before this Unit's own
    commit landed would check out backend_server.py without main() at all.
    Fixed by using `git stash create` (builds a commit object from the current
    working tree without touching HEAD/index) as the commit-to-test-against,
    falling back to plain HEAD once the tree is clean post-commit.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse File »

  • 三个都是 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »

  • 三个页面需要的读接口此前都不存在,本 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>
    bboonnee authored
     
    Browse File »
  • Python stdlib 没有 WebSocket 支持,src/ws_subscribe.py 手写 RFC 6455
    握手(hashlib+base64 算 Sec-WebSocket-Accept)+ 帧编解码(服务端帧不
    加掩码,客户端帧强制解掩码),不引入 websockets 之类第三方库
    (05 §5.7)。
    
    推送用轮询而非写路径 hook:execution_event 的写入分散在十几个调用点
    (org_node_trigger/work_graph_execute/work_node_run/task_create/
    bug_record/...),没有单一写入路径可以插桩,backend_server.py 的
    _handle_ws_subscribe 劫持 ThreadingHTTPServer 连接后每 0.5 秒按
    rowid 增量查一次 execution_event(project_id/可选 task_id 过滤),
    轻松满足 REQ-12 的 ≤2 秒目标。断线重连按 03-C §3.14 走 REST 快照,不
    在 WS 层做消息重放,新连接只看连接那一刻起的新事件。
    
    daemon_threads = True 避免长连接 WS 线程阻塞进程退出。
    
    验证:test_backend_server.py 用原始 socket 走真实握手+推送延迟断言;
    另外用 Node 原生 WebSocket 客户端做跨实现互操作性验证,连真实运行的
    服务器,从建连到收到 c_task_create 触发的真实推送全程走通。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »
  • 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>
    bboonnee authored
     
    Browse File »
  • Pure stdlib http.server (ThreadingHTTPServer), zero third-party deps --
    enforced by an AST-based test that parses backend_server.py's own imports
    and checks each against sys.stdlib_module_names, so this can't silently rot
    if someone adds a dependency later (05 §5.7).
    
    pick_folder(): macOS via `osascript 'choose folder'`; user-cancel maps to
    {"selected_path": null} (03-C §3.-1: not an error), non-macOS or any other
    osascript failure raises DialogUnavailableError -> HTTP 422
    DIALOG_UNAVAILABLE. Windows/Linux native dialogs are PH3 (U-022).
    
    Known gap, noted rather than papered over: the Done criterion asks for a
    real popped dialog returning a real path, which needs an actual human
    click -- not something this agent can do non-interactively. Verified
    `osascript` itself is present and executes on this machine, and every code
    path is covered by mocked tests, but the literal "did a human click a real
    folder and get the right path back" check is still open for whoever runs
    this interactively next.
    
    12 new stdlib unittest cases (server start/stop, pick_folder success/
    cancel/non-macOS/other-failure, the same 4 cases through the HTTP route,
    zero-third-party-import check). 62 total across U-001-007 (60 real + 2
    gated), all passing.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse File »