fix: align ticket flat-state surfaces

This commit is contained in:
Keisuke Hirata 2026-06-09 13:27:02 +09:00
parent 21114fdd6f
commit 8fe4b822ee
No known key found for this signature in database
4 changed files with 21 additions and 52 deletions

View File

@ -70,7 +70,7 @@ Intake は以下を行う。
- `TicketComment`: 既存 Ticket refinement / decision / plan の記録。 - `TicketComment`: 既存 Ticket refinement / decision / plan の記録。
- `TicketDoctor`: 必要に応じた整合性確認。 - `TicketDoctor`: 必要に応じた整合性確認。
Intake は `TicketReview`, `TicketStatus`, `TicketClose` を通常使わない。review / state transition / close は Orchestrator または reviewer / maintainer workflow の責務である。 Intake は `TicketReview`, `TicketWorkflowState`, `TicketClose` を通常使わない。review / state transition / close は Orchestrator または reviewer / maintainer workflow の責務である。
Ticket tools が利用できない環境では、勝手に file write で代替しない。ユーザーまたは Orchestrator に「Ticket tools がないため materialize できない」と報告し、必要なら `yoi ticket` を使える人間/親 workflow に戻す。 Ticket tools が利用できない環境では、勝手に file write で代替しない。ユーザーまたは Orchestrator に「Ticket tools がないため materialize できない」と報告し、必要なら `yoi ticket` を使える人間/親 workflow に戻す。
@ -94,7 +94,7 @@ Ticket tools が利用できない環境では、勝手に file write で代替
確認観点: 確認観点:
- 同じ目的の open / pending Ticket がないか。 - 同じ目的の未完了 Ticket がないか。
- closed Ticket の判断・resolution と矛盾しないか。 - closed Ticket の判断・resolution と矛盾しないか。
- 既存の umbrella/progress-container Ticket が、superseded/decomposed として退役できる状態か。 - 既存の umbrella/progress-container Ticket が、superseded/decomposed として退役できる状態か。
- 既存 concrete follow-up Ticket や Objective context で足りるか、新規 concrete Ticket が必要か。 - 既存 concrete follow-up Ticket や Objective context で足りるか、新規 concrete Ticket が必要か。

View File

