devflow-cli 6.1.0__py3-none-any.whl → 6.1.2__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 +2 -2
- devflow_cli/core/adaptive_runner.py +2 -2
- devflow_cli/core/remediation.py +2 -2
- devflow_cli/core/verification.py +57 -1
- devflow_cli/core/worktree_snapshot.py +31 -3
- {devflow_cli-6.1.0.dist-info → devflow_cli-6.1.2.dist-info}/METADATA +1 -1
- {devflow_cli-6.1.0.dist-info → devflow_cli-6.1.2.dist-info}/RECORD +11 -11
- {devflow_cli-6.1.0.dist-info → devflow_cli-6.1.2.dist-info}/WHEEL +0 -0
- {devflow_cli-6.1.0.dist-info → devflow_cli-6.1.2.dist-info}/entry_points.txt +0 -0
- {devflow_cli-6.1.0.dist-info → devflow_cli-6.1.2.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
devflow_cli/commands/adaptive.py
CHANGED
|
@@ -377,7 +377,7 @@ def run_adaptive(
|
|
|
377
377
|
attempt["contract"] = activity_contract
|
|
378
378
|
attempt["startedAt"] = _utc_now()
|
|
379
379
|
try:
|
|
380
|
-
before = snapshot_worktree(project_root)
|
|
380
|
+
before = snapshot_worktree(project_root, feature_dir)
|
|
381
381
|
with tempfile.TemporaryDirectory(prefix="devflow-adaptive-") as temp_dir:
|
|
382
382
|
temp_path = Path(temp_dir)
|
|
383
383
|
schema_file = temp_path / "output-schema.json"
|
|
@@ -403,7 +403,7 @@ def run_adaptive(
|
|
|
403
403
|
if output_file.is_file()
|
|
404
404
|
else execution.stdout
|
|
405
405
|
)
|
|
406
|
-
after = snapshot_worktree(project_root)
|
|
406
|
+
after = snapshot_worktree(project_root, feature_dir)
|
|
407
407
|
observed = tuple(changed_paths(before, after))
|
|
408
408
|
attempt["changedFileCount"] = len(observed)
|
|
409
409
|
audit_payload = {
|
|
@@ -296,7 +296,7 @@ def reconcile_current_activity(
|
|
|
296
296
|
raise ValueError(
|
|
297
297
|
"la tentative ne correspond pas au contrat de l'activite courante"
|
|
298
298
|
)
|
|
299
|
-
snapshot = snapshot_worktree(project_root_for_feature(feature_dir))
|
|
299
|
+
snapshot = snapshot_worktree(project_root_for_feature(feature_dir), feature_dir)
|
|
300
300
|
if snapshot.head != attempt.get("head") or snapshot.branch != attempt.get("branch"):
|
|
301
301
|
raise ValueError("HEAD ou la branche a change depuis la tentative")
|
|
302
302
|
drifted = sorted(
|
|
@@ -784,7 +784,7 @@ def _record_failed_attempt(
|
|
|
784
784
|
) -> None:
|
|
785
785
|
"""Consigne les faits d'une tentative refusee pour une reprise controlee."""
|
|
786
786
|
try:
|
|
787
|
-
snapshot = snapshot_worktree(project_root_for_feature(feature_dir))
|
|
787
|
+
snapshot = snapshot_worktree(project_root_for_feature(feature_dir), feature_dir)
|
|
788
788
|
except (OSError, ValueError):
|
|
789
789
|
return
|
|
790
790
|
payload = {
|
devflow_cli/core/remediation.py
CHANGED
|
@@ -208,7 +208,7 @@ def _active_attempt_files(feature_dir: Path, activity: str) -> list[str]:
|
|
|
208
208
|
or not all(isinstance(path, str) for path in carried)
|
|
209
209
|
):
|
|
210
210
|
raise StaleAttemptError(f"fiche de tentative {activity} invalide")
|
|
211
|
-
snapshot = snapshot_worktree(project_root_for_feature(feature_dir))
|
|
211
|
+
snapshot = snapshot_worktree(project_root_for_feature(feature_dir), feature_dir)
|
|
212
212
|
if snapshot.head != payload["head"] or snapshot.branch != payload["branch"]:
|
|
213
213
|
raise StaleAttemptError(
|
|
214
214
|
f"HEAD ou la branche a change depuis la tentative {activity}"
|
|
@@ -243,7 +243,7 @@ def _still_modified(feature_dir: Path, files: list[str]) -> list[str]:
|
|
|
243
243
|
if not files:
|
|
244
244
|
return []
|
|
245
245
|
try:
|
|
246
|
-
snapshot = snapshot_worktree(project_root_for_feature(feature_dir))
|
|
246
|
+
snapshot = snapshot_worktree(project_root_for_feature(feature_dir), feature_dir)
|
|
247
247
|
except (OSError, ValueError):
|
|
248
248
|
return files
|
|
249
249
|
return [path for path in files if path in snapshot.files]
|
devflow_cli/core/verification.py
CHANGED
|
@@ -61,6 +61,21 @@ SAFE_TASK_RE = re.compile(
|
|
|
61
61
|
)
|
|
62
62
|
PYTHON_RE = re.compile(r"^(?:python(?:\d+(?:\.\d+)*)?|py)$", re.IGNORECASE)
|
|
63
63
|
SAFE_PYTHON_MODULES = frozenset({"compileall", "pytest", "unittest"})
|
|
64
|
+
UV_RUN_FLAG_OPTIONS = frozenset({
|
|
65
|
+
"--all-extras", "--all-groups", "--frozen", "--isolated", "--locked", "--no-dev",
|
|
66
|
+
"--no-project", "--no-sync", "--offline", "-q", "--quiet",
|
|
67
|
+
})
|
|
68
|
+
UV_QUIET_OPTIONS = frozenset({"-q", "--quiet"})
|
|
69
|
+
UV_PYTHON_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+(\.[0-9]+)?$")
|
|
70
|
+
UV_RUN_NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
71
|
+
# Seules options de uv run a valeur acceptees : aucune ne change le code execute, le
|
|
72
|
+
# repertoire ni la source des paquets. La valeur est controlee pour ne jamais etre un chemin.
|
|
73
|
+
UV_RUN_VALUE_OPTIONS: Mapping[str, tuple[re.Pattern[str], str]] = {
|
|
74
|
+
"--python": (UV_PYTHON_VERSION_RE, "numéro de version, ex. 3.12"),
|
|
75
|
+
"-p": (UV_PYTHON_VERSION_RE, "numéro de version, ex. 3.12"),
|
|
76
|
+
"--extra": (UV_RUN_NAME_RE, "nom d'extra"),
|
|
77
|
+
"--group": (UV_RUN_NAME_RE, "nom de groupe"),
|
|
78
|
+
}
|
|
64
79
|
SHELL_EXECUTABLES = frozenset({
|
|
65
80
|
"bash", "cmd", "cmd.exe", "dash", "fish", "ksh", "powershell",
|
|
66
81
|
"powershell.exe", "pwsh", "sh", "zsh",
|
|
@@ -73,6 +88,11 @@ def validation_policy_guidance() -> list[str]:
|
|
|
73
88
|
"Executables de validation autorises par defaut : "
|
|
74
89
|
+ ", ".join(sorted(DEFAULT_ALLOWED_EXECUTABLES))
|
|
75
90
|
+ "; python est limite a -m compileall, pytest ou unittest.",
|
|
91
|
+
"uv run accepte avant l'outil uniquement les options "
|
|
92
|
+
+ ", ".join(sorted(UV_RUN_FLAG_OPTIONS | UV_RUN_VALUE_OPTIONS.keys()))
|
|
93
|
+
+ ", en forme separee (--python 3.12, pas --python=3.12); --python/-p "
|
|
94
|
+
"exige un numero de version, jamais un chemin d'interpreteur; toute autre "
|
|
95
|
+
"option de uv run est refusee.",
|
|
76
96
|
"Maven et Gradle acceptent uniquement les taches test, check, lint, verify ou build; "
|
|
77
97
|
"mvn clean verify est refuse (clean est hors politique).",
|
|
78
98
|
"Aucun shell implicite ni operateur |, &&, ; ou redirection; "
|
|
@@ -352,9 +372,45 @@ def _validate_allowed_shape(
|
|
|
352
372
|
if executable == "uv":
|
|
353
373
|
if subcommand != "run" or len(args) < 3:
|
|
354
374
|
raise ValueError(f"{validation.id}: seul 'uv run <outil>' est autorisé")
|
|
375
|
+
# Les options de uv run sont analysees ici et jamais comparees a ``explicit`` :
|
|
376
|
+
# un opt-in sur un nom d'option ne doit pas court-circuiter l'outil imbrique.
|
|
377
|
+
index = 2
|
|
378
|
+
quiet_seen = False
|
|
379
|
+
while index < len(args) and args[index].startswith("-"):
|
|
380
|
+
option = args[index]
|
|
381
|
+
if option in UV_RUN_VALUE_OPTIONS:
|
|
382
|
+
pattern, expected = UV_RUN_VALUE_OPTIONS[option]
|
|
383
|
+
if index + 1 >= len(args):
|
|
384
|
+
raise ValueError(
|
|
385
|
+
f"{validation.id}: option de 'uv run' '{option}' sans valeur; "
|
|
386
|
+
f"attendu : {expected}"
|
|
387
|
+
)
|
|
388
|
+
if not pattern.fullmatch(args[index + 1]):
|
|
389
|
+
raise ValueError(
|
|
390
|
+
f"{validation.id}: valeur invalide pour l'option de 'uv run' "
|
|
391
|
+
f"'{option}'; attendu : {expected}"
|
|
392
|
+
)
|
|
393
|
+
index += 2
|
|
394
|
+
continue
|
|
395
|
+
if option not in UV_RUN_FLAG_OPTIONS:
|
|
396
|
+
accepted = ", ".join(sorted(UV_RUN_FLAG_OPTIONS | UV_RUN_VALUE_OPTIONS.keys()))
|
|
397
|
+
raise ValueError(
|
|
398
|
+
f"{validation.id}: option de 'uv run' refusée '{option.split('=', 1)[0]}'; "
|
|
399
|
+
f"options acceptées : {accepted}"
|
|
400
|
+
)
|
|
401
|
+
if option in UV_QUIET_OPTIONS:
|
|
402
|
+
if quiet_seen:
|
|
403
|
+
raise ValueError(
|
|
404
|
+
f"{validation.id}: option de 'uv run' '-q'/'--quiet' "
|
|
405
|
+
"ne peut apparaître qu'une fois"
|
|
406
|
+
)
|
|
407
|
+
quiet_seen = True
|
|
408
|
+
index += 1
|
|
409
|
+
if index >= len(args):
|
|
410
|
+
raise ValueError(f"{validation.id}: seul 'uv run <outil>' est autorisé")
|
|
355
411
|
nested = ValidationCommand(
|
|
356
412
|
validation.id,
|
|
357
|
-
args[
|
|
413
|
+
args[index:],
|
|
358
414
|
validation.blocking,
|
|
359
415
|
validation.timeout,
|
|
360
416
|
validation.working_directory,
|
|
@@ -17,8 +17,19 @@ class WorktreeSnapshot:
|
|
|
17
17
|
files: dict[str, str]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# Traces brutes d'agent ecrites par devflow ; ignorees par le .gitignore de la feature.
|
|
21
|
+
TRACE_SUFFIX = ".raw.txt"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def snapshot_worktree(
|
|
25
|
+
project_root: Path, feature_dir: Path | None = None
|
|
26
|
+
) -> WorktreeSnapshot:
|
|
27
|
+
"""Capture HEAD, branche et contenu des fichiers modifies/non suivis.
|
|
28
|
+
|
|
29
|
+
Avec ``feature_dir``, les fichiers ignores par git dans ce seul dossier
|
|
30
|
+
sont aussi captures, hors traces brutes : un projet qui ignore ``.specify``
|
|
31
|
+
doit tout de meme voir les artefacts de sa feature.
|
|
32
|
+
"""
|
|
22
33
|
head = _git_text(project_root, ["rev-parse", "--verify", "HEAD"], allow_failure=True)
|
|
23
34
|
branch = _git_text(project_root, ["branch", "--show-current"], allow_failure=True)
|
|
24
35
|
tracked = _git_paths(
|
|
@@ -30,7 +41,15 @@ def snapshot_worktree(project_root: Path) -> WorktreeSnapshot:
|
|
|
30
41
|
project_root,
|
|
31
42
|
["ls-files", "--others", "--exclude-standard", "-z"],
|
|
32
43
|
)
|
|
33
|
-
paths =
|
|
44
|
+
paths = set(tracked) | set(untracked)
|
|
45
|
+
subtree = _subtree(project_root, feature_dir)
|
|
46
|
+
if subtree is not None:
|
|
47
|
+
ignored = _git_paths(
|
|
48
|
+
project_root,
|
|
49
|
+
["ls-files", "--others", "--ignored", "--exclude-standard", "-z", "--", subtree],
|
|
50
|
+
)
|
|
51
|
+
paths |= {path for path in ignored if not path.endswith(TRACE_SUFFIX)}
|
|
52
|
+
paths = sorted(paths)
|
|
34
53
|
return WorktreeSnapshot(
|
|
35
54
|
head=head,
|
|
36
55
|
branch=branch,
|
|
@@ -64,6 +83,15 @@ def changed_paths(before: WorktreeSnapshot, after: WorktreeSnapshot) -> list[str
|
|
|
64
83
|
return sorted(path for path in paths if before.files.get(path) != after.files.get(path))
|
|
65
84
|
|
|
66
85
|
|
|
86
|
+
def _subtree(project_root: Path, feature_dir: Path | None) -> str | None:
|
|
87
|
+
if feature_dir is None:
|
|
88
|
+
return None
|
|
89
|
+
try:
|
|
90
|
+
return feature_dir.resolve().relative_to(project_root.resolve()).as_posix()
|
|
91
|
+
except ValueError:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
67
95
|
def _git_text(project_root: Path, args: list[str], *, allow_failure: bool = False) -> str:
|
|
68
96
|
result = subprocess.run(
|
|
69
97
|
["git", *args],
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
devflow_cli/__init__.py,sha256=
|
|
1
|
+
devflow_cli/__init__.py,sha256=q9RfnymaWq6NFLqPNIMBkIwCAG45_QDkzC9mZM-JxhA,1002
|
|
2
2
|
devflow_cli/cli.py,sha256=nst8kP2QneJZJg6K5bBf9uO8Nj9lYmoHrEhcxA7nsIM,2129
|
|
3
3
|
devflow_cli/adapters/__init__.py,sha256=RkjHPU2Lp4hSVIq2iOmyTrv2zYU9NuiIF9wEMWzLdMI,377
|
|
4
4
|
devflow_cli/adapters/agents.py,sha256=8_y70AIhmtuR8kyPN3Wpj0-i76k9wd_uaycVKTqC96g,26503
|
|
5
5
|
devflow_cli/adapters/linear.py,sha256=MyqI2tHx7NNHY1KRfeYZ_Zjc-6_xhZPWD4oS-j7wnNY,2659
|
|
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=_CICxGw27XOSnXteudn75IriY12zz5jhcZoH__zNfns,45797
|
|
8
8
|
devflow_cli/commands/assess.py,sha256=GrWGGZjvBAgHWzSlUdtS-IfYocacA6VYxWduDvBunzg,4395
|
|
9
9
|
devflow_cli/commands/check.py,sha256=M_sbcH1L3FEa4DF9gm46LEADlJndRKlaEDoysIBHv8k,6668
|
|
10
10
|
devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
|
|
@@ -23,7 +23,7 @@ devflow_cli/commands/usage.py,sha256=ehH1AH5J-XfkebFx6qLBwpKN-MOoX2uUbq4LejBpNsk
|
|
|
23
23
|
devflow_cli/commands/verify.py,sha256=sKmoHfA-PRWoDbgLC0nW2nvd89Zdktlkep0sxF7q3jQ,3968
|
|
24
24
|
devflow_cli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
devflow_cli/core/activity_contracts.py,sha256=U9AknLNFIusjuh4MonY_8i3__uDeo_NF2z3zfGJynA8,42631
|
|
26
|
-
devflow_cli/core/adaptive_runner.py,sha256=
|
|
26
|
+
devflow_cli/core/adaptive_runner.py,sha256=o9HHPNZJyA9u_zui9pNZrzMkH_jzszbI-GMweoRjqB8,33713
|
|
27
27
|
devflow_cli/core/adaptive_workflow.py,sha256=qsjkahK9yklf6LjyQn69PVl9v0AoX2YPQiG2_umpb_U,13065
|
|
28
28
|
devflow_cli/core/agent_failure.py,sha256=iX4q249lAHeVdD_ZF1aObmU-FX6sjfYP9dfLSnWGAD0,3771
|
|
29
29
|
devflow_cli/core/assessment.py,sha256=l5QRntLJyBx3MdN-kepmMAsruHdJsG4TDgSdgOSHkqU,10062
|
|
@@ -38,17 +38,17 @@ devflow_cli/core/manifest.py,sha256=964iYthqhzf3qQgxN4hZ33BvS2r7BKDZoKdiOWRybag,
|
|
|
38
38
|
devflow_cli/core/omissions.py,sha256=-9dqaQLRIy-1t6it7gecKn_0KpqbEjPIT0ec86KvXCk,7449
|
|
39
39
|
devflow_cli/core/operational.py,sha256=ZEWh6DKJfHYLpV9r3vZwaDFREQwlJF0wTtgB1VleXog,1204
|
|
40
40
|
devflow_cli/core/redaction.py,sha256=o_1MDtCyvRLrk_dWlV6_7E6_mPVB1B5a0cCfTBj9j0Q,1973
|
|
41
|
-
devflow_cli/core/remediation.py,sha256=
|
|
41
|
+
devflow_cli/core/remediation.py,sha256=FknpUBR9WGZkmeDf6F2HINKKm3rG4AB5C7NTgrPhPVc,13575
|
|
42
42
|
devflow_cli/core/scanner.py,sha256=bcoC91Nc0Hw2P7aHpL-9ITxynLZhLTEKpOXo553a-18,4395
|
|
43
43
|
devflow_cli/core/staleness.py,sha256=Bn4jN8r601Ifn0AU8BHZy-Z-5NLPjj_JTK-V0KU9TUM,5256
|
|
44
44
|
devflow_cli/core/telemetry.py,sha256=emcYo6Qikc86TYKT5Mc_9-3Thkt3nztkPZrpTy9VlvA,6952
|
|
45
45
|
devflow_cli/core/traceability.py,sha256=jfNE9MZzGkmaPer9Wa6Y18yA9Y3sqdeR-nzH9h_TB-8,2968
|
|
46
46
|
devflow_cli/core/validation_approvals.py,sha256=BMjwDbkkY9K-uqk19duKWIbh7-Dk5ceprNPgQHx3lrg,8421
|
|
47
47
|
devflow_cli/core/validators.py,sha256=c2T6NAe80sPh2uS8gCQfyiCkLT3eBPLizCbDsE0PgNo,9934
|
|
48
|
-
devflow_cli/core/verification.py,sha256=
|
|
48
|
+
devflow_cli/core/verification.py,sha256=YotT8eZFGQRdRrNFwiPGnFNmA2XQ-IjGIyaVi7shA0g,19858
|
|
49
49
|
devflow_cli/core/waivers.py,sha256=w3bza1OicSxAViYU2hvbrylQTNVgmN7Rd9y2L6Wm2OE,11302
|
|
50
50
|
devflow_cli/core/worklog.py,sha256=w_R_K-xI7gx9lYQhcjJbI_nBQqztMHsOPWj1tQCXjjA,12668
|
|
51
|
-
devflow_cli/core/worktree_snapshot.py,sha256=
|
|
51
|
+
devflow_cli/core/worktree_snapshot.py,sha256=5ycGW5sYSeoht-ofUqoNhrscZuhTJlynfObzisJal90,4669
|
|
52
52
|
devflow_cli/schemas/activity-contract-v3.schema.json,sha256=_R6oKY2K3LxNCIATQunDM7e41J6w4Dg15TrN3B-NL0c,1977
|
|
53
53
|
devflow_cli/schemas/activity-result-v1.schema.json,sha256=UybTDGJcPZ_CJoD0oAlgBWJOEsQTp06KRnogoa1pFFY,3140
|
|
54
54
|
devflow_cli/schemas/assessment-v3.schema.json,sha256=aCybk-TaE5aJDKA2dhadSq58a-t2hFy6A-hCDlIl2Yo,1798
|
|
@@ -85,8 +85,8 @@ devflow_cli/resources/templates/contracts/api-template.md,sha256=rC5v7JBz71PznCn
|
|
|
85
85
|
devflow_cli/resources/templates/contracts/components-template.md,sha256=XIWfEXfC72sNmL5OA3V-7xIywkNDU2DBPLfyiagOWcs,484
|
|
86
86
|
devflow_cli/resources/templates/contracts/interfaces-template.md,sha256=kf8B7b6HsmN7jyRlLNU7RnZl0rTMlI5s97yVRyI-6yc,336
|
|
87
87
|
devflow_cli/resources/templates/contracts/services-template.md,sha256=cFafKCyLmq_a7aR7G0LXAaMO2z4Fuflq6iKdxfVg0vg,477
|
|
88
|
-
devflow_cli-6.1.
|
|
89
|
-
devflow_cli-6.1.
|
|
90
|
-
devflow_cli-6.1.
|
|
91
|
-
devflow_cli-6.1.
|
|
92
|
-
devflow_cli-6.1.
|
|
88
|
+
devflow_cli-6.1.2.dist-info/METADATA,sha256=RRQAPdlUN-T0ir2Cd62_ccfWchBkcn4exwQ88iyCOXg,4061
|
|
89
|
+
devflow_cli-6.1.2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
90
|
+
devflow_cli-6.1.2.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
|
|
91
|
+
devflow_cli-6.1.2.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
|
|
92
|
+
devflow_cli-6.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|