devflow-cli 7.2.0__py3-none-any.whl → 8.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """devflow CLI — workflow de développement adaptatif."""
2
2
 
3
- VERSION = "7.2.0"
3
+ VERSION = "8.1.0"
4
4
 
5
5
  SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
6
6
 
@@ -25,6 +25,10 @@ from devflow_cli.core.adaptive_workflow import (
25
25
  write_adaptive_state,
26
26
  )
27
27
  from devflow_cli.core.assessment import read_assessment
28
+ from devflow_cli.core.activity_base import (
29
+ enter_current_activity,
30
+ files_since_activity_base,
31
+ )
28
32
  from devflow_cli.core.activity_contracts import (
29
33
  build_activity_contract,
30
34
  NEED_STATEMENT,
@@ -34,14 +38,17 @@ from devflow_cli.core.activity_contracts import (
34
38
  write_activity_contract,
35
39
  )
36
40
  from devflow_cli.core.adaptive_runner import (
41
+ MANUAL_RECONCILIATION_ACTIVITIES,
37
42
  AgentActivityExecution,
38
43
  reconcile_current_activity,
39
44
  record_human_review,
40
45
  run_current_activity,
41
46
  )
47
+ from devflow_cli.core.agent_run_marker import AgentRunningError, ensure_no_agent_running
42
48
  from devflow_cli.core import run_progress
43
49
  from devflow_cli.core.worktree_snapshot import changed_paths, snapshot_worktree
44
50
  from devflow_cli.core.automatic_evidence import (
51
+ AutomaticVerificationOutcome,
45
52
  ensure_fresh_devflow_evidence,
46
53
  run_automatic_verification,
47
54
  stale_devflow_evidence,
@@ -51,7 +58,6 @@ from devflow_cli.core.worklog import refresh_worklog, safe_refresh_worklog
51
58
  from devflow_cli.core.telemetry import append_record, telemetry_enabled, usage_payload
52
59
  from devflow_cli.core.waivers import create_validation_waiver
53
60
  from devflow_cli.core.remediation import (
54
- carried_changed_files,
55
61
  discard_attempt,
56
62
  is_reopened_activity,
57
63
  reopen_activity,
@@ -59,6 +65,7 @@ from devflow_cli.core.remediation import (
59
65
  from devflow_cli.core.redaction import redact_command
60
66
  from devflow_cli.core.validation_approvals import (
61
67
  approve_plan_command,
68
+ pending_approvals,
62
69
  pending_plan_approval,
63
70
  )
64
71
  from devflow_cli.core.contract_schemas import LegacySchemaError, diagnose_contract_file
@@ -77,6 +84,9 @@ app = typer.Typer(help="Machine d'etat des parcours adaptatifs")
77
84
 
78
85
  LEGACY_SCHEMA_EXIT_CODE = 1
79
86
 
87
+ # Refus avant tout agent : base inutilisable (branche changee, historique reecrit).
88
+ BASE_REFUSALS = frozenset({"base-history-rewritten", "base-branch-changed"})
89
+
80
90
  RESULT_VALIDATED_ACTIVITIES = frozenset({
81
91
  "prepare",
82
92
  "risk-assessment",
@@ -167,7 +177,10 @@ def start(feature: str = typer.Argument(..., help="ID ou dossier de la feature")
167
177
  assessment = _assessment(feature_dir)
168
178
  validate_need_statement(feature_dir / NEED_STATEMENT)
169
179
  state = create_adaptive_workflow(assessment.to_dict())
170
- except ValueError as exc:
180
+ enter_current_activity(
181
+ state, project_root_for_feature(feature_dir), feature_dir
182
+ )
183
+ except (OSError, ValueError) as exc:
171
184
  fail(f"Impossible de demarrer le workflow : {exc}")
172
185
  raise typer.Exit(int(ExitCode.INVALID_CONTRACT)) from exc
173
186
  write_adaptive_state(state_file, state)
@@ -206,6 +219,7 @@ def complete(
206
219
  """Complete l'activite courante si ses gates sont satisfaites."""
207
220
  feature_dir = _feature(feature)
208
221
  with workflow_lock(feature_dir):
222
+ refuse_if_agent_running(feature_dir)
209
223
  state = _state(feature_dir)
210
224
  activity = activity or state.currentActivity
211
225
  try:
@@ -221,7 +235,10 @@ def complete(
221
235
  validate_preflight_evidence(feature_dir / "preflight-evidence.md")
222
236
  ensure_fresh_devflow_evidence(feature_dir, state, activity)
223
237
  complete_activity(state, activity)
224
- except ValueError as exc:
238
+ enter_current_activity(
239
+ state, project_root_for_feature(feature_dir), feature_dir
240
+ )
241
+ except (OSError, ValueError) as exc:
225
242
  fail(str(exc))
226
243
  console.print(f" Suite : devflow adaptive status {state.issueId}")
227
244
  raise typer.Exit(int(ExitCode.BLOCKED)) from exc
@@ -245,6 +262,7 @@ def evidence(
245
262
  """Enregistre une preuve manuelle informative, sans ouvrir les gates."""
246
263
  feature_dir = _feature(feature)
247
264
  with workflow_lock(feature_dir):
265
+ refuse_if_agent_running(feature_dir)
248
266
  state = _state(feature_dir)
249
267
  try:
250
268
  record_evidence(state, evidence_id, report or "")
@@ -276,6 +294,7 @@ def contract(
276
294
  raise typer.Exit(int(ExitCode.INVALID_CONTRACT)) from exc
277
295
 
278
296
  with workflow_lock(feature_dir):
297
+ refuse_if_agent_running(feature_dir)
279
298
  state = _state(feature_dir)
280
299
  try:
281
300
  assessment = _assessment(feature_dir)
@@ -284,11 +303,15 @@ def contract(
284
303
  state,
285
304
  assessment.to_dict(),
286
305
  feature_path,
287
- carried_changed_files(feature_dir, state.currentActivity),
288
306
  reopened=is_reopened_activity(feature_dir, state.currentActivity),
289
307
  preflight_evidence=(feature_dir / "preflight-evidence.md").is_file(),
308
+ files_since_base=(
309
+ files_since_activity_base(feature_dir, state)
310
+ if state.activityBase is not None
311
+ else ()
312
+ ),
290
313
  )
291
- except ValueError as exc:
314
+ except (OSError, ValueError) as exc:
292
315
  fail(f"Impossible de generer le contrat : {exc}")
293
316
  raise typer.Exit(int(ExitCode.INVALID_CONTRACT)) from exc
294
317
  output_file = feature_dir / "activity-contract.json"
@@ -447,7 +470,7 @@ def run_adaptive(
447
470
  agent,
448
471
  )
449
472
 
450
- outcome = run_current_activity(feature_dir, feature_path, execute)
473
+ outcome = run_current_activity(feature_dir, feature_path, execute, agent=agent)
451
474
  _record_telemetry(
452
475
  run_id, attempt, agent, project_root, outcome.reason,
453
476
  json_output=json_output,
@@ -457,22 +480,31 @@ def run_adaptive(
457
480
  _print_execution_json(
458
481
  current_state.issueId, outcome.activity, True, outcome.reason,
459
482
  outcome.detail, outcome.nextActivity, ExitCode.OK,
483
+ warnings=outcome.warnings,
460
484
  )
461
485
  else:
462
486
  ok(
463
487
  f"Activite '{outcome.activity}' validee. "
464
488
  f"Prochaine : {outcome.nextActivity}"
465
489
  )
490
+ _print_warnings(outcome.warnings)
491
+ _print_pending_approvals(feature_dir, current_state.issueId, outcome.activity)
466
492
  return
467
- safe_refresh_worklog(feature_dir)
493
+ if outcome.reason != "agent-running": # worklog.md : fichier d'execution (B4)
494
+ safe_refresh_worklog(feature_dir)
468
495
  exit_code = exit_code_for_run_reason(outcome.reason)
469
496
  if json_output:
470
497
  _print_execution_json(
471
498
  current_state.issueId, outcome.activity, False, outcome.reason,
472
499
  outcome.detail, outcome.nextActivity, exit_code,
500
+ warnings=outcome.warnings,
473
501
  )
502
+ elif outcome.reason in BASE_REFUSALS:
503
+ fail(outcome.detail)
504
+ console.print(f" Suite : devflow adaptive status {current_state.issueId}")
474
505
  else:
475
506
  fail(outcome.detail or outcome.reason)
507
+ _print_warnings(outcome.warnings)
476
508
  console.print(
477
509
  f" Activite '{outcome.activity}' non validee : {outcome.reason}"
478
510
  )
@@ -546,7 +578,10 @@ def reconcile_adaptive(
546
578
  result: Optional[Path] = typer.Option(
547
579
  None,
548
580
  "--result",
549
- help="Payload JSON corrige a revalider quand la trace est inexploitable",
581
+ help=(
582
+ "Payload JSON corrige, rejoue par les validateurs de run, quand la trace "
583
+ "est inexploitable ou que sa reponse est refusee par les validateurs"
584
+ ),
550
585
  ),
551
586
  reason: Optional[str] = typer.Option(
552
587
  None,
@@ -591,12 +626,22 @@ def reconcile_adaptive(
591
626
  f"Activite '{outcome.activity}' reconciliee. "
592
627
  f"Prochaine : {outcome.nextActivity}"
593
628
  )
629
+ _print_warnings(outcome.warnings)
630
+ _print_pending_approvals(feature_dir, state.issueId, outcome.activity)
594
631
  return
595
- safe_refresh_worklog(feature_dir)
596
- fail(
597
- f"Reconciliation de '{outcome.activity}' refusee : {outcome.reason}"
598
- + (f" ({outcome.detail})" if outcome.detail else "")
599
- )
632
+ if outcome.reason != "agent-running": # worklog.md : fichier d'execution (B4)
633
+ safe_refresh_worklog(feature_dir)
634
+ if outcome.reason in BASE_REFUSALS:
635
+ fail(outcome.detail)
636
+ else:
637
+ fail(
638
+ f"Reconciliation de '{outcome.activity}' refusee : {outcome.reason}"
639
+ + (f" ({outcome.detail})" if outcome.detail else "")
640
+ )
641
+ _print_warnings(outcome.warnings)
642
+ if outcome.reason == "agent-running":
643
+ console.print(" Suite : attendez la fin de l'activite en cours, puis relancez.")
644
+ raise typer.Exit(int(exit_code_for_run_reason(outcome.reason)))
600
645
  console.print(f" Suite : devflow adaptive status {state.issueId}")
601
646
  if outcome.reason == "reconcile-refused":
602
647
  console.print(
@@ -622,6 +667,7 @@ def review_adaptive(
622
667
  project_root = project_root_for_feature(feature_dir)
623
668
  feature_path = feature_dir.relative_to(project_root).as_posix()
624
669
  with workflow_lock(feature_dir):
670
+ refuse_if_agent_running(feature_dir)
625
671
  state = _state(feature_dir)
626
672
  try:
627
673
  report_file = report.resolve()
@@ -665,6 +711,10 @@ def verify_adaptive(
665
711
  """Genere localement les preuves requises par le profil actif."""
666
712
  feature_dir = _feature(feature, json_output=json_output)
667
713
  state = _state(feature_dir, json_output=json_output, issue_id=feature)
714
+ with workflow_lock(feature_dir):
715
+ refuse_if_agent_running(
716
+ feature_dir, json_issue_id=state.issueId if json_output else None
717
+ )
668
718
  if not refresh and state.currentActivity != "verify":
669
719
  detail = (
670
720
  "Activite courante attendue pour verify : verify "
@@ -698,6 +748,7 @@ def approve_adaptive_command(
698
748
  feature_dir = _feature(feature)
699
749
  try:
700
750
  with workflow_lock(feature_dir):
751
+ refuse_if_agent_running(feature_dir)
701
752
  item = pending_plan_approval(feature_dir, validation_id)
702
753
  console.print(f"Validation : {item.id}")
703
754
  console.print(f"Commande : {shlex.join(redact_command(item.command))}")
@@ -739,6 +790,7 @@ def waive_validation(
739
790
  """Rend non bloquant un controle planifie echoue, avec audit durable."""
740
791
  feature_dir = _feature(feature)
741
792
  with workflow_lock(feature_dir):
793
+ refuse_if_agent_running(feature_dir)
742
794
  try:
743
795
  waiver = create_validation_waiver(
744
796
  feature_dir, validation_id, reason, author=author
@@ -766,11 +818,15 @@ def reopen(
766
818
  """Revient a une activite anterieure sans effacer l'historique."""
767
819
  feature_dir = _feature(feature)
768
820
  with workflow_lock(feature_dir):
821
+ refuse_if_agent_running(feature_dir)
769
822
  state = _state(feature_dir)
770
823
  try:
771
824
  entry = reopen_activity(
772
825
  feature_dir, state, activity, reason, author=author
773
826
  )
827
+ enter_current_activity(
828
+ state, project_root_for_feature(feature_dir), feature_dir
829
+ )
774
830
  except (OSError, ValueError) as exc:
775
831
  fail(str(exc))
776
832
  console.print(f" Suite : devflow adaptive status {state.issueId}")
@@ -792,6 +848,7 @@ def discard_attempt_command(
792
848
  """Abandonne la tentative de l'activite courante, avec archive et audit."""
793
849
  feature_dir = _feature(feature)
794
850
  with workflow_lock(feature_dir):
851
+ refuse_if_agent_running(feature_dir)
795
852
  state = _state(feature_dir)
796
853
  try:
797
854
  entry = discard_attempt(feature_dir, state, reason, author=author)
@@ -806,9 +863,12 @@ def discard_attempt_command(
806
863
  )
807
864
  if entry["discardedFiles"]:
808
865
  warn(
809
- "Changements laisses dans le working tree, non reportes : "
810
- + ", ".join(entry["discardedFiles"])
866
+ "Fichiers modifies depuis le debut de l'activite "
867
+ f"'{entry['activity']}', comptes dans le lot a la relance ; remise en "
868
+ "etat a la charge du superviseur : " + ", ".join(entry["discardedFiles"])
811
869
  )
870
+ if entry.get("observationError"):
871
+ warn(f"Fichiers depuis la base non calcules : {entry['observationError']}")
812
872
 
813
873
 
814
874
  def _execute_local_verification(
@@ -822,11 +882,15 @@ def _execute_local_verification(
822
882
  if not refresh:
823
883
  sync_activity_status(issue_id, "verify", stream)
824
884
  outcome = run_automatic_verification(feature_dir, project_root, refresh=refresh)
885
+ recovery = (
886
+ None if outcome.passed else _verify_recovery(feature_dir, issue_id, outcome)
887
+ )
825
888
  if json_output:
826
889
  _print_execution_json(
827
890
  issue_id, "verify", outcome.passed, outcome.reason, outcome.detail,
828
891
  outcome.nextActivity,
829
892
  ExitCode.OK if outcome.passed else ExitCode.VERIFICATION_FAILURE,
893
+ recovery=recovery,
830
894
  )
831
895
  if not outcome.passed:
832
896
  raise typer.Exit(int(ExitCode.VERIFICATION_FAILURE))
@@ -840,19 +904,182 @@ def _execute_local_verification(
840
904
  fail(outcome.detail or outcome.reason)
841
905
  console.print(f" Verification automatique refusee : {outcome.reason}")
842
906
  console.print(" Reprise guidee :")
843
- console.print(f" devflow adaptive status {issue_id}")
844
- console.print(
845
- f" devflow adaptive reopen {issue_id} implement --reason "
846
- f'"correction apres {outcome.reason}"'
847
- )
907
+ console.print(f" {recovery['summary']}")
908
+ for step in recovery["commands"]:
909
+ console.print(f" {step['command']} # {step['purpose']}", markup=False)
848
910
  raise typer.Exit(int(ExitCode.VERIFICATION_FAILURE))
849
911
 
850
912
 
913
+ # Motifs de refus de verify dus a l'echec d'un controle execute (B2).
914
+ CHECK_FAILURE_REASONS = frozenset({
915
+ "targeted-check-failed",
916
+ "targeted-tests-failed",
917
+ "test-suite-failed",
918
+ "regression-check-failed",
919
+ "security-or-data-check-failed",
920
+ })
921
+ PLANNED_PREFIXES = {"PREP-": "prepare", "PLAN-": "plan"}
922
+
923
+
924
+ def _verify_recovery(
925
+ feature_dir: Path, issue_id: str, outcome: AutomaticVerificationOutcome
926
+ ) -> dict:
927
+ """Reprise guidee d'un refus de verify, par motif, sans heuristique (B2).
928
+
929
+ Approbation manquante : chaque approve-command puis verify. Executable introuvable
930
+ (code 127) : corriger l'environnement puis verify. Echec d'un controle : deux voies,
931
+ le code (reopen implement) ou la validation (reopen de l'activite qui l'a inscrite).
932
+ """
933
+ issue = shlex.quote(issue_id)
934
+ verify_command = {
935
+ "command": f"devflow adaptive verify {issue}",
936
+ "purpose": "relancer la verification",
937
+ }
938
+ state = read_adaptive_state(feature_dir / "workflow-state.json")
939
+ plan_activity = (
940
+ "prepare" if state is not None and state.profile == "standard" else "plan"
941
+ )
942
+ if outcome.pendingApprovals:
943
+ return {
944
+ "kind": "approval-pending",
945
+ "summary": "Approuver chaque validation en attente, puis relancer verify.",
946
+ "validations": [
947
+ {"id": item, "origin": plan_activity, "returncode": None}
948
+ for item in outcome.pendingApprovals
949
+ ],
950
+ "commands": [
951
+ *(
952
+ {
953
+ "command": (
954
+ f"devflow adaptive approve-command {issue} "
955
+ f"{shlex.quote(item)} --reason MOTIF"
956
+ ),
957
+ "purpose": f"approuver la commande exacte de {item}",
958
+ }
959
+ for item in outcome.pendingApprovals
960
+ ),
961
+ verify_command,
962
+ ],
963
+ }
964
+ status_command = {
965
+ "command": f"devflow adaptive status {issue}",
966
+ "purpose": "consulter l'etat du parcours",
967
+ }
968
+ if outcome.reason not in CHECK_FAILURE_REASONS:
969
+ return {
970
+ "kind": "status",
971
+ "summary": f"Refus {outcome.reason} : consulter l'etat avant toute reprise.",
972
+ "validations": [],
973
+ "commands": [status_command],
974
+ }
975
+ validations = []
976
+ for failure in outcome.failures:
977
+ validation_id, origin = failure.id, None
978
+ for prefix, activity in PLANNED_PREFIXES.items():
979
+ if failure.id.startswith(prefix):
980
+ validation_id, origin = failure.id.removeprefix(prefix), activity
981
+ if origin is not None and not _reopenable(state, origin):
982
+ origin = None
983
+ validations.append({
984
+ "id": validation_id,
985
+ "origin": origin,
986
+ "returncode": failure.returncode,
987
+ })
988
+ if validations and all(item["returncode"] == 127 for item in validations):
989
+ names = ", ".join(item["id"] for item in validations)
990
+ return {
991
+ "kind": "environment",
992
+ "summary": (
993
+ f"Executable introuvable (code 127) pour {names} : corriger "
994
+ "l'environnement, puis relancer verify."
995
+ ),
996
+ "validations": validations,
997
+ "commands": [verify_command],
998
+ }
999
+ reason_text = shlex.quote(f"correction apres {outcome.reason}")
1000
+ commands = [
1001
+ status_command,
1002
+ {
1003
+ "command": f"devflow adaptive reopen {issue} implement --reason {reason_text}",
1004
+ "purpose": "voie 1 : le code est en cause",
1005
+ },
1006
+ ]
1007
+ for origin in sorted({item["origin"] for item in validations if item["origin"]}):
1008
+ names = ", ".join(item["id"] for item in validations if item["origin"] == origin)
1009
+ commands.append({
1010
+ "command": f"devflow adaptive reopen {issue} {origin} --reason {reason_text}",
1011
+ "purpose": f"voie 2 : la validation {names}, inscrite par {origin}, est en cause",
1012
+ })
1013
+ return {
1014
+ "kind": "check-failed",
1015
+ "summary": (
1016
+ "Controle echoue"
1017
+ + (f" : {', '.join(item['id'] for item in validations)}" if validations else "")
1018
+ + ". Choisir la voie selon la cause ; aucune n'est choisie automatiquement."
1019
+ ),
1020
+ "validations": validations,
1021
+ "commands": commands,
1022
+ }
1023
+
1024
+
1025
+ def _reopenable(state: AdaptiveWorkflowState | None, activity: str) -> bool:
1026
+ return (
1027
+ state is not None
1028
+ and activity in state.activities
1029
+ and activity in state.completedActivities
1030
+ and state.activities.index(activity) < state.activities.index(state.currentActivity)
1031
+ )
1032
+
1033
+
1034
+ def refuse_if_agent_running(feature_dir: Path, *, json_issue_id: str | None = None) -> None:
1035
+ """Refuse sans effet une commande humaine pendant l'execution d'un agent (B4).
1036
+
1037
+ A appeler avant toute ecriture dans le dossier de la feature. Avec ``json_issue_id``,
1038
+ le refus est rendu sous la forme JSON des commandes d'execution.
1039
+ """
1040
+ try:
1041
+ warning = ensure_no_agent_running(feature_dir)
1042
+ except AgentRunningError as exc:
1043
+ if json_issue_id is not None:
1044
+ _print_execution_json(
1045
+ json_issue_id, None, False, "agent-running", str(exc), None,
1046
+ ExitCode.CONCURRENT_CHANGE,
1047
+ )
1048
+ else:
1049
+ fail(str(exc))
1050
+ raise typer.Exit(int(ExitCode.CONCURRENT_CHANGE)) from exc
1051
+ if warning:
1052
+ warn(warning)
1053
+
1054
+
1055
+ def _print_pending_approvals(feature_dir: Path, issue_id: str, activity: str) -> None:
1056
+ """Liste en une fois les approbations attendues a la fin de prepare ou plan."""
1057
+ if activity not in {"prepare", "plan"}:
1058
+ return
1059
+ try:
1060
+ pending = pending_approvals(feature_dir)
1061
+ except ValueError:
1062
+ return
1063
+ if not pending:
1064
+ return
1065
+ warn(
1066
+ "Approbations attendues avant verify : "
1067
+ + ", ".join(item.id for item in pending)
1068
+ )
1069
+ for item in pending:
1070
+ console.print(
1071
+ f" devflow adaptive approve-command {shlex.quote(issue_id)} "
1072
+ f"{shlex.quote(item.id)} --reason MOTIF",
1073
+ markup=False,
1074
+ )
1075
+
1076
+
851
1077
  def _print_execution_json(
852
1078
  issue_id: str, activity: str | None, passed: bool, reason: str,
853
1079
  detail: str, next_activity: str | None, exit_code: ExitCode,
1080
+ *, warnings: tuple[str, ...] = (), recovery: dict | None = None,
854
1081
  ) -> None:
855
- typer.echo(json.dumps({
1082
+ payload = {
856
1083
  "schemaVersion": 1,
857
1084
  "issueId": issue_id,
858
1085
  "activity": activity,
@@ -861,7 +1088,17 @@ def _print_execution_json(
861
1088
  "detail": detail,
862
1089
  "nextActivity": next_activity,
863
1090
  "exitCode": int(exit_code),
864
- }, ensure_ascii=False))
1091
+ "warnings": list(warnings),
1092
+ }
1093
+ if recovery is not None:
1094
+ payload["recovery"] = recovery
1095
+ typer.echo(json.dumps(payload, ensure_ascii=False))
1096
+
1097
+
1098
+ def _print_warnings(warnings: tuple[str, ...]) -> None:
1099
+ """Ecart de declaration : avertissement seul, sans effet sur le code de sortie."""
1100
+ for message in warnings:
1101
+ warn(message)
865
1102
 
866
1103
 
867
1104
  def _utc_now() -> str:
@@ -936,6 +1173,8 @@ def worklog(
936
1173
  ) -> None:
937
1174
  """Regenere le journal compact depuis les artefacts du workflow."""
938
1175
  feature_dir = _feature(feature)
1176
+ with workflow_lock(feature_dir):
1177
+ refuse_if_agent_running(feature_dir)
939
1178
  try:
940
1179
  output = refresh_worklog(feature_dir)
941
1180
  except LegacySchemaError as exc:
@@ -1155,13 +1394,6 @@ def _print_run_recovery(
1155
1394
  console.print(" Ne relancez pas automatiquement l'agent : reconciliez d'abord les changements partiels.")
1156
1395
  console.print(f" devflow adaptive reconcile {issue_id}")
1157
1396
  return
1158
- if reason == "stale-attempt":
1159
- console.print(" git status --short # inspecter les changements de la tentative")
1160
- console.print(
1161
- " Restaurez les fichiers de la tentative, ou abandonnez-la explicitement :"
1162
- )
1163
- console.print(f" devflow adaptive discard-attempt {issue_id} --reason MOTIF")
1164
- return
1165
1397
  if reason == "inputs-changed-during-activity":
1166
1398
  console.print(
1167
1399
  " Les entrees du besoin ont change pendant l'execution. "
@@ -1169,6 +1401,14 @@ def _print_run_recovery(
1169
1401
  )
1170
1402
  console.print(f" devflow adaptive run {issue_id} --agent {agent}")
1171
1403
  return
1404
+ if reason == "agent-running":
1405
+ console.print(" Un agent execute deja cette activite : attendez sa fin.")
1406
+ return
1407
+ if reason == "invalid-result" and activity in MANUAL_RECONCILIATION_ACTIVITIES:
1408
+ console.print(
1409
+ f" devflow adaptive reconcile {issue_id} --result FICHIER.json --reason MOTIF"
1410
+ " # corriger la reponse refusee sans relancer l'agent"
1411
+ )
1172
1412
  if reason == "agent-failed":
1173
1413
  console.print(f" devflow adaptive reconcile {issue_id} # si la trace contient une reponse finale")
1174
1414
  console.print(f" devflow check --ai {agent}")
@@ -13,7 +13,8 @@ from devflow_cli.core.verification import (
13
13
  parse_validation_commands,
14
14
  run_verifications,
15
15
  )
16
- from devflow_cli.core.adaptive_workflow import read_adaptive_state
16
+ from devflow_cli.commands.adaptive import refuse_if_agent_running
17
+ from devflow_cli.core.adaptive_workflow import read_adaptive_state, workflow_lock
17
18
  from devflow_cli.core.automatic_evidence import run_automatic_verification
18
19
  from devflow_cli.utils.console import console, fail, ok, warn
19
20
  from devflow_cli.utils.paths import project_root_for_feature, resolve_feature_dir
@@ -44,6 +45,8 @@ def verify(
44
45
  "ISSUE_ID VAL-ID --reason MOTIF."
45
46
  )
46
47
  raise typer.Exit(1)
48
+ with workflow_lock(feature_dir):
49
+ refuse_if_agent_running(feature_dir)
47
50
  outcome = run_automatic_verification(
48
51
  feature_dir, project_root_for_feature(feature_dir)
49
52
  )