devflow-cli 3.0.0__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
devflow_cli/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """devflow CLI — workflow de développement adaptatif."""
2
2
 
3
- VERSION = "3.0.0"
3
+ VERSION = "3.0.1"
4
4
 
5
5
  SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
6
6
 
@@ -230,6 +230,23 @@ class CodexAdapter(AgentAdapter):
230
230
  command.append(prompt)
231
231
  return command
232
232
 
233
+ def _structured_payload(self, stdout: str, output_file: Path) -> object:
234
+ """Lit la reponse finale Codex et distingue son absence d'un JSON invalide."""
235
+ if output_file.is_file():
236
+ payload = output_file.read_text(encoding="utf-8")
237
+ if payload.strip():
238
+ return json.loads(payload)
239
+ raise ValueError(
240
+ "Codex a termine sans reponse finale structuree "
241
+ "(--output-last-message est vide)"
242
+ )
243
+ if stdout.strip():
244
+ return json.loads(stdout)
245
+ raise ValueError(
246
+ "Codex n'a pas produit de reponse finale structuree "
247
+ "ni de sortie standard exploitable"
248
+ )
249
+
233
250
 
234
251
  class CursorAdapter(AgentAdapter):
235
252
  name = "cursor"
@@ -32,7 +32,11 @@ from devflow_cli.core.worktree_snapshot import changed_paths, snapshot_worktree
32
32
  from devflow_cli.core.automatic_evidence import run_automatic_verification
33
33
  from devflow_cli.core.worklog import refresh_worklog, safe_refresh_worklog
34
34
  from devflow_cli.core.waivers import create_validation_waiver
35
- from devflow_cli.core.remediation import carried_changed_files, reopen_activity
35
+ from devflow_cli.core.remediation import (
36
+ carried_changed_files,
37
+ is_reopened_activity,
38
+ reopen_activity,
39
+ )
36
40
  from devflow_cli.core.contract_schemas import diagnose_contract_file
37
41
  from devflow_cli.core.operational import ExitCode, exit_code_for_run_reason
38
42
  from devflow_cli.adapters.agents import get_agent_adapter
@@ -193,6 +197,8 @@ def contract(
193
197
  assessment.to_dict(),
194
198
  feature_path,
195
199
  carried_changed_files(feature_dir, state.currentActivity),
200
+ reopened=is_reopened_activity(feature_dir, state.currentActivity),
201
+ preflight_evidence=(feature_dir / "preflight-evidence.md").is_file(),
196
202
  )
197
203
  except ValueError as exc:
198
204
  fail(f"Impossible de generer le contrat : {exc}")
@@ -97,11 +97,27 @@ class ActivityResult:
97
97
  return payload
98
98
 
99
99
 
100
+ def document_artifact_name(activity: str, *, reopened: bool = False) -> str:
101
+ """Retourne l'artefact unique attendu pour une activite documentaire."""
102
+ if activity == "plan" and reopened:
103
+ return "preflight-evidence.md"
104
+ return DOCUMENT_ARTIFACTS[activity]
105
+
106
+
107
+ def contract_is_reopened_plan(contract: ActivityContract) -> bool:
108
+ return (
109
+ contract.activity == "plan"
110
+ and f"{contract.featurePath}/reopen-log.json" in contract.inputs
111
+ )
112
+
113
+
100
114
  def build_activity_contract(
101
115
  state: AdaptiveWorkflowState,
102
116
  assessment: dict[str, Any],
103
117
  feature_path: str,
104
118
  carried_changed_files: list[str] | None = None,
119
+ reopened: bool = False,
120
+ preflight_evidence: bool = False,
105
121
  ) -> ActivityContract:
106
122
  """Construit le contrat de l'activite courante sans notion d'agent."""
107
123
  activity = state.currentActivity
@@ -126,7 +142,7 @@ def build_activity_contract(
126
142
  "Signaler tout blocage au lieu de contourner une validation.",
127
143
  ]
128
144
  if activity in DOCUMENT_ACTIVITIES:
129
- artifact_name = DOCUMENT_ARTIFACTS[activity]
145
+ artifact_name = document_artifact_name(activity, reopened=reopened)
130
146
  allowed_actions = ["read", "write", "execute"]
