deterministic-task-protocol リポから miyabi-cli-standalone に統合: - docs/dtp/: PLAYBOOK, PLAN, UML, GIT-RULES, Codex レビュー 3件 - autorun/: Phase 0-8 の TASKS/ASSIGNMENT/GATE + INDEX/HANDOFF/ROLLBACK - project_memory/tasks.json: 全9 Phase の DAG SSOT - skills/: polaris-ops, rust-llm-pitfalls - .codex/instructions.md: Codex 設定 実装は miyabi-core に gate.rs, lock.rs, protocol.rs, store.rs を追加する方針。 既存の dag.rs, github.rs, approval.rs 等は変更不要。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
35 KiB
Deterministic Task Execution Protocol — Maestro Implementation Playbook
Version: 0.1.0 | Created: 2026-04-10 | Status: PLAN (未実装)
0. North Star
LLM の揺らぎを許さない。tasks.json の GATE が許可し、GitHub が確定し、git merge が不可逆にする。この三段階だけが「完了」を定義する。
1. 前提条件チェックリスト(実装開始前に全て GREEN であること)
1.1 リポジトリ・インデックス
| # | 条件 | 検証コマンド | 状態 |
|---|---|---|---|
| 1 | openclaw-workspace GNI インデックス fresh | npx gitnexus status --repo openclaw-workspace |
GREEN (50,130 nodes) |
| 2 | miyabi-private GNI インデックス fresh | npx gitnexus status --repo miyabi-private |
GREEN (12,448 nodes) |
| 3 | rust-ai-pipeline GNI インデックス fresh | npx gitnexus status --repo rust-ai-pipeline |
GREEN (813 nodes) |
| 4 | agent-skill-bus GNI インデックス fresh | npx gitnexus status --repo agent-skill-bus |
GREEN (154 nodes) |
1.2 既存テスト GREEN
| # | パッケージ | コマンド | 状態 |
|---|---|---|---|
| 5 | @miyabi/task-manager | cd Miyabi/packages/task-manager && npm test |
要確認 |
| 6 | rust-ai-pipeline | cd ~/dev/products/rust-ai-pipeline && cargo test |
要確認 |
| 7 | agent-skill-bus | cd ~/dev/tools/agent-skill-bus && npm test |
要確認 |
1.3 Codex 3体レビュー反映
| # | 指摘 | 対応方針 | 反映先 Phase |
|---|---|---|---|
| R1-1 | canTransition() が conditions を評価していない | Protocol を唯一の遷移窓口にする | Phase 2 |
| R1-2 | applyTransition() の結果が store に永続化されない | store-backed immutable update に統一 | Phase 3 |
| R1-3 | GATE が「存在チェック」で「出自検証」がない | GitHub API で PR/merge を実検証 | Phase 5 |
| R1-4 | reviewing → done が既存 27 ルールと衝突 | merged 状態を追加 |
Phase 2 |
| R1-5 | TTL 7200s 固定は不適切 | lease 300s + heartbeat 60s に変更 | Phase 4 |
| R2-1 | tasks.json の read-modify-write がクロスマシン race | atomic write + CAS version check | Phase 3 |
| R2-2 | assignAndLock() の TOCTOU 脆弱性 | OS flock + re-read + re-check + write | Phase 4 |
| R2-3 | softDependencies を DAG level に入れると過度に直列化 | soft は dispatch priority にのみ使用 | Phase 2 |
| R2-4 | dagLevels キャッシュは永続化しない方が安全 | 読み込み時再計算に変更 | Phase 2 |
| R2-5 | JSONL event log + snapshot JSON が安全 | task-events.jsonl + tasks.snapshot.json 構成 | Phase 3 |
| R3-1 | tasks.json を事実の正に昇格させすぎ | execution ledger に下げ、SSOT は GitHub | Phase 5 |
| R3-2 | GitHub API 障害時の degraded mode 未定義 | awaiting_github_sync 状態 + retry queue | Phase 5 |
| R3-3 | Issue closed → done は危険 | PR merged + issue linked で確定 | Phase 5 |
| R3-4 | mergeCommit の取得経路が未仕様 | PR API の merge_commit_sha から取得 | Phase 5 |
| R3-5 | PRなし正当完了の escape hatch がない | completionMode = manual/github-pr/external-op | Phase 6 |
2. アーキテクチャ概要
2.1 二層 SSOT + Execution Ledger
+------------------------------------------+
| GitHub (Fact SSOT) |
| Issue 状態 / PR merged / Review status |
+-----+------------------------------------+
| pull (authoritative facts)
| push (proposed state)
v
+------------------------------------------+
| tasks.snapshot.json |
| (Execution Ledger / Gate Cache) |
| ← materialized view of event log |
+-----+------------------------------------+
^
| replay / rebuild
|
+------------------------------------------+
| task-events.jsonl |
| (Append-only Event Log) |
| state_transition / lock_acquired / |
| lock_released / dag_changed / |
| github_synced / gate_passed / |
| gate_rejected / human_approved |
+------------------------------------------+
2.2 状態遷移図(Codex R1-4 反映: merged 追加)
[*] ──GATE 0──→ draft
│
GATE 1
│
v
pending ←──── blocked (dep未解決)
│ ^
GATE 2 │
(deps done) │
│ │
v │
analyzing ─────→ blocked (lock競合)
│
GATE 3
(impact + human approval)
│
v
implementing ──── GATE 4 (lock + heartbeat)
│
GATE 5+6
(branch + PR)
│
v
reviewing
│
GATE 7
(PR merged verified via GitHub API)
│
v
merged ←── NEW STATE
│
GATE 8
(Issue closed + worklog)
│
v
done
2.3 コンポーネントマップ(全既存資産の統合先)
+-----------------------------------------------------------------------+
| DeterministicExecutionProtocol (NEW: ~200 lines) |
| 唯一の状態遷移窓口。全 GATE をここに集約。 |
+--+--+--+--+--+--+--+-------------------------------------------------+
| | | | | | |
v v v v v v v
+------+ +------+ +------+ +------+ +------+ +------+ +------+
|State | |DAG | |Lock | |Store | |Sync | |Impact| |Audit |
|Machin| |Engine| |Mgr | | | | | | | | |
+------+ +------+ +------+ +------+ +------+ +------+ +------+
既存 移植 統合 NEW 改造 NEW 既存
Miyabi KOTOW miyabi events bidirec GNI→ skill-
task- ARI -priv .jsonl tional Task bus
mgr crowd lock.ts +snap -sync Impact record
+a-s-b +a-s-b shot adapter -run
queue queue
3. Phase 定義(全 8 Phase)
Phase 0: 前提条件の確定(実装前)
目的: 全テスト GREEN、全インデックス fresh、Codex レビュー反映方針確定
入力: なし 出力: 上記チェックリスト全項目 GREEN
手順:
cd ~/dev/ops/openclaw-workspace/Miyabi/packages/task-manager && npm testcd ~/dev/products/rust-ai-pipeline && cargo testcd ~/dev/tools/agent-skill-bus && npm test- 全テスト GREEN でなければ修正してから Phase 1 に進む
完了条件: チェックリスト 1.1 + 1.2 の全項目 GREEN
Phase 1: 型定義の拡張
目的: ManagedTask に DAG/Lock/Impact/確定的参照フィールドを追加
対象ファイル:
Miyabi/packages/task-manager/src/types/task.ts
追加する型:
// --- NEW: ファイルロック ---
export interface TaskLock {
lockedBy: string; // "{agentName}@{nodeName}"
lockedAt: string; // ISO 8601
leaseDurationSec: number; // R1-5: 固定 TTL ではなく lease
lastHeartbeat: string; // R1-5: heartbeat timestamp
affectedFiles: string[];
}
// --- NEW: Impact 記録 ---
export interface TaskImpact {
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
affectedSymbols: number;
depth1: string[];
analyzedAt: string;
analyzedCommit: string; // R1-3: 分析時の HEAD commit
inputHash: string; // R1-3: 対象ファイルセットのハッシュ
}
// --- NEW: GitHub 証跡 ---
export interface GitHubEvidence {
prNumber: number;
prHeadRef: string; // R1-3: PR の head branch
prState: 'OPEN' | 'MERGED' | 'CLOSED';
mergeCommitSha: string | null;
mergedAt: string | null;
reviewDecision: 'APPROVED' | 'CHANGES_REQUESTED' | 'REVIEW_REQUIRED' | null;
issueState: 'OPEN' | 'CLOSED';
issueClosedByPR: boolean;
}
// --- NEW: 完了モード (R3-5) ---
export type CompletionMode = 'github-pr' | 'manual' | 'external-op';
// --- TaskState 拡張 (R1-4: merged 追加) ---
export type TaskState =
| 'draft' | 'pending' | 'analyzing' | 'implementing'
| 'reviewing' | 'merged' // ← NEW
| 'deploying' | 'done'
| 'blocked' | 'failed' | 'cancelled'
| 'awaiting_github_sync'; // ← NEW (R3-2)
ManagedTask への追加フィールド:
export interface ManagedTask extends BaseTask {
// ...既存フィールド...
// DAG (R2-3: soft は別扱い)
dependents: string[];
softDependencies: string[];
// Lock
lock: TaskLock | null;
// Impact
impact: TaskImpact | null;
// 確定的参照
branchName: string | null;
githubEvidence: GitHubEvidence | null; // R1-3: 単なる prNumber/mergeCommit ではなく検証済み証跡
// 完了モード (R3-5)
completionMode: CompletionMode;
// 人間承認 (R1-3)
humanApproval: {
required: boolean;
approvedBy: string | null;
approvedAt: string | null;
reason: string | null;
} | null;
}
createManagedTask() の更新: 新フィールドのデフォルト値追加
テスト: 既存テストが壊れないことを確認 + 新フィールドの存在テスト
Rust 連携: rust-ai-pipeline/src/pipeline.rs の PipelineReport 構造体を参考に、StepResult パターン(name/success/duration/stdout/stderr)を GATE の通過記録に流用
完了条件: npm test GREEN、新型が compile 通る
Phase 2: ステートマシン拡張 + DAG 統合
目的:
merged状態とawaiting_github_sync状態を追加- conditions を実評価するように変更
- DAG エンジンを移植・統合
対象ファイル:
Miyabi/packages/task-manager/src/state/task-state-machine.ts(改造)Miyabi/packages/task-manager/src/dag/task-dag.ts(NEW: 移植元 KOTOWARI)
2.1 ステートマシン改造
R1-1 対応: conditions を文字列配列から predicate 関数 に変更
export interface StateTransitionRule {
from: TaskState;
to: TaskState;
predicates?: ((task: ManagedTask, context: TransitionContext) => boolean)[];
sideEffects?: string[];
}
export interface TransitionContext {
store: TaskStore; // 他タスクの状態を参照するため
triggeredBy: 'user' | 'agent' | 'system' | 'github-webhook';
reason?: string;
humanApproval?: boolean;
}
追加する遷移ルール:
// merged (NEW)
{ from: 'reviewing', to: 'merged', predicates: [
(task) => task.githubEvidence !== null,
(task) => task.githubEvidence?.prState === 'MERGED',
(task) => task.githubEvidence?.mergeCommitSha !== null,
(task) => /^[0-9a-f]{40}$/.test(task.githubEvidence?.mergeCommitSha ?? ''),
]},
{ from: 'merged', to: 'done', predicates: [
(task) => task.githubEvidence?.issueState === 'CLOSED',
]},
// awaiting_github_sync (NEW, R3-2)
{ from: 'reviewing', to: 'awaiting_github_sync', predicates: [] },
{ from: 'merged', to: 'awaiting_github_sync', predicates: [] },
{ from: 'awaiting_github_sync', to: 'merged', predicates: [
(task) => task.githubEvidence !== null,
]},
{ from: 'awaiting_github_sync', to: 'reviewing', predicates: [] },
// skip_analysis を削除 (R1-7 対応)
// { from: 'pending', to: 'implementing' } ← 削除
R1-2 対応: applyTransition() を store-backed に変更
applyTransition(
task: ManagedTask,
to: TaskState,
context: TransitionContext
): TransitionResult & { task?: ManagedTask } {
// 1. predicates を全て評価
const rule = this.getTransitionRule(task.currentState, to);
if (!rule) return { valid: false, ... };
for (const pred of rule.predicates ?? []) {
if (!pred(task, context)) {
return { valid: false, error: `Predicate failed for ${task.currentState} → ${to}` };
}
}
// 2. 新 task を生成
const updatedTask = { ...task, currentState: to, stateHistory: [...] };
// 3. store に即座に永続化(R1-2)
context.store.updateTask(task.id, updatedTask);
return { valid: true, task: updatedTask };
}
2.2 DAG エンジン移植
移植元: KOTOWARI/skills/openclaw-crowd/src/scheduling/task-dependency-graph.ts
新ファイル: Miyabi/packages/task-manager/src/dag/task-dag.ts
変更点:
ScheduledTask→ManagedTask型変換hard/softをdependencies/softDependenciesにマップ- R2-3: soft deps は level 計算に含めない(dispatch priority のみ)
- R2-4:
dagLevelsは永続化せず、computeLevels()で毎回再計算 getReadyTasks(store):dependenciesが全てdoneormergedのタスクを返すgetDispatchScore(task): priority × age × softSatisfied × lockAvailability の合成スコア
テスト: KOTOWARI の既存テストを移植 + ManagedTask 型でのテスト追加
完了条件: DAG テスト GREEN、循環検出テスト GREEN、soft deps が level に影響しないことのテスト GREEN
Phase 3: Event Store(JSONL + Snapshot)
目的: R2-1/R2-5 対応。tasks.json を monolithic truth から event log + snapshot に分離
新ファイル:
Miyabi/packages/task-manager/src/store/event-store.tsMiyabi/packages/task-manager/src/store/snapshot-store.ts
3.1 Event Store
export interface TaskEvent {
id: string; // ascending ID
ts: string; // ISO 8601
type: 'state_transition' | 'lock_acquired' | 'lock_released' | 'lock_heartbeat'
| 'dag_changed' | 'github_synced' | 'gate_passed' | 'gate_rejected'
| 'human_approved' | 'impact_recorded' | 'branch_created' | 'pr_created'
| 'merge_verified' | 'audit_recorded';
taskId: string;
agent: string;
node: string;
payload: Record<string, unknown>;
version: number; // R2-1: CAS 用バージョン
}
export class EventStore {
private filePath: string; // project_memory/task-events.jsonl
append(event: TaskEvent): void; // append-only
replay(since?: string): TaskEvent[]; // 全イベント再生
replayForTask(taskId: string): TaskEvent[];
}
3.2 Snapshot Store
export interface TasksSnapshot {
version: number; // R2-1: CAS バージョン
generatedAt: string;
generatedFromEventId: string; // どこまで replay したか
tasks: ManagedTask[];
fileLocks: Record<string, { taskId: string; agent: string; node: string; expiresAt: string }>;
}
export class SnapshotStore {
private filePath: string; // project_memory/tasks.snapshot.json
private lockFilePath: string; // project_memory/.tasks.lock
// R2-1: atomic write with OS flock + CAS
load(): TasksSnapshot;
save(snapshot: TasksSnapshot, expectedVersion: number): void; // version mismatch → throw
rebuild(eventStore: EventStore): TasksSnapshot; // event log から再構築
}
R2-1 対応: atomic write の実装:
save(snapshot: TasksSnapshot, expectedVersion: number): void {
// 1. OS flock を取得(fd-level)
const lockFd = fs.openSync(this.lockFilePath, 'w');
flock(lockFd, LOCK_EX); // blocking
try {
// 2. 現在の version を再読込(CAS)
const current = this.load();
if (current.version !== expectedVersion) {
throw new Error(`CAS conflict: expected v${expectedVersion}, got v${current.version}`);
}
// 3. atomic rename で書き込み
const tmpPath = this.filePath + '.tmp';
fs.writeFileSync(tmpPath, JSON.stringify({ ...snapshot, version: expectedVersion + 1 }, null, 2));
fs.renameSync(tmpPath, this.filePath);
} finally {
flock(lockFd, LOCK_UN);
fs.closeSync(lockFd);
}
}
Rust 連携: rust-ai-pipeline の PipelineReport の JSON シリアライズパターン(serde::Serialize)を参考に、event の JSON 形式を統一。将来的に Rust 側で event log を消費する際に互換性を持たせる。
完了条件: event append テスト、snapshot rebuild テスト、CAS conflict テスト、concurrent write テスト(fork して同時書き込み)
Phase 4: File Lock Manager(lease + heartbeat)
目的: R1-5/R2-2 対応。固定 TTL → renewable lease に変更
新ファイル: Miyabi/packages/task-manager/src/lock/file-lock-manager.ts
移植元:
miyabi-private/packages/miyabi/src/util/lock.ts(RWLock パターン)agent-skill-bus/src/queue.js(TTL + 競合チェック)
export interface LeaseConfig {
leaseDurationSec: number; // default: 300
heartbeatIntervalSec: number; // default: 60
staleAfterMissedHeartbeats: number; // default: 2
}
export class FileLockManager {
constructor(
private eventStore: EventStore,
private snapshotStore: SnapshotStore,
private config: LeaseConfig
) {}
// R2-2: 原子的な lock 獲得
acquireLock(taskId: string, agent: string, node: string, files: string[]): void {
// 1. OS flock で snapshot をロック
// 2. snapshot を再読込
// 3. 競合チェック(files が他タスクにロック中か)
// 4. stale lock の自動解放(heartbeat 2回 miss)
// 5. lock 書き込み + event 記録
// 6. OS flock 解放
}
// heartbeat(lease 更新)
renewLease(taskId: string, agent: string, node: string): void {
// event: lock_heartbeat を append
// snapshot の lastHeartbeat を更新
}
// 解放
releaseLock(taskId: string): void {
// event: lock_released を append
// snapshot から fileLock エントリを削除
}
// stale 検出
releaseExpiredLeases(): { released: string[]; active: string[] } {
// lastHeartbeat + leaseDuration + staleAfterMissed × heartbeatInterval < now → stale
}
// 競合チェック
hasConflict(files: string[]): { conflicting: boolean; heldBy?: string; taskId?: string } {
// snapshot の fileLocks をチェック
}
}
Rust 連携: rust-ai-pipeline/src/signal.rs の Signal.is_fresh(max_age_ms) パターンを heartbeat の鮮度判定に流用。同じ「timestamp + threshold → fresh/stale」の設計思想。
テスト:
- lease 獲得 → heartbeat → 解放 のライフサイクル
- 競合検出(同一ファイルに2タスク)
- stale 検出(heartbeat なしで lease 期限切れ)
- concurrent acquire(2プロセスが同時に acquireLock)→ 1つだけ成功
完了条件: 全テスト GREEN
Phase 5: GitHub 同期の再設計
目的: R3-1/R3-2/R3-3/R3-4 対応。二層 SSOT を正しく実装
対象ファイル:
Miyabi/packages/task-manager/src/sync/bidirectional-sync.ts(大幅改造)Miyabi/packages/task-manager/src/sync/github-evidence-fetcher.ts(NEW)
5.1 GitHub Evidence Fetcher
export class GitHubEvidenceFetcher {
constructor(private octokit: Octokit, private owner: string, private repo: string) {}
// R3-4: PR API から merge commit を取得
async fetchPREvidence(prNumber: number): Promise<GitHubEvidence> {
const { data: pr } = await this.octokit.pulls.get({
owner: this.owner, repo: this.repo, pull_number: prNumber
});
return {
prNumber,
prHeadRef: pr.head.ref,
prState: pr.merged ? 'MERGED' : pr.state === 'closed' ? 'CLOSED' : 'OPEN',
mergeCommitSha: pr.merge_commit_sha,
mergedAt: pr.merged_at,
reviewDecision: await this.fetchReviewDecision(prNumber),
issueState: await this.fetchLinkedIssueState(prNumber),
issueClosedByPR: await this.checkIssueClosedByPR(prNumber),
};
}
// R3-3: Issue が PR merge で close されたか検証
private async checkIssueClosedByPR(prNumber: number): Promise<boolean> {
// GitHub GraphQL: pullRequest.closingIssuesReferences
}
}
5.2 同期の再設計(R3-1: authority-aware bidirectional)
export type SyncDirection =
| 'push_proposal' // ローカル → GitHub(提案)
| 'pull_authoritative'; // GitHub → ローカル(事実の取得)
export class DeterministicSync {
// R3-1: 何の事実はどちらが authoritative かを状態ごとに定義
private authority: Record<string, 'github' | 'local'> = {
'taskCompletion': 'github', // done/merged は GitHub が authority
'executionLock': 'local', // lock/DAG はローカルが authority
'reviewStatus': 'github', // review 結果は GitHub が authority
'assignedAgent': 'local', // アサインはローカルが authority
};
// R3-2: GitHub API 障害時
async syncWithDegradedMode(tasks: ManagedTask[]): Promise<SyncResult> {
try {
return await this.fullSync(tasks);
} catch (error) {
if (this.isGitHubAPIError(error)) {
// awaiting_github_sync に遷移
// retry queue に追加
// event: github_sync_failed を記録
return { degraded: true, retryAt: new Date(Date.now() + 60000) };
}
throw error;
}
}
}
Rust 連携: rust-ai-pipeline/src/remote.rs の ssh_exec() + RemoteResult パターンを GitHub API 呼び出しの結果型に流用。success/stdout/stderr の三つ組は GitHubAPIResult にそのまま適用可能。
テスト:
- PR merged → evidence 取得 → merged 遷移
- Issue close by non-PR → done に遷移しないことを検証
- GitHub API 障害 → awaiting_github_sync 遷移
- retry 後の復旧
完了条件: 全テスト GREEN
Phase 6: DeterministicExecutionProtocol(核心)
目的: 全 GATE を1つのクラスに集約。唯一の状態遷移窓口。
新ファイル: Miyabi/packages/task-manager/src/protocol/deterministic-execution.ts
依存: Phase 1-5 の全コンポーネント
export class DeterministicExecutionProtocol {
private stateMachine: TaskStateMachine; // Phase 2
private dag: TaskDAG; // Phase 2
private lockManager: FileLockManager; // Phase 4
private eventStore: EventStore; // Phase 3
private snapshotStore: SnapshotStore; // Phase 3
private sync: DeterministicSync; // Phase 5
private evidenceFetcher: GitHubEvidenceFetcher; // Phase 5
// ==========================================
// GATE 0: タスク登録(Issue 必須)
// ==========================================
registerTask(input: CreateTaskInput & {
githubIssueNumber: number;
completionMode: CompletionMode;
}): ManagedTask {
// GATE: githubIssueNumber > 0
// GATE: completionMode は明示的に指定
// EVENT: state_transition (→ draft)
// STORE: snapshot に追加
}
// ==========================================
// GATE 1: DAG 登録(循環依存拒否)
// ==========================================
addToDAG(taskId: string, dependencies: string[], softDependencies: string[]): void {
// GATE: dag.detectCycle() === null
// GATE: dependencies は全て既存タスク ID
// EVENT: dag_changed
}
// ==========================================
// GATE 2: 依存解決チェック
// ==========================================
checkDependencies(taskId: string): 'ready' | 'blocked' {
// GATE: dependencies.every(d => ['done', 'merged'].includes(store.get(d).currentState))
// blocked → pending は dep 解決後に自動遷移
// EVENT: state_transition (pending → analyzing) or (pending → blocked)
}
// ==========================================
// GATE 3: Impact 分析記録
// ==========================================
recordImpact(taskId: string, impact: TaskImpact): void {
// GATE: impact.analyzedCommit === current HEAD
// GATE: impact.inputHash matches current file set
// GATE: HIGH/CRITICAL → humanApproval.required = true
// EVENT: impact_recorded
}
// ==========================================
// GATE 3.5: 人間承認(HIGH/CRITICAL のみ)
// ==========================================
recordHumanApproval(taskId: string, approvedBy: string, reason: string): void {
// GATE: task.impact.riskLevel in ['HIGH', 'CRITICAL']
// GATE: task.humanApproval.required === true
// EVENT: human_approved
}
// ==========================================
// GATE 4: アサイン + ロック獲得(原子的)
// ==========================================
assignAndLock(taskId: string, agent: string, node: string, files: string[]): void {
// GATE: impact !== null
// GATE: HIGH/CRITICAL → humanApproval.approvedAt !== null
// GATE: lockManager.hasConflict(files) === false
// ATOMIC: OS flock → re-read → re-check → write → release
// EVENT: lock_acquired
// EVENT: state_transition (analyzing → implementing)
}
// ==========================================
// Heartbeat(実装中に定期実行)
// ==========================================
heartbeat(taskId: string, agent: string, node: string): void {
// GATE: task.lock.lockedBy === `${agent}@${node}`
// EVENT: lock_heartbeat
}
// ==========================================
// GATE 5: ブランチ記録
// ==========================================
recordBranch(taskId: string, branchName: string): void {
// GATE: /^(feature|fix|hotfix)\/issue-\d+-/.test(branchName)
// GATE: git branch --list で実在確認
// EVENT: branch_created
}
// ==========================================
// GATE 6: PR 記録
// ==========================================
async recordPR(taskId: string, prNumber: number): Promise<void> {
// GATE: prNumber > 0
// GATE: branchName !== null
// VERIFY: evidenceFetcher.fetchPREvidence(prNumber)
// GATE: evidence.prHeadRef === task.branchName
// GATE: evidence.prState === 'OPEN'
// EVENT: pr_created
// EVENT: state_transition (implementing → reviewing)
}
// ==========================================
// GATE 7: Merge 検証
// ==========================================
async verifyMerge(taskId: string): Promise<void> {
// FETCH: evidenceFetcher.fetchPREvidence(task.githubEvidence.prNumber)
// GATE: evidence.prState === 'MERGED'
// GATE: evidence.mergeCommitSha matches /^[0-9a-f]{40}$/
// GATE: evidence.reviewDecision === 'APPROVED'
// EVENT: merge_verified
// EVENT: state_transition (reviewing → merged)
// EFFECT: lockManager.releaseLock(taskId)
// EFFECT: dependents の blocked → pending 遷移
}
// ==========================================
// GATE 8: 完了確定
// ==========================================
async confirmDone(taskId: string): Promise<void> {
// FETCH: evidence.issueState
// GATE: evidence.issueClosedByPR === true (github-pr mode)
// OR: completionMode === 'manual' + humanApproval
// OR: completionMode === 'external-op' + humanApproval
// EVENT: state_transition (merged → done)
// EVENT: audit_recorded
// EFFECT: worklog に記録
// EFFECT: agent-skill-bus に record-run
}
// ==========================================
// Escape Hatches(R3-5: 監査可能な例外経路)
// ==========================================
forceUnlock(taskId: string, reason: string, operator: string): void {
// EVENT: lock_released (forced=true, reason, operator)
}
reconcileFromGitHub(taskId: string): Promise<void> {
// GitHub の状態を pull して snapshot を上書き
// EVENT: github_synced (reconcile)
}
manualComplete(taskId: string, reason: string, operator: string): void {
// GATE: completionMode === 'manual' or 'external-op'
// EVENT: state_transition (→ done) + human_approved
}
}
Rust 連携: rust-ai-pipeline の Phase 1 パイプライン(build → clippy → test、最初の失敗で打ち切り)と同じパターン。GATE 0 → 1 → ... → 8 を順に実行し、最初の GATE 失敗で打ち切る。PipelineReport の steps[] と failure_kind をそのまま GATE の通過/拒否記録に流用。
将来的には、Rust で Phase 1 パイプライン(build/clippy/test)を GATE 4.5 として組み込み、「コンパイルが通らないコードは implementing → reviewing に遷移できない」という品質ゲートにする。
テスト:
- happy path: draft → ... → done の全シーケンス
- GATE 拒否: 各 GATE で条件不足 → 遷移不可
- 競合: 2タスクが同じファイルをロック → 1つだけ成功
- escape hatch: forceUnlock → 理由記録
- GitHub 障害: verifyMerge → awaiting_github_sync → retry → merged
完了条件: 全テスト GREEN
Phase 7: CLI コマンド
目的: Protocol を CLI から操作可能にする
新ファイル: Miyabi/packages/task-manager/src/cli/deterministic-cli.ts
miyabi task register --issue 45 --title "JWT移行" --deps task-000 --mode github-pr
miyabi task dag # DAG 可視化(ターミナル)
miyabi task dispatchable # 実行可能タスク一覧
miyabi task assign task-001 --agent kotowari-dev --node macbook-pro --files "src/auth/*.ts"
miyabi task heartbeat task-001 # lease 更新
miyabi task status [task-id] # 状態確認(JSON or human-readable)
miyabi task locks # ロック一覧
miyabi task unlock task-001 --reason "agent crashed" --operator hayashi
miyabi task verify-merge task-001 # GitHub から merge 証跡を取得
miyabi task confirm-done task-001 # 完了確定
miyabi task sync # GitHub 同期
miyabi task events [task-id] # event log 表示
miyabi task rebuild-snapshot # snapshot を event log から再構築
miyabi task reconcile task-001 # GitHub から強制同期
Rust 連携: rust-ai-pipeline の CLI パターン(parse_cli_args() → CliMode enum → run_*() 関数)を参考に、サブコマンドを enum で定義。ensure_cargo_project() のような前提条件チェックを各コマンドの冒頭に配置。
完了条件: 全サブコマンドのヘルプ表示 + register → assign → verify-merge → confirm-done の E2E テスト
Phase 8: 統合テスト + Agent Skill Bus 記録
目的: 全 Phase を結合して1タスクの draft → done を通す
テストシナリオ:
1. gh issue create → Issue #N
2. miyabi task register --issue N --title "test" --mode github-pr
3. miyabi task dag → Level 0 に task 表示
4. miyabi task dispatchable → task が ready
5. GNI impact 実行 → miyabi task record-impact
6. miyabi task assign --agent test --node local --files "src/test.ts"
7. git checkout -b feature/issue-N-test
8. miyabi task record-branch feature/issue-N-test
9. echo "// test" >> src/test.ts && git commit
10. gh pr create → PR #M
11. miyabi task record-pr M
12. gh pr merge M
13. miyabi task verify-merge
14. miyabi task confirm-done
15. miyabi task status → done ✅
16. gh issue view N → Closed ✅
17. npx agent-skill-bus record-run → recorded ✅
rust-ai-pipeline 統合テスト(将来):
Step 6.5: ai-pipeline phase1 --project . --format json
→ all_passed === true なら GATE 4.5 通過
→ failure_kind !== null なら implementing に留まる
完了条件: E2E テスト GREEN、event log に全ステップの記録あり、snapshot が正しく再構築可能
4. 実装順序と依存関係 DAG
Phase 0 ──→ Phase 1 ──→ Phase 2 ──→ Phase 3 ──→ Phase 4
│ │
└─────┬─────┘
│
v
Phase 5 ──→ Phase 6 ──→ Phase 7 ──→ Phase 8
| Phase | 依存 | 新規コード量 | リスク |
|---|---|---|---|
| 0 | なし | 0 | LOW |
| 1 | 0 | ~80 行 | LOW |
| 2 | 1 | ~250 行 | MEDIUM(ステートマシン改造) |
| 3 | 1 | ~200 行 | MEDIUM(atomic write) |
| 4 | 3 | ~150 行 | MEDIUM(lease + heartbeat) |
| 5 | 3 | ~200 行 | HIGH(GitHub API 依存) |
| 6 | 2,3,4,5 | ~300 行 | HIGH(核心の統合) |
| 7 | 6 | ~150 行 | LOW(CLI ボイラープレート) |
| 8 | 7 | ~100 行 | LOW(E2E テスト) |
| 合計 | ~1,430 行 |
5. エージェント割り当て(Maestro Playbook 実行時)
| Phase | 推奨エージェント | 理由 |
|---|---|---|
| 0 | Claude Code(ローカル) | テスト実行 + インデックス確認 |
| 1 | Codex(worktree) | 型定義のみ、副作用なし |
| 2 | Claude Code(ローカル) | ステートマシン改造は対話が必要 |
| 3 | Codex(worktree) | event store は独立実装可能 |
| 4 | Codex(worktree) | lock manager も独立実装可能 |
| 5 | Claude Code(ローカル) | GitHub API の挙動確認が必要 |
| 6 | Claude Code(ローカル) | 全コンポーネント統合は対話が必要 |
| 7 | Codex(worktree) | CLI ボイラープレート |
| 8 | Claude Code(ローカル) | E2E テストは実環境が必要 |
並列実行可能:
- Phase 3 と Phase 4 は同時実行可能(Codex 2体)
- Phase 2 は Phase 3/4 と並行可能(依存は Phase 1 のみ)
6. リスク軽減策
| リスク | 軽減策 |
|---|---|
| ステートマシン改造で既存テストが壊れる | Phase 2 開始前に既存テストを全部 GREEN にする |
| atomic write の OS flock がクロスプラットフォームで動かない | macOS + Linux でテスト。Windows は SMB 経由のため advisory lock は非保証 → 単一 writer に fallback |
| GitHub API レート制限 | secondary rate limit 対応の exponential backoff + 5000 req/hr の予算管理 |
| event log が肥大化 | 30日以上前の event は archive + snapshot rebuild で圧縮 |
| Rust pipeline 統合が遅れる | Phase 6 の GATE 4.5 は optional。なくても draft → done は通る |
7. 検証基準(全 Phase 完了後)
| # | 検証項目 | 方法 |
|---|---|---|
| 1 | 全 GATE が条件不足で拒否する | 各 GATE の境界値ユニットテスト |
| 2 | LLM が GATE をバイパスできない | Protocol 以外の経路で applyTransition を呼ぶテスト → 失敗 |
| 3 | 2エージェントが同一ファイルをロックできない | concurrent acquire テスト |
| 4 | 依存未解決タスクが analyzing に入れない | DAG + ステートマシン結合テスト |
| 5 | merge commit なしに done に遷移できない | GATE 7 のユニットテスト |
| 6 | GitHub 障害時に安全に停止する | mock API + awaiting_github_sync テスト |
| 7 | event log から snapshot を正確に再構築できる | rebuild + diff テスト |
| 8 | E2E: draft → done の全シーケンス | Phase 8 の統合テスト |
This playbook is the single source of truth for implementation. Do not start coding until Phase 0 is GREEN.