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