@ -21,7 +21,7 @@ Panel Queue / queued notification は、人間が Orchestrator に routing を
```text ```text
TicketCreate / TicketComment TicketCreate / TicketComment
-> Ticket Orchestrator Routing Workflow -> Ticket Orchestrator Routing Workflow
-> planning return / requirements sync / spike / implementation / review / blocked / close / pending -> planning return / requirements sync / spike / implementation / review / blocked / close
-> 必要に応じて他 Workflow へ接続 -> 必要に応じて他 Workflow へ接続
``` ```
@ -71,7 +71,6 @@ Orchestrator は以下を行う。
- `TicketList`: routing 候補や関連 Ticket の確認。 - `TicketList`: routing 候補や関連 Ticket の確認。
- `TicketShow`: 対象 Ticket の body / thread / artifacts / resolution 確認。 - `TicketShow`: 対象 Ticket の body / thread / artifacts / resolution 確認。
- `TicketComment`: routing decision / intent packet / blocked reason / next question の記録。 - `TicketComment`: routing decision / intent packet / blocked reason / next question の記録。
- `TicketStatus`: pending/open などの状態整理が明示的に許可された場合だけ使う。
- `TicketWorkflowState`: `queued -> inprogress` acceptance、`inprogress -> done`、または concrete missing decision/information reason を伴う `ready|queued -> planning` に使う。 - `TicketWorkflowState`: `queued -> inprogress` acceptance、`inprogress -> done`、または concrete missing decision/information reason を伴う `ready|queued -> planning` に使う。
- `TicketOrchestrationPlanQuery`: 対象 Ticket や関連 Ticket の ordering / blocker / conflict / waiting-capacity / accepted-plan 記録を読む。queued acceptance 前に必ず確認する。 - `TicketOrchestrationPlanQuery`: 対象 Ticket や関連 Ticket の ordering / blocker / conflict / waiting-capacity / accepted-plan 記録を読む。queued acceptance 前に必ず確認する。
- `TicketOrchestrationPlanRecord`: Orchestrator が routing 中に project-relevant な ordering / dependency / conflict / capacity/waiting / accepted-plan decision を残す。これは queue reorder、自動起動、state 変更ではない。 - `TicketOrchestrationPlanRecord`: Orchestrator が routing 中に project-relevant な ordering / dependency / conflict / capacity/waiting / accepted-plan decision を残す。これは queue reorder、自動起動、state 変更ではない。
@ -88,7 +87,7 @@ Orchestrator は以下を行う。
- `before` / `after` / `blocked_by` / `blocks` / `conflicts_with` / `do_not_parallelize` / waiting-capacity 記録がある場合、それを acceptance 判断の入力にする。記録は自動 scheduler ではないため、実際に進めるかどうかは Orchestrator が読んだうえで明示的に決める。 - `before` / `after` / `blocked_by` / `blocks` / `conflicts_with` / `do_not_parallelize` / waiting-capacity 記録がある場合、それを acceptance 判断の入力にする。記録は自動 scheduler ではないため、実際に進めるかどうかは Orchestrator が読んだうえで明示的に決める。
- risk flags / risky domain がある場合は、IntentPacket に invariants / reviewer focus / escalation conditions を入れる。risk flag だけを `queued -> planning` の理由にしない。 - risk flags / risky domain がある場合は、IntentPacket に invariants / reviewer focus / escalation conditions を入れる。risk flag だけを `queued -> planning` の理由にしない。
- concrete missing decision / information がある場合: `TicketWorkflowState``queued -> planning` を記録し、reason/body と `TicketComment` に不足項目、checked context、なぜ coder の implementation latitude では解決できないか、次の planning question/action を残す。既存の claimed live/restorable Intake/Planning Pod があり、既存通知経路が使える場合は同じ理由を通知する。 - concrete missing decision / information がある場合: `TicketWorkflowState``queued -> planning` を記録し、reason/body と `TicketComment` に不足項目、checked context、なぜ coder の implementation latitude では解決できないか、次の planning question/action を残す。既存の claimed live/restorable Intake/Planning Pod があり、既存通知経路が使える場合は同じ理由を通知する。
- external action 待ちなど planning では解決しない blocker の場合: concise な理由を Ticket thread に記録し、queued のまま待つか、既存の Ticket state/state mechanism で明示的に defer/block する - external action 待ちなど planning では解決しない blocker の場合: concise な理由を Ticket thread に記録し、必要に応じて attention / action-required frontmatter や `TicketOrchestrationPlanRecord` の blocker/waiting-capacity 記録で明示する。lifecycle 外の storage bucket を routing target にしない
Invariant: Invariant:
@ -214,7 +213,7 @@ Action:
- 必要な判断・外部 action を短く書く。 - 必要な判断・外部 action を短く書く。
- `TicketComment` に blocked reason と next question を記録する。 - `TicketComment` に blocked reason と next question を記録する。
- 必要なら `TicketStatus` で pending に移す(許可がある場合だけ) - 必要に応じて attention / action-required frontmatter や orchestration plan の blocker/waiting-capacity 記録で、待ち理由を current state とは別に表す。lifecycle 外の storage bucket へ移す route は使わない
### `close_ready` ### `close_ready`
@ -234,21 +233,6 @@ Action:
- umbrella/progress-container Ticket を退役する close resolution では、関連作業がすべて完了したという意味ではなく container role を retired したことを明記し、完了済み concrete Ticket と残る follow-up Ticket / Objective を列挙する。 - umbrella/progress-container Ticket を退役する close resolution では、関連作業がすべて完了したという意味ではなく container role を retired したことを明記し、完了済み concrete Ticket と残る follow-up Ticket / Objective を列挙する。
- close 権限がない場合は merge-ready / close-ready dossier を親/人間に提出する。 - close 権限がない場合は merge-ready / close-ready dossier を親/人間に提出する。
### `defer_pending`
今は進めないが blocked ではない。
条件:
- 優先度・タイミングの理由で後回し。
- 依存はあるが active blocker として扱うほどではない。
- broad request が concrete implementable Ticket に分解され、Objective context や split decision record の作成待ちである。
Action:
- defer reason を `TicketComment` に記録する。
- 必要なら `TicketStatus` で pending に移す(許可がある場合だけ)。
### `closed_or_noop` ### `closed_or_noop`
routing 不要。 routing 不要。
@ -395,7 +379,6 @@ IntentPacket が短く書けない場合、`implementation_ready` ではなく `
- `review_needed` → reviewer Pod / review workflow - `review_needed` → reviewer Pod / review workflow
- `blocked_action_required` → human / parent Orchestrator - `blocked_action_required` → human / parent Orchestrator
- `close_ready` → close workflow / maintainer decision - `close_ready` → close workflow / maintainer decision
- `defer_pending` → pending / Objective or split-decision follow-up management
## 完了条件 ## 完了条件

View File

@ -110,14 +110,6 @@ impl TicketFeature {
if !root.is_dir() { if !root.is_dir() {
return Err("ticket backend root is not a directory".to_string()); return Err("ticket backend root is not a directory".to_string());
} }
for state_dir in ["open", "pending", "closed"] {
let dir = root.join(state_dir);
if !dir.is_dir() {
return Err(format!(
"ticket backend root is missing required {state_dir}/ directory"
));
}
}
Ok(root) Ok(root)
} }
} }
@ -194,9 +186,6 @@ fn tool_description(name: &str) -> &'static str {
"TicketWorkflowState" => { "TicketWorkflowState" => {
"Transition Ticket state; queued -> inprogress is the accepted implementation start, so implementation side effects should happen only after that transition is accepted and recorded." "Transition Ticket state; queued -> inprogress is the accepted implementation start, so implementation side effects should happen only after that transition is accepted and recorded."
} }
"TicketStateCompat" => {
"Move a Ticket between open and pending; use TicketClose for closed."
}
"TicketClose" => "Close a Ticket with a resolution through the typed local Ticket backend.", "TicketClose" => "Close a Ticket with a resolution through the typed local Ticket backend.",
"TicketOrchestrationPlanRecord" => { "TicketOrchestrationPlanRecord" => {
"Append a durable typed Ticket orchestration plan record without changing state or starting work." "Append a durable typed Ticket orchestration plan record without changing state or starting work."
@ -228,9 +217,7 @@ mod tests {
use tempfile::TempDir; use tempfile::TempDir;
fn make_ticket_root(root: &Path) { fn make_ticket_root(root: &Path) {
std::fs::create_dir_all(root.join("open")).unwrap(); std::fs::create_dir_all(root).unwrap();
std::fs::create_dir_all(root.join("pending")).unwrap();
std::fs::create_dir_all(root.join("closed")).unwrap();
} }
fn write_ticket_config(workspace: &Path, content: &str) { fn write_ticket_config(workspace: &Path, content: &str) {
@ -469,21 +456,21 @@ provider = "github"
} }
#[test] #[test]
fn does_not_register_ticket_tools_when_root_lacks_state_dirs() { fn registers_ticket_tools_for_flat_backend_root() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
std::fs::create_dir_all(temp.path().join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH)).unwrap(); let root = temp.path().join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH);
std::fs::create_dir_all(&root).unwrap();
let mut pending_tools = Vec::new(); let mut pending_tools = Vec::new();
let mut hooks = HookRegistryBuilder::default(); let mut hooks = HookRegistryBuilder::default();
let report = FeatureRegistryBuilder::new() let report = FeatureRegistryBuilder::new()
.with_module(ticket_tools_feature(temp.path())) .with_module(ticket_tools_feature(temp.path()))
.install_into_pending(&mut pending_tools, &mut hooks); .install_into_pending(&mut pending_tools, &mut hooks);
assert!(pending_tools.is_empty()); assert_eq!(pending_tools.len(), TICKET_TOOL_NAMES.len());
assert!(report.reports[0].installed_tools.is_empty()); assert_eq!(report.reports[0].installed_tools, TICKET_TOOL_NAMES);
assert!( assert!(report.reports[0].diagnostics.is_empty());
report.reports[0].diagnostics[0] assert!(!root.join("open").exists());
.message assert!(!root.join("pending").exists());
.contains("missing required open/ directory") assert!(!root.join("closed").exists());
);
} }
} }

View File

@ -2,7 +2,7 @@
Yoi project work is tracked through Tickets. For normal use, interact with Tickets through `yoi panel`, Ticket tools, the `yoi ticket ...` CLI, and Ticket workflows. Git history plus Ticket files remain the authoritative state-transition record behind those interfaces. Yoi project work is tracked through Tickets. For normal use, interact with Tickets through `yoi panel`, Ticket tools, the `yoi ticket ...` CLI, and Ticket workflows. Git history plus Ticket files remain the authoritative state-transition record behind those interfaces.
The current local backend stores Ticket files under `.yoi/tickets/`. That storage detail matters for maintainers and backend compatibility, but it is not the primary user-facing workflow. The current local backend stores each Ticket in the flat `.yoi/tickets/<ticket-id>/` layout. The directory name is the canonical opaque Ticket id; slugs and frontmatter `id`/`slug` fields are not current-state authority. That storage detail matters for maintainers and backend compatibility, but it is not the primary user-facing workflow.
Do not treat ad-hoc chat summaries, memory records, or Pod notifications as the final source of project state. Notifications are hints to inspect concrete state, not proof of completion. Do not treat ad-hoc chat summaries, memory records, or Pod notifications as the final source of project state. Notifications are hints to inspect concrete state, not proof of completion.
@ -36,7 +36,7 @@ Pods with the Ticket built-in feature can use typed Ticket tools:
- `TicketShow` - `TicketShow`
- `TicketComment` - `TicketComment`
- `TicketReview` - `TicketReview`
- `TicketStatus` - `TicketWorkflowState`
- `TicketClose` - `TicketClose`
- `TicketDoctor` - `TicketDoctor`
@ -156,14 +156,13 @@ Routing classifications include:
- `review_needed` - `review_needed`
- `blocked_action_required` - `blocked_action_required`
- `close_ready` - `close_ready`
- `defer_pending`
- `closed_or_noop` - `closed_or_noop`
Routing decisions should be recorded with `TicketComment` using `plan` or `decision` role. The decision should state the classification, evidence checked, reason, next action, and escalation conditions. For `return_to_planning`, the record must also state the concrete missing decision/information, context checked, why implementation latitude is insufficient, and the next planning question/action. Routing decisions should be recorded with `TicketComment` using `plan` or `decision` role. The decision should state the classification, evidence checked, reason, next action, and escalation conditions. For `return_to_planning`, the record must also state the concrete missing decision/information, context checked, why implementation latitude is insufficient, and the next planning question/action.
### 3. Planning/requirements sync ### 3. Planning/requirements sync
Use `ticket-preflight-workflow` only as a legacy compatibility canonical id for planning/requirements sync. Return `ready` or `queued` Tickets to `planning` only when the Orchestrator can name a concrete missing decision or information item after bounded project-context checks; risk flags and risky domains are context-lookup and reviewer-focus signals, not automatic stop gates. Use `ticket-preflight-workflow` only as a legacy-compatible planning/requirements sync entry. Return `ready` or `queued` Tickets to `planning` only when the Orchestrator can name a concrete missing decision or information item after bounded project-context checks; risk flags and risky domains are context-lookup and reviewer-focus signals, not automatic stop gates.
Planning sync should resolve or record: Planning sync should resolve or record:
@ -333,17 +332,17 @@ Do not store secrets, credentials, private prompt contents, or raw logs containi
The product CLI exposes the typed Ticket backend for repository maintenance and validation. It operates on the configured `.yoi/tickets/` storage and is the preferred command-line surface when editing Tickets outside a Pod. The product CLI exposes the typed Ticket backend for repository maintenance and validation. It operates on the configured `.yoi/tickets/` storage and is the preferred command-line surface when editing Tickets outside a Pod.
```sh ```sh
yoi ticket create --title "..." [--canonical id canonical id] [--kind task] [--priority P2] [--label a,b] yoi ticket create --title "..." [--priority P2]
yoi ticket list [--state planning|ready|queued|inprogress|done|closed|all] yoi ticket list [--state planning|ready|queued|inprogress|done|closed|all]
yoi ticket show <id> yoi ticket show <id>
yoi ticket comment <id> [--role comment|plan|decision|implementation_report] [--file path] yoi ticket comment <id> [--role comment|plan|decision|implementation_report] [--file path]
yoi ticket review <id> --approve|--request-changes [--file path] yoi ticket review <id> --approve|--request-changes [--file path]
yoi ticket state <id> open|pending yoi ticket state <id> <planning|ready|queued|inprogress|done>
yoi ticket close <id> [--resolution text|--file path] yoi ticket close <id> [--resolution text|--file path]
yoi ticket doctor yoi ticket doctor
``` ```
`yoi ticket state` intentionally does not close Tickets. Closing must use `yoi ticket close` so the backend writes the required `resolution.md` and passes `yoi ticket doctor`. `yoi ticket state` records current lifecycle transitions among active states. Closing must use `yoi ticket close` so the backend writes the required `resolution.md` and passes `yoi ticket doctor`; `done` and `closed` remain distinct states.
The current LocalTicketBackend stores records under: The current LocalTicketBackend stores records under: