devflow-cli 8.4.1__py3-none-any.whl → 8.5.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.
devflow_cli/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """devflow CLI — workflow de développement adaptatif."""
2
2
 
3
- VERSION = "8.4.1"
3
+ VERSION = "8.5.1"
4
4
 
5
5
  SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
6
6
 
@@ -17,6 +17,7 @@ from devflow_cli import VERSION
17
17
  from devflow_cli.adapters.linear import sync_activity_status
18
18
  from devflow_cli.core.adaptive_workflow import (
19
19
  AdaptiveWorkflowState,
20
+ adaptive_state_violation,
20
21
  complete_activity,
21
22
  create_adaptive_workflow,
22
23
  read_adaptive_state,
@@ -57,7 +58,11 @@ from devflow_cli.core.automatic_evidence import (
57
58
  stale_devflow_evidence,
58
59
  stale_review_evidence,
59
60
  )
60
- from devflow_cli.core.worklog import refresh_worklog, safe_refresh_worklog
61
+ from devflow_cli.core.worklog import (
62
+ latest_remaining_risks,
63
+ refresh_worklog,
64
+ safe_refresh_worklog,
65
+ )
61
66
  from devflow_cli.core.telemetry import append_record, telemetry_enabled, usage_payload
62
67
  from devflow_cli.core.waivers import create_validation_waiver
63
68
  from devflow_cli.core.remediation import (
@@ -144,8 +149,12 @@ def _state(
144
149
  raise typer.Exit(LEGACY_SCHEMA_EXIT_CODE) from exc
145
150
  if state is None:
146
151
  diagnostic = diagnose_contract_file("workflow-state", state_file)
147
- detail = (
148
- diagnostic or "workflow-state.json respecte le schema mais ses invariants "
152
+ violation = None if diagnostic else adaptive_state_violation(state_file)
153
+ detail = diagnostic or (
154
+ "workflow-state.json respecte le schema mais viole le verrou "
155
+ f"{violation}."
156
+ if violation
157
+ else "workflow-state.json respecte le schema mais ses invariants "
149
158
  "métier sont incoherents."
150
159
  )
151
160
  if json_output:
@@ -194,10 +203,10 @@ def start(feature: str = typer.Argument(..., help="ID ou dossier de la feature")
194
203
  enter_current_activity(
195
204
  state, project_root_for_feature(feature_dir), feature_dir
196
205
  )
206
+ write_adaptive_state(state_file, state)
197
207
  except (OSError, ValueError) as exc:
198
208
  fail(f"Impossible de demarrer le workflow : {exc}")
199
209
  raise typer.Exit(int(ExitCode.INVALID_CONTRACT)) from exc
200
- write_adaptive_state(state_file, state)
201
210
  safe_refresh_worklog(feature_dir)
202
211
  ok(
203
212
  f"Workflow {state.profile} demarre. "
@@ -252,11 +261,11 @@ def complete(
252
261
  enter_current_activity(
253
262
  state, project_root_for_feature(feature_dir), feature_dir
254
263
  )
264
+ write_adaptive_state(feature_dir / "workflow-state.json", state)
255
265
  except (OSError, ValueError) as exc:
256
266
  fail(str(exc))
257
267
  console.print(f" Suite : devflow adaptive status {state.issueId}")
258
268
  raise typer.Exit(int(ExitCode.BLOCKED)) from exc
259
- write_adaptive_state(feature_dir / "workflow-state.json", state)
260
269
  safe_refresh_worklog(feature_dir)
261
270
  if activity == "done":
262
271
  sync_activity_status(state.issueId, "done", run_progress.progress_stream(False))
@@ -280,10 +289,10 @@ def evidence(
280
289
  state = _state(feature_dir)
281
290
  try:
282
291
  record_evidence(state, evidence_id, report or "")
292
+ write_adaptive_state(feature_dir / "workflow-state.json", state)
283
293
  except ValueError as exc:
284
294
  fail(str(exc))
285
295
  raise typer.Exit(int(ExitCode.BLOCKED)) from exc
286
- write_adaptive_state(feature_dir / "workflow-state.json", state)
287
296
  safe_refresh_worklog(feature_dir)
288
297
  ok(f"Preuve '{evidence_id}' enregistree.")
289
298
  warn("Source manuelle : cette preuve ne valide pas les gates du workflow.")
@@ -755,8 +764,8 @@ def review_adaptive(
755
764
  )
756
765
  if result.advanced:
757
766
  ok(
758
- f"Revue humaine de {by.strip()} validee pour '{result.activity}'. "
759
- f"Prochaine : {result.nextActivity}"
767
+ f"Revue humaine de {by.strip()} validee pour '{result.activity}' : "
768
+ f"activite completee. Prochaine : {result.nextActivity}"
760
769
  )
761
770
  return
762
771
  safe_refresh_worklog(feature_dir)
@@ -899,11 +908,11 @@ def reopen(
899
908
  enter_current_activity(
900
909
  state, project_root_for_feature(feature_dir), feature_dir
901
910
  )
911
+ write_adaptive_state(feature_dir / "workflow-state.json", state)
902
912
  except (OSError, ValueError) as exc:
903
913
  fail(str(exc))
904
914
  console.print(f" Suite : devflow adaptive status {state.issueId}")
905
915
  raise typer.Exit(int(ExitCode.BLOCKED)) from exc
906
- write_adaptive_state(feature_dir / "workflow-state.json", state)
907
916
  safe_refresh_worklog(feature_dir)
908
917
  ok(
909
918
  f"Workflow rouvert sur '{activity}' (audit #{entry['sequence']}, "
@@ -928,18 +937,19 @@ def discard_attempt_command(
928
937
  fail(str(exc))
929
938
  console.print(f" Suite : devflow adaptive status {state.issueId}")
930
939
  raise typer.Exit(int(ExitCode.BLOCKED)) from exc
940
+ remove_result_template(state.issueId, entry["activity"])
931
941
  safe_refresh_worklog(feature_dir)
932
942
  ok(
933
943
  f"Tentative '{entry['activity']}' abandonnee (audit #{entry['sequence']}, "
934
944
  f"archive : {entry['archivePath']})."
935
945
  )
936
946
  discarded = list(entry["discardedFiles"])
947
+ feature_path = feature_path_for(project_root_for_feature(feature_dir), feature_dir)
948
+ imputed = _imputed_inputs_of_discarded_attempt(feature_dir, entry, feature_path)
937
949
  inputs = (
938
950
  [
939
951
  path for path in discarded
940
- if _is_need_input(path, feature_path_for(
941
- project_root_for_feature(feature_dir), feature_dir
942
- ))
952
+ if _is_need_input(path, feature_path) and path in imputed
943
953
  ]
944
954
  if entry["activity"] in DOCUMENT_ARTIFACTS
945
955
  else []
@@ -962,6 +972,27 @@ def discard_attempt_command(
962
972
  warn(f"Fichiers depuis la base non calcules : {entry['observationError']}")
963
973
 
964
974
 
975
+ def _imputed_inputs_of_discarded_attempt(
976
+ feature_dir: Path, entry: dict, feature_path: str | None
977
+ ) -> set[str]:
978
+ """Entrees du besoin que la fiche abandonnee imputait a un agent (lecture seule)."""
979
+ if feature_path is None:
980
+ return set()
981
+ card = (
982
+ feature_dir
983
+ / entry["archivePath"]
984
+ / activity_attempt_file(feature_dir, entry["activity"]).name
985
+ )
986
+ try:
987
+ payload = json.loads(card.read_text(encoding="utf-8"))
988
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
989
+ return set()
990
+ names = payload.get("changedInputs") if isinstance(payload, dict) else None
991
+ if not isinstance(names, list):
992
+ return set()
993
+ return {f"{feature_path}/{name}" for name in names if isinstance(name, str)}
994
+
995
+
965
996
  def _is_need_input(path: str, feature_path: str | None) -> bool:
966
997
  """Vrai pour need.md ou issue-*.md directement sous le dossier de la feature."""
967
998
  if feature_path is None:
@@ -1305,7 +1336,7 @@ def _operational_status(
1305
1336
  ) -> dict:
1306
1337
  result_file, result = _latest_activity_result(feature_dir, state.currentActivity)
1307
1338
  blockers = result.get("blockers", []) if isinstance(result, dict) else []
1308
- risks = _remaining_risks(feature_dir)
1339
+ risks = latest_remaining_risks(feature_dir, state.activities)
1309
1340
  if not isinstance(blockers, list):
1310
1341
  blockers = []
1311
1342
  stale: set[str] = set()
@@ -1456,28 +1487,6 @@ def _latest_activity_result(
1456
1487
  return None, {}
1457
1488
 
1458
1489
 
1459
- def _remaining_risks(feature_dir: Path) -> list[str]:
1460
- risks: list[str] = []
1461
- for path in sorted(feature_dir.glob("*-result.json")):
1462
- if not path.is_file():
1463
- continue
1464
- try:
1465
- payload = json.loads(path.read_text(encoding="utf-8"))
1466
- except (OSError, UnicodeDecodeError, json.JSONDecodeError):
1467
- continue
1468
- values = (
1469
- payload.get("remainingRisks", [])
1470
- if isinstance(payload, dict)
1471
- else []
1472
- )
1473
- if not isinstance(values, list):
1474
- continue
1475
- for value in values:
1476
- if isinstance(value, str) and value not in risks:
1477
- risks.append(value)
1478
- return risks
1479
-
1480
-
1481
1490
  def _fallback_agents(agent: str) -> list[str]:
1482
1491
  """Autres agents pris en charge, pour reprendre pendant une indisponibilite."""
1483
1492
  current = get_agent_adapter(agent).name
@@ -1724,12 +1733,28 @@ def _print_recovery_steps(recovery: dict) -> None:
1724
1733
  )
1725
1734
 
1726
1735
 
1736
+ # Noms de `run --agent` traduits en noms que `devflow check --ai` accepte.
1737
+ CHECK_AGENT_NAMES = {"claude": "claude-code", "claude-code": "claude-code", "codex": "codex"}
1738
+
1739
+
1740
+ def _print_check_command(agent: str, comment: str) -> None:
1741
+ """Propose `devflow check --ai` avec un nom que cette commande accepte."""
1742
+ name = CHECK_AGENT_NAMES.get(agent.casefold())
1743
+ if name is not None:
1744
+ console.print(f" devflow check --ai {name} --live{comment}")
1745
+
1746
+
1727
1747
  def _print_run_recovery(
1728
1748
  issue_id: str, reason: str, activity: str, agent: str, feature_dir: Path,
1729
1749
  *, recovery: dict | None = None,
1730
1750
  ) -> None:
1731
1751
  console.print(" Reprise guidee :")
1732
- console.print(f" devflow adaptive status {issue_id}")
1752
+ status_command = f"devflow adaptive status {issue_id}"
1753
+ status_commands = {status_command, f"devflow adaptive status {shlex.quote(issue_id)}"}
1754
+ if recovery is None or not any(
1755
+ step["command"] in status_commands for step in recovery["commands"]
1756
+ ):
1757
+ console.print(f" {status_command}")
1733
1758
  if reason in {"agent-failed", "agent-failed-after-mutation", "agent-unavailable"}:
1734
1759
  console.print(
1735
1760
  " Trace agent : "
@@ -1742,7 +1767,7 @@ def _print_run_recovery(
1742
1767
  return
1743
1768
  if reason == "agent-unavailable":
1744
1769
  console.print(" Agent indisponible : l'activite reste courante et rien n'est invalide.")
1745
- console.print(f" devflow check --ai {agent} --live # verifier le retour de l'agent")
1770
+ _print_check_command(agent, " # verifier le retour de l'agent")
1746
1771
  console.print(f" devflow adaptive reconcile {issue_id} # si l'agent avait deja produit son travail")
1747
1772
  for fallback in _fallback_agents(agent):
1748
1773
  console.print(f" devflow adaptive run {issue_id} --agent {fallback}")
@@ -1787,7 +1812,7 @@ def _print_run_recovery(
1787
1812
  )
1788
1813
  if reason == "agent-failed":
1789
1814
  console.print(f" devflow adaptive reconcile {issue_id} # si la trace contient une reponse finale")
1790
- console.print(f" devflow check --ai {agent}")
1815
+ _print_check_command(agent, " # verifier l'agent")
1791
1816
  elif reason == "state-changed":
1792
1817
  console.print(" Rechargez le statut avant toute nouvelle execution.")
1793
1818
  return
@@ -555,8 +555,8 @@ def parse_activity_result(payload: dict[str, Any], contract: ActivityContract) -
555
555
  for removed in REMOVED_RESULT_FIELDS:
556
556
  if removed in payload:
557
557
  raise ValueError(
558
- f"champ {removed} supprime en devflow 7.0 ; mettre a jour les consignes "
559
- "avec devflow upgrade"
558
+ f"champ {removed} supprime en devflow 7.0 ; relancer l'activite, qui "
559
+ "produit un nouveau contrat sans ce champ"
560
560
  )
561
561
  expected_fields = {
562
562
  "schemaVersion",
@@ -847,6 +847,13 @@ def _parse_checks(value: Any) -> list[ActivityCheck]:
847
847
  return checks
848
848
 
849
849
 
850
+ _EVIDENCE_SHAPE_MESSAGE = (
851
+ "preuve invalide : chaque element de evidence doit etre un objet aux cles "
852
+ "id (texte non vide, unique), outcome (passed ou failed) et report (texte non vide), "
853
+ "sans autre cle"
854
+ )
855
+
856
+
850
857
  def _parse_evidence(value: Any) -> list[ActivityEvidence]:
851
858
  if not isinstance(value, list):
852
859
  raise ValueError("evidence doit etre une liste")
@@ -854,9 +861,9 @@ def _parse_evidence(value: Any) -> list[ActivityEvidence]:
854
861
  seen: set[str] = set()
855
862
  for item in value:
856
863
  if not isinstance(item, dict):
857
- raise ValueError("preuve invalide")
864
+ raise ValueError(_EVIDENCE_SHAPE_MESSAGE)
858
865
  if set(item) != {"id", "outcome", "report"}:
859
- raise ValueError("preuve invalide")
866
+ raise ValueError(_EVIDENCE_SHAPE_MESSAGE)
860
867
  evidence_id, outcome, report = item.get("id"), item.get("outcome"), item.get("report")
861
868
  if (
862
869
  not isinstance(evidence_id, str)
@@ -866,7 +873,7 @@ def _parse_evidence(value: Any) -> list[ActivityEvidence]:
866
873
  or not isinstance(report, str)
867
874
  or not report.strip()
868
875
  ):
869
- raise ValueError("preuve invalide")
876
+ raise ValueError(_EVIDENCE_SHAPE_MESSAGE)
870
877
  seen.add(evidence_id)
871
878
  evidence.append(ActivityEvidence(evidence_id, outcome, report.strip()))
872
879
  return evidence
@@ -797,6 +797,7 @@ def _apply_activity_result(
797
797
  enter_current_activity(
798
798
  latest, project_root_for_feature(feature_dir), feature_dir
799
799
  )
800
+ write_adaptive_state(state_file, latest)
800
801
  except (OSError, ValueError) as exc:
801
802
  return AdaptiveRunOutcome(
802
803
  "transition-refused",
@@ -806,7 +807,6 @@ def _apply_activity_result(
806
807
  result,
807
808
  warnings,
808
809
  )
809
- write_adaptive_state(state_file, latest)
810
810
  next_activity = latest.currentActivity
811
811
 
812
812
  safe_refresh_worklog(feature_dir)
@@ -187,32 +187,13 @@ def record_evidence(
187
187
  return state
188
188
 
189
189
 
190
- def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
191
- """Lit un etat coherent, ou None. Un etat produit par devflow 6.x ou 7.x
192
- leve ``LegacySchemaError``."""
193
- try:
194
- data = json.loads(path.read_text(encoding="utf-8"))
195
- except (OSError, UnicodeDecodeError, json.JSONDecodeError):
196
- return None
197
- try:
198
- check_schema_version("workflow-state", path, data)
199
- except LegacySchemaError:
200
- raise
201
- except ValueError:
202
- return None
203
- if (
204
- isinstance(data, dict)
205
- and data.get("schemaVersion") == ADAPTIVE_STATE_SCHEMA_VERSION
206
- and data.get("currentActivity") != "done"
207
- and data.get("activityBase") is None
208
- ):
209
- # Etat sans base : aucune base n'est reconstruite (rupture 8.0.0).
210
- raise legacy_workflow_state_error(path, data["schemaVersion"])
211
- try:
212
- validate_contract_payload("workflow-state", data)
213
- state = AdaptiveWorkflowState(**data)
214
- except (TypeError, ContractSchemaError):
215
- return None
190
+ class StateInvariantError(ValueError):
191
+ """Etat refuse a l'ecriture : il viole un verrou de la lecture."""
192
+
193
+
194
+ def state_invariant_violation(state: AdaptiveWorkflowState) -> str | None:
195
+ """Verrou metier viole par l'etat, ou None : seule definition, partagee par
196
+ la lecture et l'ecriture. Le texte commence par le nom stable du verrou."""
216
197
  if (
217
198
  not isinstance(state.activities, list)
218
199
  or not all(isinstance(item, str) for item in state.activities)
@@ -221,7 +202,10 @@ def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
221
202
  or not isinstance(state.completedActivities, list)
222
203
  or not all(isinstance(item, str) for item in state.completedActivities)
223
204
  or not isinstance(state.evidence, dict)
224
- or not all(
205
+ ):
206
+ return "types : activites, preuves ou activites completees mal typees"
207
+ for key, entry in state.evidence.items():
208
+ if not (
225
209
  isinstance(key, str)
226
210
  and isinstance(entry, dict)
227
211
  and isinstance(entry.get("recordedAt"), str)
@@ -230,12 +214,10 @@ def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
230
214
  and entry.get("outcome") in {"passed", "failed"}
231
215
  and (entry.get("source") in AGENT_SOURCES) == ("agent" in entry)
232
216
  and (entry.get("source") == HUMAN_SOURCE) == ("reviewer" in entry)
233
- for key, entry in state.evidence.items()
234
- )
235
- ):
236
- return None
217
+ ):
218
+ return f"evidence-entry : preuve {key} mal formee"
237
219
  if state.profile not in PROFILE_RANK:
238
- return None
220
+ return f"profile : profil inconnu {state.profile}"
239
221
  try:
240
222
  route = compute_route(
241
223
  state.profile,
@@ -243,36 +225,97 @@ def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
243
225
  guardrails=state.guardrails,
244
226
  )
245
227
  except ValueError:
246
- return None
228
+ return f"route : aucune route pour le profil {state.profile}"
229
+ if state.schemaVersion != ADAPTIVE_STATE_SCHEMA_VERSION:
230
+ return f"schema-version : version {state.schemaVersion} inattendue"
247
231
  if (
248
- state.schemaVersion != ADAPTIVE_STATE_SCHEMA_VERSION
249
- or state.activities != list(route.activities)
232
+ state.activities != list(route.activities)
250
233
  or state.requiredEvidence != list(route.requiredEvidence)
251
- or state.status not in {"active", "completed"}
252
- or state.currentActivity not in state.activities
253
- or not set(state.completedActivities) <= set(state.activities)
254
- or not set(state.evidence) <= set(state.requiredEvidence)
255
234
  ):
256
- return None
235
+ return "route : activites ou preuves differentes de la route du profil"
236
+ if state.status not in {"active", "completed"}:
237
+ return f"status : statut inconnu {state.status}"
238
+ if state.currentActivity not in state.activities:
239
+ return f"current-activity : {state.currentActivity} hors de la route"
240
+ if not set(state.completedActivities) <= set(state.activities):
241
+ return "completed-activities : activite completee hors de la route"
242
+ unexpected = sorted(set(state.evidence) - set(state.requiredEvidence))
243
+ if unexpected:
244
+ return f"evidence-ids : preuve non attendue {', '.join(unexpected)}"
257
245
  current_index = state.activities.index(state.currentActivity)
258
246
  expected_completed = state.activities[:current_index]
259
247
  if state.status == "active" and state.completedActivities != expected_completed:
260
- return None
248
+ return (
249
+ "completed-activities : les activites completees ne sont pas celles "
250
+ f"qui precedent {state.currentActivity}"
251
+ )
261
252
  for gated_activity in ("verify", "review", "rollback-check"):
262
253
  if gated_activity not in state.completedActivities:
263
254
  continue
264
- if any(
265
- item not in state.evidence
266
- or state.evidence[item].get("source") not in accepted_evidence_sources(item)
267
- or state.evidence[item].get("outcome") != "passed"
268
- for item in gate_evidence(state, gated_activity)
269
- ):
270
- return None
255
+ for item in gate_evidence(state, gated_activity):
256
+ if (
257
+ item not in state.evidence
258
+ or state.evidence[item].get("source")
259
+ not in accepted_evidence_sources(item)
260
+ or state.evidence[item].get("outcome") != "passed"
261
+ ):
262
+ return (
263
+ f"gate-evidence : {gated_activity} est completee sans la preuve "
264
+ f"{item} en passed d'une source acceptee"
265
+ )
271
266
  if state.status == "completed" and (
272
267
  state.currentActivity != "done"
273
268
  or state.completedActivities != state.activities
274
269
  or any(item not in state.evidence for item in state.requiredEvidence)
275
270
  ):
271
+ return "completed-state : parcours termine sans toutes ses activites et preuves"
272
+ return None
273
+
274
+
275
+ def adaptive_state_violation(path: Path) -> str | None:
276
+ """Verrou viole par un etat conforme au schema, pour nommer un refus de lecture."""
277
+ try:
278
+ data = json.loads(path.read_text(encoding="utf-8"))
279
+ state = AdaptiveWorkflowState(**data)
280
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError, TypeError):
281
+ return None
282
+ violation = state_invariant_violation(state)
283
+ if violation is not None:
284
+ return violation
285
+ try:
286
+ validate_state_bases(state, feature_path=_state_feature_path(path))
287
+ except ValueError as exc:
288
+ return f"bases : {exc}"
289
+ return None
290
+
291
+
292
+ def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
293
+ """Lit un etat coherent, ou None. Un etat produit par devflow 6.x ou 7.x
294
+ leve ``LegacySchemaError``."""
295
+ try:
296
+ data = json.loads(path.read_text(encoding="utf-8"))
297
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
298
+ return None
299
+ try:
300
+ check_schema_version("workflow-state", path, data)
301
+ except LegacySchemaError:
302
+ raise
303
+ except ValueError:
304
+ return None
305
+ if (
306
+ isinstance(data, dict)
307
+ and data.get("schemaVersion") == ADAPTIVE_STATE_SCHEMA_VERSION
308
+ and data.get("currentActivity") != "done"
309
+ and data.get("activityBase") is None
310
+ ):
311
+ # Etat sans base : aucune base n'est reconstruite (rupture 8.0.0).
312
+ raise legacy_workflow_state_error(path, data["schemaVersion"])
313
+ try:
314
+ validate_contract_payload("workflow-state", data)
315
+ state = AdaptiveWorkflowState(**data)
316
+ except (TypeError, ContractSchemaError):
317
+ return None
318
+ if state_invariant_violation(state) is not None:
276
319
  return None
277
320
  try:
278
321
  validate_state_bases(state, feature_path=_state_feature_path(path))
@@ -309,7 +352,13 @@ def workflow_lock(feature_dir: Path) -> Iterator[Path]:
309
352
 
310
353
 
311
354
  def write_adaptive_state(path: Path, state: AdaptiveWorkflowState) -> None:
355
+ """Ecrit un etat que la lecture accepte ; sinon leve sans toucher au fichier."""
312
356
  validate_state_bases(state, feature_path=_state_feature_path(path))
357
+ violation = state_invariant_violation(state)
358
+ if violation is not None:
359
+ raise StateInvariantError(
360
+ f"etat incoherent non ecrit, verrou viole : {violation}"
361
+ )
313
362
  state.updatedAt = _now_iso()
314
363
  payload = state.to_dict()
315
364
  validate_contract_payload("workflow-state", payload)
@@ -15,6 +15,7 @@ from pathlib import Path
15
15
 
16
16
  from devflow_cli.core.adaptive_workflow import (
17
17
  AdaptiveWorkflowState,
18
+ StateInvariantError,
18
19
  complete_activity,
19
20
  guardrails_active,
20
21
  read_adaptive_state,
@@ -545,7 +546,7 @@ def run_automatic_verification(
545
546
  preparation_payload, prepare_contract
546
547
  )
547
548
  if preparation.status != "completed":
548
- raise ValueError("prepare-result n'est pas complete")
549
+ raise ValueError(f"{plan_activity}-result n'est pas complete")
549
550
  planned_prefix = "PREP" if state.profile == "standard" else "PLAN"
550
551
  planned_commands = []
551
552
  for item in preparation.validationPlan:
@@ -920,7 +921,10 @@ def run_automatic_verification(
920
921
  "transition-refused",
921
922
  f"base de l'activite {latest.currentActivity} non enregistree : {exc}",
922
923
  )
923
- write_adaptive_state(state_file, latest)
924
+ try:
925
+ write_adaptive_state(state_file, latest)
926
+ except StateInvariantError as exc:
927
+ return AutomaticVerificationOutcome(False, "transition-refused", str(exc))
924
928
  next_activity = latest.currentActivity
925
929
  safe_refresh_worklog(feature_dir)
926
930
  detail = (
@@ -1011,6 +1015,7 @@ def _inspect_diff(project_root: Path, changed_files: list[str]) -> dict:
1011
1015
  if missing:
1012
1016
  findings.append(
1013
1017
  "fichiers implementes absents du diff courant : " + ", ".join(missing)
1018
+ + " ; cause possible : lot commite entre implement et verify"
1014
1019
  )
1015
1020
 
1016
1021
  diff_result = run_verifications(
@@ -38,7 +38,13 @@ class LegacySchemaError(ValueError):
38
38
  ) -> None:
39
39
  self.path = path
40
40
  self.version = version
41
- if producer == "7.x":
41
+ if producer == "no-base":
42
+ message = (
43
+ f"{path} : schemaVersion {version} : l'etat n'a pas de base d'activite "
44
+ "valide (activityBase absente ou nulle) ; aucune base n'est reconstruite ; "
45
+ "recreez le parcours"
46
+ )
47
+ elif producer == "7.x":
42
48
  message = (
43
49
  f"{path} : schemaVersion {version} : parcours cree avec devflow 7.x ; "
44
50
  f"terminez-le avec devflow 7.x ({install}) ou recreez-le ; aucune migration"
@@ -46,8 +52,8 @@ class LegacySchemaError(ValueError):
46
52
  else:
47
53
  message = (
48
54
  f"{path} : schemaVersion {version} produit par devflow {producer}, non "
49
- f"lisible par devflow 8 ; terminer ce parcours avec devflow {producer} "
50
- f"({install})"
55
+ f"lisible par devflow 8 ; le terminer ou le consulter avec "
56
+ f"devflow {producer} ({install})"
51
57
  )
52
58
  super().__init__(message)
53
59
 
@@ -58,7 +64,9 @@ WORKFLOW_STATE_7X_VERSION = 5
58
64
 
59
65
 
60
66
  def legacy_workflow_state_error(path: Path, version: int) -> LegacySchemaError:
61
- """Refus de rupture d'un etat adaptatif cree avec devflow 7.x (sans base)."""
67
+ """Refus de rupture d'un etat adaptatif sans base (schema 5 de 7.x, ou schema 6 sans base)."""
68
+ if version != WORKFLOW_STATE_7X_VERSION:
69
+ return LegacySchemaError(path, version, producer="no-base")
62
70
  return LegacySchemaError(path, version, producer="7.x", install=LEGACY_7X_INSTALL)
63
71
 
64
72
 
@@ -442,7 +442,7 @@ def render_export(
442
442
  treated: dict[str, dict[str, Any]],
443
443
  home: Path,
444
444
  ) -> str:
445
- """Markdown au format de FEEDBACK.md ; chemins normalises, structure neutralisee."""
445
+ """Markdown de l'export (format de l'ancien FEEDBACK.md, fichier supprime) ; chemins normalises, structure neutralisee."""
446
446
  out = ["# Retours d'usage devflow — export", ""]
447
447
  if not entries:
448
448
  out += ["Aucun retour.", ""]
@@ -42,6 +42,7 @@ OBSOLETE_CLAUDE_RESOURCES = {
42
42
  "devflow.tasks.md",
43
43
  ),
44
44
  "agents": ("devflow-autofix.md", "devflow-review.md"),
45
+ "templates": ("research-template.md",),
45
46
  }
46
47
 
47
48
 
@@ -6,11 +6,17 @@ import json
6
6
  import os
7
7
  import shutil
8
8
  import tempfile
9
+ from dataclasses import replace
9
10
  from datetime import datetime, timezone
10
11
  from pathlib import Path
11
12
 
12
13
  from devflow_cli.core.activity_base import BaseObservationError, files_since_activity_base
13
- from devflow_cli.core.adaptive_workflow import AdaptiveWorkflowState
14
+ from devflow_cli.core.adaptive_workflow import (
15
+ AdaptiveWorkflowState,
16
+ StateInvariantError,
17
+ gate_evidence,
18
+ state_invariant_violation,
19
+ )
14
20
  from devflow_cli.core.assessment import compute_route
15
21
  from devflow_cli.utils.paths import activity_attempt_file, activity_raw_file
16
22
 
@@ -41,6 +47,36 @@ def reopen_activity(
41
47
  "la cible doit etre une activite deja completee avant l'activite courante"
42
48
  )
43
49
 
50
+ activities = list(
51
+ compute_route(
52
+ state.profile,
53
+ contract_change=state.contractChange,
54
+ guardrails=state.guardrails,
55
+ ).activities
56
+ )
57
+ completed = activities[:activities.index(target)]
58
+ reopened = replace(
59
+ state,
60
+ activities=activities,
61
+ currentActivity=target,
62
+ completedActivities=completed,
63
+ evidence={},
64
+ status="active",
65
+ )
66
+ # Seules restent les preuves des gates des activites encore completees.
67
+ kept = {
68
+ item for activity in completed for item in gate_evidence(reopened, activity)
69
+ }
70
+ reopened.evidence = {
71
+ key: entry for key, entry in state.evidence.items() if key in kept
72
+ }
73
+ # Verrous controles avant toute trace : un refus n'ecrit ni journal ni archive.
74
+ violation = state_invariant_violation(reopened)
75
+ if violation is not None:
76
+ raise StateInvariantError(
77
+ f"reouverture refusee, verrou viole : {violation}"
78
+ )
79
+
44
80
  log_file = feature_dir / "reopen-log.json"
45
81
  payload = _read_log(log_file, state.issueId)
46
82
  sequence = len(payload["entries"]) + 1
@@ -63,17 +99,11 @@ def reopen_activity(
63
99
  payload["entries"].append(entry)
64
100
  _write_json_atomic(log_file, payload)
65
101
 
66
- state.activities = list(
67
- compute_route(
68
- state.profile,
69
- contract_change=state.contractChange,
70
- guardrails=state.guardrails,
71
- ).activities
72
- )
73
- state.currentActivity = target
74
- state.completedActivities = state.activities[:state.activities.index(target)]
75
- state.evidence = {}
76
- state.status = "active"
102
+ state.activities = reopened.activities
103
+ state.currentActivity = reopened.currentActivity
104
+ state.completedActivities = reopened.completedActivities
105
+ state.evidence = reopened.evidence
106
+ state.status = reopened.status
77
107
  return entry
78
108
 
79
109
 
@@ -31,6 +31,7 @@ def refresh_worklog(feature_dir: Path) -> Path:
31
31
 
32
32
  result = _read_json(activity_result_file(feature_dir, "implement"), required=False)
33
33
  review = _read_json(activity_result_file(feature_dir, "review"), required=False)
34
+ risks = latest_remaining_risks(feature_dir, state.activities)
34
35
  rollback = _read_json(
35
36
  activity_result_file(feature_dir, "rollback-check"), required=False
36
37
  )
@@ -71,11 +72,28 @@ def refresh_worklog(feature_dir: Path) -> Path:
71
72
  execution,
72
73
  observation_warnings,
73
74
  approvals,
75
+ risks,
74
76
  )
75
77
  _write_atomic(worklog_file, managed + manual)
76
78
  return worklog_file
77
79
 
78
80
 
81
+ def latest_remaining_risks(feature_dir: Path, activities: list[str]) -> list[str]:
82
+ """Risques du resultat le plus recent de la route (dernier lisible), sans doublon."""
83
+ for activity in reversed(activities):
84
+ payload = _read_json(activity_result_file(feature_dir, activity), required=False)
85
+ if not payload:
86
+ continue
87
+ values = payload.get("remainingRisks", [])
88
+ risks: list[str] = []
89
+ if isinstance(values, list):
90
+ for value in values:
91
+ if isinstance(value, str) and value not in risks:
92
+ risks.append(value)
93
+ return risks
94
+ return []
95
+
96
+
79
97
  def safe_refresh_worklog(feature_dir: Path) -> Path | None:
80
98
  """Actualise la vue derivee sans compromettre une transition deja validee."""
81
99
  try:
@@ -99,6 +117,7 @@ def _render_managed(
99
117
  execution: dict[str, Any],
100
118
  observation_warnings: list[str] | None = None,
101
119
  approvals: list[str] | None = None,
120
+ risks: list[str] | None = None,
102
121
  ) -> str:
103
122
  issue_id = state["issueId"]
104
123
  summary = assessment.get("summary")
@@ -117,14 +136,7 @@ def _render_managed(
117
136
  checks.extend(_verification_lines(security, {}))
118
137
  checks.extend(_activity_evidence_lines(review))
119
138
  checks.extend(_activity_evidence_lines(rollback))
120
- risk_source = rollback or review or result
121
- risks = (
122
- risk_source.get("remainingRisks", [])
123
- if isinstance(risk_source, dict)
124
- else []
125
- )
126
- if not isinstance(risks, list) or not all(isinstance(item, str) for item in risks):
127
- risks = []
139
+ risks = risks or []
128
140
 
129
141
  lines = [
130
142
  f"# Worklog — {issue_id}",
@@ -343,7 +355,7 @@ def _manual_suffix(path: Path) -> str:
343
355
  def _read_json(path: Path, *, required: bool = True) -> dict[str, Any]:
344
356
  try:
345
357
  payload = json.loads(path.read_text(encoding="utf-8"))
346
- except (OSError, json.JSONDecodeError):
358
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
347
359
  if required:
348
360
  raise ValueError(f"artefact JSON invalide ou absent : {path.name}")
349
361
  return {}
@@ -12,13 +12,17 @@ interpréter de `state.json`.
12
12
  1. Lire `$ARGUMENTS` et extraire l'identifiant de feature, l'agent demandé
13
13
  (`claude` par défaut) et la cible `--until` (`done` par défaut).
14
14
  2. Exécuter `devflow status ISSUE_ID`.
15
- 3. Si la feature n'existe pas, demander à l'utilisateur de lancer
16
- `devflow feature ISSUE_ID`.
17
- 4. Si elle n'est pas évaluée, demander les signaux nécessaires puis lancer
15
+ 3. Si la feature n'existe pas, exécuter
16
+ `devflow feature ISSUE_ID --short-name NOM` (nom court tiré du titre de
17
+ l'issue) et annoncer la branche et le dossier créés.
18
+ 4. Si la feature n'est pas encore évaluée et que `need.md` n'existe pas dans
19
+ son dossier, le rédiger à partir de l'issue et de la demande. En cas de
20
+ doute sur le besoin, poser la question avant de l'écrire.
21
+ 5. Si elle n'est pas évaluée, demander les signaux nécessaires puis lancer
18
22
  `devflow assess ISSUE_ID` avec des options explicites.
19
- 5. Si elle est évaluée mais non démarrée, lancer
23
+ 6. Si elle est évaluée mais non démarrée, lancer
20
24
  `devflow adaptive start ISSUE_ID`.
21
- 6. Enchaîner les activités avec
25
+ 7. Enchaîner les activités avec
22
26
  `devflow adaptive run ISSUE_ID --agent AGENT --until CIBLE`.
23
27
  `--until` inclut l'activité ciblée.
24
28
  La CLI s'arrête au premier refus. Distinguer alors deux cas :
@@ -31,7 +35,7 @@ interpréter de `state.json`.
31
35
  CLI plante (traceback, commande inconnue), sortir du parcours : terminer
32
36
  la feature hors Devflow, clôturer par un `bilan` dont `--what` indique
33
37
  l'abandon et sa cause, et indiquer cette cause dans le compte rendu.
34
- 7. Afficher ensuite `devflow adaptive status ISSUE_ID`.
38
+ 8. Afficher ensuite `devflow adaptive status ISSUE_ID`.
35
39
 
36
40
  ## Retours d'usage
37
41
 
@@ -46,7 +46,7 @@
46
46
 
47
47
  ## Gates obligatoires
48
48
 
49
- Les gates sont vérifiées à chaque `/devflow.plan`. Une violation non justifiée est BLOQUANTE.
49
+ Les gates sont vérifiées avant l'implémentation. Une violation non justifiée est BLOQUANTE.
50
50
 
51
51
  ### Gate 1 — Simplicité
52
52
 
@@ -64,7 +64,7 @@ Les gates sont vérifiées à chaque `/devflow.plan`. Une violation non justifi
64
64
  | Conventions | Suivre celles du codebase existant |
65
65
  | Modèle de données | Représentation unique, pas de duplication |
66
66
 
67
- ### Gate 3 — Tests d'abord
67
+ ### Gate 3 — Tests
68
68
 
69
69
  | Critère | Règle |
70
70
  |---------|-------|
@@ -80,6 +80,6 @@ Les gates sont vérifiées à chaque `/devflow.plan`. Une violation non justifi
80
80
 
81
81
  ## Suivi des dérogations
82
82
 
83
- | Date | Article | Dérogation | Justification |
84
- |------|---------|------------|---------------|
85
- | | | | |
83
+ | Date | Article | Dérogation | Justification | Échéance |
84
+ |------|---------|------------|---------------|----------|
85
+ | | | | | |
@@ -14,14 +14,14 @@
14
14
  |------|--------|-------------|
15
15
  | Simplicité | | |
16
16
  | Patterns existants | | |
17
- | Tests d'abord | | |
17
+ | Tests | | |
18
18
 
19
19
  ### Dérogations
20
20
 
21
21
  <!-- Si une gate est violée, justifier ici. Sans justification = BLOQUANT -->
22
22
 
23
- | Article | Dérogation | Justification |
24
- |---------|------------|---------------|
23
+ | Article | Dérogation | Justification | Échéance |
24
+ |---------|------------|---------------|----------|
25
25
 
26
26
  ### Complexity Tracking
27
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devflow-cli
3
- Version: 8.4.1
3
+ Version: 8.5.1
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
@@ -65,7 +65,8 @@ devflow init --ai codex
65
65
  Après chaque mise à jour de la CLI, lancer `devflow upgrade` : il met à jour
66
66
  les commandes `/devflow` installées dans `~/.claude`, que l'installation de la
67
67
  CLI ne touche pas. `--refresh` évite qu'`uv` ignore une version tout juste
68
- publiée. Tant que les commandes installées diffèrent de la CLI,
68
+ publiée. Juste après une publication, l'installation peut échouer pendant une
69
+ à quatre minutes, le temps que l'index se propage : réessayer. Tant que les commandes installées diffèrent de la CLI,
69
70
  `devflow status` affiche un avertissement avec cette commande ; rien n'est
70
71
  modifié sans vous.
71
72
 
@@ -1,10 +1,10 @@
1
- devflow_cli/__init__.py,sha256=MQEclHX75ybzzUEyJ3iAVMLUfzOpBzaQ_E5gi0d-TUc,925
1
+ devflow_cli/__init__.py,sha256=yUxpKLKephIrgPv-hHWMO6tznMd47ejm6oL_zFuipxA,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=5D8K4aevxMlehsE65wnEn6ny6g_4A1Bes_OBVIUrbSM,71520
7
+ devflow_cli/commands/adaptive.py,sha256=8zYUrtPxU3NRpQ4yv0ScHmn8IjQfnx4U24qMW8Y4_Uo,72797
8
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
@@ -24,24 +24,24 @@ 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=YId_tUxiL6TYmnVpG2VYGkA31nPjIrskx6Dof27XR6E,16696
27
- devflow_cli/core/activity_contracts.py,sha256=n9arBw-SWEFqTlCDNed6lV4WolkZKAvmQg1qcfk57ss,39036
28
- devflow_cli/core/adaptive_runner.py,sha256=BU3LZzM2f7dNvgKm_ul0CZYIs8z7WN1hM--TrF9o07s,48852
29
- devflow_cli/core/adaptive_workflow.py,sha256=F4uV5okQpFidZmneVZwW52XAO7gAmtuBAqBO9K9q7Uk,12173
27
+ devflow_cli/core/activity_contracts.py,sha256=ui-9JYwtZYcjEVppgcD5YNW_O7gkxxCkfXlIevLrJ-c,39295
28
+ devflow_cli/core/adaptive_runner.py,sha256=ePnwJLPG2hpt76yE6WTTzXgljb7jkpVWAyTZRRdKVhs,48856
29
+ devflow_cli/core/adaptive_workflow.py,sha256=CGfKOzCDwKGlp2ez9IDgBq6-FNjFYRhpJagZORpBGSc,14562
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=Ntsy2eQKLRl_hUD27V9NRBLavyhLxxkEcbPySKmd4W4,42337
33
+ devflow_cli/core/automatic_evidence.py,sha256=wBsOwQvwNSETefSMkZK6tcb3aDErgbVK9WANknT8HN4,42592
34
34
  devflow_cli/core/catalog.py,sha256=nLPc78JkbISaSxJnHlWiMi_lvepaumbfwcjgLuVy7Zc,3102
35
- devflow_cli/core/contract_schemas.py,sha256=-7SlJCwQ31OjPfwA-tP4ctgbp9hxQAVwNvvOV09Hoho,6313
35
+ devflow_cli/core/contract_schemas.py,sha256=TOGXwpNmm_MEzDkxR1wYvcCqASv1j4C1vxT6RjDFMiM,6744
36
36
  devflow_cli/core/docgen.py,sha256=47JoVDWetkcctgHwJZC67AVebOKgl10VB9hZ3Xr63mo,3908
37
- devflow_cli/core/feedback.py,sha256=A4yz2HqD5umMqrY4YQaqugxZM2tnS7NkXOObo1Nbm4M,18927
37
+ devflow_cli/core/feedback.py,sha256=4LJk-stAz_1toVVK4kGlWs6yUIQEEjjXVLglDXc37JU,18965
38
38
  devflow_cli/core/git.py,sha256=gsDfT-hDNtMQFRUl1WzZSqltrdwZQ2BJBcCIh4TwDkA,2780
39
39
  devflow_cli/core/hooks.py,sha256=nFPaziFJgeZgAPKPxbTibrB_0MJAST3cAmg3gtcvDBQ,8067
40
- devflow_cli/core/installer.py,sha256=MfcGZKoOcQc2qviNnGShoD0P_MqOgRuVHX7YwCrG2vw,6259
40
+ devflow_cli/core/installer.py,sha256=fmfaJs5ApkhRmQi1B4vz52iAHBtqgeyBoAnE8CXAzJI,6303
41
41
  devflow_cli/core/manifest.py,sha256=h058_m5-HJrYsNUdgfwnKts2riHiYPXUwMzPImHOs2g,7023
42
42
  devflow_cli/core/operational.py,sha256=11iLTbyZF0ZjYTQ5HDmVCaw4bpbXpLtH2v9f1WQOrYg,1228
43
43
  devflow_cli/core/redaction.py,sha256=o_1MDtCyvRLrk_dWlV6_7E6_mPVB1B5a0cCfTBj9j0Q,1973
44
- devflow_cli/core/remediation.py,sha256=AI5fBs-F8xF3uaxgYJDNJHKxJ7V5obEtnfptMm351AU,8426
44
+ devflow_cli/core/remediation.py,sha256=gvoemlDDC6GmIwAu4BLWnrBTJ240Z6cFCjyxSVrX61c,9407
45
45
  devflow_cli/core/result_template.py,sha256=XfrjUlSUDYJxE2nN8EJjHCXq1l06hn-wW1gDn1c1rBA,2493
46
46
  devflow_cli/core/run_progress.py,sha256=hawaMo5sGcxvugexeryMWanFajdf46SY6b37O7dToZM,1855
47
47
  devflow_cli/core/runtime_backup.py,sha256=dowz6a1vcJsqkl-m9wGhS4F6XGfsU2rwSNJvIZMJjhI,4345
@@ -53,7 +53,7 @@ devflow_cli/core/validation_approvals.py,sha256=3AHOpnW3BtY3BQrRZr38K7VNYRvbxGNh
53
53
  devflow_cli/core/validators.py,sha256=fREjEx2fBW36q3vB6WJNqoGUM9ejYlQUXie5A9lNuPs,7440
54
54
  devflow_cli/core/verification.py,sha256=rvbdAdLyrZYb20oauti7D8nV5TDFBIf_8-hqLahfRag,23576
55
55
  devflow_cli/core/waivers.py,sha256=9XXzMmoWCn-2Dc3486WhZwtGJQLQPXjP0qAaLuL_3qU,13299
56
- devflow_cli/core/worklog.py,sha256=GkaXCsx22jntcWuC75OystUoqb5AlzBbXXLYH581Mwg,14164
56
+ devflow_cli/core/worklog.py,sha256=i4zgJIlS1OSIBMrhT1AKU97bh7IHAhNYbnr4eJVGv_Y,14692
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
@@ -67,7 +67,7 @@ devflow_cli/utils/strings.py,sha256=bfFXCwBDnFxshgZOYfy6mtysnjiaWO1wFnqBf7HLg4o,
67
67
  devflow_cli/resources/commands/devflow.context.md,sha256=SdvZ286cJKkgQIKYwnjd30v5zDeEXTFBhpEEsEOInoc,2048
68
68
  devflow_cli/resources/commands/devflow.extension.md,sha256=w7hoFfxRS0y-3rsvUmQDx1fvZhtE9KhANmRgFDwYVaA,2373
69
69
  devflow_cli/resources/commands/devflow.init.md,sha256=wplv3IiJrNkUoPXGZtOw9k6m3dzXBQDYAcPPN8d3EF8,2011
70
- devflow_cli/resources/commands/devflow.md,sha256=ojhLIwQvl0pS7Zxct6RS2ihpiT9f9-F1yH7jRTO47Ng,2689
70
+ devflow_cli/resources/commands/devflow.md,sha256=EZ1yo5Hx7lnHX8G1EB0U6V4wNJ4kzb8nSmXI2PneY7s,2984
71
71
  devflow_cli/resources/commands/devflow.regen.md,sha256=yjbMNAt6k0y6M9Y5HZDhCRgj_tb33Om6_-5OijmD5Bw,1169
72
72
  devflow_cli/resources/commands/devflow.status.md,sha256=tqjQ1IboU99ztG6C4nunBHGJCvMn3qDqx3AoF4SPjCk,338
73
73
  devflow_cli/resources/extensions/EXTENSION-API-REFERENCE.md,sha256=1wuAV-K8WI0BJ6SJ0HViieSHe3zazlfHemkKDT7bbKM,4478
@@ -76,12 +76,12 @@ devflow_cli/resources/extensions/catalog.json,sha256=O2Uqgkvudf6JXIMQFKEk8d1MuA1
76
76
  devflow_cli/resources/extensions/template/example-command.md,sha256=3qlpEOzbYQdL79iucaZ5wU5zDX2Q_pxz0Fg9jSF-B84,1020
77
77
  devflow_cli/resources/extensions/template/example-extension.json,sha256=Az9Ihq3ANlWmUzWZDfttaUCUmfesOFH4IbYq23CmWC4,416
78
78
  devflow_cli/resources/templates/agent-context-template.md,sha256=D5wd2hcxGrVR0sIE2JEDUPcsFHoZWJHf5m8LuD12J9Y,520
79
- devflow_cli/resources/templates/constitution-template.md,sha256=r2UkG3f9l-zlWJ4yt5aIXhuwj37zdF9VbmqPC9J1uXA,2086
79
+ devflow_cli/resources/templates/constitution-template.md,sha256=uSzNuwF-xIRx8cHt9gloynRjNgDGmnN5z2Xck6fCOsI,2102
80
80
  devflow_cli/resources/templates/contracts-template.md,sha256=dx7slFV_5mG6BJ-OfQ8rfRXSid-ZY_NKmVjllmN2fAA,1768
81
81
  devflow_cli/resources/templates/data-model-template.md,sha256=RliumtOY20L-Z0grx7tfvg9pbulGDKR6EN7k-7zKNUU,1015
82
82
  devflow_cli/resources/templates/devflow-hooks-template.yml,sha256=dGjsh4PLEkCjwNWWhMOI0F7Md-Edu3oYkHtmaPLlg3o,1205
83
83
  devflow_cli/resources/templates/extension-manifest-template.json,sha256=GVCE2ZFObN93dlMRyf7UUawpmy5GSu11Yj11UPfqTBc,226
84
- devflow_cli/resources/templates/plan-template.md,sha256=zAhlp5DVy8q3A5zexZVhId4WDbvmEgBgWUNctsWjGsk,2609
84
+ devflow_cli/resources/templates/plan-template.md,sha256=0mZ0hBCbP8mYdAjzDzrJY4bZDhAXvJNHynm_mKu6Vmk,2625
85
85
  devflow_cli/resources/templates/quickstart-template.md,sha256=LkB7FfO7ie0-wD5NltR7BGDudNVOW5_huDvha6NcOsg,1226
86
86
  devflow_cli/resources/templates/spec-template.md,sha256=XiUSkj-GkzhN-kHO4p5i1u1thbSCgK1x-CIAHSso3Go,1918
87
87
  devflow_cli/resources/templates/tasks-template.md,sha256=PnWRAeok9MGTj1F30UeYu0njru7RsAOdj16Cgyd45Ww,2525
@@ -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.4.1.dist-info/METADATA,sha256=Z1ZlFp1ND90t29jlf-ofmvs_JOnZUOk7KMJB8M-HmEA,9846
94
- devflow_cli-8.4.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
- devflow_cli-8.4.1.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
96
- devflow_cli-8.4.1.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
97
- devflow_cli-8.4.1.dist-info/RECORD,,
93
+ devflow_cli-8.5.1.dist-info/METADATA,sha256=Vaqrf7U9UaMMnfl_sHbt_bK6B0WeyDBdaouxnd0E6Yo,9982
94
+ devflow_cli-8.5.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
95
+ devflow_cli-8.5.1.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
96
+ devflow_cli-8.5.1.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
97
+ devflow_cli-8.5.1.dist-info/RECORD,,