• Implements docs/todo/validator-loop-until-dry-skill-design.md. Pure Skill +
    Agent-definition wiring, no execution-engine changes -- the convergence
    loop runs inside a single Work Node's one claude -p session (multi-turn
    tool use), not a graph_manifest-level cycle, so topological_layers()/
    run_graph() are untouched.
    
    New Skill skills/unknown-scope-defect-sweep/SKILL.md: K=2 consecutive
    no-new-finding rounds to stop, 20-round hard cap, dedup key must be a
    structural identifier (file path + line, route path, package name) not a
    natural-language description (the same finding gets reworded call to
    call, so text-keyed dedup never converges), dedup set is "everything ever
    seen" not "everything confirmed" (the common bug the 14-step reference
    names: a rejected candidate reappearing as a false "new finding" every
    round). Each candidate is verified via qa-task-validation's existing
    PASS/FAIL/INCONCLUSIVE framework -- deliberately NOT root_cause_trace's
    three-perspective jury (F), since that answers a different question
    (root cause AFTER a failure) from this one (is this candidate real at
    all) -- documented in the design doc's own tradeoff note.
    
    Wired into agent_profile.py's FRONTMATTER["validator"]["skills"] and
    agents/validator/AGENTS.md's "必须加载" section with a concrete trigger
    signal (task description can't state an exact expected count/boundary
    up front).
    
    Only FR-4 (Skill wired into the rendered .claude/agents/validator.md) has
    deterministic, mockable behavior -- new test
    test_validator_references_unknown_scope_defect_sweep_skill. Acceptance
    criteria 1-3 (actually finds everything, actually dedups correctly,
    actually stops at the hard cap) need real multi-turn claude -p behavior,
    out of scope for mocked tests (RUN_REAL_AGENT_E2E=1 territory), same
    category of limitation as F's own real-concurrency-timing note.
    
    Full src suite (587 tests) passes with zero regressions.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Implements docs/todo/verifier-fan-out-jury-design.md. root_cause_trace.py:
    _tally_perspectives() extracts the existing >=2/3 rule out of
    judge_root_cause so the closure-based version and the new
    judge_root_cause_via_work_graph() share one implementation, not two that
    could drift apart. The fan-in vote stays plain Python (zero_token
    aggregation per the design-dynamic-work-graph Skill's own edge_type
    distinction) rather than becoming a fourth Work Node -- no reason to pay
    for an Agent call just to count votes.
    
    judge_root_cause_via_work_graph(): each perspective becomes an
    independent Work Node with real schema-constrained structured output
    ({"category", "detail"} directly), not real_validator_perspective's
    manual "CATEGORY:"/"DETAIL:" text-line parsing. judge_root_cause /
    real_validator_perspective are kept, not removed -- documented as the
    lightweight fallback for scenarios that don't need worktree/Work Graph
    overhead.
    
    org_level_loop.run_fix_loop (the one real production caller) now calls
    the Work Graph version; the fixer/verify calls are unchanged flat
    trigger_org_node calls.
    
    Real bug found and fixed along the way: this is the first production code
    path that actually triggers a run_graph() multi-node CONCURRENT layer
    (every graph_manifest before F was single-node or a strict serial chain,
    so G's ThreadPoolExecutor path had never really run in production).
    scheduler.py::run_goal_to_completion and
    task_execution.py::start_task_execution_in_background both created their
    conn without check_same_thread=False -- fixed both, since their conn can
    now reach run_graph()'s worker threads via run_fix_loop.
    
    Test boundary migrated in test_org_level_loop.py/test_scheduler.py: the
    3 perspective calls are now mocked at work_graph_execute.write_workflow_script/
    execute_work_graph (matching test_work_graph_orchestrate.py's pattern)
    instead of org_node_trigger.trigger_org_node. New tests cover: three real
    independent Work Node executions, judgment-rule equivalence with the
    closure version, clean per-node input_payload, and concurrent wall-clock
    overlap across the three perspectives.
    
    Full src suite (586 tests) + supervisor suite (125 tests) pass with zero
    regressions.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Implements docs/todo/dynamic-work-graph-design-skill-design.md. G already
    answered FR-2 (how to mark a parallel-eligible node group): no new field
    needed -- topological_layers() infers independence purely from
    depends_on/edges, so the new Skill's job is just "express real
    dependencies honestly, don't add fake ones."
    
    New Skill skills/design-dynamic-work-graph/SKILL.md: the node-boundary /
    zero_token-vs-needs_judgment / per-node self-contained task_prompt
    methodology, wired into agent_profile.py's FRONTMATTER for coder and
    validator (Planner's own doc-chain path is untouched -- FR-4 only names
    Coder/Validator).
    
    work_graph_plan.py: new _GENERIC_PROMPT_TEMPLATE + generic_node_prompt_builder(),
    used whenever org_node != 'planner'. Planner keeps its existing fixed
    brd/prd/sdd/benchmark/... chain (_PROMPT_TEMPLATE) completely unchanged --
    zero regression risk to the one real production planning path.
    
    Scoped per the requirements doc's non-goal #1 ("prove the mechanism works,
    don't cover every real scenario yet"): does NOT wire into
    e2e_pipeline.py::implement_code()'s existing single-node production path.
    New tests prove the mechanism itself: a Coder scenario that naturally
    splits into 3 independent modules produces a correctly-shaped
    graph_manifest, topological_layers() puts all three in one concurrent
    layer (ties E directly to G), and generic_node_prompt_builder drives it
    through run_graph() to completion.
    
    Full src suite (580 tests) passes with zero regressions.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Implements docs/todo/work-graph-parallel-execution-design.md: run_graph()
    now schedules graph_manifest nodes layer-by-layer (topological_layers(),
    Kahn's algorithm grouped by round instead of flattened), running a
    multi-node layer's nodes concurrently via ThreadPoolExecutor (default 4
    workers) with a barrier between layers; a single-node layer stays
    synchronous, matching every real graph_manifest observed so far.
    topological_order() is now a thin flattening wrapper, unchanged behavior
    (regression-tested).
    
    _LockedConn/_FetchedRows makes the existing scattered conn.execute() call
    sites in work_node_run.py/work_graph_orchestrate.py/execution_log.py safe
    to call from multiple threads without touching those call sites -- note
    the lock must cover the full execute()-through-fetchone()/fetchall()
    cycle, not just execute() itself, or two threads can still collide on the
    shared sqlite3 connection handle. That gap was caught for real (not
    theoretically) by RunGraphRealSqliteConcurrencyTest during development --
    committing the fixed version, not the first draft.
    
    create_worktree() (work_node_run.py) gets a module-level lock around the
    `git worktree add` call for concurrent layers touching the same repo.
    
    New tests: layer grouping, same-layer concurrency + inter-layer barrier,
    one node's failure not cancelling a concurrent sibling (WorkGraphLayerError
    aggregates), breakpoint-recovery resume of a concurrent layer, a quota-wait
    timeout coexisting with a sibling's real persisted output, real (non-mocked)
    sqlite under concurrent writes across several rounds, and real concurrent
    `git worktree add` calls. Full src (575 tests) + supervisor (125 tests)
    suites pass with zero regressions.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • …de13-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • The literal token value was committed in plaintext across 4 files
    (agent-memory notes, a stored run artifact, and a test assertion)
    while documenting that it had been redacted from migrated skill
    files -- the documentation itself failed to redact it. Replacing
    with placeholders/omission ahead of pushing this repo to a remote
    for the first time; the pre-existing commits (2a71bd5, 0cbf4b1,
    14dee05) still contain the plaintext value in history.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Root cause traced (docs/implementation-gap-analysis.md): the Benchmark
    document Validator relies on for its verdict was frequently a narrative
    about the work session ("The planner workflow completed and produced a
    Benchmark defining 18 scenarios...") instead of the actual scenarios --
    found via a real skill-detail-page task's benchmark.md, then confirmed
    present since the earliest real self-hosting runs. Validator's own
    judgment was, in practice, resting on thin, sometimes-vacuous prose.
    
    Planner's work graph (brd->prd->sdd->benchmark) extends to
    benchmark->test_dataset->test_script: test_dataset/test_script are always
    present as nodes, but the generating subagent judges per-goal whether a
    real dataset/script applies (many goals, e.g. UI wiring, legitimately
    don't have one -- replying 'N/A: <reason>' is a real, expected answer, not
    a failure). test_script gets a genuinely mechanical verify_fn (real
    py_compile/node --check, not LLM judgment) -- the one node type where an
    objective check is actually possible. All four document prompts also gained
    an explicit anti-narration instruction (only 'brd' had a weaker version of
    this before).
    
    Coder's implementation prompt now receives the real benchmark/dataset/
    script and must make the Planner's real script pass for real, not just
    whatever tests it invents on its own.
    
    Validator gets its own 3-node Work Graph (validator_benchmark ->
    validator_test_dataset -> validator_test_script), generated independently
    from SDD/PRD -- deliberately NOT reusing Coder's or Planner's own copies,
    same reasoning as a held-out eval set vs. the dev set a model was tuned
    against. The resulting script, if real, is actually executed against the
    current repository state; a non-zero real exit code force-overrides the
    verdict to FAIL in code (user-confirmed hard gate: the script is not up
    for LLM debate on a failure, only on a pass can subjective judgment add
    INCONCLUSIVE/risk notes).
    
    schema.py: artifact.artifact_type's CHECK constraint gains the 5 new
    types, plus migrate_expand_artifact_types() -- SQLite has no ALTER-based
    constraint migration, so this is the standard rebuild-copy-swap dance,
    applied for real to the live ~/.graphlemon/graphlemon.db (42 rows,
    verified byte-identical count before/after, integrity_check clean).
    
    skills/qa-task-validation.md and skills/project-sdd-planning.md already
    gained a standing principle from this same incident: verify cross-task
    data dependencies against real system state, not seeded fixtures alone
    (see prior commit d7035fc) -- this commit is the mechanism-level half of
    the same fix, not just the prose-guidance half.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • graphlemon authored
     
    Browse Dir »
  • …c231-main-checkout-drift
    graphlemon authored
     
    Browse Dir »


  • Found for real: a work_graph_execute.WorkGraphExecuteError ("API Error:
    Response stalled mid-stream") wasn't in _ROUTABLE_EXCEPTIONS, so it
    propagated uncaught and crashed the calling process entirely -- leaving the
    task neither running nor blocked_pending_human, just stuck with
    blocked_reason=NULL. run_scheduler_once's own try/except only protects
    OTHER goals in the same batch from one goal's failure; it never applied the
    task-level blocked_pending_human consequence either.
    
    Add a catch-all mirroring task_execution._mark_blocked's existing fallback
    for its own (Start-button) entry point, so both ways of running a task now
    guarantee it always lands on a real terminal-ish status with the real error
    message persisted, never a caller-crashes-and-the-task-is-just-stuck outcome
    nothing can manual_resume from.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • graphlemon authored
     
    Browse Dir »
  • Auto-continuation after a successful manual_resume only fired for
    blocked_reason in ('user_paused', 'user_stopped'). A real transient network
    blip ("API Error: Unable to connect to API (ENOTFOUND)") blocks a task with
    blocked_reason='execution_error' the same way any other unroutable
    exception does -- resuming it only flipped the status field back, with no
    path to actually re-trigger execution (/execute requires status='queued',
    but a resumed task lands on 'planning_brd_prd_sdd'/'coding' instead).
    Clicking "resume" was a dead end a second click couldn't fix.
    
    The user has already made the judgment call to retry by clicking resume at
    all. Withholding continuation only made sense for the two reasons with
    their own dedicated resume path requiring a different action first:
    budget_exceeded (must raise the limit) and loop_limit_reached (excluded by
    name, not just by its usual restored status). Every other blocked_reason
    now auto-continues on a successful resume to a resumable status.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »

  • …10c5-main-checkout-drift
    graphlemon authored
     
    Browse Dir »
  • New target projects never got .claude/agents/*.md deployed, so their first
    Planner call always failed immediately (claude --agent planner finds nothing
    in cwd) -- self-target usage never noticed since those files are already
    committed as part of this repo. create_project() now deploys and commits
    them alongside the existing .graphlemon/ init commit.
    
    Also: unroutable execution failures were recorded as a bare
    blocked_reason='execution_error' with no way to see what actually broke.
    scheduler._block_task()/task_execution._mark_blocked() now persist the real
    exception message into execution_event.payload (blocked_reason itself is
    unchanged -- budget_guardrail matches its exact value elsewhere).
    
    Found via a real console Start-button failure on a freshly created project;
    see docs/implementation-gap-analysis.md for the full writeup.
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • 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 Dir »
  • 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 Dir »
  • Attempt 10 crashed immediately in the fix committed last: git add -A --
    . ':!.graphlemon-worktrees' throws "paths are ignored by one of your
    .gitignore files" (exit 1, not just advisory text -- confirmed even with
    advice.addIgnoredFile=false suppressed) the moment that path is ALSO
    gitignored, which it now always is. Reproduced locally.
    
    Plain `git add -A` (no explicit pathspec) already skips gitignored paths
    correctly on its own -- the explicit exclude was redundant now that
    .gitignore covers it, and actively conflicting. Simplified
    ensure_worktree_committed() back down. Also added the same .gitignore
    entry to test_work_node_run.py's _init_git_repo() fixture so real-worktree
    tests match production config instead of coincidentally not hitting this.
    
    562 tests green (441 src + 121 supervisor).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • Attempts 8 and 9 both hit "local changes would be overwritten by merge"
    again, despite the earlier _check_prd_consistency worktree-isolation fix.
    Root cause is deeper: git worktrees deliberately SHARE project identity
    with their main checkout (same .git, just another checkout) -- a real
    `claude -p` call's memory:project write doesn't reliably respect the cwd it
    was invoked with, and can land directly on the main checkout regardless of
    which specific call triggered it. Worktree-wrapping one call (as done for
    _check_prd_consistency) only moves around which call happens to trigger
    this; it doesn't fix the underlying issue.
    
    ensure_worktree_committed() now also runs against repo_path itself (not
    just the worktree) right before every merge_worktree() call, in both
    work_node_run.run_work_node() and e2e_pipeline._check_prd_consistency().
    Same already-tested mechanism, now applied generically regardless of which
    real call caused the drift or why.
    
    Fixing this surfaced a second real bug in the fix itself: running `git add
    -A` against repo_path while a node's own .graphlemon-worktrees/<id>/
    checkout is still present on disk picks it up as a gitlink-like entry,
    which then shows as "deleted" the instant remove_worktree() runs right
    after. Fixed by excluding .graphlemon-worktrees/ from both the dirty-check
    and the add via pathspec (not relying on the caller repo's own .gitignore),
    plus added it to graphlemon's own .gitignore for correctness generally.
    
    New regression test: test_drift_on_main_checkout_is_auto_committed_before_merge
    reproduces a run_fn writing directly to repo_path instead of its worktree,
    and confirms a second node's own merge isn't affected by it.
    
    562 tests green (441 src + 121 supervisor).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • graphlemon authored
     
    Browse Dir »
  • The 6th real self-hosting attempt (first to reach the full 4-node doc-writing
    graph after the previous five bugs) hit a genuinely new failure: this real
    graph_manifest had no edges at all, not just wrong field names -- worse than
    the case _normalize_graph_manifest already handled. With zero dependency
    info to normalize from, topological_order() scheduled 'benchmark' before
    'sdd' (alphabetically first among zero-in-degree nodes), and build_prompt()
    crashed with KeyError: 'sdd_content' trying to format a template needing
    sdd's not-yet-produced output.
    
    _PROMPT_TEMPLATE always asks for exactly the same fixed 4-node linear chain
    -- that's a constant architectural fact, not something that should be
    re-inferred from the LLM's output every call. _normalize_graph_manifest now
    deterministically enforces brd->prd->sdd->benchmark whenever all four
    canonical artifact_types are present, regardless of what depends_on/edges
    the LLM did or didn't provide. Same "机制用代码" principle as
    ensure_worktree_committed and the earlier normalize logic.
    
    Three new regression tests: zero dependency info still gets the chain
    enforced, node_id != artifact_type still resolves to the real node_id (not
    a guessed string), and a partial set (missing one of the four types) is
    correctly left alone rather than guessed at.
    
    560 tests green (440 src + 120 supervisor).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • The 4th real self-hosting attempt (first to reach the actual doc-writing
    Work Graph after the previous three bugs) hit a real git merge failure:
    "local changes would be overwritten by merge", exit code 2.
    
    Root cause: _check_prd_consistency() calls execute_work_graph() directly
    against target_folder_path, completely bypassing work_node_run.run_work_node's
    worktree isolation. It uses agentType='planner' with memory:project, which
    writes a real update to .claude/agent-memory/planner/MEMORY.md in whatever
    cwd it runs with. Every prior real run of this check only ever targeted a
    throwaway clone, so this never mattered -- until self-targeting, where cwd
    IS the main checkout: the memory write landed as an uncommitted local
    change on master, which then blocked the SDD node's own (correctly
    isolated) worktree merge right after it.
    
    Fix: apply 05 §5.8's same unconditional self-target worktree rule here,
    reusing work_node_run's existing create_worktree/ensure_worktree_committed/
    merge_worktree/remove_worktree rather than inventing a new mechanism.
    Non-self-target calls are unaffected (verified by both regression tests).
    
    Also cleaned up the real, now-recovered state from the failed 4th attempt:
    merged a real SDD-node worktree branch that got stranded uncommitted
    (conflict was just two near-identical memory file wordings, resolved by
    hand), and removed three orphaned worktrees left by earlier test runs.
    
    557 tests green (437 src + 120 supervisor).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »
  • graphlemon authored
     
    Browse Dir »
  • A real self-hosting run (U-042, first real exercise of needs_worktree=True
    since U-035 forces it unconditionally for self-target) exposed a real bug
    RealGitWorktreeIsolationTest never caught: its own run_fn always committed
    its edits, but a real Coder isn't instructed anywhere to git commit its own
    changes. The Coder genuinely edited src/org_loop_route.py and verified it
    in its own test_report -- but never committed, so merge_worktree()'s `git
    merge` was a true no-op ("Already up to date"), and remove_worktree() then
    silently discarded the dirty worktree along with the real work. code_diff
    came out empty; the change never reached the branch at all.
    
    Adds ensure_worktree_committed(): checks `git status --porcelain` in the
    worktree before merging and auto-commits any dirty state found. A no-op
    when the worktree is legitimately clean (e.g. a pure investigation task).
    Same "判断用 Agent,机制用代码" principle already used elsewhere in this
    codebase -- don't rely on persona prompt-engineering for a mechanical
    correctness guarantee that the system itself should hold.
    
    Two new regression tests: one reproducing the exact "edited but never
    committed" scenario, one confirming a legitimately-clean worktree still
    merges fine. 554 tests green (435 src + 119 supervisor).
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    bboonnee authored
     
    Browse Dir »

  • 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 Dir »
  • 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 Dir »
  • 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 Dir »
  • 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 Dir »
  • 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 Dir »
  • 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 Dir »
  • 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 Dir »

  • 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 Dir »
  • 三个都是 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 Dir »
  • 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 Dir »
  • 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 Dir »
  • 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 Dir »

  • 纯前端 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 Dir »
  • 三个页面需要的读接口此前都不存在,本 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 Dir »
  • 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 Dir »
  • 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 Dir »