devflow-cli 8.5.1__py3-none-any.whl → 9.0.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 +67 -62
- devflow_cli/commands/status.py +1 -1
- devflow_cli/core/activity_base.py +96 -7
- devflow_cli/core/activity_contracts.py +3 -2
- devflow_cli/core/adaptive_runner.py +95 -141
- devflow_cli/core/adaptive_workflow.py +4 -2
- devflow_cli/core/contract_schemas.py +43 -3
- devflow_cli/core/feedback.py +2 -0
- devflow_cli/core/remediation.py +67 -2
- devflow_cli/core/worklog.py +10 -4
- devflow_cli/schemas/{activity-result-v1.schema.json → activity-result-v2.schema.json} +3 -3
- {devflow_cli-8.5.1.dist-info → devflow_cli-9.0.0.dist-info}/METADATA +10 -1
- {devflow_cli-8.5.1.dist-info → devflow_cli-9.0.0.dist-info}/RECORD +17 -17
- {devflow_cli-8.5.1.dist-info → devflow_cli-9.0.0.dist-info}/WHEEL +0 -0
- {devflow_cli-8.5.1.dist-info → devflow_cli-9.0.0.dist-info}/entry_points.txt +0 -0
- {devflow_cli-8.5.1.dist-info → devflow_cli-9.0.0.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
devflow_cli/commands/adaptive.py
CHANGED
|
@@ -76,7 +76,11 @@ from devflow_cli.core.validation_approvals import (
|
|
|
76
76
|
pending_approvals,
|
|
77
77
|
pending_plan_approval,
|
|
78
78
|
)
|
|
79
|
-
from devflow_cli.core.contract_schemas import
|
|
79
|
+
from devflow_cli.core.contract_schemas import (
|
|
80
|
+
LegacySchemaError,
|
|
81
|
+
check_feature_files,
|
|
82
|
+
diagnose_contract_file,
|
|
83
|
+
)
|
|
80
84
|
from devflow_cli.core.operational import ExitCode, exit_code_for_run_reason
|
|
81
85
|
from devflow_cli.core.result_template import (
|
|
82
86
|
remove_result_template,
|
|
@@ -196,6 +200,7 @@ def start(feature: str = typer.Argument(..., help="ID ou dossier de la feature")
|
|
|
196
200
|
fail("workflow-state.json existe deja ; le workflow ne sera pas ecrase.")
|
|
197
201
|
console.print(f" Suite : devflow adaptive status {feature}")
|
|
198
202
|
raise typer.Exit(int(ExitCode.CONCURRENT_CHANGE))
|
|
203
|
+
check_feature_files(feature_dir)
|
|
199
204
|
try:
|
|
200
205
|
assessment = _assessment(feature_dir)
|
|
201
206
|
validate_need_statement(feature_dir / NEED_STATEMENT)
|
|
@@ -483,7 +488,7 @@ def run_adaptive(
|
|
|
483
488
|
}
|
|
484
489
|
_write_json_atomic(feature_dir / "agent-execution.json", audit_payload)
|
|
485
490
|
except (OSError, ValueError) as exc:
|
|
486
|
-
return AgentActivityExecution(1, stderr=str(exc))
|
|
491
|
+
return AgentActivityExecution(1, stderr=str(exc), agent=agent)
|
|
487
492
|
return AgentActivityExecution(
|
|
488
493
|
execution.returncode,
|
|
489
494
|
output,
|
|
@@ -533,6 +538,8 @@ def run_adaptive(
|
|
|
533
538
|
recovery = _stale_preflight_recovery(current_state)
|
|
534
539
|
if outcome.reason == "artifact-files-mismatch":
|
|
535
540
|
recovery = _artifact_mismatch_recovery(current_state, outcome, agent)
|
|
541
|
+
if outcome.reason == "imputed-input":
|
|
542
|
+
recovery = _imputed_input_recovery(current_state, outcome, agent)
|
|
536
543
|
if json_output:
|
|
537
544
|
_print_execution_json(
|
|
538
545
|
current_state.issueId, outcome.activity, False, outcome.reason,
|
|
@@ -721,6 +728,14 @@ def reconcile_adaptive(
|
|
|
721
728
|
state, outcome, _attempt_agent(feature_dir, outcome.activity)
|
|
722
729
|
)
|
|
723
730
|
)
|
|
731
|
+
if outcome.reason == "imputed-input":
|
|
732
|
+
console.print(" Reprise guidee :")
|
|
733
|
+
_print_recovery_steps(
|
|
734
|
+
_imputed_input_recovery(
|
|
735
|
+
state, outcome, _attempt_agent(feature_dir, outcome.activity),
|
|
736
|
+
reconcile=True,
|
|
737
|
+
)
|
|
738
|
+
)
|
|
724
739
|
if (
|
|
725
740
|
outcome.reason == "reconcile-refused"
|
|
726
741
|
and activity_attempt_file(feature_dir, outcome.activity).is_file()
|
|
@@ -1610,10 +1625,41 @@ def _stale_preflight_recovery(state: AdaptiveWorkflowState) -> dict:
|
|
|
1610
1625
|
ARTIFACT_FILE_CAUSES = {
|
|
1611
1626
|
"during-agent": "modifie pendant l'agent",
|
|
1612
1627
|
"before-launch": "modifie avant le lancement de l'agent",
|
|
1613
|
-
"agent-input": "entree du besoin modifiee par un agent de cette activite",
|
|
1614
1628
|
}
|
|
1615
1629
|
|
|
1616
1630
|
|
|
1631
|
+
def _imputed_input_recovery(
|
|
1632
|
+
state: AdaptiveWorkflowState, outcome: AdaptiveRunOutcome, agent: str,
|
|
1633
|
+
*, reconcile: bool = False,
|
|
1634
|
+
) -> dict:
|
|
1635
|
+
"""Reprise guidee d'un refus `imputed-input` : remise en etat ou abandon audite."""
|
|
1636
|
+
issue = shlex.quote(state.issueId)
|
|
1637
|
+
not_done = "reconcile refuse" if reconcile else "l'agent n'a pas ete lance"
|
|
1638
|
+
return {
|
|
1639
|
+
"kind": "imputed-input",
|
|
1640
|
+
"summary": (
|
|
1641
|
+
"Entree du besoin modifiee par un agent de cette activite et pas remise "
|
|
1642
|
+
f"en etat : {not_done}. Deux sorties : remettre l'entree dans son etat de "
|
|
1643
|
+
"base, ou abandonner la tentative (l'entree modifiee est gardee, a verifier)."
|
|
1644
|
+
),
|
|
1645
|
+
"files": [{"path": path} for path in outcome.imputedInputs],
|
|
1646
|
+
"commands": [
|
|
1647
|
+
{
|
|
1648
|
+
"command": f"devflow adaptive run {issue}"
|
|
1649
|
+
+ (f" --agent {shlex.quote(agent)}" if agent else ""),
|
|
1650
|
+
"purpose": "apres remise en etat de l'entree dans son etat de base",
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
"command": f"devflow adaptive discard-attempt {issue} --reason MOTIF",
|
|
1654
|
+
"purpose": (
|
|
1655
|
+
"sinon, garder l'entree modifiee : abandon audite de la "
|
|
1656
|
+
"tentative, puis devflow adaptive run"
|
|
1657
|
+
),
|
|
1658
|
+
},
|
|
1659
|
+
],
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
|
|
1617
1663
|
def _artifact_mismatch_recovery(
|
|
1618
1664
|
state: AdaptiveWorkflowState, outcome: AdaptiveRunOutcome, agent: str
|
|
1619
1665
|
) -> dict:
|
|
@@ -1638,34 +1684,6 @@ def _artifact_mismatch_recovery(
|
|
|
1638
1684
|
"purpose": "relancer l'agent pour qu'il ecrive l'artefact",
|
|
1639
1685
|
}],
|
|
1640
1686
|
}
|
|
1641
|
-
if causes == {"agent-input"}:
|
|
1642
|
-
# Refus avant l'agent : ni reconcile ni reopen ne leve l'imputation.
|
|
1643
|
-
run = f"devflow adaptive run {issue}" + (
|
|
1644
|
-
f" --agent {shlex.quote(agent)}" if agent else ""
|
|
1645
|
-
)
|
|
1646
|
-
return {
|
|
1647
|
-
"kind": "artifact-files-mismatch",
|
|
1648
|
-
"summary": (
|
|
1649
|
-
"Entree du besoin modifiee par un agent de cette activite et pas remise "
|
|
1650
|
-
"en etat : l'agent n'a pas ete lance. Deux sorties : remettre l'entree "
|
|
1651
|
-
"dans son etat de base, ou abandonner la tentative (l'entree modifiee "
|
|
1652
|
-
"est gardee, a verifier)."
|
|
1653
|
-
),
|
|
1654
|
-
"files": files,
|
|
1655
|
-
"commands": [
|
|
1656
|
-
{
|
|
1657
|
-
"command": run,
|
|
1658
|
-
"purpose": "apres remise en etat de l'entree dans son etat de base",
|
|
1659
|
-
},
|
|
1660
|
-
{
|
|
1661
|
-
"command": f"devflow adaptive discard-attempt {issue} --reason MOTIF",
|
|
1662
|
-
"purpose": (
|
|
1663
|
-
"sinon, garder l'entree modifiee : abandon audite de la "
|
|
1664
|
-
"tentative, puis devflow adaptive run"
|
|
1665
|
-
),
|
|
1666
|
-
},
|
|
1667
|
-
],
|
|
1668
|
-
}
|
|
1669
1687
|
commands = [{
|
|
1670
1688
|
"command": f"devflow adaptive reconcile {issue}",
|
|
1671
1689
|
"purpose": (
|
|
@@ -1677,7 +1695,6 @@ def _artifact_mismatch_recovery(
|
|
|
1677
1695
|
previous = state.activities[index - 1] if index > 0 else ""
|
|
1678
1696
|
if (
|
|
1679
1697
|
"before-launch" in causes
|
|
1680
|
-
and "agent-input" not in causes
|
|
1681
1698
|
and previous not in {"", "assess"}
|
|
1682
1699
|
and _reopenable(state, previous)
|
|
1683
1700
|
):
|
|
@@ -1688,14 +1705,6 @@ def _artifact_mismatch_recovery(
|
|
|
1688
1705
|
"l'activite precedente pour repartir d'une base fraiche"
|
|
1689
1706
|
),
|
|
1690
1707
|
})
|
|
1691
|
-
if "agent-input" in causes:
|
|
1692
|
-
commands.append({
|
|
1693
|
-
"command": f"devflow adaptive discard-attempt {issue} --reason MOTIF",
|
|
1694
|
-
"purpose": (
|
|
1695
|
-
"sinon, garder l'entree du besoin modifiee par l'agent : abandon "
|
|
1696
|
-
"audite de la tentative, puis devflow adaptive run"
|
|
1697
|
-
),
|
|
1698
|
-
})
|
|
1699
1708
|
return {
|
|
1700
1709
|
"kind": "artifact-files-mismatch",
|
|
1701
1710
|
"summary": (
|
|
@@ -1724,9 +1733,8 @@ def _print_recovery_steps(recovery: dict) -> None:
|
|
|
1724
1733
|
# soft_wrap : la commande a copier reste sur une seule ligne.
|
|
1725
1734
|
console.print(f" {recovery['summary']}", markup=False, soft_wrap=True)
|
|
1726
1735
|
for item in recovery.get("files", []):
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
)
|
|
1736
|
+
label = f" : {item['label']}" if "label" in item else ""
|
|
1737
|
+
console.print(f" {item['path']}{label}", markup=False, soft_wrap=True)
|
|
1730
1738
|
for step in recovery["commands"]:
|
|
1731
1739
|
console.print(
|
|
1732
1740
|
f" {step['command']} # {step['purpose']}", markup=False, soft_wrap=True
|
|
@@ -1782,25 +1790,22 @@ def _print_run_recovery(
|
|
|
1782
1790
|
" Les entrees du besoin ont change pendant l'execution. "
|
|
1783
1791
|
"Verifiez-les avant une nouvelle tentative."
|
|
1784
1792
|
)
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
)
|
|
1802
|
-
return
|
|
1803
|
-
console.print(f" devflow adaptive run {issue_id} --agent {agent}")
|
|
1793
|
+
console.print(
|
|
1794
|
+
" La relance reste refusee tant que l'entree "
|
|
1795
|
+
"n'est pas remise dans son etat de base, ou la tentative abandonnee :",
|
|
1796
|
+
markup=False, soft_wrap=True,
|
|
1797
|
+
)
|
|
1798
|
+
issue, agent_arg = shlex.quote(issue_id), shlex.quote(agent)
|
|
1799
|
+
console.print(
|
|
1800
|
+
f" devflow adaptive discard-attempt {issue} --reason MOTIF"
|
|
1801
|
+
" # garder l'entree modifiee : abandon audite",
|
|
1802
|
+
markup=False, soft_wrap=True,
|
|
1803
|
+
)
|
|
1804
|
+
console.print(
|
|
1805
|
+
f" devflow adaptive run {issue} --agent {agent_arg}"
|
|
1806
|
+
" # apres remise en etat de l'entree, ou apres l'abandon",
|
|
1807
|
+
markup=False, soft_wrap=True,
|
|
1808
|
+
)
|
|
1804
1809
|
return
|
|
1805
1810
|
if reason == "agent-running":
|
|
1806
1811
|
console.print(" Un agent execute deja cette activite : attendez sa fin.")
|
devflow_cli/commands/status.py
CHANGED
|
@@ -8,6 +8,7 @@ la declaration de l'agent ne produit qu'un avertissement.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import copy
|
|
11
|
+
import json
|
|
11
12
|
import re
|
|
12
13
|
import subprocess
|
|
13
14
|
from dataclasses import dataclass
|
|
@@ -16,6 +17,7 @@ from pathlib import Path
|
|
|
16
17
|
from typing import TYPE_CHECKING, Any, Iterable
|
|
17
18
|
|
|
18
19
|
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
20
|
+
from devflow_cli.core.contract_schemas import ATTEMPT_SCHEMA_VERSION
|
|
19
21
|
from devflow_cli.core.worktree_snapshot import (
|
|
20
22
|
_fingerprint,
|
|
21
23
|
_git_paths,
|
|
@@ -24,7 +26,7 @@ from devflow_cli.core.worktree_snapshot import (
|
|
|
24
26
|
snapshot_worktree,
|
|
25
27
|
untracked_paths,
|
|
26
28
|
)
|
|
27
|
-
from devflow_cli.utils.paths import project_root_for_feature
|
|
29
|
+
from devflow_cli.utils.paths import activity_attempt_file, project_root_for_feature
|
|
28
30
|
|
|
29
31
|
if TYPE_CHECKING:
|
|
30
32
|
from devflow_cli.core.activity_contracts import ActivityContract, ActivityResult
|
|
@@ -49,6 +51,14 @@ RUNTIME_FILE_NAMES = frozenset({
|
|
|
49
51
|
RUNTIME_FILE_SUFFIXES = ("-result.json", "-attempt.json", ".raw.txt")
|
|
50
52
|
RUNTIME_DIRECTORIES = ("attempts", "evidence")
|
|
51
53
|
|
|
54
|
+
# Fiche de tentative (CT-04) : cles exactes, validees par ``attempt_violation`` seule.
|
|
55
|
+
ATTEMPT_KEYS = frozenset({
|
|
56
|
+
"schemaVersion", "contractId", "activity", "agent", "reason", "recordedAt",
|
|
57
|
+
"head", "branch", "rawOutputSha256", "observedFiles", "executionFiles",
|
|
58
|
+
"changedInputs",
|
|
59
|
+
})
|
|
60
|
+
RAW_OUTPUT_SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
|
|
61
|
+
|
|
52
62
|
OBSERVATION_SCHEMA: dict[str, Any] = {
|
|
53
63
|
"type": "object",
|
|
54
64
|
"required": ["baseRecordedAt", "baseHead", "observedAt", "head"],
|
|
@@ -61,6 +71,7 @@ OBSERVATION_SCHEMA: dict[str, Any] = {
|
|
|
61
71
|
"type": "array", "items": {"type": "string", "minLength": 1}
|
|
62
72
|
},
|
|
63
73
|
"warning": {"type": "string", "minLength": 1},
|
|
74
|
+
"amendmentWarning": {"type": "string", "minLength": 1},
|
|
64
75
|
},
|
|
65
76
|
"additionalProperties": False,
|
|
66
77
|
"dependentRequired": {
|
|
@@ -378,7 +389,8 @@ def recorded_observation(
|
|
|
378
389
|
"""Bloc ``observation`` ecrit par la CLI seule dans le resultat enregistre.
|
|
379
390
|
|
|
380
391
|
``exempted`` : entrees du besoin amendees avant l'agent, hors ecart de
|
|
381
|
-
declaration ; ``amendment_warning`` les nomme dans la cle ``
|
|
392
|
+
declaration ; ``amendment_warning`` les nomme dans la cle ``amendmentWarning``,
|
|
393
|
+
distincte de ``warning`` (ecart de declaration seul).
|
|
382
394
|
"""
|
|
383
395
|
observed = sorted(set(observation.sinceBase) - set(exempted))
|
|
384
396
|
block: dict[str, Any] = {
|
|
@@ -387,17 +399,94 @@ def recorded_observation(
|
|
|
387
399
|
"observedAt": observation.observedAt,
|
|
388
400
|
"head": observation.head,
|
|
389
401
|
}
|
|
390
|
-
warning =
|
|
391
|
-
message
|
|
392
|
-
for message in (declaration_warning(declared, observed), amendment_warning)
|
|
393
|
-
if message
|
|
394
|
-
)
|
|
402
|
+
warning = declaration_warning(declared, observed)
|
|
395
403
|
if warning:
|
|
396
404
|
block["declaredChangedFiles"] = list(declared)
|
|
397
405
|
block["warning"] = warning
|
|
406
|
+
if amendment_warning:
|
|
407
|
+
block["amendmentWarning"] = amendment_warning
|
|
398
408
|
return block
|
|
399
409
|
|
|
400
410
|
|
|
411
|
+
def attempt_violation(attempt: object) -> str | None:
|
|
412
|
+
"""Premier invariant A1 a A10 viole par une fiche de tentative, ou None (CT-04).
|
|
413
|
+
|
|
414
|
+
Seule validation de la fiche : l'ecriture et la lecture l'appellent.
|
|
415
|
+
"""
|
|
416
|
+
prefix = "fiche de tentative : "
|
|
417
|
+
if not isinstance(attempt, dict):
|
|
418
|
+
return prefix + "objet attendu"
|
|
419
|
+
missing = sorted(ATTEMPT_KEYS - set(attempt))
|
|
420
|
+
if missing:
|
|
421
|
+
return prefix + "cles manquantes : " + ", ".join(missing)
|
|
422
|
+
extra = sorted(set(attempt) - ATTEMPT_KEYS)
|
|
423
|
+
if extra:
|
|
424
|
+
return prefix + "cles en trop : " + ", ".join(extra)
|
|
425
|
+
version = attempt["schemaVersion"]
|
|
426
|
+
if isinstance(version, bool) or version != ATTEMPT_SCHEMA_VERSION:
|
|
427
|
+
return prefix + f"schemaVersion {ATTEMPT_SCHEMA_VERSION} attendu"
|
|
428
|
+
for key in ("contractId", "activity", "recordedAt", "head", "branch"):
|
|
429
|
+
if not isinstance(attempt[key], str):
|
|
430
|
+
return prefix + f"{key} doit etre une chaine"
|
|
431
|
+
for key in ("contractId", "activity", "agent", "reason", "recordedAt"):
|
|
432
|
+
if not isinstance(attempt[key], str) or not attempt[key]:
|
|
433
|
+
return prefix + f"{key} vide"
|
|
434
|
+
raw_sha = attempt["rawOutputSha256"]
|
|
435
|
+
if not isinstance(raw_sha, str) or not RAW_OUTPUT_SHA256_PATTERN.fullmatch(raw_sha):
|
|
436
|
+
return prefix + "rawOutputSha256 invalide"
|
|
437
|
+
observed = attempt["observedFiles"]
|
|
438
|
+
if not isinstance(observed, dict) or not all(
|
|
439
|
+
isinstance(path, str) and isinstance(fingerprint, str)
|
|
440
|
+
for path, fingerprint in observed.items()
|
|
441
|
+
):
|
|
442
|
+
return prefix + "observedFiles invalide"
|
|
443
|
+
execution_files = attempt["executionFiles"]
|
|
444
|
+
if not isinstance(execution_files, list) or not all(
|
|
445
|
+
isinstance(item, str) for item in execution_files
|
|
446
|
+
):
|
|
447
|
+
return prefix + "executionFiles invalide"
|
|
448
|
+
changed = attempt["changedInputs"]
|
|
449
|
+
if (
|
|
450
|
+
not isinstance(changed, list)
|
|
451
|
+
or not all(isinstance(item, str) and item for item in changed)
|
|
452
|
+
or changed != sorted(set(changed))
|
|
453
|
+
):
|
|
454
|
+
return prefix + "changedInputs invalide"
|
|
455
|
+
if attempt["head"] == "" and attempt["branch"] == "" and observed == {} and not changed:
|
|
456
|
+
return prefix + "fiche minimale incoherente"
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def imputed_inputs(
|
|
461
|
+
feature_dir: Path,
|
|
462
|
+
feature_path: str,
|
|
463
|
+
activity: str,
|
|
464
|
+
contract_id: str,
|
|
465
|
+
since_base: dict[str, str],
|
|
466
|
+
) -> list[str]:
|
|
467
|
+
"""Entrees que la fiche de tentative impute a un agent de l'activite (CT-01).
|
|
468
|
+
|
|
469
|
+
Seules comptent celles qui different encore de la base : une entree remise en
|
|
470
|
+
etat n'est plus imputee. Une fiche absente, illisible, invalide, d'une autre
|
|
471
|
+
activite ou d'un autre contrat n'impute rien. Aucune ecriture.
|
|
472
|
+
"""
|
|
473
|
+
try:
|
|
474
|
+
attempt = json.loads(
|
|
475
|
+
activity_attempt_file(feature_dir, activity).read_text(encoding="utf-8")
|
|
476
|
+
)
|
|
477
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
|
478
|
+
return []
|
|
479
|
+
if attempt_violation(attempt) is not None:
|
|
480
|
+
return []
|
|
481
|
+
if attempt["activity"] != activity or attempt["contractId"] != contract_id:
|
|
482
|
+
return []
|
|
483
|
+
return sorted(
|
|
484
|
+
name
|
|
485
|
+
for name in attempt["changedInputs"]
|
|
486
|
+
if f"{feature_path}/{name}" in since_base
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
401
490
|
def parse_recorded_result(
|
|
402
491
|
payload: object, contract: "ActivityContract"
|
|
403
492
|
) -> tuple["ActivityResult", dict[str, Any]]:
|
|
@@ -19,6 +19,7 @@ from devflow_cli.core.adaptive_workflow import (
|
|
|
19
19
|
from devflow_cli.core.assessment import ASSESSMENT_SCHEMA_VERSION, compute_route
|
|
20
20
|
from devflow_cli.core.contract_schemas import (
|
|
21
21
|
activity_result_schema,
|
|
22
|
+
contract_schema_version,
|
|
22
23
|
validate_contract_payload,
|
|
23
24
|
)
|
|
24
25
|
from devflow_cli.core.validators import ARTIFACT_RULES
|
|
@@ -573,7 +574,7 @@ def parse_activity_result(payload: dict[str, Any], contract: ActivityContract) -
|
|
|
573
574
|
expected_fields.add("validationPlan")
|
|
574
575
|
if expected_fields != set(payload):
|
|
575
576
|
raise ValueError("champs du resultat manquants ou inattendus")
|
|
576
|
-
if payload.get("schemaVersion") !=
|
|
577
|
+
if payload.get("schemaVersion") != contract_schema_version("activity-result"):
|
|
577
578
|
raise ValueError("schema de resultat non supporte")
|
|
578
579
|
if payload.get("contractId") != contract.contractId:
|
|
579
580
|
raise ValueError("contractId du resultat incorrect")
|
|
@@ -678,7 +679,7 @@ def parse_activity_result(payload: dict[str, Any], contract: ActivityContract) -
|
|
|
678
679
|
)
|
|
679
680
|
|
|
680
681
|
return ActivityResult(
|
|
681
|
-
schemaVersion=
|
|
682
|
+
schemaVersion=contract_schema_version("activity-result"),
|
|
682
683
|
contractId=contract.contractId,
|
|
683
684
|
status=status,
|
|
684
685
|
summary=summary.strip(),
|
|
@@ -20,6 +20,7 @@ from devflow_cli.core.activity_contracts import (
|
|
|
20
20
|
DOCUMENT_ARTIFACTS,
|
|
21
21
|
NEED_STATEMENT,
|
|
22
22
|
PreflightEvidenceStaleError,
|
|
23
|
+
activity_contract_id,
|
|
23
24
|
build_activity_contract,
|
|
24
25
|
contract_is_reopened_plan,
|
|
25
26
|
document_artifact_name,
|
|
@@ -31,12 +32,15 @@ from devflow_cli.core.activity_contracts import (
|
|
|
31
32
|
from devflow_cli.core.activity_base import (
|
|
32
33
|
ActivityObservation,
|
|
33
34
|
BaseObservationError,
|
|
35
|
+
attempt_violation,
|
|
34
36
|
declaration_warning,
|
|
35
37
|
enter_current_activity,
|
|
36
38
|
files_since_activity_base,
|
|
39
|
+
imputed_inputs,
|
|
37
40
|
is_runtime_file,
|
|
38
41
|
observe_activity,
|
|
39
42
|
observe_attempt,
|
|
43
|
+
parse_recorded_result,
|
|
40
44
|
recorded_observation,
|
|
41
45
|
)
|
|
42
46
|
from devflow_cli.core.agent_failure import diagnose_agent_failure
|
|
@@ -59,6 +63,10 @@ from devflow_cli.core.adaptive_workflow import (
|
|
|
59
63
|
write_adaptive_state,
|
|
60
64
|
)
|
|
61
65
|
from devflow_cli.core.assessment import read_assessment
|
|
66
|
+
from devflow_cli.core.contract_schemas import (
|
|
67
|
+
ATTEMPT_SCHEMA_VERSION,
|
|
68
|
+
contract_schema_version,
|
|
69
|
+
)
|
|
62
70
|
from devflow_cli.core.worklog import safe_refresh_worklog
|
|
63
71
|
from devflow_cli.core.traceability import analyze_traceability
|
|
64
72
|
from devflow_cli.core.validators import validate_artifact
|
|
@@ -95,6 +103,8 @@ class AdaptiveRunOutcome:
|
|
|
95
103
|
warnings: tuple[str, ...] = ()
|
|
96
104
|
# artifact-files-mismatch : (chemin, cause observee) de chaque fichier en trop.
|
|
97
105
|
extraFiles: tuple[tuple[str, str], ...] = ()
|
|
106
|
+
# imputed-input : chemins (relatifs a la racine) des entrees imputees, tries.
|
|
107
|
+
imputedInputs: tuple[str, ...] = ()
|
|
98
108
|
|
|
99
109
|
@property
|
|
100
110
|
def advanced(self) -> bool:
|
|
@@ -147,11 +157,20 @@ def run_current_activity(
|
|
|
147
157
|
preflight_evidence=(feature_dir / "preflight-evidence.md").is_file(),
|
|
148
158
|
files_since_base=since_base,
|
|
149
159
|
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
# D1 : l'imputation precede la peremption du pre-vol (PL-04), qui
|
|
161
|
+
# compare des mtime que la reecriture d'une entree par l'agent invalide.
|
|
162
|
+
imputed = imputed_inputs(
|
|
163
|
+
feature_dir, feature_path, contract.activity, contract.contractId,
|
|
164
|
+
since_base,
|
|
154
165
|
)
|
|
166
|
+
if imputed:
|
|
167
|
+
return AdaptiveRunOutcome(
|
|
168
|
+
"imputed-input",
|
|
169
|
+
contract.activity,
|
|
170
|
+
contract.activity,
|
|
171
|
+
_imputed_detail(imputed, "agent non lance"),
|
|
172
|
+
imputedInputs=tuple(f"{feature_path}/{name}" for name in imputed),
|
|
173
|
+
)
|
|
155
174
|
if state.currentActivity == "implement":
|
|
156
175
|
upstream = [
|
|
157
176
|
feature_dir / Path(item).name
|
|
@@ -176,18 +195,6 @@ def run_current_activity(
|
|
|
176
195
|
return AdaptiveRunOutcome(
|
|
177
196
|
"unsupported-activity", state.currentActivity, state.currentActivity, str(exc)
|
|
178
197
|
)
|
|
179
|
-
if imputed_inputs:
|
|
180
|
-
# N5 : refus avant toute ecriture et avant l'agent ; la fiche reste intacte.
|
|
181
|
-
return AdaptiveRunOutcome(
|
|
182
|
-
"artifact-files-mismatch",
|
|
183
|
-
contract.activity,
|
|
184
|
-
contract.activity,
|
|
185
|
-
"entrees du besoin modifiees par un agent de cette activite et pas "
|
|
186
|
-
"remises en etat : " + ", ".join(imputed_inputs) + " ; agent non lance",
|
|
187
|
-
extraFiles=tuple(
|
|
188
|
-
(f"{feature_path}/{name}", "agent-input") for name in imputed_inputs
|
|
189
|
-
),
|
|
190
|
-
)
|
|
191
198
|
raw_file = activity_raw_file(feature_dir, contract.activity)
|
|
192
199
|
guard = ExitStack()
|
|
193
200
|
try:
|
|
@@ -238,13 +245,19 @@ def run_current_activity(
|
|
|
238
245
|
for name in set(input_fingerprints) | set(current_inputs)
|
|
239
246
|
if input_fingerprints.get(name) != current_inputs.get(name)
|
|
240
247
|
)
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
|
|
249
|
+
def record(reason: str) -> str:
|
|
250
|
+
"""Ecrit la fiche ; rend le suffixe de detail si elle n'a pas pu l'etre (PL-07)."""
|
|
251
|
+
try:
|
|
252
|
+
_record_failed_attempt(
|
|
253
|
+
feature_dir, contract, execution, reason, changed_inputs=changed_inputs
|
|
254
|
+
)
|
|
255
|
+
except ValueError as exc:
|
|
256
|
+
return f" ; fiche de tentative non ecrite : {exc}"
|
|
257
|
+
return ""
|
|
258
|
+
|
|
243
259
|
if runtime_mutated:
|
|
244
|
-
|
|
245
|
-
feature_dir, contract, execution, "runtime-file-mutated",
|
|
246
|
-
changed_inputs=attributed_inputs,
|
|
247
|
-
)
|
|
260
|
+
suffix = record("runtime-file-mutated")
|
|
248
261
|
return AdaptiveRunOutcome(
|
|
249
262
|
"runtime-file-mutated",
|
|
250
263
|
contract.activity,
|
|
@@ -252,16 +265,14 @@ def run_current_activity(
|
|
|
252
265
|
"fichiers d'execution devflow modifies pendant l'activite : "
|
|
253
266
|
+ ", ".join(runtime_mutated)
|
|
254
267
|
+ " ; remis dans leur etat d'avant l'agent : "
|
|
255
|
-
+ (", ".join(restored) if restored else "aucun fichier a restaurer")
|
|
268
|
+
+ (", ".join(restored) if restored else "aucun fichier a restaurer")
|
|
269
|
+
+ suffix,
|
|
256
270
|
)
|
|
257
271
|
if changed_inputs:
|
|
258
|
-
|
|
259
|
-
feature_dir, contract, execution,
|
|
260
|
-
"inputs-changed-during-activity", changed_inputs=attributed_inputs,
|
|
261
|
-
)
|
|
272
|
+
suffix = record("inputs-changed-during-activity")
|
|
262
273
|
return AdaptiveRunOutcome(
|
|
263
274
|
"inputs-changed-during-activity", contract.activity, contract.activity,
|
|
264
|
-
"entrees modifiees pendant l'activite : " + ", ".join(changed_inputs),
|
|
275
|
+
"entrees modifiees pendant l'activite : " + ", ".join(changed_inputs) + suffix,
|
|
265
276
|
)
|
|
266
277
|
if execution.returncode != 0:
|
|
267
278
|
diagnosis = diagnose_agent_failure(execution.output, execution.stderr)
|
|
@@ -277,22 +288,16 @@ def run_current_activity(
|
|
|
277
288
|
reason = "agent-failed"
|
|
278
289
|
if execution.observedChangedFiles:
|
|
279
290
|
detail += f"; mutations observees : {len(set(execution.observedChangedFiles))} fichier(s)"
|
|
280
|
-
|
|
281
|
-
feature_dir, contract, execution, reason, changed_inputs=attributed_inputs
|
|
282
|
-
)
|
|
291
|
+
detail += record(reason)
|
|
283
292
|
return AdaptiveRunOutcome(reason, contract.activity, contract.activity, detail)
|
|
284
293
|
|
|
285
294
|
try:
|
|
286
295
|
raw_payload = json.loads(execution.output)
|
|
287
296
|
except json.JSONDecodeError as exc:
|
|
288
|
-
|
|
289
|
-
"invalid-result", contract.activity, contract.activity,
|
|
290
|
-
|
|
291
|
-
_record_failed_attempt(
|
|
292
|
-
feature_dir, contract, execution, outcome.reason,
|
|
293
|
-
changed_inputs=attributed_inputs,
|
|
297
|
+
return AdaptiveRunOutcome(
|
|
298
|
+
"invalid-result", contract.activity, contract.activity,
|
|
299
|
+
str(exc) + record("invalid-result"),
|
|
294
300
|
)
|
|
295
|
-
return outcome
|
|
296
301
|
try:
|
|
297
302
|
observation = observe_activity(
|
|
298
303
|
feature_dir, state, during_execution=execution.observedChangedFiles
|
|
@@ -313,14 +318,12 @@ def run_current_activity(
|
|
|
313
318
|
source="agent",
|
|
314
319
|
agent=execution.agent,
|
|
315
320
|
execution_files=execution.observedChangedFiles,
|
|
316
|
-
imputed_inputs=imputed_inputs,
|
|
317
321
|
closed_activities=_closed_activities(state),
|
|
318
322
|
)
|
|
319
323
|
if not outcome.advanced and outcome.reason not in {"blocked", "state-changed"}:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
)
|
|
324
|
+
suffix = record(outcome.reason)
|
|
325
|
+
if suffix:
|
|
326
|
+
outcome = replace(outcome, detail=outcome.detail + suffix)
|
|
324
327
|
return outcome
|
|
325
328
|
|
|
326
329
|
|
|
@@ -394,25 +397,28 @@ def reconcile_current_activity(
|
|
|
394
397
|
f"devflow adaptive run {shlex.quote(state.issueId)}"
|
|
395
398
|
) from None
|
|
396
399
|
attempt = json.loads(attempt_text)
|
|
397
|
-
|
|
398
|
-
|
|
400
|
+
violation = attempt_violation(attempt)
|
|
401
|
+
if violation is not None:
|
|
402
|
+
raise ValueError(f"fiche de tentative invalide : {violation}")
|
|
399
403
|
agent = attempt["agent"]
|
|
400
404
|
attempt_reason = attempt["reason"]
|
|
401
405
|
observed_files = attempt["observedFiles"]
|
|
402
406
|
execution_files = attempt["executionFiles"]
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
+
imputed = imputed_inputs(
|
|
408
|
+
feature_dir,
|
|
409
|
+
feature_path,
|
|
410
|
+
activity,
|
|
411
|
+
activity_contract_id(state.issueId, state.profile, activity),
|
|
412
|
+
since_base,
|
|
407
413
|
)
|
|
408
|
-
if
|
|
414
|
+
if imputed:
|
|
409
415
|
# N5 : reconcile ne blanchit pas une entree imputee a un agent.
|
|
410
|
-
|
|
411
|
-
"
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
"
|
|
416
|
+
return AdaptiveRunOutcome(
|
|
417
|
+
"imputed-input",
|
|
418
|
+
activity,
|
|
419
|
+
activity,
|
|
420
|
+
_imputed_detail(imputed, "reconcile refuse"),
|
|
421
|
+
imputedInputs=tuple(f"{feature_path}/{name}" for name in imputed),
|
|
416
422
|
)
|
|
417
423
|
if attempt_reason == "inputs-changed-during-activity":
|
|
418
424
|
raise ValueError(
|
|
@@ -533,48 +539,6 @@ def reconcile_current_activity(
|
|
|
533
539
|
return outcome
|
|
534
540
|
|
|
535
541
|
|
|
536
|
-
ATTEMPT_SCHEMA_VERSION = 2
|
|
537
|
-
ATTEMPT_KEYS = frozenset({
|
|
538
|
-
"schemaVersion", "contractId", "activity", "agent", "reason", "recordedAt",
|
|
539
|
-
"head", "branch", "rawOutputSha256", "observedFiles", "executionFiles",
|
|
540
|
-
})
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
def _valid_attempt(attempt: object) -> bool:
|
|
544
|
-
"""Fiche de tentative schema 2 aux cles exactes (CT-10)."""
|
|
545
|
-
if not isinstance(attempt, dict):
|
|
546
|
-
return False
|
|
547
|
-
keys = set(attempt)
|
|
548
|
-
if keys != ATTEMPT_KEYS and keys != ATTEMPT_KEYS | {"changedInputs"}:
|
|
549
|
-
return False
|
|
550
|
-
observed = attempt["observedFiles"]
|
|
551
|
-
execution_files = attempt["executionFiles"]
|
|
552
|
-
changed_inputs = attempt.get("changedInputs", [])
|
|
553
|
-
return (
|
|
554
|
-
attempt["schemaVersion"] == ATTEMPT_SCHEMA_VERSION
|
|
555
|
-
and not isinstance(attempt["schemaVersion"], bool)
|
|
556
|
-
and all(
|
|
557
|
-
isinstance(attempt[key], str)
|
|
558
|
-
for key in ("contractId", "activity", "recordedAt", "head", "branch")
|
|
559
|
-
)
|
|
560
|
-
and isinstance(attempt["agent"], str)
|
|
561
|
-
and bool(attempt["agent"])
|
|
562
|
-
and isinstance(attempt["reason"], str)
|
|
563
|
-
and bool(attempt["reason"])
|
|
564
|
-
and isinstance(attempt["rawOutputSha256"], str)
|
|
565
|
-
and len(attempt["rawOutputSha256"]) == 64
|
|
566
|
-
and isinstance(observed, dict)
|
|
567
|
-
and all(
|
|
568
|
-
isinstance(path, str) and isinstance(fingerprint, str)
|
|
569
|
-
for path, fingerprint in observed.items()
|
|
570
|
-
)
|
|
571
|
-
and isinstance(execution_files, list)
|
|
572
|
-
and all(isinstance(item, str) for item in execution_files)
|
|
573
|
-
and isinstance(changed_inputs, list)
|
|
574
|
-
and all(isinstance(item, str) for item in changed_inputs)
|
|
575
|
-
)
|
|
576
|
-
|
|
577
|
-
|
|
578
542
|
def _recover_attempt_payload(
|
|
579
543
|
raw_output: str,
|
|
580
544
|
contract: ActivityContract,
|
|
@@ -607,7 +571,6 @@ def _apply_activity_result(
|
|
|
607
571
|
source: str,
|
|
608
572
|
agent: str,
|
|
609
573
|
execution_files: Iterable[str] = (),
|
|
610
|
-
imputed_inputs: Iterable[str] = (),
|
|
611
574
|
closed_activities: Iterable[str] = (),
|
|
612
575
|
) -> AdaptiveRunOutcome:
|
|
613
576
|
"""Valide un resultat agent puis transitionne ; commun a run et reconcile.
|
|
@@ -615,8 +578,7 @@ def _apply_activity_result(
|
|
|
615
578
|
L'observation fait foi : ``O`` (fichiers depuis la base) et ``E`` (fichiers
|
|
616
579
|
modifies pendant l'agent) fondent les refus ; la declaration de l'agent ne
|
|
617
580
|
produit qu'un avertissement. ``execution_files`` est la liste brute des fichiers
|
|
618
|
-
modifies pendant l'agent
|
|
619
|
-
a un agent de l'activite (N5) : ils fondent l'exemption N1.
|
|
581
|
+
modifies pendant l'agent : il fonde l'exemption N1.
|
|
620
582
|
"""
|
|
621
583
|
state_file = feature_dir / "workflow-state.json"
|
|
622
584
|
observed = sorted(observation.sinceBase)
|
|
@@ -642,7 +604,6 @@ def _apply_activity_result(
|
|
|
642
604
|
)
|
|
643
605
|
|
|
644
606
|
executed = set(execution_files)
|
|
645
|
-
imputed_paths = {f"{feature_path}/{name}" for name in imputed_inputs}
|
|
646
607
|
# N1 : entrees du besoin amendees avant le lancement de l'agent, non comptees.
|
|
647
608
|
amended = (
|
|
648
609
|
[
|
|
@@ -650,7 +611,6 @@ def _apply_activity_result(
|
|
|
650
611
|
for path in _input_paths(feature_dir, feature_path)
|
|
651
612
|
if path in observation.sinceBase
|
|
652
613
|
and path not in executed
|
|
653
|
-
and path not in imputed_paths
|
|
654
614
|
]
|
|
655
615
|
if contract.activity in DOCUMENT_ARTIFACTS
|
|
656
616
|
else []
|
|
@@ -690,9 +650,7 @@ def _apply_activity_result(
|
|
|
690
650
|
tuple(
|
|
691
651
|
(
|
|
692
652
|
path,
|
|
693
|
-
"agent
|
|
694
|
-
else "during-agent" if path in executed
|
|
695
|
-
else "before-launch",
|
|
653
|
+
"during-agent" if path in executed else "before-launch",
|
|
696
654
|
)
|
|
697
655
|
for path in counted
|
|
698
656
|
if path != expected
|
|
@@ -743,6 +701,15 @@ def _apply_activity_result(
|
|
|
743
701
|
recorded["observation"] = recorded_observation(
|
|
744
702
|
observation, declared, exempted=amended, amendment_warning=amendment
|
|
745
703
|
)
|
|
704
|
+
try:
|
|
705
|
+
# changedFiles du resultat enregistre est la liste observee, qui peut inclure une
|
|
706
|
+
# entree du besoin amendee (N1) : la relecture porte sur la forme du bloc et du
|
|
707
|
+
# resultat valide, pas sur l'ecart entre observation et artefact attendu.
|
|
708
|
+
parse_recorded_result(
|
|
709
|
+
{**result.to_dict(), "observation": recorded["observation"]}, contract
|
|
710
|
+
)
|
|
711
|
+
except ValueError as exc:
|
|
712
|
+
return refuse("transition-refused", f"resultat enregistre invalide : {exc}")
|
|
746
713
|
_write_json_atomic(activity_result_file(feature_dir, contract.activity), recorded)
|
|
747
714
|
if result.status == "blocked":
|
|
748
715
|
safe_refresh_worklog(feature_dir)
|
|
@@ -992,7 +959,7 @@ def record_human_review(
|
|
|
992
959
|
except (OSError, ValueError) as exc:
|
|
993
960
|
return AdaptiveRunOutcome("unsupported-activity", activity, activity, str(exc))
|
|
994
961
|
payload = {
|
|
995
|
-
"schemaVersion":
|
|
962
|
+
"schemaVersion": contract_schema_version("activity-result"),
|
|
996
963
|
"contractId": contract.contractId,
|
|
997
964
|
"status": "completed" if outcome == "passed" else "blocked",
|
|
998
965
|
"summary": f"Revue humaine de {reviewer.strip()} : {outcome}",
|
|
@@ -1026,15 +993,16 @@ def _record_failed_attempt(
|
|
|
1026
993
|
execution: AgentActivityExecution,
|
|
1027
994
|
reason: str,
|
|
1028
995
|
*,
|
|
1029
|
-
changed_inputs: list[str]
|
|
996
|
+
changed_inputs: list[str],
|
|
1030
997
|
) -> None:
|
|
1031
998
|
"""Consigne les faits d'une tentative refusee pour une reprise controlee.
|
|
1032
999
|
|
|
1033
1000
|
``executionFiles`` est la liste brute des fichiers modifies pendant
|
|
1034
|
-
l'agent, non filtree. ``observedFiles`` vaut les fichiers depuis la base de l'activite ;
|
|
1035
|
-
l'observation est impossible, une fiche minimale (``head`` et ``branch`` vides,
|
|
1036
|
-
``observedFiles`` vide) n'est ecrite que si une entree du besoin
|
|
1037
|
-
sinon aucune fiche n'est ecrite (N7).
|
|
1001
|
+
l'agent, non filtree. ``observedFiles`` vaut les fichiers depuis la base de l'activite ;
|
|
1002
|
+
si l'observation est impossible, une fiche minimale (``head`` et ``branch`` vides,
|
|
1003
|
+
``observedFiles`` vide) n'est ecrite que si une entree du besoin a ete modifiee
|
|
1004
|
+
pendant cette tentative ; sinon aucune fiche n'est ecrite (N7). La fiche est validee
|
|
1005
|
+
par ``attempt_violation`` avant toute ecriture : ``ValueError`` si elle est invalide.
|
|
1038
1006
|
"""
|
|
1039
1007
|
try:
|
|
1040
1008
|
state = read_adaptive_state(feature_dir / "workflow-state.json")
|
|
@@ -1048,13 +1016,12 @@ def _record_failed_attempt(
|
|
|
1048
1016
|
if not changed_inputs:
|
|
1049
1017
|
return
|
|
1050
1018
|
head, branch, observed = "", "", {}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1019
|
+
input_paths = {f"{contract.featurePath}/{name}" for name in changed_inputs}
|
|
1020
|
+
observed = {
|
|
1021
|
+
path: fingerprint
|
|
1022
|
+
for path, fingerprint in observed.items()
|
|
1023
|
+
if path not in input_paths
|
|
1024
|
+
}
|
|
1058
1025
|
payload = {
|
|
1059
1026
|
"schemaVersion": ATTEMPT_SCHEMA_VERSION,
|
|
1060
1027
|
"contractId": contract.contractId,
|
|
@@ -1067,9 +1034,11 @@ def _record_failed_attempt(
|
|
|
1067
1034
|
"rawOutputSha256": _sha256(execution.output),
|
|
1068
1035
|
"observedFiles": observed,
|
|
1069
1036
|
"executionFiles": sorted(set(execution.observedChangedFiles)),
|
|
1037
|
+
"changedInputs": list(changed_inputs),
|
|
1070
1038
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1039
|
+
violation = attempt_violation(payload)
|
|
1040
|
+
if violation is not None:
|
|
1041
|
+
raise ValueError(violation)
|
|
1073
1042
|
_write_json_atomic(activity_attempt_file(feature_dir, contract.activity), payload)
|
|
1074
1043
|
|
|
1075
1044
|
|
|
@@ -1085,26 +1054,11 @@ def _input_paths(feature_dir: Path, feature_path: str) -> list[str]:
|
|
|
1085
1054
|
]
|
|
1086
1055
|
|
|
1087
1056
|
|
|
1088
|
-
def
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
Seules comptent celles qui different encore de la base : une entree remise en
|
|
1094
|
-
etat n'est plus imputee.
|
|
1095
|
-
"""
|
|
1096
|
-
try:
|
|
1097
|
-
attempt = json.loads(
|
|
1098
|
-
activity_attempt_file(feature_dir, activity).read_text(encoding="utf-8")
|
|
1099
|
-
)
|
|
1100
|
-
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
|
1101
|
-
return []
|
|
1102
|
-
if not _valid_attempt(attempt) or attempt["activity"] != activity:
|
|
1103
|
-
return []
|
|
1104
|
-
return sorted(
|
|
1105
|
-
name
|
|
1106
|
-
for name in attempt.get("changedInputs", [])
|
|
1107
|
-
if f"{feature_path}/{name}" in since_base
|
|
1057
|
+
def _imputed_detail(imputed: list[str], ending: str) -> str:
|
|
1058
|
+
"""Detail du refus ``imputed-input`` de run et de reconcile (CT-02, CT-03)."""
|
|
1059
|
+
return (
|
|
1060
|
+
"entrees du besoin modifiees par un agent de cette activite et pas "
|
|
1061
|
+
"remises en etat : " + ", ".join(imputed) + " ; " + ending
|
|
1108
1062
|
)
|
|
1109
1063
|
|
|
1110
1064
|
|
|
@@ -20,6 +20,7 @@ from devflow_cli.core.assessment import (
|
|
|
20
20
|
from devflow_cli.core.contract_schemas import (
|
|
21
21
|
ContractSchemaError,
|
|
22
22
|
LegacySchemaError,
|
|
23
|
+
check_feature_files,
|
|
23
24
|
check_schema_version,
|
|
24
25
|
legacy_workflow_state_error,
|
|
25
26
|
validate_contract_payload,
|
|
@@ -290,8 +291,8 @@ def adaptive_state_violation(path: Path) -> str | None:
|
|
|
290
291
|
|
|
291
292
|
|
|
292
293
|
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``."""
|
|
294
|
+
"""Lit un etat coherent, ou None. Un etat produit par devflow 6.x ou 7.x, ou un
|
|
295
|
+
dossier avec un resultat v1 ou une fiche de schema < 3, leve ``LegacySchemaError``."""
|
|
295
296
|
try:
|
|
296
297
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
297
298
|
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
|
@@ -310,6 +311,7 @@ def read_adaptive_state(path: Path) -> AdaptiveWorkflowState | None:
|
|
|
310
311
|
):
|
|
311
312
|
# Etat sans base : aucune base n'est reconstruite (rupture 8.0.0).
|
|
312
313
|
raise legacy_workflow_state_error(path, data["schemaVersion"])
|
|
314
|
+
check_feature_files(path.parent)
|
|
313
315
|
try:
|
|
314
316
|
validate_contract_payload("workflow-state", data)
|
|
315
317
|
state = AdaptiveWorkflowState(**data)
|
|
@@ -17,8 +17,10 @@ PUBLIC_CONTRACT_SCHEMAS = {
|
|
|
17
17
|
"assessment": "assessment-v4.schema.json",
|
|
18
18
|
"workflow-state": "workflow-state-v6.schema.json",
|
|
19
19
|
"activity-contract": "activity-contract-v4.schema.json",
|
|
20
|
-
"activity-result": "activity-result-
|
|
20
|
+
"activity-result": "activity-result-v2.schema.json",
|
|
21
21
|
}
|
|
22
|
+
# Fiche de tentative (<activite>-attempt.json) : seule definition (CT-04).
|
|
23
|
+
ATTEMPT_SCHEMA_VERSION = 3
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class ContractSchemaError(ValueError):
|
|
@@ -26,7 +28,7 @@ class ContractSchemaError(ValueError):
|
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class LegacySchemaError(ValueError):
|
|
29
|
-
"""A feature file was produced by an older devflow and cannot be read by devflow
|
|
31
|
+
"""A feature file was produced by an older devflow and cannot be read by devflow 9."""
|
|
30
32
|
|
|
31
33
|
def __init__(
|
|
32
34
|
self,
|
|
@@ -44,6 +46,12 @@ class LegacySchemaError(ValueError):
|
|
|
44
46
|
"valide (activityBase absente ou nulle) ; aucune base n'est reconstruite ; "
|
|
45
47
|
"recreez le parcours"
|
|
46
48
|
)
|
|
49
|
+
elif producer == "8.x":
|
|
50
|
+
message = (
|
|
51
|
+
f"{path} : schemaVersion {version} : parcours demarre avec devflow 8.x ; "
|
|
52
|
+
f"le terminer ou le consulter avec devflow 8.5.1 ({install}) ; "
|
|
53
|
+
"aucune migration"
|
|
54
|
+
)
|
|
47
55
|
elif producer == "7.x":
|
|
48
56
|
message = (
|
|
49
57
|
f"{path} : schemaVersion {version} : parcours cree avec devflow 7.x ; "
|
|
@@ -52,13 +60,14 @@ class LegacySchemaError(ValueError):
|
|
|
52
60
|
else:
|
|
53
61
|
message = (
|
|
54
62
|
f"{path} : schemaVersion {version} produit par devflow {producer}, non "
|
|
55
|
-
f"lisible par devflow
|
|
63
|
+
f"lisible par devflow 9 ; le terminer ou le consulter avec "
|
|
56
64
|
f"devflow {producer} ({install})"
|
|
57
65
|
)
|
|
58
66
|
super().__init__(message)
|
|
59
67
|
|
|
60
68
|
|
|
61
69
|
LEGACY_7X_INSTALL = "uv tool install devflow-cli==7.2.0"
|
|
70
|
+
LEGACY_8X_INSTALL = "uv tool install devflow-cli==8.5.1"
|
|
62
71
|
# Dernier schemaVersion de workflow-state produit par devflow 7.x.
|
|
63
72
|
WORKFLOW_STATE_7X_VERSION = 5
|
|
64
73
|
|
|
@@ -91,6 +100,37 @@ def check_schema_version(name: str, path: Path, payload: object) -> None:
|
|
|
91
100
|
)
|
|
92
101
|
|
|
93
102
|
|
|
103
|
+
def check_feature_files(feature_dir: Path) -> None:
|
|
104
|
+
"""Refuse un dossier dont la racine porte un resultat v1 ou une fiche de schema < 3.
|
|
105
|
+
|
|
106
|
+
Seuls les fichiers ordinaires de la racine sont parcourus, par ordre de nom ; un
|
|
107
|
+
fichier illisible, non JSON, non objet ou sans version entiere est laisse aux
|
|
108
|
+
validations existantes.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
entries = sorted(feature_dir.iterdir(), key=lambda entry: entry.name)
|
|
112
|
+
except OSError:
|
|
113
|
+
return
|
|
114
|
+
for entry in entries:
|
|
115
|
+
if entry.name.endswith("-result.json"):
|
|
116
|
+
current = contract_schema_version("activity-result")
|
|
117
|
+
elif entry.name.endswith("-attempt.json"):
|
|
118
|
+
current = ATTEMPT_SCHEMA_VERSION
|
|
119
|
+
else:
|
|
120
|
+
continue
|
|
121
|
+
try:
|
|
122
|
+
if not entry.is_file():
|
|
123
|
+
continue
|
|
124
|
+
payload = json.loads(entry.read_text(encoding="utf-8"))
|
|
125
|
+
except (OSError, ValueError):
|
|
126
|
+
continue
|
|
127
|
+
version = payload.get("schemaVersion") if isinstance(payload, dict) else None
|
|
128
|
+
if isinstance(version, int) and not isinstance(version, bool) and version < current:
|
|
129
|
+
raise LegacySchemaError(
|
|
130
|
+
entry, version, producer="8.x", install=LEGACY_8X_INSTALL
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
94
134
|
@lru_cache(maxsize=None)
|
|
95
135
|
def _schema(name: str) -> dict[str, Any]:
|
|
96
136
|
try:
|
devflow_cli/core/feedback.py
CHANGED
|
@@ -24,6 +24,7 @@ from devflow_cli import VERSION
|
|
|
24
24
|
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
25
25
|
from devflow_cli.core.contract_schemas import (
|
|
26
26
|
LegacySchemaError,
|
|
27
|
+
check_feature_files,
|
|
27
28
|
check_schema_version,
|
|
28
29
|
contract_schema_version,
|
|
29
30
|
legacy_workflow_state_error,
|
|
@@ -162,6 +163,7 @@ def _read_state(feature_dir: Path) -> tuple[str | None, str | None]:
|
|
|
162
163
|
and state.get("activityBase") is None
|
|
163
164
|
):
|
|
164
165
|
raise legacy_workflow_state_error(path, state["schemaVersion"])
|
|
166
|
+
check_feature_files(feature_dir)
|
|
165
167
|
activity = state.get("currentActivity")
|
|
166
168
|
profile = state.get("profile")
|
|
167
169
|
return (
|
devflow_cli/core/remediation.py
CHANGED
|
@@ -4,13 +4,21 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import shlex
|
|
7
8
|
import shutil
|
|
8
9
|
import tempfile
|
|
9
10
|
from dataclasses import replace
|
|
10
11
|
from datetime import datetime, timezone
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
|
|
13
|
-
from devflow_cli.core.activity_base import
|
|
14
|
+
from devflow_cli.core.activity_base import (
|
|
15
|
+
BaseObservationError,
|
|
16
|
+
attempt_violation,
|
|
17
|
+
feature_path_for,
|
|
18
|
+
files_since_activity_base,
|
|
19
|
+
imputed_inputs,
|
|
20
|
+
)
|
|
21
|
+
from devflow_cli.core.activity_contracts import activity_contract_id
|
|
14
22
|
from devflow_cli.core.adaptive_workflow import (
|
|
15
23
|
AdaptiveWorkflowState,
|
|
16
24
|
StateInvariantError,
|
|
@@ -18,7 +26,11 @@ from devflow_cli.core.adaptive_workflow import (
|
|
|
18
26
|
state_invariant_violation,
|
|
19
27
|
)
|
|
20
28
|
from devflow_cli.core.assessment import compute_route
|
|
21
|
-
from devflow_cli.utils.paths import
|
|
29
|
+
from devflow_cli.utils.paths import (
|
|
30
|
+
activity_attempt_file,
|
|
31
|
+
activity_raw_file,
|
|
32
|
+
project_root_for_feature,
|
|
33
|
+
)
|
|
22
34
|
|
|
23
35
|
|
|
24
36
|
def reopen_activity(
|
|
@@ -46,6 +58,8 @@ def reopen_activity(
|
|
|
46
58
|
raise ValueError(
|
|
47
59
|
"la cible doit etre une activite deja completee avant l'activite courante"
|
|
48
60
|
)
|
|
61
|
+
if state.currentActivity != "done":
|
|
62
|
+
_refuse_imputed_inputs(feature_dir, state)
|
|
49
63
|
|
|
50
64
|
activities = list(
|
|
51
65
|
compute_route(
|
|
@@ -98,6 +112,18 @@ def reopen_activity(
|
|
|
98
112
|
}
|
|
99
113
|
payload["entries"].append(entry)
|
|
100
114
|
_write_json_atomic(log_file, payload)
|
|
115
|
+
# Les fiches copiees dans l'archive quittent la racine (leur trace est conservee).
|
|
116
|
+
for name in archived_files:
|
|
117
|
+
if "/" in name or not name.endswith("-attempt.json"):
|
|
118
|
+
continue
|
|
119
|
+
try:
|
|
120
|
+
(feature_dir / name).unlink()
|
|
121
|
+
except OSError as exc:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"reouverture interrompue : fiche de tentative non retiree : {name} : "
|
|
124
|
+
f"{exc} ; l'archive {entry['archivePath']} et reopen-log.json "
|
|
125
|
+
"en gardent la trace"
|
|
126
|
+
) from exc
|
|
101
127
|
|
|
102
128
|
state.activities = reopened.activities
|
|
103
129
|
state.currentActivity = reopened.currentActivity
|
|
@@ -107,6 +133,45 @@ def reopen_activity(
|
|
|
107
133
|
return entry
|
|
108
134
|
|
|
109
135
|
|
|
136
|
+
def _refuse_imputed_inputs(feature_dir: Path, state: AdaptiveWorkflowState) -> None:
|
|
137
|
+
"""Refuse la reouverture si la fiche courante impute des entrees encore modifiees."""
|
|
138
|
+
activity = state.currentActivity
|
|
139
|
+
try:
|
|
140
|
+
attempt = json.loads(
|
|
141
|
+
activity_attempt_file(feature_dir, activity).read_text(encoding="utf-8")
|
|
142
|
+
)
|
|
143
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
|
|
144
|
+
return
|
|
145
|
+
if attempt_violation(attempt) is not None or attempt["activity"] != activity:
|
|
146
|
+
return
|
|
147
|
+
contract_id = activity_contract_id(state.issueId, state.profile, activity)
|
|
148
|
+
if attempt["contractId"] != contract_id or not attempt["changedInputs"]:
|
|
149
|
+
return
|
|
150
|
+
issue = shlex.quote(state.issueId)
|
|
151
|
+
discard = f"devflow adaptive discard-attempt {issue} --reason MOTIF"
|
|
152
|
+
try:
|
|
153
|
+
feature_path = feature_path_for(project_root_for_feature(feature_dir), feature_dir)
|
|
154
|
+
if feature_path is None:
|
|
155
|
+
raise ValueError("dossier de la feature hors du depot")
|
|
156
|
+
since_base = files_since_activity_base(feature_dir, state)
|
|
157
|
+
except (BaseObservationError, OSError, ValueError) as exc:
|
|
158
|
+
cause = f"{exc.reason}: {exc}" if isinstance(exc, BaseObservationError) else str(exc)
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"reouverture refusee : entrees du besoin imputees a un agent de '{activity}' "
|
|
161
|
+
f"({', '.join(attempt['changedInputs'])}) et fichiers depuis la base non "
|
|
162
|
+
f"calcules : {cause} ; abandonner la tentative ({discard}), puis relancer "
|
|
163
|
+
"la reouverture"
|
|
164
|
+
) from exc
|
|
165
|
+
names = imputed_inputs(feature_dir, feature_path, activity, contract_id, since_base)
|
|
166
|
+
if names:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"reouverture refusee : entrees du besoin modifiees par un agent de "
|
|
169
|
+
f"'{activity}' et pas remises en etat : {', '.join(names)} ; les remettre "
|
|
170
|
+
f"dans leur etat de base, ou abandonner la tentative ({discard}), puis "
|
|
171
|
+
"relancer la reouverture"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
110
175
|
def discard_attempt(
|
|
111
176
|
feature_dir: Path,
|
|
112
177
|
state: AdaptiveWorkflowState,
|
devflow_cli/core/worklog.py
CHANGED
|
@@ -217,14 +217,20 @@ def _render_managed(
|
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
def _observation_warnings(feature_dir: Path, activities: list[str]) -> list[str]:
|
|
220
|
-
"""Une ligne par resultat enregistre portant un ecart de declaration (S9)."""
|
|
220
|
+
"""Une ligne par resultat enregistre portant un ecart de declaration ou amendement (S9)."""
|
|
221
221
|
lines: list[str] = []
|
|
222
222
|
for activity in activities:
|
|
223
223
|
result = _read_json(activity_result_file(feature_dir, activity), required=False)
|
|
224
224
|
observation = result.get("observation") if isinstance(result, dict) else None
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
if not isinstance(observation, dict):
|
|
226
|
+
continue
|
|
227
|
+
texts = [
|
|
228
|
+
observation[key]
|
|
229
|
+
for key in ("warning", "amendmentWarning")
|
|
230
|
+
if isinstance(observation.get(key), str) and observation[key]
|
|
231
|
+
]
|
|
232
|
+
if texts:
|
|
233
|
+
lines.append(f"- `{activity}` : {' ; '.join(texts)}")
|
|
228
234
|
return lines
|
|
229
235
|
|
|
230
236
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://devflow.dev/schemas/activity-result/
|
|
4
|
-
"title": "Devflow Activity Result
|
|
3
|
+
"$id": "https://devflow.dev/schemas/activity-result/v2",
|
|
4
|
+
"title": "Devflow Activity Result v2",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": [
|
|
7
7
|
"schemaVersion",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"blockers"
|
|
16
16
|
],
|
|
17
17
|
"properties": {
|
|
18
|
-
"schemaVersion": {"type": "integer", "const":
|
|
18
|
+
"schemaVersion": {"type": "integer", "const": 2},
|
|
19
19
|
"contractId": {"type": "string", "minLength": 1},
|
|
20
20
|
"status": {"type": "string", "enum": ["completed", "blocked"]},
|
|
21
21
|
"summary": {"type": "string", "minLength": 1},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: devflow-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.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
|
|
@@ -150,6 +150,15 @@ devflow adaptive run KS-123 --agent claude
|
|
|
150
150
|
devflow adaptive run KS-123 --agent cursor
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
## Features créées avant devflow 9
|
|
154
|
+
|
|
155
|
+
Devflow 9 refuse, sans migration, un dossier de feature dont la racine contient
|
|
156
|
+
un `*-result.json` de `schemaVersion` 1 ou un `*-attempt.json` de schéma 2 ou
|
|
157
|
+
moins (parcours démarré avec devflow 8.x). Terminez les parcours en cours avec
|
|
158
|
+
devflow 8.5.1, ou gardez la CLI épinglée :
|
|
159
|
+
`uv tool install devflow-cli==8.5.1`. Voir les
|
|
160
|
+
[notes de version 9.0.0](docs/releases/9.0.0.md).
|
|
161
|
+
|
|
153
162
|
## Features créées avant devflow 8
|
|
154
163
|
|
|
155
164
|
Devflow 8 refuse, sans migration, un `workflow-state.json` (schéma 5) produit
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
devflow_cli/__init__.py,sha256=
|
|
1
|
+
devflow_cli/__init__.py,sha256=TVrbUn4dfhBCXtCVCypThAPpg7SGLsuYX076rhwMzac,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=iCS0BvaD2Z1fgI3cUsdrpQs52w_THS-v2I8AyvJy2N8,72771
|
|
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
|
|
@@ -17,31 +17,31 @@ devflow_cli/commands/hooks_cmd.py,sha256=BMzNBKHIpQkMj5y-2bxtCNqAa8cncGfGTWMgx6S
|
|
|
17
17
|
devflow_cli/commands/init_cmd.py,sha256=VXR52ocqkO23J18pgij0Mhc_BTBl2PolxArjoCOuNow,5383
|
|
18
18
|
devflow_cli/commands/linear.py,sha256=i066d5ryyBzZVfrSudbD0VWg9fSLivnB8yNe4vAMMW0,972
|
|
19
19
|
devflow_cli/commands/stale.py,sha256=UiaUb48lrXmPyjfvbFkChfLE-rs58KhgKu-zgL_NBIo,3182
|
|
20
|
-
devflow_cli/commands/status.py,sha256=
|
|
20
|
+
devflow_cli/commands/status.py,sha256=Votdh_sZkRuC_iui1krrdXE9cZ5dpY2OO2au4PVa7IQ,8647
|
|
21
21
|
devflow_cli/commands/traceability.py,sha256=DhUOBzSmKVk17j1Qz0yxD5zD2-iNWaR9F133VNPjy70,1767
|
|
22
22
|
devflow_cli/commands/upgrade_cmd.py,sha256=d5uBFUPzPsCJDLxhQBFaeHWJv3GWWLmPpkYHRVd1gwM,5664
|
|
23
23
|
devflow_cli/commands/usage.py,sha256=ehH1AH5J-XfkebFx6qLBwpKN-MOoX2uUbq4LejBpNsk,3148
|
|
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
|
-
devflow_cli/core/activity_base.py,sha256=
|
|
27
|
-
devflow_cli/core/activity_contracts.py,sha256=
|
|
28
|
-
devflow_cli/core/adaptive_runner.py,sha256=
|
|
29
|
-
devflow_cli/core/adaptive_workflow.py,sha256=
|
|
26
|
+
devflow_cli/core/activity_base.py,sha256=eEC_YIr5O7WhCglA7xAEPypVzhcdC6f2h9d-SDDecVA,20475
|
|
27
|
+
devflow_cli/core/activity_contracts.py,sha256=oN8ATaRhsEz-o4PhdX9ASwpCooXK0Wo_wExmWXkiRCY,39406
|
|
28
|
+
devflow_cli/core/adaptive_runner.py,sha256=BuVGk-P0nmtD5PRuHBXNOS4epwA6RFivPRxn3zb_LrU,47068
|
|
29
|
+
devflow_cli/core/adaptive_workflow.py,sha256=NgDEr67monxalcGiO3R_ACVUrZu_OX6LQRhQJbMTWT8,14687
|
|
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
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=JXI8PVgjdagTlFxnyuIkNRRrVi319Mv8GwuMxopJ_GI,8424
|
|
36
36
|
devflow_cli/core/docgen.py,sha256=47JoVDWetkcctgHwJZC67AVebOKgl10VB9hZ3Xr63mo,3908
|
|
37
|
-
devflow_cli/core/feedback.py,sha256=
|
|
37
|
+
devflow_cli/core/feedback.py,sha256=_YvUBA4nMzXYZchvQagMbazNIvZIqRsnqL94ESfOde4,19027
|
|
38
38
|
devflow_cli/core/git.py,sha256=gsDfT-hDNtMQFRUl1WzZSqltrdwZQ2BJBcCIh4TwDkA,2780
|
|
39
39
|
devflow_cli/core/hooks.py,sha256=nFPaziFJgeZgAPKPxbTibrB_0MJAST3cAmg3gtcvDBQ,8067
|
|
40
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=ywOOJUyT6SYbK79NnsIZcpn90HBZhavFxl4JSB9Vz3s,12254
|
|
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,10 +53,10 @@ 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=itKIgzduDvl5STv3B_Yle9qi9W11Xhbg4_bDyCFVBqQ,14850
|
|
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
|
-
devflow_cli/schemas/activity-result-
|
|
59
|
+
devflow_cli/schemas/activity-result-v2.schema.json,sha256=2yDf6S--mlNWMxE8h8ruwTsM3Tnaa96SJDjluc4ePbI,2772
|
|
60
60
|
devflow_cli/schemas/assessment-v4.schema.json,sha256=byFBaoG_Vu-qS7vERREKThtpQa3dzNtPiFCMyEpWFVQ,2254
|
|
61
61
|
devflow_cli/schemas/workflow-state-v6.schema.json,sha256=fYyJS-6Aq-Ea7y2bVqJYSmKzdF2DvL3Nb6IdDNgFGfY,3239
|
|
62
62
|
devflow_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -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-
|
|
94
|
-
devflow_cli-
|
|
95
|
-
devflow_cli-
|
|
96
|
-
devflow_cli-
|
|
97
|
-
devflow_cli-
|
|
93
|
+
devflow_cli-9.0.0.dist-info/METADATA,sha256=P54H3nX_kUKvDUIgpC69PPpSkrT59fM-yLGl1Q2K9rs,10403
|
|
94
|
+
devflow_cli-9.0.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
95
|
+
devflow_cli-9.0.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
|
|
96
|
+
devflow_cli-9.0.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
|
|
97
|
+
devflow_cli-9.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|