devflow-cli 8.2.0__py3-none-any.whl → 8.3.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 = "8.2.0"
3
+ VERSION = "8.3.0"
4
4
 
5
5
  SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
6
6
 
@@ -510,12 +510,15 @@ def run_adaptive(
510
510
  _missing_output_recovery(
511
511
  feature_dir, current_state.issueId, outcome.activity, agent,
512
512
  execution.stdout,
513
+ mutated=outcome.reason == "agent-failed-after-mutation",
513
514
  )
514
515
  if outcome.reason in {"agent-failed", "agent-failed-after-mutation"}
515
516
  and execution is not None
516
517
  and execution.final_output_diagnostic in MISSING_STRUCTURED_OUTPUT_DIAGNOSTICS
517
518
  else None
518
519
  )
520
+ if outcome.reason == "preflight-evidence-stale":
521
+ recovery = _stale_preflight_recovery(current_state)
519
522
  if json_output:
520
523
  _print_execution_json(
521
524
  current_state.issueId, outcome.activity, False, outcome.reason,
@@ -697,7 +700,10 @@ def reconcile_adaptive(
697
700
  console.print(" Suite : attendez la fin de l'activite en cours, puis relancez.")
698
701
  raise typer.Exit(int(exit_code_for_run_reason(outcome.reason)))
699
702
  console.print(f" Suite : devflow adaptive status {state.issueId}")
700
- if outcome.reason == "reconcile-refused":
703
+ if (
704
+ outcome.reason == "reconcile-refused"
705
+ and activity_attempt_file(feature_dir, outcome.activity).is_file()
706
+ ):
701
707
  console.print(
702
708
  f" Abandon audite : devflow adaptive discard-attempt {state.issueId} --reason MOTIF"
703
709
  )
@@ -937,7 +943,8 @@ def _execute_local_verification(
937
943
  sync_activity_status(issue_id, "verify", stream)
938
944
  outcome = run_automatic_verification(feature_dir, project_root, refresh=refresh)
939
945
  recovery = (
940
- None if outcome.passed else _verify_recovery(feature_dir, issue_id, outcome)
946
+ None if outcome.passed
947
+ else _verify_recovery(feature_dir, issue_id, outcome, refresh=refresh)
941
948
  )
942
949
  if json_output:
943
950
  _print_execution_json(
@@ -954,6 +961,8 @@ def _execute_local_verification(
954
961
  ok(f"Preuves de verify rafraichies. Activite courante : {outcome.nextActivity}")
955
962
  else:
956
963
  ok(f"Preuves generees et validees. Prochaine : {outcome.nextActivity}")
964
+ if outcome.detail:
965
+ console.print(f" Motif : {outcome.detail}", markup=False, soft_wrap=True)
957
966
  return
958
967
  fail(outcome.detail or outcome.reason)
959
968
  console.print(f" Verification automatique refusee : {outcome.reason}")
@@ -976,17 +985,20 @@ PLANNED_PREFIXES = {"PREP-": "prepare", "PLAN-": "plan"}
976
985
 
977
986
 
978
987
  def _verify_recovery(
979
- feature_dir: Path, issue_id: str, outcome: AutomaticVerificationOutcome
988
+ feature_dir: Path, issue_id: str, outcome: AutomaticVerificationOutcome,
989
+ *, refresh: bool = False,
980
990
  ) -> dict:
981
991
  """Reprise guidee d'un refus de verify, par motif, sans heuristique (B2).
982
992
 
983
993
  Approbation manquante : chaque approve-command puis verify. Executable introuvable
984
- (code 127) : corriger l'environnement puis verify. Echec d'un controle : deux voies,
985
- le code (reopen implement) ou la validation (reopen de l'activite qui l'a inscrite).
994
+ (code 127) : corriger l'environnement puis verify. Echec d'un controle : trois voies,
995
+ le code (reopen implement), la validation (reopen de l'activite qui l'a inscrite) ou
996
+ un echec passager (relancer verify). Apres un verify --refresh refuse, toutes les
997
+ commandes verify portent --refresh.
986
998
  """
987
999
  issue = shlex.quote(issue_id)
988
1000
  verify_command = {
989
- "command": f"devflow adaptive verify {issue}",
1001
+ "command": f"devflow adaptive verify {issue}" + (" --refresh" if refresh else ""),
990
1002
  "purpose": "relancer la verification",
991
1003
  }
992
1004
  state = read_adaptive_state(feature_dir / "workflow-state.json")
@@ -1064,6 +1076,13 @@ def _verify_recovery(
1064
1076
  "command": f"devflow adaptive reopen {issue} {origin} --reason {reason_text}",
1065
1077
  "purpose": f"voie 2 : la validation {names}, inscrite par {origin}, est en cause",
1066
1078
  })
1079
+ commands.append({
1080
+ "command": verify_command["command"],
1081
+ "purpose": (
1082
+ "voie 3 : echec passager (reseau, conteneur, service externe), "
1083
+ "relancer la verification"
1084
+ ),
1085
+ })
1067
1086
  return {
1068
1087
  "kind": "check-failed",
1069
1088
  "summary": (
@@ -1248,8 +1267,10 @@ def _operational_status(
1248
1267
  risks = _remaining_risks(feature_dir)
1249
1268
  if not isinstance(blockers, list):
1250
1269
  blockers = []
1251
- stale = set(stale_devflow_evidence(feature_dir, state))
1252
- stale.update(stale_review_evidence(feature_dir, state))
1270
+ stale: set[str] = set()
1271
+ if state.status != "completed": # aucune gate ne consulte la peremption apres done
1272
+ stale.update(stale_devflow_evidence(feature_dir, state))
1273
+ stale.update(stale_review_evidence(feature_dir, state))
1253
1274
  evidence = [
1254
1275
  {
1255
1276
  "id": evidence_id,
@@ -1442,7 +1463,8 @@ def _last_run_missed_structured_output(feature_dir: Path, activity: str) -> bool
1442
1463
 
1443
1464
 
1444
1465
  def _missing_output_recovery(
1445
- feature_dir: Path, issue_id: str, activity: str, agent: str, raw_output: str
1466
+ feature_dir: Path, issue_id: str, activity: str, agent: str, raw_output: str,
1467
+ *, mutated: bool | None = None,
1446
1468
  ) -> dict:
1447
1469
  """Reprise guidee d'une sortie structuree absente apres un agent termine sans erreur.
1448
1470
 
@@ -1467,6 +1489,8 @@ def _missing_output_recovery(
1467
1489
  attempt = json.loads(
1468
1490
  activity_attempt_file(feature_dir, activity).read_text(encoding="utf-8")
1469
1491
  )
1492
+ if mutated is None: # reconcile : le motif vient de la fiche de tentative
1493
+ mutated = attempt["reason"] == "agent-failed-after-mutation"
1470
1494
  contract = json.loads(
1471
1495
  (feature_dir / "activity-contract.json").read_text(encoding="utf-8")
1472
1496
  )
@@ -1504,9 +1528,35 @@ def _missing_output_recovery(
1504
1528
  ),
1505
1529
  },
1506
1530
  ])
1531
+ if not mutated:
1532
+ # Apres des ecritures, la relance contredirait « reconciliez d'abord ».
1533
+ recovery["commands"].append({
1534
+ "command": f"devflow adaptive run {issue} --agent {shlex.quote(agent)}",
1535
+ "purpose": "relancer l'agent : il n'a rien ecrit, la tentative est remplacee",
1536
+ })
1507
1537
  return recovery
1508
1538
 
1509
1539
 
1540
+ def _stale_preflight_recovery(state: AdaptiveWorkflowState) -> dict:
1541
+ """Reprise guidee d'une preuve de pre-vol perimee au lancement d'implement."""
1542
+ issue = shlex.quote(state.issueId)
1543
+ if _reopenable(state, "preflight-evidence"):
1544
+ command = {
1545
+ "command": f"devflow adaptive reopen {issue} preflight-evidence --reason MOTIF",
1546
+ "purpose": "refaire la preuve de pre-vol sur les entrees actuelles",
1547
+ }
1548
+ else:
1549
+ command = {
1550
+ "command": f"devflow adaptive status {issue}",
1551
+ "purpose": "consulter l'etat du parcours",
1552
+ }
1553
+ return {
1554
+ "kind": "preflight-evidence-stale",
1555
+ "summary": "Preuve de pre-vol perimee : la refaire avant implement.",
1556
+ "commands": [command],
1557
+ }
1558
+
1559
+
1510
1560
  def _print_recovery_steps(recovery: dict) -> None:
1511
1561
  # soft_wrap : la commande a copier reste sur une seule ligne.
1512
1562
  console.print(f" {recovery['summary']}", markup=False, soft_wrap=True)
@@ -16,6 +16,7 @@ from devflow_cli.core.assessment import (
16
16
  write_assessment,
17
17
  )
18
18
  from devflow_cli.core.adaptive_workflow import workflow_lock
19
+ from devflow_cli.core.activity_contracts import NEED_STATEMENT, validate_need_statement
19
20
  from devflow_cli.core.contract_schemas import check_schema_version
20
21
  from devflow_cli.utils.console import console, fail, ok
21
22
  from devflow_cli.utils.paths import resolve_feature_dir
@@ -138,4 +139,13 @@ def assess(
138
139
  console.print(f" Activites : {' -> '.join(assessment.activities)}")
139
140
  console.print(f" Preuves : {', '.join(assessment.requiredEvidence)}")
140
141
  console.print(f" Rapport : {output_file}")
141
- console.print(f" Suite : devflow adaptive start {assessment.issueId}")
142
+ try:
143
+ validate_need_statement(feature_dir / NEED_STATEMENT)
144
+ except ValueError:
145
+ console.print(
146
+ f" Suite : rediger {NEED_STATEMENT} ({feature_dir / NEED_STATEMENT}), "
147
+ f"puis devflow adaptive start {assessment.issueId}",
148
+ markup=False, soft_wrap=True,
149
+ )
150
+ else:
151
+ console.print(f" Suite : devflow adaptive start {assessment.issueId}")
@@ -54,7 +54,9 @@ SUBAGENT_CONSTRAINT = (
54
54
  PROTECTED_VALIDATION_CONSTRAINT = (
55
55
  "Les garde-fous sont actifs : inclure dans validationPlan au moins une validation "
56
56
  "bloquante explicitement consacree a la securite, aux donnees, a l'authentification, "
57
- "a une migration ou au rollback, dont id ou purpose contient l'un de ces termes."
57
+ "a une migration ou au rollback, dont id ou purpose contient l'un de ces termes. "
58
+ "Toute validation protegee est bloquante et doit reussir en code 0 : une recherche "
59
+ "dont l'absence de correspondance est le resultat attendu ne s'exprime pas ainsi."
58
60
  )
59
61
 
60
62
  PREFLIGHT_HEADINGS = (
@@ -178,6 +180,10 @@ def validate_need_statement(path: Path) -> None:
178
180
  raise ValueError(f"{NEED_STATEMENT} est vide : decrivez le besoin avant de demarrer")
179
181
 
180
182
 
183
+ class PreflightEvidenceStaleError(ValueError):
184
+ """Preuve de pre-vol anterieure a une entree : motif de refus dedie."""
185
+
186
+
181
187
  def validate_preflight_evidence(path: Path, *, newer_than: list[Path] | None = None) -> None:
182
188
  """Validate the V4 preflight artifact without exporting its contents."""
183
189
  try:
@@ -205,7 +211,7 @@ def validate_preflight_evidence(path: Path, *, newer_than: list[Path] | None = N
205
211
  artifact_mtime = path.stat().st_mtime_ns
206
212
  stale_inputs = [item.name for item in newer_than if item.is_file() and item.stat().st_mtime_ns > artifact_mtime]
207
213
  if stale_inputs:
208
- raise ValueError("preuve de pre-vol desuete apres : " + ", ".join(sorted(stale_inputs)))
214
+ raise PreflightEvidenceStaleError("preuve de pre-vol desuete apres : " + ", ".join(sorted(stale_inputs)))
209
215
 
210
216
 
211
217
  def build_activity_contract(
@@ -6,6 +6,7 @@ import hashlib
6
6
  import json
7
7
  import os
8
8
  import re
9
+ import shlex
9
10
  import tempfile
10
11
  from contextlib import ExitStack
11
12
  from dataclasses import dataclass, replace
@@ -18,6 +19,7 @@ from devflow_cli.core.activity_contracts import (
18
19
  ActivityResult,
19
20
  DOCUMENT_ARTIFACTS,
20
21
  NEED_STATEMENT,
22
+ PreflightEvidenceStaleError,
21
23
  build_activity_contract,
22
24
  contract_is_reopened_plan,
23
25
  document_artifact_name,
@@ -155,6 +157,13 @@ def run_current_activity(
155
157
  return AdaptiveRunOutcome(
156
158
  exc.reason, state.currentActivity, state.currentActivity, str(exc)
157
159
  )
160
+ except PreflightEvidenceStaleError as exc:
161
+ return AdaptiveRunOutcome(
162
+ "preflight-evidence-stale",
163
+ state.currentActivity,
164
+ state.currentActivity,
165
+ str(exc),
166
+ )
158
167
  except (OSError, ValueError) as exc:
159
168
  return AdaptiveRunOutcome(
160
169
  "unsupported-activity", state.currentActivity, state.currentActivity, str(exc)
@@ -337,9 +346,18 @@ def reconcile_current_activity(
337
346
  raise ValueError("le resultat corrige doit etre un objet JSON")
338
347
  if not correction_reason.strip():
339
348
  raise ValueError("un motif est obligatoire pour corriger le resultat")
340
- attempt = json.loads(
341
- activity_attempt_file(feature_dir, activity).read_text(encoding="utf-8")
342
- )
349
+ try:
350
+ attempt_text = activity_attempt_file(feature_dir, activity).read_text(
351
+ encoding="utf-8"
352
+ )
353
+ except FileNotFoundError:
354
+ raise ValueError(
355
+ f"aucune tentative echouee a reconcilier pour '{activity}' : "
356
+ "reconcile ne s'applique qu'a une tentative agent echouee ; apres "
357
+ "un resultat blocked, lever le blocage puis relancer "
358
+ f"devflow adaptive run {shlex.quote(state.issueId)}"
359
+ ) from None
360
+ attempt = json.loads(attempt_text)
343
361
  if not _valid_attempt(attempt):
344
362
  raise ValueError("fiche de tentative invalide")
345
363
  agent = attempt["agent"]
@@ -733,6 +751,17 @@ def _validated_result(
733
751
  raise ValueError(
734
752
  f"validation {item.id}, commande : {command_label} — {exc}"
735
753
  ) from exc
754
+ if contract.guardrails:
755
+ for item in result.validationPlan:
756
+ if is_protected_validation(item.id, item.purpose) and not item.blocking:
757
+ raise ValueError(
758
+ f"validation {item.id} : un controle protege (securite, "
759
+ "donnees, authentification, migration, rollback) est toujours "
760
+ "bloquant et doit reussir en code 0 ; declarer blocking a true. "
761
+ "Une recherche dont l'absence de correspondance est le resultat "
762
+ "attendu ne s'exprime pas ainsi : utiliser une commande qui "
763
+ "renvoie 0 en cas de succes."
764
+ )
736
765
  if contract.guardrails and not any(
737
766
  item.blocking and is_protected_validation(item.id, item.purpose)
738
767
  for item in result.validationPlan
@@ -22,7 +22,13 @@ from devflow_cli.core.adaptive_workflow import (
22
22
  workflow_lock,
23
23
  write_adaptive_state,
24
24
  )
25
- from devflow_cli.core.activity_base import enter_current_activity, parse_recorded_result
25
+ from devflow_cli.core.activity_base import (
26
+ ActivityBase,
27
+ BaseObservationError,
28
+ enter_current_activity,
29
+ files_since_base,
30
+ parse_recorded_result,
31
+ )
26
32
  from devflow_cli.core.activity_contracts import (
27
33
  DOCUMENT_ARTIFACTS,
28
34
  activity_contract_id,
@@ -297,6 +303,38 @@ DOCUMENT_INSENSITIVE_EVIDENCE_IDS = frozenset({"test-suite", "requirements-cover
297
303
  NON_DOCUMENT_FINGERPRINT_PREFIX = "non-document-v1:"
298
304
 
299
305
 
306
+ def is_document_path(path: str) -> bool:
307
+ """Vrai pour un README ou un fichier Markdown du dossier docs."""
308
+ return Path(path).name.casefold() == "readme.md" or (
309
+ path.startswith("docs/") and path.casefold().endswith(".md")
310
+ )
311
+
312
+
313
+ def _observed_document_batch(
314
+ state: AdaptiveWorkflowState, feature_dir: Path, project_root: Path
315
+ ) -> list[str] | None:
316
+ """Fichiers observes depuis la base d'implement si tous sont documentaires.
317
+
318
+ None quand le lot n'est pas eligible : base inobservable, lot vide, fichier
319
+ non documentaire ou lien symbolique. L'observation decide, pas l'agent.
320
+ """
321
+ try:
322
+ base = ActivityBase.from_dict(
323
+ state.implementBase,
324
+ feature_path=feature_dir.resolve().relative_to(project_root.resolve()).as_posix(),
325
+ )
326
+ observed = sorted(
327
+ files_since_base(project_root, feature_dir, base, issue_id=state.issueId)
328
+ )
329
+ except (BaseObservationError, ValueError, OSError):
330
+ return None
331
+ if not observed or not all(is_document_path(path) for path in observed):
332
+ return None
333
+ if any((project_root / path).is_symlink() for path in observed):
334
+ return None
335
+ return observed
336
+
337
+
300
338
  def _non_document_fingerprint(snapshot: WorktreeSnapshot, excluded_dir: str) -> str:
301
339
  """Empreinte des sources, sans README ni Markdown du dossier docs.
302
340
 
@@ -306,10 +344,7 @@ def _non_document_fingerprint(snapshot: WorktreeSnapshot, excluded_dir: str) ->
306
344
  source_files = {
307
345
  path: fingerprint
308
346
  for path, fingerprint in snapshot.files.items()
309
- if not (
310
- Path(path).name.casefold() == "readme.md"
311
- or (path.startswith("docs/") and path.casefold().endswith(".md"))
312
- )
347
+ if not is_document_path(path)
313
348
  }
314
349
  return NON_DOCUMENT_FINGERPRINT_PREFIX + worktree_fingerprint(
315
350
  replace(snapshot, files=source_files), excluded_dir
@@ -562,7 +597,10 @@ def run_automatic_verification(
562
597
  # par run_verifications avant leur execution locale par devflow.
563
598
  commands = regression_commands
564
599
  reuse_targeted_as_regression = True
565
- if not commands:
600
+ document_batch: list[str] | None = None
601
+ if not commands and state.profile == "quick" and not guardrails_active(state):
602
+ document_batch = _observed_document_batch(state, feature_dir, project_root)
603
+ if not commands and document_batch is None:
566
604
  return AutomaticVerificationOutcome(
567
605
  False,
568
606
  "no-safe-validation",
@@ -661,7 +699,7 @@ def run_automatic_verification(
661
699
  break
662
700
  return AutomaticVerificationOutcome(False, "validation-policy-refused", detail)
663
701
  completed_at = _now_iso()
664
- checks_passed = bool(results) and all(
702
+ checks_passed = (bool(results) or document_batch is not None) and all(
665
703
  result.succeeded or not result.blocking for result in results
666
704
  )
667
705
  regression_passed = all(
@@ -673,7 +711,9 @@ def run_automatic_verification(
673
711
  and all(result.succeeded for result in security_results)
674
712
  and not security_findings
675
713
  )
676
- diff_payload = _inspect_diff(project_root, changed_files)
714
+ diff_payload = _inspect_diff(
715
+ project_root, document_batch if document_batch is not None else changed_files
716
+ )
677
717
  final_snapshot = snapshot_worktree(project_root)
678
718
  try:
679
719
  test_mutations = changed_paths(before, final_snapshot)
@@ -883,7 +923,15 @@ def run_automatic_verification(
883
923
  write_adaptive_state(state_file, latest)
884
924
  next_activity = latest.currentActivity
885
925
  safe_refresh_worklog(feature_dir)
886
- return AutomaticVerificationOutcome(True, "passed", nextActivity=next_activity)
926
+ detail = (
927
+ "lot documentaire, aucun controle executable : verification sur "
928
+ "inspection du diff (" + ", ".join(document_batch) + ")"
929
+ if document_batch is not None
930
+ else ""
931
+ )
932
+ return AutomaticVerificationOutcome(
933
+ True, "passed", detail, nextActivity=next_activity
934
+ )
887
935
 
888
936
 
889
937
  def _failures(results: list[VerificationResult]) -> tuple[VerificationFailure, ...]:
@@ -203,10 +203,51 @@ def _find_failed_command(
203
203
  if not matches:
204
204
  raise ValueError(f"aucun echec execute trouve pour {command_id}")
205
205
  if len(matches) > 1:
206
- raise ValueError(f"validation ambigue dans les preuves : {command_id}")
206
+ return _select_duplicated_failure(command_id, matches)
207
207
  return matches[0]
208
208
 
209
209
 
210
+ _TIE_BREAK_ORDER = {"targeted-tests": 0, "regression-check": 1}
211
+
212
+
213
+ def _select_duplicated_failure(
214
+ command_id: str, matches: list[tuple[dict[str, Any], dict[str, Any]]]
215
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
216
+ """Retient la preuve la plus récente si toutes les occurrences sont cohérentes."""
217
+ names = sorted(str(payload.get("id")) for payload, _ in matches)
218
+ protected = [name for name in names if name in PROTECTED_EVIDENCE]
219
+ if protected:
220
+ raise ValueError(
221
+ f"validation {command_id} presente dans une preuve protegee "
222
+ f"non derogable : {', '.join(protected)}"
223
+ )
224
+ reference = matches[0][1]
225
+ return_code = reference.get("returncode")
226
+ consistent = isinstance(return_code, int) and return_code != 0
227
+ for _, command in matches:
228
+ consistent = consistent and (
229
+ command.get("command") == reference.get("command")
230
+ and command.get("workingDirectory", ".") == reference.get("workingDirectory", ".")
231
+ and command.get("blocking") is True
232
+ and command.get("succeeded") is False
233
+ and command.get("timedOut") is not True
234
+ and command.get("returncode") == return_code
235
+ )
236
+ if not consistent:
237
+ raise ValueError(
238
+ f"validation {command_id} presente dans plusieurs preuves "
239
+ f"({', '.join(names)}) avec des commandes, dossiers ou resultats differents"
240
+ )
241
+ ordered = sorted(
242
+ matches,
243
+ key=lambda match: (
244
+ _TIE_BREAK_ORDER.get(str(match[0].get("id")), len(_TIE_BREAK_ORDER)),
245
+ str(match[0].get("id")),
246
+ ),
247
+ )
248
+ return max(ordered, key=lambda match: str(match[0].get("completedAt") or ""))
249
+
250
+
210
251
  def _read_registry(path: Path, issue_id: str) -> dict[str, Any]:
211
252
  if not path.is_file():
212
253
  return {
@@ -288,6 +288,16 @@ def _verification_lines(
288
288
  f"`{waiver.get('id', 'inconnue')}` sur `{waiver.get('commandId', 'inconnu')}` "
289
289
  f"par {waiver.get('author', 'inconnu')} : {waiver.get('reason', 'sans justification')}"
290
290
  )
291
+ if (
292
+ targeted
293
+ and targeted.get("id") == "targeted-check"
294
+ and targeted.get("outcome") == "passed"
295
+ and commands == []
296
+ ):
297
+ lines.append(
298
+ " - Lot documentaire, aucun contrôle exécutable : "
299
+ "vérification sur inspection du diff."
300
+ )
291
301
  return lines or ["- Vérification non exécutée."]
292
302
 
293
303
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devflow-cli
3
- Version: 8.2.0
3
+ Version: 8.3.0
4
4
  Summary: Spec-Driven Development workflow CLI
5
5
  Project-URL: Homepage, https://github.com/sopequenoteck/devflow
6
6
  Project-URL: Repository, https://github.com/sopequenoteck/devflow
@@ -45,7 +45,8 @@ Le profil fixe la route documentaire :
45
45
 
46
46
  Un bloc de garde-fous s'y ajoute quand `--data-security` est déclaré ou que
47
47
  `--risk high` : `risk-assessment` après l'évaluation, validation bloquante de
48
- sécurité ou de données dans le plan de validation, preuve
48
+ sécurité ou de données dans le plan de validation (une validation protégée non
49
+ bloquante est refusée dès `prepare` ou `plan`), preuve
49
50
  `security-or-data-check` produite par `verify`, revue `independent-review`,
50
51
  puis `rollback-check` avec `rollback-plan`. Un bloc actif impose au minimum le
51
52
  profil `standard`, ne se désactive par aucune option et interdit les
@@ -90,8 +91,11 @@ devflow adaptive status KS-123
90
91
  distinguent explicitement blocage métier, contrat invalide, panne agent,
91
92
  validation échouée et conflit concurrent ; chaque refus affiche la commande de
92
93
  reprise recommandée. Après un refus de `verify`, cette reprise dépend du motif :
93
- approbations à donner, environnement à corriger, ou deux voies de réouverture
94
- (`implement` si le code est en cause, `prepare` ou `plan` si la validation l'est).
94
+ approbations à donner, environnement à corriger, ou trois voies après un
95
+ contrôle échoué (rouvrir `implement` si le code est en cause, rouvrir `prepare`
96
+ ou `plan` si la validation l'est, relancer `verify` après un échec passager).
97
+ En `quick`, un lot purement documentaire (`README.md`, Markdown sous `docs/`)
98
+ sans contrôle détecté franchit `verify` sur l'inspection du diff.
95
99
 
96
100
  Un plan de `prepare` ou de `plan` refusé par les validateurs se corrige sans
97
101
  relancer l'agent : `devflow adaptive reconcile KS-123 --result FICHIER.json
@@ -1,11 +1,11 @@
1
- devflow_cli/__init__.py,sha256=Y5UdblxgAc6mD_GvD_R7nfzo_uyAi09mJw3AJoB8Stk,925
1
+ devflow_cli/__init__.py,sha256=IpmjnS3nun7PP7DJ0y0oLHgWCL8HVul9hsCpjbAVK4s,925
2
2
  devflow_cli/cli.py,sha256=0_SmhJX_k7OQfp-jNV_jq9EOgWY4rTjStGIoc9pH6Ec,2686
3
3
  devflow_cli/adapters/__init__.py,sha256=RkjHPU2Lp4hSVIq2iOmyTrv2zYU9NuiIF9wEMWzLdMI,377
4
4
  devflow_cli/adapters/agents.py,sha256=01ZQb96koWMKH-mma3AtttdEjNEbu3mM89vFetD9iR8,29689
5
5
  devflow_cli/adapters/linear.py,sha256=HE1YzjBxwoj2jJ_3bmh9Pn_7Mt5u3EOLk891vMXBEj4,8700
6
6
  devflow_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- devflow_cli/commands/adaptive.py,sha256=kYSpQWZ-xUTw1Kquv5fE2kpFjoVClqNl4Nd7pUj7FX8,62199
8
- devflow_cli/commands/assess.py,sha256=lyj6TI5hXUWX55dY589fG4bKJSMztvMPxZ0HZjo0O-w,5230
7
+ devflow_cli/commands/adaptive.py,sha256=OdESwOyoMgySOyyLkAEk3oIkVPXS-bli_SvJe5VeHGU,64445
8
+ devflow_cli/commands/assess.py,sha256=V-E3ft41ljCrqp2WLjR1Dqw-JtswcqsK2nD1-euNbNE,5655
9
9
  devflow_cli/commands/check.py,sha256=hsfeOg9yDOTAIvNm6XhRmTIFgv7w9wC8zvrwm4ojfZs,6923
10
10
  devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
11
11
  devflow_cli/commands/docs_sync.py,sha256=Xc0mulpNTjDyeUDumKL7VNG0eumAf3016wEM_dqXpSU,1021
@@ -24,13 +24,13 @@ devflow_cli/commands/usage.py,sha256=ehH1AH5J-XfkebFx6qLBwpKN-MOoX2uUbq4LejBpNsk
24
24
  devflow_cli/commands/verify.py,sha256=GBFCfsN-xWyROCcngOANz-3ftNVeNZ4Y_JeWFo2muwE,4139
25
25
  devflow_cli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  devflow_cli/core/activity_base.py,sha256=haEVVgZAi3-iX75oo2-fK9GQZeFgN_xRw5U0Z0bdV5w,16343
27
- devflow_cli/core/activity_contracts.py,sha256=2-pjjxMyS7iER2eim118U5QSgOqwrP47X_HcfZ7jZ58,38717
28
- devflow_cli/core/adaptive_runner.py,sha256=MLws62PW986dd00nA4m_3Y4BAR-ZmmYT3q8N6KiPU-Q,40375
27
+ devflow_cli/core/activity_contracts.py,sha256=n9arBw-SWEFqTlCDNed6lV4WolkZKAvmQg1qcfk57ss,39036
28
+ devflow_cli/core/adaptive_runner.py,sha256=GRL4w9FsIPplmypPx1KUnel_byc9PVe7jkTq6Q5Uy0c,41879
29
29
  devflow_cli/core/adaptive_workflow.py,sha256=F4uV5okQpFidZmneVZwW52XAO7gAmtuBAqBO9K9q7Uk,12173
30
30
  devflow_cli/core/agent_failure.py,sha256=iX4q249lAHeVdD_ZF1aObmU-FX6sjfYP9dfLSnWGAD0,3771
31
31
  devflow_cli/core/agent_run_marker.py,sha256=QW3ScA2ZpqfQdUo5ANTbenDVyY2iHpB_wTaN6VUKlzc,3451
32
32
  devflow_cli/core/assessment.py,sha256=ACtJbXq1qZjVfeERjy8Qg6-ioLfSMUBVQm2l-mtGwgI,11522
33
- devflow_cli/core/automatic_evidence.py,sha256=y1f6L3tsJpV57I54zcTySetho1ASHklu5mUQF1PaRDY,40600
33
+ devflow_cli/core/automatic_evidence.py,sha256=Ntsy2eQKLRl_hUD27V9NRBLavyhLxxkEcbPySKmd4W4,42337
34
34
  devflow_cli/core/catalog.py,sha256=nLPc78JkbISaSxJnHlWiMi_lvepaumbfwcjgLuVy7Zc,3102
35
35
  devflow_cli/core/contract_schemas.py,sha256=-7SlJCwQ31OjPfwA-tP4ctgbp9hxQAVwNvvOV09Hoho,6313
36
36
  devflow_cli/core/docgen.py,sha256=47JoVDWetkcctgHwJZC67AVebOKgl10VB9hZ3Xr63mo,3908
@@ -52,8 +52,8 @@ devflow_cli/core/traceability.py,sha256=jfNE9MZzGkmaPer9Wa6Y18yA9Y3sqdeR-nzH9h_T
52
52
  devflow_cli/core/validation_approvals.py,sha256=3AHOpnW3BtY3BQrRZr38K7VNYRvbxGNhmQRYAufyhSA,9279
53
53
  devflow_cli/core/validators.py,sha256=fREjEx2fBW36q3vB6WJNqoGUM9ejYlQUXie5A9lNuPs,7440
54
54
  devflow_cli/core/verification.py,sha256=rvbdAdLyrZYb20oauti7D8nV5TDFBIf_8-hqLahfRag,23576
55
- devflow_cli/core/waivers.py,sha256=xpfSiC4K51ZVlBOZTRNJ5ftS05-bsLyMW-W9CdQHqH4,11595
56
- devflow_cli/core/worklog.py,sha256=ECmGJL63eVwfKQl94-r-Zd56ubhApuufAqDq1h5xcAU,13854
55
+ devflow_cli/core/waivers.py,sha256=9XXzMmoWCn-2Dc3486WhZwtGJQLQPXjP0qAaLuL_3qU,13299
56
+ devflow_cli/core/worklog.py,sha256=GkaXCsx22jntcWuC75OystUoqb5AlzBbXXLYH581Mwg,14164
57
57
  devflow_cli/core/worktree_snapshot.py,sha256=Q2_KTkhJTGvdM6Ay4RK9OHkyPEMrHQRTb-f-Eptek-8,5255
58
58
  devflow_cli/schemas/activity-contract-v4.schema.json,sha256=IeduWDiJ5IV6EL7FDZWnHBjzyUt1uwig2rB-SMbcpOU,2006
59
59
  devflow_cli/schemas/activity-result-v1.schema.json,sha256=xQuyD5fwHGIdeoJoDJxJn26qBGJX74lD6Dc5gi5CLQo,2772
@@ -90,8 +90,8 @@ devflow_cli/resources/templates/contracts/api-template.md,sha256=rC5v7JBz71PznCn
90
90
  devflow_cli/resources/templates/contracts/components-template.md,sha256=XIWfEXfC72sNmL5OA3V-7xIywkNDU2DBPLfyiagOWcs,484
91
91
  devflow_cli/resources/templates/contracts/interfaces-template.md,sha256=kf8B7b6HsmN7jyRlLNU7RnZl0rTMlI5s97yVRyI-6yc,336
92
92
  devflow_cli/resources/templates/contracts/services-template.md,sha256=cFafKCyLmq_a7aR7G0LXAaMO2z4Fuflq6iKdxfVg0vg,477
93
- devflow_cli-8.2.0.dist-info/METADATA,sha256=9zPuMV6XtqesVYitXPdUdkg2KqmqP_zQ7LSRCa9jksQ,8470
94
- devflow_cli-8.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
- devflow_cli-8.2.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
96
- devflow_cli-8.2.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
97
- devflow_cli-8.2.0.dist-info/RECORD,,
93
+ devflow_cli-8.3.0.dist-info/METADATA,sha256=8ck7BHBw2l01PS43k3pKc2d79l719w4YcK6RjUESOp0,8772
94
+ devflow_cli-8.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
+ devflow_cli-8.3.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
96
+ devflow_cli-8.3.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
97
+ devflow_cli-8.3.0.dist-info/RECORD,,