• U-004 migrated the Hermes personas into agents/{planner,coder,validator}/SOUL.md
    (the canonical source) but nothing had ever actually deployed the compiled
    .claude/agents/*.md into graphlemon's own repo -- every real test throughout
    PH1-PH4 ran against throwaway target-project clones that got this step via
    project_scaffold, never against graphlemon self-targeting itself. The real
    U-042 self-hosting attempt surfaced this immediately: `claude --agent planner`
    failed outright with "--agent 'planner' not found". Deployed via
    agent_profile.deploy_agent_definitions() and committed -- self-target worktree
    isolation only sees committed content, so this has to be tracked, not just
    written to disk, for any future self-target task to find its own persona.
    
    Also commits the real agent-memory files a genuine self-target run generated
    (architecture doc §8 decision 1: memory must be git-traceable) and gitignores
    .claude/workflows/ (one-shot, uniquely-named dynamic-workflow scripts written
    per Work Node call -- pure runtime clutter, never reused, not source content).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »

  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • graphlemon authored
     
    Browse Code »
  • 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 Code »
  • Adds supervisor/self_hosting_e2e.py: run_self_hosting_closed_loop() chains
    every PH5 module built this session (U-034~U-041) with the Graph Engine's
    own scheduler (U-032) into one real, callable path matching self-development-
    feasibility.md's 9-step scenario -- self-target Goal -> Planner/Coder/
    Validator -> core_engine classification -> stage -> health check -> cutover
    (or rollback path A if health check fails) -> monitor until either
    monitoring_window_clean (finalized) or anomaly_detected (rollback path B).
    Both real outcome paths are branches of the same call, not two separate
    scripted scenarios. Explicitly documented as this acceptance scenario's own
    orchestration glue, not a claim about Deploy Supervisor's eventual
    production poll loop (U-036's discovery-loop hook is still unfilled;
    building that is future work).
    
    Mocked wiring tests (6, run in normal regression) stub each already
    independently-tested module boundary and only verify the branching logic.
    
    The real, literal acceptance test is built and ready (RealSelfHostingClosedLoopTest,
    reproduces the document's own "loop_round > 3 should be >= 3" example) but
    gated behind RUN_REAL_SELF_HOSTING_E2E=1 -- deliberately a separate,
    distinctly-named env var from this project's usual RUN_REAL_AGENT_E2E, since
    this is the highest-blast-radius action in the project (can replace the
    running graphlemon process and git-revert commits on this real repo).
    Per this Unit's own explicit build instruction, not executed as part of
    implementing it -- doing so requires checking with the user first.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Adds supervisor/deploy_rollback.py: rollback_path_a (pre-cutover, health
    check failed -- terminate candidate, delete its one-time DB copy, git
    revert, record a bug_record(core_engine) audit trail) and rollback_path_b
    (post-cutover, anomaly detected -- stop the bad Live, revive
    previous_instance_dir, re-run the same three health-check signals directly
    against it, git revert only once re-verification passes).
    
    Design choices follow established conventions rather than inventing new
    ones: no unified rollback() entry point (deployment has no PID columns,
    same reason deploy_stage/deploy_cutover already require callers to pass
    pids explicitly); ROLLBACK_FAILED is the raised error name while the
    persisted deployment.status is 'blocked_pending_human' (same pattern
    CheckoutFailedError/CutoverFailedError already use). git revert conflicts
    fold into the same rollback-itself-failed fallback both paths share, per
    design doc §10 open question 3's own recommendation, with `git revert
    --abort` run afterward so a failed attempt doesn't leave the repo mid-conflict.
    
    This is PH5's highest-rework-risk Unit per development-plan.md's own flag,
    so its own "rollback itself fails" fallback is tested for real, not assumed:
    a genuinely broken previous_instance_dir (a real empty directory with no
    Python package in it) that really fails to bind its port, and a genuinely
    conflicting git history (a later commit touching the same line the
    self-modification changed) that really fails `git revert` -- both correctly
    land on blocked_pending_human without retrying forever or silently
    succeeding, and the conflict case leaves the repo clean afterward.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Adds supervisor/deploy_monitor.py: run_monitor_cycle() is one sampling
    pass, called repeatedly by Deploy Supervisor's own poll loop (03-C's own
    text: not a one-shot call). process_alive/http_health/canary_task/
    validator_approval_rate sampled in order, stopping at the first anomaly.
    
    validator_approval_rate's "approve rate" has no literal definition anywhere
    in 01-M/03-C/04-P (no such execution_event type exists) -- derived instead
    from a verified structural fact in scheduler.finish_task_lifecycle: PASS
    closes straight to done with no bug_record ever created, FAIL/INCONCLUSIVE
    always creates one via judge_root_cause. A task counts as approved iff no
    bug_record(source='validator') exists for it. Baseline is computed by the
    same function looking back MONITORING_WINDOW_HOURS from cutover_at rather
    than persisted as a new column; baseline=None (cold start) never flags an
    anomaly rather than defaulting to 0.
    
    Crash-count/consecutive-http-fail windows are approximated by sample count
    (N = CRASH_WINDOW / MONITOR_SAMPLE_INTERVAL) rather than a true rolling
    time window -- documented simplification, correct as long as sampling stays
    roughly interval-paced.
    
    Caught my own design misreading via the tests: process_alive's monitoring-
    phase anomaly condition is MAX_CRASH_COUNT crashes within CRASH_WINDOW (a
    crash-loop detector), not "currently not alive" -- unlike U-038's health
    check where any single failure fails closed. A single dead reading during
    monitoring is recorded but must not trigger an immediate rollback by itself.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Adds supervisor/deploy_cutover.py: stops the old Live process, retires the
    health-check-mode candidate, and relaunches the same already-verified
    candidate checkout bound to LIVE_PORT/PROD_DB_PATH -- http.server has no
    live socket-rebind operation, so "switch to production port/db" is
    implemented as stop-then-relaunch-from-the-same-dir rather than an in-place
    rebind (functionally equivalent, the only mechanism stdlib actually offers).
    
    Adds LIVE_PORT=8420 to 05 §5.9's MVP-fixed parameter table -- the one
    parameter that table was missing (frontend/app.js never needed one, same-
    origin relative fetches don't care, but successive deployments need to keep
    landing on the same port for a human bookmark to keep working).
    
    "在途 work_node_execution 重启后可恢复" is satisfied by relying on the
    existing REQ-06 recovery machinery unchanged -- cutover doesn't add any
    special-case logic, a cutover-triggered restart is just another interruption
    REQ-06 already survives.
    
    A real (not mocked) test caught a real bug: deploy_supervisor.is_alive()'s
    os.kill(pid, 0) check can't distinguish "truly running" from "exited but
    never reaped by its own parent" -- a zombie's PID slot answers kill(pid, 0)
    successfully until waited on. U-036's own subprocess test never caught this
    because that skeleton deletes its own PID file before exiting, sidestepping
    is_alive() entirely; deploy_cutover's test is the first one to actually poll
    for a stopped process to disappear, and it hung until fixed. Added
    reap_if_exited() (real os.waitpid for processes we spawned ourselves, falls
    back to is_alive() for pids from a prior session where ChildProcessError
    means we're not the real parent) and switched deploy_cutover._stop_process,
    deploy_health_check.check_process_alive, and deploy_supervisor.stop() to use
    it.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • 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 Code »
  • 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 Code »
  • Ports graphloop's test_isolated_coding_workspace.py/test_workspace_security.py/
    test_change_manifest.py/test_selective_apply.py into supervisor/isolation/
    (only the import path changed -- the graphlemon modules were already adapted
    verbatim from graphloop, confirmed API-identical) plus a few extra edge
    cases the originals didn't cover. 52 tests green, replacing what had been a
    single manual smoke test since these modules were staged.
    
    Adds supervisor/deploy_supervisor.py: process lifecycle only (PID file,
    atomic status file, start/stop/is_running), no deploy decision logic yet --
    that's U-037+'s job, injected via the poll_fn hook run_foreground() calls on
    a schedule. PID/status conventions adapted (not imported) from graphloop's
    hermes_watchdog.py atomic-write pattern. Fail-closed: any exit path
    (normal, SIGTERM, or an exception from poll_fn) unconditionally writes
    status='stopped' and removes the PID file, so is_running() never lies.
    RealSubprocessLifecycleTest actually spawns and stops a real
    `python3 -m supervisor.deploy_supervisor` process rather than mocking it,
    per this Unit's own "能独立启停" Done criterion.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Adds paths.repo_root()/is_self_target() and forces needs_worktree=True in
    work_graph_orchestrate.run_graph() whenever target_folder_path is graphlemon's
    own checkout, regardless of caller intent -- 05 §5.8's unconditional rule,
    since a self-target in-place edit risks corrupting the currently-running
    process's own source out from under it.
    
    Adds src/core_engine_classify.py (path-based rule: .claude/agents/*.md,
    src/*, supervisor/* = core_engine; frontend/, docs/ = not) and
    bug_record.record_core_engine_change() (a PASS-path audit record, distinct
    from create_bug_record's FAIL-path root-cause attribution -- no route
    decision, status='resolved' from creation). scheduler.finish_task_lifecycle()
    records this audit trail as an additive side effect after close_task_done()
    on self-target PASS closures.
    
    Found and resolved a real SDD-internal conflict (confirmed with the user via
    AskUserQuestion) before wiring this up: supervisor-auto-rollback-design.md §1
    triggers Deploy Supervisor on self-target+Validator-PASS alone, but 03-C
    §3.11/04-P §4.6 literally gate it on an existing core_engine bug_record --
    and task_create.py confirms goal-type self-improvement tasks (the exact
    self-development-feasibility.md walkthrough scenario) never produce a
    bug_record on their happy path, which would silently defeat that whole task
    class under the literal reading. Resolved per user decision: trigger on
    self-target+PASS alone; the bug_record is a byproduct, not a gate.
    p_deploy_supervisor_blue_green (04-P §4.6) never touches task.status, so
    close_task_done() still runs unconditionally -- Deploy Supervisor is a fully
    decoupled state machine, matching §2's "no shared failure domain" principle.
    
    org_loop_route.py is intentionally untouched -- fast_lane/full_replan is
    FAIL-path retry routing, orthogonal to this PASS-path trigger; deployment
    row creation stays U-037's job per its own Done criterion.
    
    417 tests green; RealGitWorktreeIsolationTest (U-013/REQ-03's real git
    worktree regression) re-verified passing per this Unit's own Done criterion.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Adds the two Deploy Supervisor entities from 01-M §1.15/§1.16 (17 tables
    total): deployment (full 15-value blue-green state machine, per 01-M's own
    text rather than supervisor-auto-rollback-design.md §3's abbreviated
    summary table) and deploy_health_signal (append-only health/monitoring
    samples). ux_deployment_active enforces 01-M's "only one non-terminal
    deployment at a time" constraint via an expression-based unique index
    (CREATE UNIQUE INDEX ... ON deployment((1)) WHERE status NOT IN (...)) --
    the same partial-unique-index idiom work_graph_run already uses, just with
    a constant instead of a real grouping column since there's exactly one
    graphlemon self-target.
    
    bug_record.root_cause_category's schema-level CHECK now accepts
    core_engine, but bug_record.py's own application-layer whitelist
    deliberately does not follow yet -- classifying a bug as core_engine is a
    file-path-based rule (05 §5.8), not something the Validator's existing
    3-perspective judge_root_cause can produce, and wiring that classification
    + c_org_loop_route's routing is U-035's job.
    
    392 tests green.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • CLAUDE.md is explicit that PH5 (self-modification/auto-rollback) must not
    start until PH4 has genuinely closed -- not just "all unit tests green,"
    but 06-验收与测试锚点.md's REQ-01~14 happy/boundary/failure cases actually
    covered. A REQ-by-REQ audit (the same discipline that caught U-031's
    integration gap) found 5 real gaps:
    
    - REQ-03 failure: work_node_execution.status jumped straight
      running->retrying, skipping 'reflecting' (declared in the schema enum,
      never once written by any code).
    - REQ-05 failure / REQ-09 boundary: the "scheduler keeps serving other
      queued tasks past one blocked_pending_human" scenario was never tested
      against a real scheduler -- the old test literally said "no scheduler
      exists yet to test against directly" (stale since U-032).
    - REQ-06 / REQ-07 / REQ-14 failure (the biggest gap, shared root cause):
      CorruptPersistedStateError, ClassifierBlockedError, and
      UsageLimitWaitTimeoutError were each correctly raised and each correctly
      marked their own work_node_execution 'blocked', but nothing ever routed
      that to task.status='blocked_pending_human' with the blocked_reason each
      REQ's acceptance text names. e2e_pipeline.run_end_to_end is split into
      run_task_lifecycle() so scheduler.py's run_goal_to_completion can create
      the task itself and catch these three specific exceptions, following the
      same UPDATE+execution_event pattern budget_guardrail.check_budget()
      already established for REQ-10.
    - REQ-02 boundary: PRD internal-contradiction check (verifying->reflecting)
      had no implementation anywhere -- artifact_write always marked
      validation_status='passed' unconditionally. Added an optional verify_fn
      hook to work_graph_orchestrate.run_graph() (one regeneration attempt,
      then ConsistencyCheckFailedError) and a real consistency-check agent()
      call scoped to the PRD node only. The first real run of this new call
      caught a genuine bug: the model returned "consistent": "false" as a JSON
      string, not a boolean, which `if ok:` silently treated as truthy -- fixed
      with explicit _coerce_bool().
    - REQ-13 failure: minor test-assertion weakness (WS 404 rejection test
      didn't check the JSON error body).
    
    384 tests green. REQ-01~14 now have real, exercised coverage rather than a
    "tests are green" assumption -- PH4 can be treated as genuinely closed.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Reviews every real invocation of the migrated Planner/Coder/Validator personas
    since U-004's initial 2026-07-31 verdict (U-011's "SOUL 原则 #10" reference,
    U-014's full real snake-game closed loop, U-031's agentType spike + full
    plan_documents run) and finds no new persona-content deviation beyond the
    three already fixed in U-004 (Feishu Base token, lark-cli coupling,
    --dangerously-skip-permissions instruction). The disallowedTools bug found
    along the way (05 §5.4.1.2) is a tool-permission-layer Claude Code behavior,
    not persona content, so it doesn't extend A-2's deviation scope.
    
    Final verdict: stays "部分成立" (not upgraded to confirmed) -- the Coder
    rewrite already happened and later clean runs don't retroactively undo that
    -- but the deviation scope is now confirmed closed rather than open-ended,
    which is what U-033 exists to establish before PH5 planning.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • 04-P's blocked_pending_human branch, 01-M's task-table indexing rationale,
    and REQ-09's boundary case all presume an autonomous dispatcher scans queued
    tasks and keeps serving the queue past a blocked one -- but no such component
    existed. org_node_lifecycle._check_concurrency (U-010) only gates a single
    trigger call; e2e_pipeline.run_end_to_end (U-014) drives exactly one task,
    synchronously, only ever called from tests.
    
    Adds src/scheduler.py: run_scheduler_once() dispatches multiple Goals via
    ThreadPoolExecutor bounded by org_concurrency_config.max_concurrent_goals,
    each worker opening its own sqlite connection (with a busy_timeout) rather
    than sharing one across threads -- U-027's SIGSEGV already proved that needs
    a hand-held lock, which would just serialize everything back to front.
    
    Also closes a gap run_end_to_end's own docstring already flagged: it never
    called close_task_done (PASS) or org_level_loop.run_fix_loop (FAIL), leaving
    every task stuck at 'validating'. finish_task_lifecycle() applies that step
    without changing run_end_to_end's own tested behavior or scope.
    
    Real-write concurrency (multiple threads, separate connections, same db
    file) is stress-tested for real in test_scheduler.py without spending API
    budget -- the actually-new risk surface here is the threading/connection
    design, not the already-validated (U-031/U-017) work itself.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »

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

  • 纯前端 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>
    bboonnee authored
     
    Browse Code »
  • 三个页面需要的读接口此前都不存在,本 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 Code »
  • 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 Code »
  • 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 Code »
  • 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 Code »
  • now 默认值从 time.monotonic 改成 time.time(墙钟),使 quota 等待的
    已耗时计算能在进程重启后正确续算;run_work_node 对已 done 的节点直接
    返回存量结果不重跑,对 retrying/waiting_for_quota 节点从持久化的
    retry_count/quota_wait_started_at 续算而非清零;needs_worktree=True
    时复用已存在的 worktree 而不是重新 create。新增
    resume_work_graph_run() 返回一次 run 里尚未完成的 node_exec_id 列表,
    并对内部不一致的持久化状态统一抛 CorruptPersistedStateError。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Re-checking REQ-14's boundary case against the letter of its text found a
    real functional gap, not just a testing gap: U-013's opus handling only
    did "skip backoff" -- it never actually retried on a downgraded model,
    just failed immediately. REQ-14 boundary says "不走退避轮询,直接降级到非
    Opus 模型重试" (skip backoff, retry immediately on a non-opus model) --
    the retry was the part that was missing.
    
    Fixed: run_fn's signature is now run_fn(cwd, model=None); an opus quota
    signal retries immediately (no backoff) with opus_downgrade_model
    (default "claude-sonnet-5"). If the downgraded model somehow also reports
    an opus signal (shouldn't happen -- it's not using Opus), this doesn't
    loop forever chasing it, raises UsageLimitWaitTimeoutError instead.
    
    work_graph_execute.py's own opus handling (U-012) deliberately NOT changed
    to match: REQ-14's coverage only names c_work_node_run/c_org_node_trigger,
    and that mechanism (a multi-agent dynamic workflow, no single top-level
    --agent identity) has no one "the model" to downgrade -- a deeper A-1
    question, documented as out of scope rather than silently left
    inconsistent without explanation.
    
    Also filled in a real test gap: quota_limit_type=weekly (detection +
    recovery, and the 9-day timeout) was never tested despite the mechanism
    already supporting it since U-013.
    
    11 new/changed stdlib unittest cases across work_node_run.py's test suite
    (all existing run_fn lambdas updated for the new signature). 178 total
    across U-001-020, all passing.
    
    This closes PH2-S3 alongside U-019. REQ-14 is now genuinely complete, not
    just "mostly right."
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • Found running U-014's full real closed loop: the Validator gave an
    INCONCLUSIVE verdict and explicitly said it had no Bash tool, despite its
    frontmatter granting one. Isolated with a minimal test subagent: any
    "Bash(...)" scoped pattern (e.g. "Bash(git commit:*)") in a subagent's
    disallowedTools frontmatter field disables Bash *entirely*, not just that
    subcommand -- confirmed by comparing a disallowedTools=[Edit,Write] agent
    (Bash works) against one adding a single Bash(git commit:*) entry (Bash
    breaks completely). The CLI's own --disallowedTools flag documents this
    exact scoped syntax as supported; subagent frontmatter just doesn't honor
    it the same way, and nothing in the docs says so.
    
    Fixed agent_profile.py's validator FRONTMATTER to disallowedTools=
    [Edit, Write] only -- dropping the four Bash(git *) entries that were
    silently breaking test execution. The "don't commit/push/reset/clean"
    constraint now relies on the prose instruction already in the migrated
    AGENTS.md instead of a tool-level block that never actually worked; fixed
    that file's own (now-false) claim that it was enforced at the tool level,
    and documented the finding centrally in 05-实施约束与依赖.md §5.4.1.2 since
    it'll matter for any future agent definition, not just this one.
    
    Verified for real, redeployed: Bash now works for the actual Validator
    identity, Edit/Write remain correctly blocked.
    
    1 test fixed (was asserting the now-removed scoped pattern) + 1 new test
    (disallowedTools frontmatter must never contain "Bash(" again). 174 total
    across U-001-019, all passing.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »
  • check_budget(): accumulates current_usage on success (04-P monitoring->
    monitoring), and on reaching/exceeding limit_value transitions the task to
    blocked_pending_human with blocked_reason='budget_exceeded' (never
    'usage_limit_wait_timeout' -- 04-P is explicit these two guardrails, human-
    configured budget vs Claude Code's own plan quota, must not share a
    blocked_reason) and raises BudgetExceededError rather than returning
    allowed=false silently, matching 03-C §3.12's own errors list.
    
    update_budget_limit_and_resume(): REQ-10's boundary case needs "raise the
    limit -> paused tasks resume" to be real; built the minimal version this
    Unit needs rather than the full settings-page controller
    (c_budget_config_update, U-030/PH3's job) -- reuses U-016's manual_resume
    (org_loop_route.route_org_loop) for the actual status restoration instead
    of reimplementing it.
    
    6 new stdlib unittest cases, all real (sqlite, no mocking needed). 173
    total across U-001-019, all passing.
    
    Also: the U-014 full real closed-loop run that's been going in a detached
    background process since earlier this session finished -- 957s (~16min).
    All 4 real BRD/PRD/SDD/Benchmark docs, a real JS snake-game implementation
    (engine.js/engine.test.js/index.html/app.js) from the Coder, and a
    genuinely careful INCONCLUSIVE verdict from the Validator: it explicitly
    refused to accept the Coder's own claimed "10/10 passed, verified with
    headless Chrome" without being able to reproduce it, correctly citing its
    own migrated SOUL.md rule ("工具错误和权限不足属于INCONCLUSIVE,不能洗成PASS").
    It also caught a real test-fidelity gap (T3/T7 not matching the Benchmark's
    literal wording) via careful static tracing. Result preserved at
    /private/tmp/.../scratchpad/snake-game-result for inspection. The Validator
    also reported no Bash tool available despite its frontmatter granting one
    -- investigating that as a follow-up, separate from this commit.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Code »