devflow-cli 8.0.0__py3-none-any.whl → 8.2.0__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.
- devflow_cli/__init__.py +1 -1
- devflow_cli/adapters/agents.py +72 -4
- devflow_cli/commands/adaptive.py +372 -17
- devflow_cli/commands/verify.py +4 -1
- devflow_cli/core/activity_base.py +1 -0
- devflow_cli/core/activity_contracts.py +9 -0
- devflow_cli/core/adaptive_runner.py +202 -51
- devflow_cli/core/agent_run_marker.py +99 -0
- devflow_cli/core/automatic_evidence.py +85 -7
- devflow_cli/core/operational.py +3 -1
- devflow_cli/core/result_template.py +74 -0
- devflow_cli/core/runtime_backup.py +119 -0
- devflow_cli/core/validation_approvals.py +20 -0
- devflow_cli/core/verification.py +98 -13
- devflow_cli/core/worklog.py +24 -0
- {devflow_cli-8.0.0.dist-info → devflow_cli-8.2.0.dist-info}/METADATA +23 -2
- {devflow_cli-8.0.0.dist-info → devflow_cli-8.2.0.dist-info}/RECORD +20 -17
- {devflow_cli-8.0.0.dist-info → devflow_cli-8.2.0.dist-info}/WHEEL +0 -0
- {devflow_cli-8.0.0.dist-info → devflow_cli-8.2.0.dist-info}/entry_points.txt +0 -0
- {devflow_cli-8.0.0.dist-info → devflow_cli-8.2.0.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
devflow_cli/adapters/agents.py
CHANGED
|
@@ -22,6 +22,24 @@ from devflow_cli.adapters.linear import environment_without_linear_key
|
|
|
22
22
|
PermissionMode = Literal["auto", "acceptEdits", "manual"]
|
|
23
23
|
SandboxMode = Literal["read-only", "workspace-write"]
|
|
24
24
|
|
|
25
|
+
# L'agent a termine sans erreur (code 0) mais sa sortie structuree manque ou n'est
|
|
26
|
+
# pas le JSON attendu. Claude porte le diagnostic dedie ; Codex et Cursor gardent
|
|
27
|
+
# leurs valeurs detaillees, traitees comme la meme famille par la reprise guidee.
|
|
28
|
+
STRUCTURED_OUTPUT_MISSING = "structured-output-missing"
|
|
29
|
+
MISSING_STRUCTURED_OUTPUT_DIAGNOSTICS = frozenset({
|
|
30
|
+
STRUCTURED_OUTPUT_MISSING,
|
|
31
|
+
"final-output-empty",
|
|
32
|
+
"final-output-absent",
|
|
33
|
+
"final-output-invalid-json",
|
|
34
|
+
})
|
|
35
|
+
STRUCTURED_OUTPUT_MISSING_MESSAGE = (
|
|
36
|
+
"l'agent a termine sans erreur mais sa sortie structuree manque "
|
|
37
|
+
"ou n'est pas le JSON attendu"
|
|
38
|
+
)
|
|
39
|
+
# Impose le premier plan aux sous-agents de l'agent Claude d'une activite ; desactive
|
|
40
|
+
# aussi ses commandes shell en arriere-plan.
|
|
41
|
+
CLAUDE_FOREGROUND_ENV = "CLAUDE_CODE_DISABLE_BACKGROUND_TASKS"
|
|
42
|
+
|
|
25
43
|
_SAFE_DIAGNOSTIC_MESSAGES = {
|
|
26
44
|
"final-output-empty": "--output-last-message est vide",
|
|
27
45
|
"final-output-absent": "--output-last-message est absent",
|
|
@@ -165,7 +183,7 @@ class AgentAdapter(ABC):
|
|
|
165
183
|
capture_output=True,
|
|
166
184
|
stdin=subprocess.DEVNULL,
|
|
167
185
|
shell=False,
|
|
168
|
-
env=
|
|
186
|
+
env=self.structured_environment(),
|
|
169
187
|
)
|
|
170
188
|
execution = AgentExecution(
|
|
171
189
|
result.returncode,
|
|
@@ -223,7 +241,9 @@ class AgentAdapter(ABC):
|
|
|
223
241
|
"Sortie structuree invalide "
|
|
224
242
|
f"({self.name}) [{diagnostic}]"
|
|
225
243
|
+ (
|
|
226
|
-
f" : {
|
|
244
|
+
f" : {self.missing_output_message(execution.stdout)}"
|
|
245
|
+
if diagnostic == STRUCTURED_OUTPUT_MISSING
|
|
246
|
+
else f" : {_SAFE_DIAGNOSTIC_MESSAGES[diagnostic]}"
|
|
227
247
|
if diagnostic in _SAFE_DIAGNOSTIC_MESSAGES
|
|
228
248
|
else ""
|
|
229
249
|
),
|
|
@@ -251,6 +271,27 @@ class AgentAdapter(ABC):
|
|
|
251
271
|
except FileNotFoundError:
|
|
252
272
|
return AgentExecution(127, stderr=f"Executable '{self.executable}' introuvable", duration_seconds=time.monotonic() - started)
|
|
253
273
|
|
|
274
|
+
def structured_environment(self) -> dict[str, str]:
|
|
275
|
+
"""Environnement de l'agent d'une activite : jamais la cle Linear."""
|
|
276
|
+
return environment_without_linear_key()
|
|
277
|
+
|
|
278
|
+
def subagent_counts(self, stdout: str) -> tuple[int | None, int | None]:
|
|
279
|
+
"""Sous-agents lances et lances en arriere-plan ; None si l'agent ne le dit pas."""
|
|
280
|
+
return None, None
|
|
281
|
+
|
|
282
|
+
def missing_output_message(self, stdout: str) -> str:
|
|
283
|
+
"""Cause d'une sortie structuree absente : message fixe et entiers seulement."""
|
|
284
|
+
try:
|
|
285
|
+
spawned, background = self.subagent_counts(stdout)
|
|
286
|
+
except Exception: # noqa: BLE001 - format externe non contractuel
|
|
287
|
+
spawned, background = None, None
|
|
288
|
+
message = STRUCTURED_OUTPUT_MISSING_MESSAGE
|
|
289
|
+
if spawned is not None:
|
|
290
|
+
message += f" ; sous-agents lances : {spawned}"
|
|
291
|
+
if background is not None:
|
|
292
|
+
message += f", dont {background} en arriere-plan"
|
|
293
|
+
return message
|
|
294
|
+
|
|
254
295
|
def session_id(self, stdout: str, stderr: str) -> str | None:
|
|
255
296
|
"""Extrait un identifiant opaque de session, sans conserver la sortie."""
|
|
256
297
|
return _session_id(stdout, stderr)
|
|
@@ -326,8 +367,23 @@ class ClaudeCodeAdapter(AgentAdapter):
|
|
|
326
367
|
return envelope["structured_output"]
|
|
327
368
|
result = envelope.get("result")
|
|
328
369
|
if isinstance(result, str) and result.strip():
|
|
329
|
-
|
|
330
|
-
|
|
370
|
+
try:
|
|
371
|
+
return json.loads(_strip_json_fence(result))
|
|
372
|
+
except json.JSONDecodeError:
|
|
373
|
+
pass
|
|
374
|
+
raise StructuredOutputError(
|
|
375
|
+
STRUCTURED_OUTPUT_MISSING, "champ structured_output absent"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def structured_environment(self) -> dict[str, str]:
|
|
379
|
+
return {**environment_without_linear_key(), CLAUDE_FOREGROUND_ENV: "1"}
|
|
380
|
+
|
|
381
|
+
def subagent_counts(self, stdout: str) -> tuple[int | None, int | None]:
|
|
382
|
+
envelope = json.loads(stdout)
|
|
383
|
+
stats = envelope.get("subagent_stats") if isinstance(envelope, dict) else None
|
|
384
|
+
if not isinstance(stats, dict):
|
|
385
|
+
return None, None
|
|
386
|
+
return _count(stats.get("spawned")), _count(stats.get("started_in_background"))
|
|
331
387
|
|
|
332
388
|
def usage(self, stdout: str, session_id: str | None) -> AgentUsage | None:
|
|
333
389
|
"""Lit l'enveloppe --output-format json : usage cumule de tous les tours."""
|
|
@@ -515,6 +571,14 @@ def get_agent_adapter(name: str) -> AgentAdapter:
|
|
|
515
571
|
return adapter_type()
|
|
516
572
|
|
|
517
573
|
|
|
574
|
+
def describe_missing_output(agent: str, stdout: str) -> str:
|
|
575
|
+
"""Cause d'une sortie structuree absente, pour la reprise guidee."""
|
|
576
|
+
try:
|
|
577
|
+
return get_agent_adapter(agent).missing_output_message(stdout)
|
|
578
|
+
except ValueError:
|
|
579
|
+
return STRUCTURED_OUTPUT_MISSING_MESSAGE
|
|
580
|
+
|
|
581
|
+
|
|
518
582
|
def _portable_prompt(prompt: str) -> str:
|
|
519
583
|
"""Traduit une slash-command Claude en instruction portable pour les autres agents."""
|
|
520
584
|
match = re.fullmatch(r"/devflow\.([a-z0-9-]+)(?:\s+(.*))?", prompt.strip())
|
|
@@ -610,6 +674,10 @@ def _int(value: object) -> int:
|
|
|
610
674
|
return value if isinstance(value, int) and not isinstance(value, bool) else 0
|
|
611
675
|
|
|
612
676
|
|
|
677
|
+
def _count(value: object) -> int | None:
|
|
678
|
+
return value if isinstance(value, int) and not isinstance(value, bool) else None
|
|
679
|
+
|
|
680
|
+
|
|
613
681
|
def _codex_home() -> Path:
|
|
614
682
|
return Path(os.environ.get("CODEX_HOME") or Path.home() / ".codex")
|
|
615
683
|
|