kairos-chain 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00c55f8f91953d2e57924bdc4748c756c3dce18b64a77e8ff17ebed825f506ce
4
- data.tar.gz: d4f1a418e9fbeecde4a5a8f9eecc37116b9620b625af0a2e91f09445fd60c673
3
+ metadata.gz: f00cda5bb11b581435b296790b3cac3b49a1919f7280897b545b23ed2ead72a9
4
+ data.tar.gz: cb213562d6fa26ee5cec11ee68f2242bff54ae72e23d51457a9beee11ece5fbd
5
5
  SHA512:
6
- metadata.gz: 96fc0ef27dcab7f578021254b8e0356644d958fb2da8a61eff02ba9862f02e4542bde04750173edeef0029a34d3a042d308217c17aa98182ebeed134f49a7337
7
- data.tar.gz: 0dcbbf48a3c2d1fc6226101b204b1aa7a9f5b0f24e5f1172f29caffd3237195b611aad3684f79034c6528596e5aafa90d0e6750b347b9d5d2e7c419596418747
6
+ metadata.gz: 7b53f2bde852c3a0476509819b31cea297904c9515cddf438b0922d1efcf8c7f4eaa2a9b1762d39ba2e8b2f9057287354e946bca8bc9654459b92ef0389a8323
7
+ data.tar.gz: 832dcf7703ce7cbfcf035684841e7922370430725e412044b4427e87353459e87984b122717966b3c1aca1e5b874b2cf1fffc2b391844b3e9636ff6a0267e7c8
data/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
4
4
 
5
5
  This project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [2.9.0] - 2026-03-14
8
+
9
+ ### Added
10
+
11
+ - **AutoExec SkillSet** (`autoexec` v0.1.1): New opt-in SkillSet for semi-autonomous task planning and execution with constitutive chain recording.
12
+ - `autoexec_plan`: Generate structured task plans from natural language descriptions. Outputs `.kdsl` DSL files with SHA-256 hash-locked integrity. Supports DSL and JSON input formats.
13
+ - `autoexec_run`: Execute planned tasks with graduated approval (`dry_run` default, `execute`, `status` modes). Two-phase commit records intent before and outcome after execution. Checkpoint resume for halted tasks.
14
+ - **Regex DSL parser**: No `eval`, no `BasicObject` sandbox — 18 forbidden patterns checked before parsing. Roundtrip-safe (`parse` → `to_source` → `parse`).
15
+ - **Risk classifier**: Static rule-based risk classification (low/medium/high) with L0 deny-list (read from L0 governance skill, self-referential design), protected file detection, and L0 firewall.
16
+ - **Plan store**: File-based plan storage with atomic execution lock (`File::CREAT|EXCL|WRONLY`), PID liveness checks, stale lock timeout, and checkpoint management.
17
+ - **`requires_human_cognition`**: Step-level halt for human cognitive participation (constitutive, not cautionary — Proposition 9). Saves checkpoint and resumes on re-run.
18
+ - **TOCTOU prevention**: Single load + in-memory hash verification (no separate verify then load).
19
+ - **Chain recording**: Two-phase commit with intent block before execution and outcome block after (validity-conditional recording, Proposition 5). Chain failures surfaced in response, never silently swallowed.
20
+ - **Path traversal prevention**: `task_id` validated with `\A\w+\z` in both DSL and JSON input paths.
21
+ - Bundled L1 knowledge: `autoexec_guide` (usage guide with examples)
22
+ - 75 unit tests across TaskDsl, RiskClassifier, and PlanStore
23
+
24
+ ### Fixed
25
+
26
+ - **TaskDsl colon false-positive**: Unknown key scanner no longer matches colons inside quoted action strings (e.g., `"run command: echo hello"`)
27
+ - **TaskDsl missing requires**: Added `require 'digest'` and `require 'json'` for module independence
28
+ - **AutoexecRun lock release**: Moved `acquire_lock` inside `begin/ensure` block with `lock_acquired` flag to guarantee lock release on any exception
29
+
30
+ ---
31
+
7
32
  ## [2.8.0] - 2026-03-08
