Add implementation-close CLI to hand-finish an open implementation and stop its daemon #617

Closed
opened 2026-09-18 14:38:17 +00:00 by jared · 3 comments
Owner

Context

A hand-finished implementation stays open in .sdlc/pipeline.db: completed_at is null, os-sdlc-runner next SESSION_ID still points at it, implementation-report prints elapsed_s: null, and LspDaemonLifecycle.close never runs, so the lsp-probe daemon for the worktree root stays up until its idle timeout. Implementation#complete! exists on the model and already stops the daemon, but no CLI subcommand reaches it. abandon_unfinished! runs only at the next implementation-open for the same session.

Observed on #607 implementation 3 (session 4974deff-0140-4813-8cd1-e96c6356a214): all eight dispatches passed, the run was hand-finished after #614's crash, and the row is still open at position refactorer.

Change

# plugins/os-sdlc/lib/os_sdlc/pipeline/cli.rb
desc "implementation-close IMPLEMENTATION_ID", "hand-finish an open implementation: stamp completed_at and stop the daemon"
def implementation_close(id)
  implementation = Model::Db.models(root).implementation[id]
  raise HarnessError, "no implementation #{id}" unless implementation
  raise HarnessError, "implementation #{id} is already closed" if implementation.closed?

  implementation.complete!(root)
  puts({ implementation_id: implementation.id, completed_at: implementation.completed_at }.to_json)
end

closed? is true when any of completed_at, failed_at, escalated_at, abandoned_at is set. No schema change: the reason for the hand finish lives on the ticket close comment. LspProbe::Daemon.stop(root:) must tolerate a root whose worktree directory is already gone; the first use is implementation 3, whose worktree was removed.

Out of scope: a closed_by column (schema bump wipes the shared DB from a worktree runner, ADR-0129).

Tasks

  • Implementation#closed?.
  • implementation-close IMPLEMENTATION_ID subcommand: refuses a missing or closed implementation, else complete!(root) and prints the JSON line.
  • Daemon.stop(root:) returns cleanly when the root directory does not exist (already true on main).
  • Run ruby plugins/os-sdlc/tests/all.rb; green.
  • After merge: run os-sdlc-runner implementation-close 3 from the main checkout.

Acceptance criteria

  • os-sdlc-runner implementation-close 3 sets completed_at on implementation 3 and exits 0.
  • A second call on the same id exits non-zero with implementation 3 is already closed.
  • implementation-report for the session prints a numeric elapsed_s.

Owner sketch

module OsSdlc
  module Pipeline
    class CLI
      def implementation_close(id)
    end
  end
  class Model
    module Implementation
      def closed?
      def complete!(root)
    end
  end
end

Cases

  1. Given an open implementation, when implementation-close ID runs, then completed_at is stamped, the lifecycle close is called with the root, and stdout is a JSON line with implementation_id and completed_at.
  2. Given an implementation with completed_at set, when implementation-close ID runs, then it exits non-zero with implementation ID is already closed and changes nothing.
  3. Given an id with no row, when implementation-close ID runs, then it exits non-zero with no implementation ID.
  4. (Already satisfied on main, verified in implementation 6: LspProbe::Daemon.stop(root:) returns nil for a missing root directory. No test added.)

Origin

  • Trigger: user decision, option B, after /os-sdlc:implement #607 left implementation 3 open
  • Session: 4974deff-0140-4813-8cd1-e96c6356a214
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4974deff-0140-4813-8cd1-e96c6356a214.jsonl
  • Where: plugins/os-sdlc/lib/os_sdlc/pipeline/cli.rb, plugins/os-sdlc/lib/os_sdlc/model/implementation/instance_methods.rb
