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 +1 -1
- devflow_cli/commands/adaptive.py +63 -38
- devflow_cli/core/activity_contracts.py +12 -5
- devflow_cli/core/adaptive_runner.py +1 -1
- devflow_cli/core/adaptive_workflow.py +97 -48
- devflow_cli/core/automatic_evidence.py +7 -2
- devflow_cli/core/contract_schemas.py +12 -4
- devflow_cli/core/feedback.py +1 -1
- devflow_cli/core/installer.py +1 -0
- devflow_cli/core/remediation.py +42 -12
- devflow_cli/core/worklog.py +21 -9
- devflow_cli/resources/commands/devflow.md +10 -6
- devflow_cli/resources/templates/constitution-template.md +5 -5
- devflow_cli/resources/templates/plan-template.md +3 -3
- {devflow_cli-8.4.1.dist-info → devflow_cli-8.5.1.dist-info}/METADATA +3 -2
- {devflow_cli-8.4.1.dist-info → devflow_cli-8.5.1.dist-info}/RECORD +19 -19
- {devflow_cli-8.4.1.dist-info → devflow_cli-8.5.1.dist-info}/WHEEL +0 -0
- {devflow_cli-8.4.1.dist-info → devflow_cli-8.5.1.dist-info}/entry_points.txt +0 -0
- {devflow_cli-8.4.1.dist-info → devflow_cli-8.5.1.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
devflow_cli/commands/adaptive.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
148
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ;
|
|
559
|
-
"
|
|
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(
|
|
864
|
+
raise ValueError(_EVIDENCE_SHAPE_MESSAGE)
|
|
858
865
|
if set(item) != {"id", "outcome", "report"}:
|
|
859
|
-
raise ValueError(
|
|
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(
|
|
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
|
-
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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("
|
|
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
|
-
|
|
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 == "
|
|
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
|
|
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
|
|
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
|
|
devflow_cli/core/feedback.py
CHANGED
|
@@ -442,7 +442,7 @@ def render_export(
|
|
|
442
442
|
treated: dict[str, dict[str, Any]],
|
|
443
443
|
home: Path,
|
|
444
444
|
) -> str:
|
|
445
|
-
"""Markdown
|
|
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.", ""]
|
devflow_cli/core/installer.py
CHANGED
devflow_cli/core/remediation.py
CHANGED
|
@@ -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
|
|
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 =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
devflow_cli/core/worklog.py
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
16
|
-
`devflow feature ISSUE_ID
|
|
17
|
-
|
|
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
|
-
|
|
23
|
+
6. Si elle est évaluée mais non démarrée, lancer
|
|
20
24
|
`devflow adaptive start ISSUE_ID`.
|
|
21
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
28
|
-
devflow_cli/core/adaptive_runner.py,sha256=
|
|
29
|
-
devflow_cli/core/adaptive_workflow.py,sha256=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
94
|
-
devflow_cli-8.
|
|
95
|
-
devflow_cli-8.
|
|
96
|
-
devflow_cli-8.
|
|
97
|
-
devflow_cli-8.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|