8
33
 
9
34
  ### Added
@@ -371,6 +396,7 @@ This project follows [Semantic Versioning](https://semver.org/).
371
396
  - Skill promotion with Persona Assembly
372
397
  - Tool guide and metadata system
373
398
 
399
+ [2.9.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.8.0...v2.9.0
374
400
  [2.8.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.7.0...v2.8.0
375
401
  [2.7.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.6.0...v2.7.0
376
402
  [2.6.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.5.0...v2.6.0
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "2.8.0"
2
+ VERSION = "2.9.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: multi_agent_design_workflow
3
+ description: "Multi-agent deliberation workflow for design, implementation, and iterative review"
4
+ version: "1.0"
5
+ layer: L1
6
+ tags: [workflow, multi-agent, design, review, persona-assembly, multi-llm]
7
+ ---
8
+
9
+ # Multi-Agent Design Workflow
10
+
11
+ Implementation decisions are made through structured multi-agent deliberation,
12
+ not by a single agent acting alone. This workflow applies to both initial design
13
+ and post-implementation review, forming an iterative loop.
14
+
15
+ ## Workflow Overview
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────┐
19
+ │ 1. Rough Idea (Human) │
20
+ │ - Present rough thoughts, goals, constraints │
21
+ │ │
22
+ │ 2. Agent Team Analysis │
23
+ │ - Multiple agents analyze from different angles │
24
+ │ - Surface trade-offs, risks, alternatives │
25
+ │ │
26
+ │ 3. Persona Assembly │
27
+ │ - Project-philosophy-aware discussion │
28
+ │ - e.g., KairosChain: consider 9 propositions │
29
+ │ - Filter proposals through project identity │
30
+ │ │
31
+ │ 4. Final Proposal Selection │
32
+ │ - Design: select design plan │
33
+ │ - Review: identify critical blockers │
34
+ │ - Human makes the final call │
35
+ │ │
36
+ │ 5. (Optional) Multi-LLM Integration │
37
+ │ - Run Gemini, GPT, etc. in separate terminals │
38
+ │ - Integrate diverse LLM perspectives │
39
+ │ - Synthesize before proceeding │
40
+ │ │
41
+ │ 6. Implementation / Revision │
42
+ │ - Execute the agreed plan │
43
+ │ - On first pass: implement │
44
+ │ - On subsequent passes: apply fixes │
45
+ │ │
46
+ │ ┌─── Review Loop (steps 2-6) ───────────────┐ │
47
+ │ │ After implementation, loop back to step 2 │ │
48
+ │ │ for review. Continue until exit condition. │ │
49
+ │ └────────────────────────────────────────────┘ │
50
+ └─────────────────────────────────────────────────────┘
51
+ ```
52
+
53
+ ## Review Loop and Termination
54
+
55
+ After the initial implementation (step 6), the workflow loops back to step 2
56
+ for review. Each review iteration may produce further fixes, which are then
57
+ reviewed again.
58
+
59
+ ### Exit Conditions (whichever comes first)
60
+
61
+ | Condition | Description |
62
+ |-----------|-------------|
63
+ | **No critical defects** | No blockers or fatal flaws remain after review |
64
+ | **Max loop count reached** | Default: **3 iterations**. Adjustable per task. |
65
+
66
+ ### Loop Behavior
67
+
68
+ - **Iteration 1**: Full review — architectural, logical, edge cases
69
+ - **Iteration 2**: Focused review — only issues from iteration 1 fixes
70
+ - **Iteration 3+**: Regression check — confirm no new issues introduced
71
+
72
+ If the max loop count is reached but blockers remain, the workflow pauses
73
+ and escalates to the human for a decision (continue, defer, or accept as-is).
74
+
75
+ ## Output Artifacts
76
+
77
+ Each workflow execution produces two distinct artifacts, saved to both
78
+ L2 context and the `log/` directory.
79
+
80
+ ### Pre-Implementation Plan
81
+
82
+ Saved **before** step 6 (first implementation):
83
+
84
+ - **L2 context**: `context_save()` with tag `plan`
85
+ - **log/ file**: `log/{date}_{feature}_plan.md`
86
+ - **Contents**: design decision, rationale, rejected alternatives, risks
87
+
88
+ ### Post-Implementation Review Log
89
+
90
+ Saved **after** each review loop iteration:
91
+
92
+ - **L2 context**: `context_save()` with tag `review`
93
+ - **log/ file**: `log/{date}_{feature}_review_N.md` (N = iteration number)
94
+ - **Contents**: findings, severity, fixes applied, remaining issues
95
+
96
+ ### Naming Convention
97
+
98
+ ```
99
+ log/20260313_auth_refactor_plan.md
100
+ log/20260313_auth_refactor_review_1.md
101
+ log/20260313_auth_refactor_review_2.md
102
+ ```
103
+
104
+ ## When to Use This Workflow
105
+
106
+ - New feature design with significant architectural impact
107
+ - Refactoring that touches multiple components
108
+ - Any change where "just implement it" risks cascading problems
109
+ - Bug fixes that require root cause analysis before patching
110
+
111
+ ## When NOT to Use
112
+
113
+ - Trivial fixes (typos, single-line changes)
114
+ - Well-understood, isolated changes with no design ambiguity
115
+ - Exploratory prototyping where speed matters more than correctness
116
+
117
+ ## Relation to Existing Tools
118
+
119
+ | Step | KairosChain Tool |
120
+ |------|-----------------|
121
+ | Agent Team Analysis | Agent tool with multiple subagents |
122
+ | Persona Assembly | `skills_audit(command: "check")` or manual persona invocation |
123
+ | Multi-LLM Integration | External (separate terminal sessions) |
124
+ | Plan/Review output | `context_save()` + file write to `log/` |
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: multi_agent_design_workflow_jp
3
+ description: "マルチエージェント審議ワークフロー:設計・実装・反復レビュー"
4
+ version: "1.0"
5
+ layer: L1
6
+ tags: [workflow, multi-agent, design, review, persona-assembly, multi-llm]
7
+ ---
8
+
9
+ # マルチエージェント設計ワークフロー
10
+
11
+ 実装の意思決定は、単一エージェントではなく、構造化されたマルチエージェント審議を
12
+ 通じて行う。このワークフローは初期設計と実装後レビューの両方に適用され、
13
+ 反復ループを形成する。
14
+
15
+ ## ワークフロー概要
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────┐
19
+ │ 1. ラフなアイデア提示(人間) │
20
+ │ - 大まかな考え、目標、制約を伝える │
21
+ │ │
22
+ │ 2. エージェントチーム多角的分析 │
23
+ │ - 複数エージェントが異なる角度から分析 │
24
+ │ - トレードオフ、リスク、代替案を洗い出す │
25
+ │ │
26
+ │ 3. Persona Assembly │
27
+ │ - プロジェクトの哲学を考慮した議論 │
28
+ │ - 例:KairosChainなら9命題を考慮 │
29
+ │ - プロジェクトのアイデンティティで提案をフィルタ │
30
+ │ │
31
+ │ 4. 最終案選定 │
32
+ │ - 設計:設計案を選定 │
33
+ │ - レビュー:致命的ブロッカーを特定 │
34
+ │ - 最終判断は人間が行う │
35
+ │ │
36
+ │ 5.(任意)マルチLLM統合 │
37
+ │ - Gemini、GPT等を別ターミナルで起動 │
38
+ │ - 異なるLLMの視点を統合 │
39
+ │ - 統合結果をもとに次のステップへ │
40
+ │ │
41
+ │ 6. 実装 / 修正 │
42
+ │ - 合意された計画を実行 │
43
+ │ - 初回:実装 │
44
+ │ - 2回目以降:修正適用 │
45
+ │ │
46
+ │ ┌─── レビューループ(ステップ2-6)──────────┐ │
47
+ │ │ 実装後、ステップ2に戻りレビュー。 │ │
48
+ │ │ 終了条件を満たすまで繰り返す。 │ │
49
+ │ └──────────────────────────────────────────┘ │
50
+ └─────────────────────────────────────────────────────┘
51
+ ```
52
+
53
+ ## レビューループと終了条件
54
+
55
+ 初回実装(ステップ6)の後、ステップ2に戻りレビューを行う。
56
+ 各レビューで追加修正が発生した場合、再度レビューを繰り返す。
57
+
58
+ ### 終了条件(いずれか先に満たされた方)
59
+
60
+ | 条件 | 説明 |
61
+ |------|------|
62
+ | **致命的欠陥なし** | ブロッカーや致命的欠陥がレビューで検出されない |
63
+ | **最大ループ回数到達** | デフォルト:**3回**。タスクごとに調整可能。 |
64
+
65
+ ### ループごとの挙動
66
+
67
+ - **1回目**:フルレビュー — アーキテクチャ、ロジック、エッジケース
68
+ - **2回目**:焦点レビュー — 1回目の修正に起因する問題のみ
69
+ - **3回目以降**:リグレッションチェック — 新たな問題の混入がないか確認
70
+
71
+ 最大ループ回数に到達してもブロッカーが残っている場合、ワークフローを一時停止し
72
+ 人間に判断をエスカレーションする(継続、延期、現状受け入れ)。
73
+
74
+ ## 出力成果物
75
+
76
+ ワークフロー実行ごとに2種類の成果物を生成し、L2コンテキストと
77
+ `log/` ディレクトリの両方に保存する。
78
+
79
+ ### 実装前プラン
80
+
81
+ ステップ6(初回実装)の**前**に保存:
82
+
83
+ - **L2コンテキスト**: `context_save()` タグ `plan`
84
+ - **log/ ファイル**: `log/{日付}_{機能名}_plan.md`
85
+ - **内容**: 設計判断、理由、却下された代替案、リスク
86
+
87
+ ### 実装後レビューログ
88
+
89
+ 各レビューループ反復の**後**に保存:
90
+
91
+ - **L2コンテキスト**: `context_save()` タグ `review`
92
+ - **log/ ファイル**: `log/{日付}_{機能名}_review_N.md`(N = 反復回数)
93
+ - **内容**: 発見事項、重大度、適用した修正、残存課題
94
+
95
+ ### 命名規則
96
+
97
+ ```
98
+ log/20260313_auth_refactor_plan.md
99
+ log/20260313_auth_refactor_review_1.md
100
+ log/20260313_auth_refactor_review_2.md
101
+ ```
102
+
103
+ ## 使用すべき場面
104
+
105
+ - アーキテクチャに大きな影響を与える新機能の設計
106
+ - 複数コンポーネントにまたがるリファクタリング
107
+ - 「とりあえず実装」では連鎖的な問題を引き起こすリスクがある変更
108
+ - パッチ適用前に根本原因分析が必要なバグ修正
109
+
110
+ ## 使用しない場面
111
+
112
+ - 些細な修正(タイポ、1行の変更)
113
+ - 設計に曖昧さのない、よく理解された独立した変更
114
+ - 正確さよりもスピードが重要な探索的プロトタイピング
115
+
116
+ ## 既存ツールとの対応
117
+
118
+ | ステップ | KairosChainツール |
119
+ |----------|------------------|
120
+ | エージェントチーム分析 | Agent tool(複数サブエージェント) |
121
+ | Persona Assembly | `skills_audit(command: "check")` またはペルソナ手動起動 |
122
+ | マルチLLM統合 | 外部(別ターミナルセッション) |
123
+ | プラン/レビュー出力 | `context_save()` + `log/` へのファイル書き出し |
@@ -0,0 +1,75 @@
1
+ # AutoExec SkillSet Configuration
2
+ # Semi-autonomous task planning and execution
3
+
4
+ # Default execution mode: dry_run or execute
5
+ default_mode: dry_run
6
+
7
+ # Maximum steps per task plan
8
+ max_steps: 20
9
+
10
+ # Maximum concurrent tasks (enforced by execution mutex)
11
+ max_concurrent: 1
12
+
13
+ # Stale lock timeout in seconds (3600 = 1 hour)
14
+ stale_lock_timeout: 3600
15
+
16
+ # Loop detection thresholds
17
+ loop_detection:
18
+ generic_repeat:
19
+ warn: 5
20
+ halt: 10
21
+ poll_no_progress:
22
+ warn: 3
23
+ halt: 5
24
+ ping_pong:
25
+ warn: 4
26
+ halt: 8
27
+ replan_cycle:
28
+ warn: 2
29
+ halt: 3
30
+
31
+ # Risk classification defaults
32
+ risk_defaults:
33
+ read: low
34
+ search: low
35
+ analyze: low
36
+ edit: medium
37
+ create: medium
38
+ test: medium
39
+ delete: high
40
+ push: high
41
+ deploy: high
42
+
43
+ # L0 deny-list (hardcoded, cannot be overridden)
44
+ # These operations are NEVER allowed regardless of risk classification
45
+ # l0_deny_list:
46
+ # - l0_evolution
47
+ # - chain_modification
48
+ # - skill_deletion
49
+
50
+ # Content firewall
51
+ content_firewall:
52
+ enabled: true
53
+ block_on_detection: true # false = warn only (NOT recommended)
54
+
55
+ # Runtime boundary
56
+ runtime_boundary:
57
+ enabled: true
58
+ network_allowlist_mode: true # only declared URLs allowed
59
+ forbidden_read_paths:
60
+ - "~/.ssh"
61
+ - "~/.aws"
62
+ - "~/.gnupg"
63
+ - "~/.config/gcloud"
64
+ - "/etc/shadow"
65
+ forbidden_write_paths:
66
+ - "/dev/"
67
+ - "/proc/"
68
+ - "/sys/"
69
+ - "/boot/"
70
+
71
+ # Storage paths (relative to .kairos/)
72
+ storage:
73
+ plans_dir: "autoexec/plans"
74
+ state_dir: "autoexec/state"
75
+ violations_dir: "autoexec/violations"
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: AutoExec SkillSet Guide
3
+ tags: [autoexec, task-planning, semi-autonomous, execution]
4
+ version: 0.1.0
5
+ ---
6
+
7
+ # AutoExec SkillSet Guide
8
+
9
+ ## Overview
10
+
11
+ AutoExec enables semi-autonomous task planning and execution with constitutive
12
+ chain recording. It decomposes tasks into structured DSL plans, classifies risk,
13
+ and executes with graduated approval.
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Create a Plan
18
+
19
+ Provide a JSON task decomposition to `autoexec_plan`:
20
+
21
+ ```json
22
+ {
23
+ "task_id": "add_mcp_tool",
24
+ "meta": { "description": "Add token_balance MCP tool", "risk_default": "medium" },
25
+ "steps": [
26
+ { "step_id": "analyze", "action": "read existing tool patterns", "risk": "low" },
27
+ { "step_id": "implement", "action": "create tool file", "risk": "medium", "depends_on": ["analyze"] },
28
+ { "step_id": "test", "action": "write and run tests", "risk": "medium", "depends_on": ["implement"] }
29
+ ]
30
+ }
31
+ ```
32
+
33
+ ### 2. Review and Dry Run
34
+
35
+ Use the returned `plan_hash` to dry-run:
36
+
37
+ ```
38
+ autoexec_run(task_id: "add_mcp_tool", mode: "dry_run", approved_hash: "<hash>")
39
+ ```
40
+
41
+ ### 3. Execute
42
+
43
+ After reviewing the dry run, switch to execute mode:
44
+
45
+ ```
46
+ autoexec_run(task_id: "add_mcp_tool", mode: "execute", approved_hash: "<hash>")
47
+ ```
48
+
49
+ ## Safety Model
50
+
51
+ - **Dry-run default**: All plans start in dry_run mode
52
+ - **Hash-locked plans**: Plans are SHA-256 hashed at creation; execution verifies the hash
53
+ - **L0 deny-list**: Operations like L0 evolution and chain modification are always blocked
54
+ - **Protected files**: Writes to config files force :high risk classification
55
+ - **Human cognition markers**: Steps with `requires_human_cognition: true` halt execution
56
+
57
+ ## Risk Classification
58
+
59
+ | Level | Auto-execute | Examples |
60
+ |-------|-------------|---------|
61
+ | Low | Yes | read, search, analyze, list |
62
+ | Medium | With approval | edit, create, test, build |
63
+ | High | Individual approval | delete, push, deploy |
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Autoexec
4
+ # Stores task plans, manages hash verification, execution locks, and checkpoints.
5
+ # Plans stored as .kdsl (non-executable DSL text) with .json metadata sidecar.
6
+ class PlanStore
7
+ LOCK_FILE = 'autoexec.lock'
8
+
9
+ # --- Plan Storage ---
10
+
11
+ def self.save(task_id, plan, source)
12
+ raise ArgumentError, "Invalid task_id: must contain only word characters" unless task_id.to_s.match?(/\A\w+\z/)
13
+
14
+ plans_dir = Autoexec.storage_path('plans')
15
+ plan_path = File.join(plans_dir, "#{task_id}.kdsl")
16
+ meta_path = File.join(plans_dir, "#{task_id}.json")
17
+ plan_hash = TaskDsl.compute_hash(source)
18
+
19
+ File.write(plan_path, source)
20
+ File.write(meta_path, JSON.pretty_generate({
21
+ task_id: task_id.to_s,
22
+ plan_hash: plan_hash,
23
+ step_count: plan.steps.size,
24
+ risk_summary: RiskClassifier.risk_summary(plan.steps),
25
+ created_at: Time.now.iso8601,
26
+ status: 'planned'
27
+ }))
28
+
29
+ plan_hash
30
+ end
31
+
32
+ def self.load(task_id)
33
+ plans_dir = Autoexec.storage_path('plans')
34
+ plan_path = File.join(plans_dir, "#{task_id}.kdsl")
35
+ meta_path = File.join(plans_dir, "#{task_id}.json")
36
+
37
+ return nil unless File.exist?(plan_path) && File.exist?(meta_path)
38
+
39
+ source = File.read(plan_path)
40
+ metadata = JSON.parse(File.read(meta_path), symbolize_names: true)
41
+ plan = TaskDsl.parse(source)
42
+ computed_hash = TaskDsl.compute_hash(source)
43
+
44
+ {
45
+ plan: plan,
46
+ source: source,
47
+ hash: computed_hash,
48
+ metadata: metadata
49
+ }
50
+ end
51
+
52
+ def self.verify_hash(task_id, expected_hash)
53
+ stored = load(task_id)
54
+ return false unless stored
55
+
56
+ stored[:hash] == expected_hash
57
+ end
58
+
59
+ def self.list
60
+ plans_dir = Autoexec.storage_path('plans')
61
+ Dir.glob(File.join(plans_dir, '*.json')).map do |meta_path|
62
+ JSON.parse(File.read(meta_path), symbolize_names: true)
63
+ rescue JSON::ParserError => e
64
+ warn "[autoexec] Corrupted metadata file: #{meta_path} (#{e.message})"
65
+ nil
66
+ end.compact
67
+ end
68
+
69
+ def self.update_status(task_id, status)
70
+ plans_dir = Autoexec.storage_path('plans')
71
+ meta_path = File.join(plans_dir, "#{task_id}.json")
72
+ return unless File.exist?(meta_path)
73
+
74
+ metadata = JSON.parse(File.read(meta_path))
75
+ metadata['status'] = status
76
+ metadata['updated_at'] = Time.now.iso8601
77
+ File.write(meta_path, JSON.pretty_generate(metadata))
78
+ end
79
+
80
+ # --- Execution Lock (OpenClaw session-write-lock pattern, simplified) ---
81
+
82
+ def self.acquire_lock(task_id)
83
+ state_dir = Autoexec.storage_path('state')
84
+ lock_path = File.join(state_dir, LOCK_FILE)
85
+
86
+ # Check for existing lock and handle stale/dead locks
87
+ if File.exist?(lock_path)
88
+ lock_data = JSON.parse(File.read(lock_path)) rescue {}
89
+ pid = lock_data['pid']
90
+
91
+ pid_alive = begin
92
+ Process.kill(0, pid)
93
+ true
94
+ rescue Errno::ESRCH, Errno::EPERM
95
+ false
96
+ end
97
+
98
+ if pid_alive
99
+ stale_timeout = Autoexec.config.dig('stale_lock_timeout') || 3600
100
+ lock_age = Time.now - Time.parse(lock_data['started_at'])
101
+ if lock_age < stale_timeout
102
+ raise "Execution locked by task '#{lock_data['task_id']}' (PID #{pid}, " \
103
+ "started #{lock_data['started_at']}). Use autoexec_run to check status."
104
+ end
105
+ end
106
+ # PID dead or stale — remove before atomic create
107
+ File.delete(lock_path) rescue nil
108
+ end
109
+
110
+ # Atomic lock creation using CREAT|EXCL (prevents race condition)
111
+ lock_data = JSON.pretty_generate({
112
+ task_id: task_id.to_s,
113
+ pid: Process.pid,
114
+ started_at: Time.now.iso8601
115
+ })
116
+ begin
117
+ fd = File.open(lock_path, File::CREAT | File::EXCL | File::WRONLY)
118
+ fd.write(lock_data)
119
+ fd.close
120
+ rescue Errno::EEXIST
121
+ # Another process acquired the lock between our check and create
122
+ raise "Execution locked by another process (race condition). Retry shortly."
123
+ end
124
+ true
125
+ end
126
+
127
+ def self.release_lock
128
+ state_dir = Autoexec.storage_path('state')
129
+ lock_path = File.join(state_dir, LOCK_FILE)
130
+ File.delete(lock_path) if File.exist?(lock_path)
131
+ end
132
+
133
+ def self.locked?
134
+ state_dir = Autoexec.storage_path('state')
135
+ lock_path = File.join(state_dir, LOCK_FILE)
136
+ return false unless File.exist?(lock_path)
137
+
138
+ lock_data = JSON.parse(File.read(lock_path))
139
+ pid_alive = begin
140
+ Process.kill(0, lock_data['pid'])
141
+ true
142
+ rescue Errno::ESRCH, Errno::EPERM
143
+ false
144
+ end
145
+
146
+ pid_alive
147
+ rescue StandardError
148
+ false
149
+ end
150
+
151
+ # --- Checkpoints ---
152
+
153
+ def self.save_checkpoint(task_id, state)
154
+ state_dir = Autoexec.storage_path('state')
155
+ path = File.join(state_dir, "#{task_id}.checkpoint.json")
156
+ File.write(path, JSON.pretty_generate(state))
157
+ end
158
+
159
+ def self.load_checkpoint(task_id)
160
+ state_dir = Autoexec.storage_path('state')
161
+ path = File.join(state_dir, "#{task_id}.checkpoint.json")
162
+ return nil unless File.exist?(path)
163
+
164
+ JSON.parse(File.read(path), symbolize_names: true)
165
+ end
166
+
167
+ def self.clear_checkpoint(task_id)
168
+ state_dir = Autoexec.storage_path('state')
169
+ path = File.join(state_dir, "#{task_id}.checkpoint.json")
170
+ File.delete(path) if File.exist?(path)
171
+ end
172
+ end
173
+ end