## Context A hand-finished implementation stays open in `.sdlc/pipeline.db`: `completed_at` is null, `os-sdlc-runner next SESSION_ID` still points at it, `implementation-report` prints `elapsed_s: null`, and `LspDaemonLifecycle.close` never runs, so the lsp-probe daemon for the worktree root stays up until its idle timeout. `Implementation#complete!` exists on the model and already stops the daemon, but no CLI subcommand reaches it. `abandon_unfinished!` runs only at the next `implementation-open` for the same session. Observed on #607 implementation 3 (session 4974deff-0140-4813-8cd1-e96c6356a214): all eight dispatches passed, the run was hand-finished after #614's crash, and the row is still open at position `refactorer`. ## Change ```ruby # plugins/os-sdlc/lib/os_sdlc/pipeline/cli.rb desc "implementation-close IMPLEMENTATION_ID", "hand-finish an open implementation: stamp completed_at and stop the daemon" def implementation_close(id) implementation = Model::Db.models(root).implementation[id] raise HarnessError, "no implementation #{id}" unless implementation raise HarnessError, "implementation #{id} is already closed" if implementation.closed? implementation.complete!(root) puts({ implementation_id: implementation.id, completed_at: implementation.completed_at }.to_json) end ``` `closed?` is true when any of `completed_at`, `failed_at`, `escalated_at`, `abandoned_at` is set. No schema change: the reason for the hand finish lives on the ticket close comment. `LspProbe::Daemon.stop(root:)` must tolerate a root whose worktree directory is already gone; the first use is implementation 3, whose worktree was removed. Out of scope: a `closed_by` column (schema bump wipes the shared DB from a worktree runner, ADR-0129). ## Tasks - [ ] `Implementation#closed?`. - [ ] `implementation-close IMPLEMENTATION_ID` subcommand: refuses a missing or closed implementation, else `complete!(root)` and prints the JSON line. - [x] `Daemon.stop(root:)` returns cleanly when the root directory does not exist (already true on main). - [ ] Run `ruby plugins/os-sdlc/tests/all.rb`; green. - [ ] After merge: run `os-sdlc-runner implementation-close 3` from the main checkout. ## Acceptance criteria - [ ] `os-sdlc-runner implementation-close 3` sets `completed_at` on implementation 3 and exits 0. - [ ] A second call on the same id exits non-zero with `implementation 3 is already closed`. - [ ] `implementation-report` for the session prints a numeric `elapsed_s`. ## Owner sketch ```ruby module OsSdlc module Pipeline class CLI def implementation_close(id) end end class Model module Implementation def closed? def complete!(root) end end end ``` ## Cases 1. Given an open implementation, when `implementation-close ID` runs, then `completed_at` is stamped, the lifecycle close is called with the root, and stdout is a JSON line with `implementation_id` and `completed_at`. 2. Given an implementation with `completed_at` set, when `implementation-close ID` runs, then it exits non-zero with `implementation ID is already closed` and changes nothing. 3. Given an id with no row, when `implementation-close ID` runs, then it exits non-zero with `no implementation ID`. 4. (Already satisfied on main, verified in implementation 6: `LspProbe::Daemon.stop(root:)` returns nil for a missing root directory. No test added.) ## Origin - Trigger: user decision, option B, after /os-sdlc:implement #607 left implementation 3 open - Session: 4974deff-0140-4813-8cd1-e96c6356a214 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/4974deff-0140-4813-8cd1-e96c6356a214.jsonl - Where: plugins/os-sdlc/lib/os_sdlc/pipeline/cli.rb, plugins/os-sdlc/lib/os_sdlc/model/implementation/instance_methods.rb
Author
Owner

Work started via os-sdlc pipeline on branch ticket-617 (session 4974deff). #614 and #613 merged first.

Work started via os-sdlc pipeline on branch ticket-617 (session 4974deff). #614 and #613 merged first.
Author
Owner

Deviation (impl 6): case 4 is already satisfied on main (Daemon.stop tolerates a missing root; FileUtils.rm_f is lenient). Three test-reviewer rounds (49, 51, 53) failed on the case-4 example only. Main loop marked case 4 as already satisfied and instructed the test-writer to drop the example.

Deviation (impl 6): case 4 is already satisfied on main (Daemon.stop tolerates a missing root; FileUtils.rm_f is lenient). Three test-reviewer rounds (49, 51, 53) failed on the case-4 example only. Main loop marked case 4 as already satisfied and instructed the test-writer to drop the example.
Author
Owner

Resolution

Done: implementation-close IMPLEMENTATION_ID subcommand: refuses a missing or closed implementation, else complete!(root:) and prints a JSON line; Implementation#closed?; shared DispatchCommands.implementation_for.

Evidence: Branch ticket-617, pipeline implementation 6 reached implementation_complete (14 dispatches, 1 retry, suite-check 58 green); three examples in cli_implementation_close_test.rb; case 4 was already true on main and dropped by decision (see ticket comment).

Follow-ups: none

## Resolution **Done:** implementation-close IMPLEMENTATION_ID subcommand: refuses a missing or closed implementation, else complete!(root:) and prints a JSON line; Implementation#closed?; shared DispatchCommands.implementation_for. **Evidence:** Branch ticket-617, pipeline implementation 6 reached implementation_complete (14 dispatches, 1 retry, suite-check 58 green); three examples in cli_implementation_close_test.rb; case 4 was already true on main and dropped by decision (see ticket comment). **Follow-ups:** none
jared closed this issue 2026-09-18 15:53:55 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#617
No description provided.