impliforge 0.1.1__py3-none-any.whl
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.
- impliforge/__init__.py +5 -0
- impliforge/__main__.py +6 -0
- impliforge/agents/base.py +135 -0
- impliforge/agents/documentation.py +355 -0
- impliforge/agents/fixer.py +529 -0
- impliforge/agents/implementation.py +319 -0
- impliforge/agents/planner.py +142 -0
- impliforge/agents/requirements.py +213 -0
- impliforge/agents/reviewer.py +553 -0
- impliforge/agents/test_design.py +443 -0
- impliforge/agents/test_execution.py +371 -0
- impliforge/main.py +1315 -0
- impliforge/models/routing.py +386 -0
- impliforge/orchestration/artifact_writer.py +932 -0
- impliforge/orchestration/edit_phase.py +706 -0
- impliforge/orchestration/orchestrator.py +409 -0
- impliforge/orchestration/runtime_support.py +161 -0
- impliforge/orchestration/session_manager.py +375 -0
- impliforge/orchestration/state_store.py +197 -0
- impliforge/orchestration/workflow.py +416 -0
- impliforge/runtime/code_editing.py +585 -0
- impliforge/runtime/copilot_client.py +733 -0
- impliforge/runtime/editor.py +678 -0
- impliforge-0.1.1.dist-info/METADATA +174 -0
- impliforge-0.1.1.dist-info/RECORD +28 -0
- impliforge-0.1.1.dist-info/WHEEL +4 -0
- impliforge-0.1.1.dist-info/entry_points.txt +2 -0
- impliforge-0.1.1.dist-info/licenses/LICENSE +21 -0
impliforge/__init__.py
ADDED
impliforge/__main__.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Shared agent interfaces for the impliforge workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class AgentTask:
|
|
12
|
+
"""Structured input passed from the orchestrator to an agent."""
|
|
13
|
+
|
|
14
|
+
name: str
|
|
15
|
+
objective: str
|
|
16
|
+
inputs: dict[str, Any] = field(default_factory=dict)
|
|
17
|
+
constraints: dict[str, Any] = field(default_factory=dict)
|
|
18
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class AgentResult:
|
|
23
|
+
"""Structured output returned by an agent."""
|
|
24
|
+
|
|
25
|
+
status: str
|
|
26
|
+
summary: str
|
|
27
|
+
outputs: dict[str, Any] = field(default_factory=dict)
|
|
28
|
+
artifacts: list[str] = field(default_factory=list)
|
|
29
|
+
next_actions: list[str] = field(default_factory=list)
|
|
30
|
+
risks: list[str] = field(default_factory=list)
|
|
31
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
failure_category: str | None = None
|
|
33
|
+
failure_cause: str | None = None
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
self.status = str(self.status).strip()
|
|
37
|
+
self.summary = self._normalize_summary(self.summary)
|
|
38
|
+
self.outputs = self._normalize_mapping(self.outputs)
|
|
39
|
+
self.artifacts = self._normalize_string_list(self.artifacts)
|
|
40
|
+
self.next_actions = self._normalize_string_list(self.next_actions)
|
|
41
|
+
self.risks = self._normalize_string_list(self.risks)
|
|
42
|
+
self.metrics = self._normalize_mapping(self.metrics)
|
|
43
|
+
self.failure_category = self._normalize_optional_string(self.failure_category)
|
|
44
|
+
self.failure_cause = self._normalize_optional_string(self.failure_cause)
|
|
45
|
+
|
|
46
|
+
if self.status == "failed":
|
|
47
|
+
if self.failure_category is None:
|
|
48
|
+
self.failure_category = "unknown_failure"
|
|
49
|
+
if self.failure_cause is None:
|
|
50
|
+
self.failure_cause = "No failure cause provided."
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def is_success(self) -> bool:
|
|
54
|
+
return self.status in {"completed", "success"}
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def success(
|
|
58
|
+
cls,
|
|
59
|
+
summary: str,
|
|
60
|
+
*,
|
|
61
|
+
outputs: Mapping[str, Any] | None = None,
|
|
62
|
+
artifacts: list[str] | None = None,
|
|
63
|
+
next_actions: list[str] | None = None,
|
|
64
|
+
risks: list[str] | None = None,
|
|
65
|
+
metrics: Mapping[str, Any] | None = None,
|
|
66
|
+
) -> AgentResult:
|
|
67
|
+
return cls(
|
|
68
|
+
status="completed",
|
|
69
|
+
summary=summary,
|
|
70
|
+
outputs=dict(outputs or {}),
|
|
71
|
+
artifacts=list(artifacts or []),
|
|
72
|
+
next_actions=list(next_actions or []),
|
|
73
|
+
risks=list(risks or []),
|
|
74
|
+
metrics=dict(metrics or {}),
|
|
75
|
+
failure_category=None,
|
|
76
|
+
failure_cause=None,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def failure(
|
|
81
|
+
cls,
|
|
82
|
+
summary: str,
|
|
83
|
+
*,
|
|
84
|
+
outputs: Mapping[str, Any] | None = None,
|
|
85
|
+
artifacts: list[str] | None = None,
|
|
86
|
+
next_actions: list[str] | None = None,
|
|
87
|
+
risks: list[str] | None = None,
|
|
88
|
+
metrics: Mapping[str, Any] | None = None,
|
|
89
|
+
failure_category: str | None = None,
|
|
90
|
+
failure_cause: str | None = None,
|
|
91
|
+
) -> AgentResult:
|
|
92
|
+
return cls(
|
|
93
|
+
status="failed",
|
|
94
|
+
summary=summary,
|
|
95
|
+
outputs=dict(outputs or {}),
|
|
96
|
+
artifacts=list(artifacts or []),
|
|
97
|
+
next_actions=list(next_actions or []),
|
|
98
|
+
risks=list(risks or []),
|
|
99
|
+
metrics=dict(metrics or {}),
|
|
100
|
+
failure_category=failure_category,
|
|
101
|
+
failure_cause=failure_cause,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _normalize_summary(value: Any) -> str:
|
|
106
|
+
summary = str(value).strip()
|
|
107
|
+
return summary or "No summary provided."
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _normalize_mapping(value: Any) -> dict[str, Any]:
|
|
111
|
+
return dict(value) if isinstance(value, Mapping) else {}
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _normalize_string_list(value: Any) -> list[str]:
|
|
115
|
+
if not isinstance(value, list):
|
|
116
|
+
return []
|
|
117
|
+
return [str(item).strip() for item in value if str(item).strip()]
|
|
118
|
+
|
|
119
|
+
@staticmethod
|
|
120
|
+
def _normalize_optional_string(value: Any) -> str | None:
|
|
121
|
+
if not isinstance(value, str):
|
|
122
|
+
return None
|
|
123
|
+
normalized = value.strip()
|
|
124
|
+
return normalized or None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BaseAgent(ABC):
|
|
128
|
+
"""Base contract implemented by all workflow agents."""
|
|
129
|
+
|
|
130
|
+
agent_name = "base"
|
|
131
|
+
|
|
132
|
+
@abstractmethod
|
|
133
|
+
async def run(self, task: AgentTask, state: Any) -> AgentResult:
|
|
134
|
+
"""Execute the agent task and return a structured result."""
|
|
135
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""Documentation generation agent for the impliforge workflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from impliforge.agents.base import AgentResult, AgentTask, BaseAgent
|
|
8
|
+
from impliforge.orchestration.workflow import WorkflowState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DocumentationAgent(BaseAgent):
|
|
12
|
+
"""Generate design-oriented documentation artifacts from workflow context."""
|
|
13
|
+
|
|
14
|
+
agent_name = "documentation"
|
|
15
|
+
|
|
16
|
+
async def run(self, task: AgentTask, state: WorkflowState) -> AgentResult:
|
|
17
|
+
normalized_requirements = self._as_dict(
|
|
18
|
+
task.inputs.get("normalized_requirements", {})
|
|
19
|
+
)
|
|
20
|
+
plan = self._as_dict(task.inputs.get("plan", {}))
|
|
21
|
+
copilot_response = str(task.inputs.get("copilot_response", "")).strip()
|
|
22
|
+
|
|
23
|
+
objective = str(
|
|
24
|
+
normalized_requirements.get("objective", state.requirement)
|
|
25
|
+
).strip()
|
|
26
|
+
constraints = self._normalize_list(normalized_requirements.get("constraints"))
|
|
27
|
+
acceptance_criteria = self._normalize_list(
|
|
28
|
+
normalized_requirements.get("acceptance_criteria")
|
|
29
|
+
)
|
|
30
|
+
open_questions = self._normalize_list(
|
|
31
|
+
normalized_requirements.get("open_questions")
|
|
32
|
+
)
|
|
33
|
+
resolved_decisions = self._normalize_list(
|
|
34
|
+
normalized_requirements.get("resolved_decisions")
|
|
35
|
+
)
|
|
36
|
+
inferred_capabilities = self._normalize_list(
|
|
37
|
+
normalized_requirements.get("inferred_capabilities")
|
|
38
|
+
)
|
|
39
|
+
out_of_scope = self._normalize_list(normalized_requirements.get("out_of_scope"))
|
|
40
|
+
phases = self._normalize_list(plan.get("phases"))
|
|
41
|
+
deliverables = self._normalize_list(plan.get("deliverables"))
|
|
42
|
+
task_breakdown = self._normalize_task_breakdown(plan.get("task_breakdown"))
|
|
43
|
+
|
|
44
|
+
design_document = self._build_design_document(
|
|
45
|
+
objective=objective,
|
|
46
|
+
constraints=constraints,
|
|
47
|
+
acceptance_criteria=acceptance_criteria,
|
|
48
|
+
open_questions=open_questions,
|
|
49
|
+
resolved_decisions=resolved_decisions,
|
|
50
|
+
inferred_capabilities=inferred_capabilities,
|
|
51
|
+
out_of_scope=out_of_scope,
|
|
52
|
+
phases=phases,
|
|
53
|
+
task_breakdown=task_breakdown,
|
|
54
|
+
copilot_response=copilot_response,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
runbook_document = self._build_runbook_document(
|
|
58
|
+
objective=objective,
|
|
59
|
+
deliverables=deliverables,
|
|
60
|
+
phases=phases,
|
|
61
|
+
open_questions=open_questions,
|
|
62
|
+
resolved_decisions=resolved_decisions,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
outputs = {
|
|
66
|
+
"design_document": design_document,
|
|
67
|
+
"runbook_document": runbook_document,
|
|
68
|
+
"documentation_bundle": {
|
|
69
|
+
"design": design_document,
|
|
70
|
+
"runbook": runbook_document,
|
|
71
|
+
},
|
|
72
|
+
"documentation_targets": [
|
|
73
|
+
"docs/design.md",
|
|
74
|
+
"docs/runbook.md",
|
|
75
|
+
],
|
|
76
|
+
"open_questions": open_questions,
|
|
77
|
+
"resolved_decisions": resolved_decisions,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
risks = []
|
|
81
|
+
if open_questions and not resolved_decisions:
|
|
82
|
+
risks.append("未解決の open questions があるため、設計文書は暫定版となる")
|
|
83
|
+
if not phases:
|
|
84
|
+
risks.append("計画フェーズ情報が不足しているため、運用手順の粒度が粗い")
|
|
85
|
+
|
|
86
|
+
return AgentResult.success(
|
|
87
|
+
"設計文書と運用向けドキュメントの草案を生成した。",
|
|
88
|
+
outputs=outputs,
|
|
89
|
+
artifacts=["docs/design.md", "docs/runbook.md"],
|
|
90
|
+
next_actions=[
|
|
91
|
+
"docs/design.md と docs/runbook.md を保存する",
|
|
92
|
+
"implementation agent に設計文書を渡す",
|
|
93
|
+
],
|
|
94
|
+
risks=risks,
|
|
95
|
+
metrics={
|
|
96
|
+
"constraint_count": len(constraints),
|
|
97
|
+
"acceptance_criteria_count": len(acceptance_criteria),
|
|
98
|
+
"open_question_count": len(open_questions),
|
|
99
|
+
"resolved_decision_count": len(resolved_decisions),
|
|
100
|
+
"task_breakdown_count": len(task_breakdown),
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def _build_design_document(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
objective: str,
|
|
108
|
+
constraints: list[str],
|
|
109
|
+
acceptance_criteria: list[str],
|
|
110
|
+
open_questions: list[str],
|
|
111
|
+
resolved_decisions: list[str],
|
|
112
|
+
inferred_capabilities: list[str],
|
|
113
|
+
out_of_scope: list[str],
|
|
114
|
+
phases: list[str],
|
|
115
|
+
task_breakdown: list[dict[str, Any]],
|
|
116
|
+
copilot_response: str,
|
|
117
|
+
) -> str:
|
|
118
|
+
lines: list[str] = [
|
|
119
|
+
"# Design",
|
|
120
|
+
"",
|
|
121
|
+
"## Objective",
|
|
122
|
+
objective or "TBD",
|
|
123
|
+
"",
|
|
124
|
+
"## Architecture Direction",
|
|
125
|
+
"- Orchestrator-centric multi-agent workflow",
|
|
126
|
+
"- GitHub Copilot SDK is isolated behind a client layer",
|
|
127
|
+
"- Session continuity is handled through snapshot and resume flow",
|
|
128
|
+
"- Model routing is selected per task kind",
|
|
129
|
+
"",
|
|
130
|
+
"## Constraints",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
lines.extend(self._render_bullets(constraints))
|
|
134
|
+
lines.extend(
|
|
135
|
+
[
|
|
136
|
+
"",
|
|
137
|
+
"## Acceptance Criteria",
|
|
138
|
+
]
|
|
139
|
+
)
|
|
140
|
+
lines.extend(self._render_bullets(acceptance_criteria))
|
|
141
|
+
lines.extend(
|
|
142
|
+
[
|
|
143
|
+
"",
|
|
144
|
+
"## Inferred Capabilities",
|
|
145
|
+
]
|
|
146
|
+
)
|
|
147
|
+
lines.extend(self._render_bullets(inferred_capabilities))
|
|
148
|
+
lines.extend(
|
|
149
|
+
[
|
|
150
|
+
"",
|
|
151
|
+
"## Planned Phases",
|
|
152
|
+
]
|
|
153
|
+
)
|
|
154
|
+
lines.extend(self._render_numbered(phases))
|
|
155
|
+
lines.extend(
|
|
156
|
+
[
|
|
157
|
+
"",
|
|
158
|
+
"## Task Breakdown",
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
lines.extend(self._render_task_breakdown(task_breakdown))
|
|
162
|
+
lines.extend(
|
|
163
|
+
[
|
|
164
|
+
"",
|
|
165
|
+
"## Out of Scope",
|
|
166
|
+
]
|
|
167
|
+
)
|
|
168
|
+
lines.extend(self._render_bullets(out_of_scope))
|
|
169
|
+
lines.extend(
|
|
170
|
+
[
|
|
171
|
+
"",
|
|
172
|
+
"## Persistent Context Policy",
|
|
173
|
+
"- persistent context は `artifacts/workflow-state.json`、`artifacts/sessions/<session_id>/session-snapshot.json`、`artifacts/summaries/<workflow_id>/run-summary.json` に保存する。",
|
|
174
|
+
"- 復元粒度は workflow 単位と session 単位を基本とし、最低限 `requirement`、`phase`、`workflow_id`、`session_id`、完了済みタスク、未完了タスク、直近の要約、resume prompt を保証対象にする。",
|
|
175
|
+
"- エージェントごとの一時的な推論全文やトークン列は保証対象に含めず、再開に必要な構造化状態のみを永続化する。",
|
|
176
|
+
"- 永続化データが欠落している場合は、最後に整合している checkpoint まで戻して再開する。",
|
|
177
|
+
"",
|
|
178
|
+
"## Approval Policy",
|
|
179
|
+
"- 破壊的変更はデフォルトで自動承認しない。",
|
|
180
|
+
"- `src/impliforge/` 配下の allowlisted source edit は構造化 edit proposal と approval hook を通した場合のみ許可する。",
|
|
181
|
+
"- delete 操作、広範囲 overwrite、依存追加、実行環境変更は human approval を必須とする。",
|
|
182
|
+
"- `docs/` と `artifacts/` への生成物保存は通常運用として許可するが、protected roots は常に対象外とする。",
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
if resolved_decisions:
|
|
186
|
+
lines.extend(
|
|
187
|
+
[
|
|
188
|
+
"",
|
|
189
|
+
"## Resolved Decisions",
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
lines.extend(self._render_bullets(resolved_decisions))
|
|
193
|
+
else:
|
|
194
|
+
lines.extend(
|
|
195
|
+
[
|
|
196
|
+
"",
|
|
197
|
+
"## Open Questions",
|
|
198
|
+
]
|
|
199
|
+
)
|
|
200
|
+
lines.extend(self._render_bullets(open_questions))
|
|
201
|
+
lines.extend(
|
|
202
|
+
[
|
|
203
|
+
"",
|
|
204
|
+
"## Copilot Draft Notes",
|
|
205
|
+
copilot_response or "No additional Copilot draft content was provided.",
|
|
206
|
+
]
|
|
207
|
+
)
|
|
208
|
+
return "\n".join(lines).strip() + "\n"
|
|
209
|
+
|
|
210
|
+
def _build_runbook_document(
|
|
211
|
+
self,
|
|
212
|
+
*,
|
|
213
|
+
objective: str,
|
|
214
|
+
deliverables: list[str],
|
|
215
|
+
phases: list[str],
|
|
216
|
+
open_questions: list[str],
|
|
217
|
+
resolved_decisions: list[str],
|
|
218
|
+
) -> str:
|
|
219
|
+
lines: list[str] = [
|
|
220
|
+
"# Runbook",
|
|
221
|
+
"",
|
|
222
|
+
"## Goal",
|
|
223
|
+
objective or "TBD",
|
|
224
|
+
"",
|
|
225
|
+
"## Expected Deliverables",
|
|
226
|
+
]
|
|
227
|
+
lines.extend(self._render_bullets(deliverables))
|
|
228
|
+
lines.extend(
|
|
229
|
+
[
|
|
230
|
+
"",
|
|
231
|
+
"## Execution Flow",
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
lines.extend(self._render_numbered(phases))
|
|
235
|
+
lines.extend(
|
|
236
|
+
[
|
|
237
|
+
"",
|
|
238
|
+
"## Operator Checklist",
|
|
239
|
+
"- Confirm the requirement file path is finalized and points to the intended input document",
|
|
240
|
+
"- Confirm session snapshot and workflow state are persisted",
|
|
241
|
+
"- Confirm generated docs are reviewed before implementation",
|
|
242
|
+
"- Confirm unresolved questions are either answered or explicitly deferred",
|
|
243
|
+
"- Confirm persistent context is stored in `artifacts/workflow-state.json`, `artifacts/sessions/<session_id>/session-snapshot.json`, and `artifacts/summaries/<workflow_id>/run-summary.json`",
|
|
244
|
+
"- Confirm resume can recover `requirement`, `phase`, `workflow_id`, `session_id`, completed tasks, pending tasks, latest summary, and resume prompt",
|
|
245
|
+
"- Confirm destructive changes, dependency additions, and environment changes are not auto-approved",
|
|
246
|
+
"- Confirm blocked work is reflected in operator-facing outputs with explicit next actions and escalation triggers",
|
|
247
|
+
"- Confirm run summary surfaces budget pressure signals before cost ceilings are exceeded",
|
|
248
|
+
"- Confirm artifact lists stay deduplicated and limited to operator-meaningful outputs",
|
|
249
|
+
"",
|
|
250
|
+
"## Persistence Policy",
|
|
251
|
+
"- Persist workflow-level state in `artifacts/workflow-state.json`",
|
|
252
|
+
"- Persist session-level resume state in `artifacts/sessions/<session_id>/session-snapshot.json`",
|
|
253
|
+
"- Persist operator-facing execution summary in `artifacts/summaries/<workflow_id>/run-summary.json`",
|
|
254
|
+
"- Guarantee structured recovery data only; transient model reasoning and raw token streams are out of scope",
|
|
255
|
+
"- If persisted state is incomplete, resume from the latest consistent checkpoint instead of guessing missing context",
|
|
256
|
+
"- Surface token usage ratio in the run summary so operators can react before budget ceilings are exceeded",
|
|
257
|
+
"- Keep artifact references deduplicated so repeated generated paths do not inflate operator-facing artifact volume",
|
|
258
|
+
"",
|
|
259
|
+
"## Approval Policy",
|
|
260
|
+
"- Allow routine generated writes under `docs/` and `artifacts/`",
|
|
261
|
+
"- Allow source edits under `src/impliforge/` only when they pass the structured edit path and approval checks",
|
|
262
|
+
"- Require human approval for delete operations, broad overwrites, dependency additions, and execution-environment changes",
|
|
263
|
+
"- Never allow edits under protected roots",
|
|
264
|
+
"",
|
|
265
|
+
"## Blocked-State Handling",
|
|
266
|
+
"- Mark the workflow as blocked when unresolved questions, repository-policy conflicts, or missing restore data prevent safe progress",
|
|
267
|
+
"- Record the blocking reason in operator-facing outputs before requesting human intervention",
|
|
268
|
+
"- Preserve the latest consistent checkpoint and resume prompt so the next operator can continue without reconstructing context",
|
|
269
|
+
"- List the immediate next action, the human decision needed, and the condition for resuming execution",
|
|
270
|
+
"",
|
|
271
|
+
"## Escalation Conditions",
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if open_questions and not resolved_decisions:
|
|
276
|
+
lines.extend(
|
|
277
|
+
[
|
|
278
|
+
"- Open questions remain unresolved before implementation starts",
|
|
279
|
+
"- Design assumptions conflict with repository constraints",
|
|
280
|
+
"- Session restore data is incomplete or inconsistent",
|
|
281
|
+
"",
|
|
282
|
+
"## Operator Escalation Actions",
|
|
283
|
+
"- Pause implementation until the open questions are answered or explicitly deferred",
|
|
284
|
+
"- Ask the operator to record the decision owner and the expected follow-up action in the run summary",
|
|
285
|
+
"- Resume only after blocked-state outputs include the chosen next action and restart condition",
|
|
286
|
+
]
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
lines.extend(
|
|
290
|
+
[
|
|
291
|
+
"- Generated implementation plan conflicts with repository constraints",
|
|
292
|
+
"- Session restore data is incomplete or inconsistent",
|
|
293
|
+
"- A requested change requires destructive modification or dependency addition without explicit approval",
|
|
294
|
+
"",
|
|
295
|
+
"## Operator Escalation Actions",
|
|
296
|
+
"- Stop before applying the blocked change and request explicit human approval or a narrower alternative",
|
|
297
|
+
"- Ask the operator to capture the blocking constraint, required decision, and approved next action in the run summary",
|
|
298
|
+
"- Resume only after the blocked-state record identifies the approval or repository decision that unblocks execution",
|
|
299
|
+
]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return "\n".join(lines).strip() + "\n"
|
|
303
|
+
|
|
304
|
+
def _render_bullets(self, items: list[str]) -> list[str]:
|
|
305
|
+
if not items:
|
|
306
|
+
return ["- none"]
|
|
307
|
+
return [f"- {item}" for item in items]
|
|
308
|
+
|
|
309
|
+
def _render_numbered(self, items: list[str]) -> list[str]:
|
|
310
|
+
if not items:
|
|
311
|
+
return ["1. none"]
|
|
312
|
+
return [f"{index}. {item}" for index, item in enumerate(items, start=1)]
|
|
313
|
+
|
|
314
|
+
def _render_task_breakdown(self, tasks: list[dict[str, Any]]) -> list[str]:
|
|
315
|
+
if not tasks:
|
|
316
|
+
return ["- none"]
|
|
317
|
+
|
|
318
|
+
lines: list[str] = []
|
|
319
|
+
for task in tasks:
|
|
320
|
+
task_id = str(task.get("task_id", "unknown")).strip()
|
|
321
|
+
objective = str(task.get("objective", "")).strip()
|
|
322
|
+
depends_on = self._normalize_list(task.get("depends_on"))
|
|
323
|
+
dependency_text = ", ".join(depends_on) if depends_on else "none"
|
|
324
|
+
lines.append(f"- `{task_id}`: {objective or 'TBD'}")
|
|
325
|
+
lines.append(f" - depends_on: {dependency_text}")
|
|
326
|
+
return lines
|
|
327
|
+
|
|
328
|
+
def _normalize_task_breakdown(self, value: Any) -> list[dict[str, Any]]:
|
|
329
|
+
if not isinstance(value, list):
|
|
330
|
+
return []
|
|
331
|
+
normalized: list[dict[str, Any]] = []
|
|
332
|
+
for item in value:
|
|
333
|
+
if isinstance(item, dict):
|
|
334
|
+
normalized.append(item)
|
|
335
|
+
return normalized
|
|
336
|
+
|
|
337
|
+
def _normalize_list(self, value: Any) -> list[str]:
|
|
338
|
+
if not isinstance(value, list):
|
|
339
|
+
return []
|
|
340
|
+
return [str(item).strip() for item in value if str(item).strip()]
|
|
341
|
+
|
|
342
|
+
def _as_dict(self, value: Any) -> dict[str, Any]:
|
|
343
|
+
if isinstance(value, dict):
|
|
344
|
+
return value
|
|
345
|
+
return {}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# SAFE-EDIT-NOTE
|
|
349
|
+
# Proposed implementation slice: Add a documentation agent that produces design and runbook artifacts.
|
|
350
|
+
|
|
351
|
+
# SAFE-EDIT-FIX-NOTE
|
|
352
|
+
# Proposed fix slice: 未解決の open questions が残っているため、実装前に確認が必要。
|
|
353
|
+
|
|
354
|
+
# SAFE-EDIT-FIX-NOTE
|
|
355
|
+
# Proposed fix slice: テスト結果が `needs_review` のため、追加確認または修正が必要。
|