131
147
  constraints = [
132
148
  *common_constraints,
@@ -134,6 +150,11 @@ def build_activity_contract(
134
150
  "Produire un artefact complet sans placeholder ni section vide.",
135
151
  "Declarer uniquement cet artefact dans changedFiles.",
136
152
  ]
153
+ if activity == "plan" and reopened:
154
+ constraints.append(
155
+ "Archiver les faits, la source, la reproductibilite et les limites connues "
156
+ "dans preflight-evidence.md, sans y recopier de donnees sensibles."
157
+ )
137
158
  completion_criteria = [
138
159
  f"{artifact_name} existe et respecte la structure devflow.",
139
160
  "Le contenu est coherent avec l'objectif et les artefacts en entree.",
@@ -301,8 +322,18 @@ def build_activity_contract(
301
322
  f"{feature_path}/assessment.json",
302
323
  f"{feature_path}/workflow-state.json",
303
324
  ]
304
- if activity == "implement" and carried_changed_files:
325
+ if reopened:
305
326
  inputs.append(f"{feature_path}/reopen-log.json")
327
+ constraints.append(
328
+ "Cette execution suit une reouverture auditee : lire reopen-log.json et corriger explicitement le motif qui y est consigne."
329
+ )
330
+ if activity == "plan" and reopened:
331
+ inputs.append(f"{feature_path}/plan.md")
332
+ if activity == "implement" and preflight_evidence:
333
+ inputs.append(f"{feature_path}/preflight-evidence.md")
334
+ constraints.append(
335
+ "Prendre en compte la preuve de pre-vol archivee et ne pas la presenter comme absente."
336
+ )
306
337
  if activity == "implement" and state.profile == "standard":
307
338
  inputs.append(f"{feature_path}/prepare-result.json")
308
339
  if state.profile == "deep":
@@ -351,6 +382,8 @@ def build_activity_contract(
351
382
  if activity == "rollback-check":
352
383
  inputs.append(f"{feature_path}/review-result.json")
353
384
 
385
+ expected_output = _result_schema(activity)
386
+
354
387
  return ActivityContract(
355
388
  schemaVersion=ACTIVITY_CONTRACT_SCHEMA_VERSION,
356
389
  contractId=f"{state.issueId}:{state.profile}:{activity}:v1",
@@ -364,7 +397,7 @@ def build_activity_contract(
364
397
  constraints=constraints,
365
398
  completionCriteria=completion_criteria,
366
399
  requiredEvidence=required_evidence,
367
- expectedOutput=_result_schema(activity),
400
+ expectedOutput=expected_output,
368
401
  )
369
402
 
370
403
 
@@ -432,9 +465,7 @@ def parse_activity_result(payload: dict[str, Any], contract: ActivityContract) -
432
465
  if contract.activity == "prepare" and status == "completed" and not validation_plan:
433
466
  raise ValueError("prepare complete requiert un plan de validation")
434
467
  if contract.activity in DOCUMENT_ACTIVITIES and status == "completed":
435
- expected_artifact = (
436
- f"{contract.featurePath}/{DOCUMENT_ARTIFACTS[contract.activity]}"
437
- )
468
+ expected_artifact = f"{contract.featurePath}/{document_artifact_name(contract.activity, reopened=contract_is_reopened_plan(contract))}"
438
469
  if changed_files != [expected_artifact]:
439
470
  raise ValueError(
440
471
  f"{contract.activity} doit declarer uniquement {expected_artifact}"
@@ -14,6 +14,8 @@ from devflow_cli.core.activity_contracts import (
14
14
  ActivityResult,
15
15
  DOCUMENT_ARTIFACTS,
16
16
  build_activity_contract,
17
+ contract_is_reopened_plan,
18
+ document_artifact_name,
17
19
  parse_activity_result,
18
20
  write_activity_contract,
19
21
  )
@@ -30,7 +32,7 @@ from devflow_cli.core.traceability import analyze_traceability
30
32
  from devflow_cli.core.validators import validate_artifact
31
33
  from devflow_cli.core.verification import ValidationCommand, validate_command_policy
32
34
  from devflow_cli.core.waivers import is_protected_validation
33
- from devflow_cli.core.remediation import carried_changed_files
35
+ from devflow_cli.core.remediation import carried_changed_files, is_reopened_activity
34
36
 
35
37
 
36
38
  @dataclass(frozen=True)
@@ -81,7 +83,12 @@ def run_current_activity(
81
83
  try:
82
84
  carried = carried_changed_files(feature_dir, state.currentActivity)
83
85
  contract = build_activity_contract(
84
- state, assessment.to_dict(), feature_path, carried
86
+ state,
87
+ assessment.to_dict(),
88
+ feature_path,
89
+ carried,
90
+ reopened=is_reopened_activity(feature_dir, state.currentActivity),
91
+ preflight_evidence=(feature_dir / "preflight-evidence.md").is_file(),
85
92
  )
86
93
  except ValueError as exc:
87
94
  return AdaptiveRunOutcome(
@@ -176,7 +183,9 @@ def run_current_activity(
176
183
  result,
177
184
  )
178
185
  if contract.activity in DOCUMENT_ARTIFACTS:
179
- artifact_name = DOCUMENT_ARTIFACTS[contract.activity]
186
+ artifact_name = document_artifact_name(
187
+ contract.activity, reopened=contract_is_reopened_plan(contract)
188
+ )
180
189
  expected = f"{feature_path}/{artifact_name}"
181
190
  allowed_declared = not declared or declared == [expected]
182
191
  allowed_observed = not observed or observed == [expected]
@@ -193,17 +202,29 @@ def run_current_activity(
193
202
  result,
194
203
  )
195
204
  if contract.activity in DOCUMENT_ARTIFACTS and result.status == "completed":
196
- artifact_name = DOCUMENT_ARTIFACTS[contract.activity]
197
- validator_name = (
198
- f"critical-{contract.activity}"
199
- if contract.profile == "critical"
200
- and contract.activity in {"research", "contracts"}
201
- else contract.activity
202
- )
203
- validation = validate_artifact(
204
- validator_name, feature_dir / artifact_name
205
+ artifact_name = document_artifact_name(
206
+ contract.activity, reopened=contract_is_reopened_plan(contract)
205
207
  )
206
- if not validation.valid:
208
+ if artifact_name == "preflight-evidence.md":
209
+ try:
210
+ if not (feature_dir / artifact_name).read_text(encoding="utf-8").strip():
211
+ raise OSError("artefact vide")
212
+ except OSError as exc:
213
+ return AdaptiveRunOutcome(
214
+ "invalid-artifact", contract.activity, contract.activity, str(exc), result
215
+ )
216
+ validation = None
217
+ else:
218
+ validator_name = (
219
+ f"critical-{contract.activity}"
220
+ if contract.profile == "critical"
221
+ and contract.activity in {"research", "contracts"}
222
+ else contract.activity
223
+ )
224
+ validation = validate_artifact(
225
+ validator_name, feature_dir / artifact_name
226
+ )
227
+ if validation is not None and not validation.valid:
207
228
  return AdaptiveRunOutcome(
208
229
  "invalid-artifact",
209
230
  contract.activity,
@@ -82,6 +82,17 @@ def carried_changed_files(feature_dir: Path, activity: str) -> list[str]:
82
82
  return sorted(set(files))
83
83
 
84
84
 
85
+ def is_reopened_activity(feature_dir: Path, activity: str) -> bool:
86
+ """Indique si l'activite courante provient de la derniere reouverture auditee."""
87
+ try:
88
+ payload = json.loads((feature_dir / "reopen-log.json").read_text(encoding="utf-8"))
89
+ entries = payload["entries"]
90
+ entry = entries[-1]
91
+ except (OSError, json.JSONDecodeError, KeyError, IndexError, TypeError):
92
+ return False
93
+ return entry.get("targetActivity") == activity
94
+
95
+
85
96
  def _read_log(path: Path, issue_id: str) -> dict:
86
97
  if not path.exists():
87
98
  return {"schemaVersion": 1, "issueId": issue_id, "entries": []}
@@ -42,7 +42,7 @@ ARTIFACT_RULES: dict[str, dict] = {
42
42
  "spec": {
43
43
  "required_section_groups": [
44
44
  ["User Scenarios & Testing", "User Stories"],
45
- ["Requirements"],
45
+ ["Requirements", "Exigences"],
46
46
  ["Success Criteria"],
47
47
  ],
48
48
  "min_size": 50,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devflow-cli
3
- Version: 3.0.0
3
+ Version: 3.0.1
4
4
  Summary: Spec-Driven Development workflow CLI
5
5
  Project-URL: Homepage, https://github.com/sopequenoteck/devflow
6
6
  Project-URL: Repository, https://github.com/sopequenoteck/devflow
@@ -1,10 +1,10 @@
1
- devflow_cli/__init__.py,sha256=4ePEWmFMSyinkrF5OebrDxCoWIpGWewZ2oPpOmZ3T5k,938
1
+ devflow_cli/__init__.py,sha256=BpztDtcz1AjuyzEjqH-J9D8Bp2JXv8vfkPYNoXHB69U,938
2
2
  devflow_cli/cli.py,sha256=HpDAnapEm2Hmnn2ltTmdjwpmoXvW7fat6ipQo3xBCks,2139
3
3
  devflow_cli/adapters/__init__.py,sha256=RkjHPU2Lp4hSVIq2iOmyTrv2zYU9NuiIF9wEMWzLdMI,377
4
- devflow_cli/adapters/agents.py,sha256=eHoR6Ko2_aV0bzWRQfFZeYRAkD6LjJQ04nJjvpSFpys,11161
4
+ devflow_cli/adapters/agents.py,sha256=RGVFNV8YJZGsVDxykh1pvnGZu5gevX6zhGeiSq9Jm14,11873
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=Cu2fJv7-rvY7_5AzgrAtokdInvqOz724XuX0nEJez5g,22504
7
+ devflow_cli/commands/adaptive.py,sha256=8afSr85JilTJKJQTN8IGV9eh464jslQGBssKieZAa2k,22712
8
8
  devflow_cli/commands/assess.py,sha256=GrWGGZjvBAgHWzSlUdtS-IfYocacA6VYxWduDvBunzg,4395
9
9
  devflow_cli/commands/check.py,sha256=ULsMNzc-VM635Kkykt9QYVXQk_69T3E2nj4Cx-Cp6xk,5337
10
10
  devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
@@ -22,8 +22,8 @@ devflow_cli/commands/traceability.py,sha256=DhUOBzSmKVk17j1Qz0yxD5zD2-iNWaR9F133
22
22
  devflow_cli/commands/upgrade_cmd.py,sha256=Dha72Cqy-7pVi7jcvpOyxe6qNp--I47OaElfztKriPg,5380
23
23
  devflow_cli/commands/verify.py,sha256=1Fn5dxhVEvuG2C4OC7cMHv3oV4pthu3QLsFjQaCZrnA,2670
24
24
  devflow_cli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- devflow_cli/core/activity_contracts.py,sha256=JEI6NJZy_AXIlTo6dlX6PhuDk47fsjtBtW4M1dddkxw,29424
26
- devflow_cli/core/adaptive_runner.py,sha256=X0BL13mhTvn2wZrpBvMW4iF8ZQkT52Zy2T0sURJ0aO0,12480
25
+ devflow_cli/core/activity_contracts.py,sha256=uPk770qJYCJKvF3sJNMmueAwtBSLPjouac_JMUa8c18,30796
26
+ devflow_cli/core/adaptive_runner.py,sha256=-1eZYaZ2cIe_1gHGlSaOS-rOD3TsuMthXRgvCGV5SKE,13432
27
27
  devflow_cli/core/adaptive_workflow.py,sha256=BZ3N7-hbbvUT0UjSsknqG0I1CJN6ZYuuA566GzdKM-A,10731
28
28
  devflow_cli/core/assessment.py,sha256=_fL5bsl90-yZYjf7CSX7Nxhf1auyeAcKHsoo_reafTE,9824
29
29
  devflow_cli/core/automatic_evidence.py,sha256=lQI7siiMUb5-c-XDhAsDn-5UdrZPbt4XIreU1qGvuNg,28723
@@ -36,11 +36,11 @@ devflow_cli/core/installer.py,sha256=MfcGZKoOcQc2qviNnGShoD0P_MqOgRuVHX7YwCrG2vw
36
36
  devflow_cli/core/manifest.py,sha256=J7g2SzhXy-Vi4w9-rEPmARSZXo8KTOxfd2pEc9jNUME,5933
37
37
  devflow_cli/core/operational.py,sha256=wJ0Q87thNyCGpUs420DdkqzFHM-nIw_1tDAuPqlXX8w,977
38
38
  devflow_cli/core/redaction.py,sha256=o_1MDtCyvRLrk_dWlV6_7E6_mPVB1B5a0cCfTBj9j0Q,1973
39
- devflow_cli/core/remediation.py,sha256=sXbRI8HGh75QXXiZO7qL_89cpRoF2dw18uvbtDAQwI8,5794
39
+ devflow_cli/core/remediation.py,sha256=cReuxUi_k-xP74Hj16MFT6PgDwNBRV0jUm8asFIBztk,6265
40
40
  devflow_cli/core/scanner.py,sha256=bcoC91Nc0Hw2P7aHpL-9ITxynLZhLTEKpOXo553a-18,4395
41
41
  devflow_cli/core/staleness.py,sha256=Bn4jN8r601Ifn0AU8BHZy-Z-5NLPjj_JTK-V0KU9TUM,5256
42
42
  devflow_cli/core/traceability.py,sha256=jfNE9MZzGkmaPer9Wa6Y18yA9Y3sqdeR-nzH9h_TB-8,2968
43
- devflow_cli/core/validators.py,sha256=RK4_gfnm6MWLioGtG7fX_MjoGkepr3XRwjF1w6dyC_k,8063
43
+ devflow_cli/core/validators.py,sha256=U4Rf9sZNI75XpzixRAeiJhf53Ktexgu5OtV6SBjSM6w,8076
44
44
  devflow_cli/core/verification.py,sha256=zChsM8m-KtoJPeJiTwqOHTwMgmbg_GR7T7FfYoyHdlE,12466
45
45
  devflow_cli/core/waivers.py,sha256=w3bza1OicSxAViYU2hvbrylQTNVgmN7Rd9y2L6Wm2OE,11302
46
46
  devflow_cli/core/worklog.py,sha256=VLyn_IcOiM0VEVnQScDebaAIQ3vcHQigiTGS5Ldc6LQ,11000
@@ -81,8 +81,8 @@ devflow_cli/resources/templates/contracts/api-template.md,sha256=rC5v7JBz71PznCn
81
81
  devflow_cli/resources/templates/contracts/components-template.md,sha256=XIWfEXfC72sNmL5OA3V-7xIywkNDU2DBPLfyiagOWcs,484
82
82
  devflow_cli/resources/templates/contracts/interfaces-template.md,sha256=kf8B7b6HsmN7jyRlLNU7RnZl0rTMlI5s97yVRyI-6yc,336
83
83
  devflow_cli/resources/templates/contracts/services-template.md,sha256=cFafKCyLmq_a7aR7G0LXAaMO2z4Fuflq6iKdxfVg0vg,477
84
- devflow_cli-3.0.0.dist-info/METADATA,sha256=MLgV-oGw9mDEqo2ZgofpNwS7lIgt0FQdWw-WaHaCMr4,3789
85
- devflow_cli-3.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
86
- devflow_cli-3.0.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
87
- devflow_cli-3.0.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
88
- devflow_cli-3.0.0.dist-info/RECORD,,
84
+ devflow_cli-3.0.1.dist-info/METADATA,sha256=Rf2yqEkAPVZmI72ovh0h4VdOeucNSHyC7-Je1ZMDlOI,3789
85
+ devflow_cli-3.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
86
+ devflow_cli-3.0.1.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
87
+ devflow_cli-3.0.1.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
88
+ devflow_cli-3.0.1.dist-info/RECORD,,