devflow-cli 2.9.0__py3-none-any.whl → 3.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 +20 -86
- devflow_cli/adapters/agents.py +109 -1
- devflow_cli/adapters/linear.py +18 -14
- devflow_cli/cli.py +0 -10
- devflow_cli/commands/adaptive.py +285 -48
- devflow_cli/commands/assess.py +39 -37
- devflow_cli/commands/check.py +1 -15
- devflow_cli/commands/docs_sync.py +2 -2
- devflow_cli/commands/export_cmd.py +15 -73
- devflow_cli/commands/feature.py +32 -47
- devflow_cli/commands/init_cmd.py +12 -2
- devflow_cli/commands/linear.py +2 -2
- devflow_cli/commands/migrate_cmd.py +170 -75
- devflow_cli/commands/stale.py +9 -9
- devflow_cli/commands/status.py +124 -61
- devflow_cli/commands/upgrade_cmd.py +9 -0
- devflow_cli/core/activity_contracts.py +16 -95
- devflow_cli/core/adaptive_runner.py +17 -6
- devflow_cli/core/adaptive_workflow.py +16 -30
- devflow_cli/core/assessment.py +112 -0
- devflow_cli/core/automatic_evidence.py +10 -8
- devflow_cli/core/contract_schemas.py +110 -0
- devflow_cli/core/docgen.py +29 -32
- devflow_cli/core/installer.py +62 -4
- devflow_cli/core/operational.py +37 -0
- devflow_cli/core/redaction.py +68 -0
- devflow_cli/core/remediation.py +6 -3
- devflow_cli/core/staleness.py +22 -35
- devflow_cli/core/verification.py +5 -3
- devflow_cli/resources/commands/devflow.context.md +57 -0
- devflow_cli/resources/commands/devflow.extension.md +63 -0
- devflow_cli/resources/commands/devflow.init.md +50 -0
- devflow_cli/resources/commands/devflow.md +25 -0
- devflow_cli/resources/commands/devflow.regen.md +34 -0
- devflow_cli/resources/commands/devflow.status.md +11 -0
- devflow_cli/resources/extensions/EXTENSION-API-REFERENCE.md +140 -0
- devflow_cli/resources/extensions/catalog.community.json +5 -0
- devflow_cli/resources/extensions/catalog.json +19 -0
- devflow_cli/resources/extensions/template/example-command.md +29 -0
- devflow_cli/resources/extensions/template/example-extension.json +18 -0
- devflow_cli/resources/templates/agent-context-template.md +29 -0
- devflow_cli/resources/templates/constitution-template.md +85 -0
- devflow_cli/resources/templates/contracts/README-template.md +24 -0
- devflow_cli/resources/templates/contracts/api-template.md +38 -0
- devflow_cli/resources/templates/contracts/components-template.md +28 -0
- devflow_cli/resources/templates/contracts/interfaces-template.md +20 -0
- devflow_cli/resources/templates/contracts/services-template.md +22 -0
- devflow_cli/resources/templates/contracts-template.md +105 -0
- devflow_cli/resources/templates/data-model-template.md +55 -0
- devflow_cli/resources/templates/devflow-hooks-template.yml +38 -0
- devflow_cli/resources/templates/extension-manifest-template.json +9 -0
- devflow_cli/resources/templates/plan-template.md +98 -0
- devflow_cli/resources/templates/quickstart-template.md +65 -0
- devflow_cli/resources/templates/research-template.md +51 -0
- devflow_cli/resources/templates/spec-template.md +79 -0
- devflow_cli/resources/templates/tasks-template.md +105 -0
- devflow_cli/schemas/activity-contract-v1.schema.json +76 -0
- devflow_cli/schemas/activity-result-v1.schema.json +85 -0
- devflow_cli/schemas/assessment-v1.schema.json +60 -0
- devflow_cli/schemas/workflow-state-v2.schema.json +66 -0
- devflow_cli/utils/paths.py +23 -12
- devflow_cli-3.0.0.dist-info/METADATA +113 -0
- devflow_cli-3.0.0.dist-info/RECORD +88 -0
- devflow_cli/commands/migrate_speckit.py +0 -344
- devflow_cli/commands/review.py +0 -54
- devflow_cli/commands/rollback.py +0 -53
- devflow_cli/commands/run.py +0 -123
- devflow_cli/commands/step.py +0 -118
- devflow_cli/core/orchestrator.py +0 -170
- devflow_cli/core/state.py +0 -576
- devflow_cli/core/state_store.py +0 -45
- devflow_cli/schemas/state.schema.json +0 -39
- devflow_cli-2.9.0.dist-info/METADATA +0 -223
- devflow_cli-2.9.0.dist-info/RECORD +0 -63
- {devflow_cli-2.9.0.dist-info → devflow_cli-3.0.0.dist-info}/WHEEL +0 -0
- {devflow_cli-2.9.0.dist-info → devflow_cli-3.0.0.dist-info}/entry_points.txt +0 -0
- {devflow_cli-2.9.0.dist-info → devflow_cli-3.0.0.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
|
@@ -1,113 +1,47 @@
|
|
|
1
|
-
"""devflow CLI —
|
|
1
|
+
"""devflow CLI — workflow de développement adaptatif."""
|
|
2
2
|
|
|
3
|
-
VERSION = "
|
|
4
|
-
STATE_SCHEMA_VERSION = 3
|
|
3
|
+
VERSION = "3.0.0"
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
"constitution", # Pré-requis projet (1/projet). Auto-complétée si .specify/memory/constitution.md existe.
|
|
8
|
-
"spec",
|
|
9
|
-
"clarify",
|
|
10
|
-
"review-spec",
|
|
11
|
-
"research",
|
|
12
|
-
"plan",
|
|
13
|
-
"contracts",
|
|
14
|
-
"tasks",
|
|
15
|
-
"review-tasks",
|
|
16
|
-
"implement",
|
|
17
|
-
"review-impl",
|
|
18
|
-
"docs",
|
|
19
|
-
"done",
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
OPTIONAL_STEPS = {"clarify", "research", "contracts", "docs"}
|
|
23
|
-
|
|
24
|
-
# Le mode fast conserve les gates et les bornes du pipeline. Il compte donc
|
|
25
|
-
# 9 etapes effectives, dont 4 etapes productives principales.
|
|
26
|
-
FAST_PRODUCTIVE_STEPS = ("spec", "plan", "tasks", "implement")
|
|
27
|
-
FAST_STEP_COUNT = len(STEPS) - len(OPTIONAL_STEPS)
|
|
28
|
-
FULL_STEP_COUNT = len(STEPS)
|
|
29
|
-
|
|
30
|
-
REVIEW_GATES = {"review-spec", "review-tasks", "review-impl"}
|
|
31
|
-
|
|
32
|
-
STEP_ARTIFACTS = {
|
|
33
|
-
"spec": "spec.md",
|
|
34
|
-
"clarify": "clarify-log.md",
|
|
35
|
-
"review-spec": "review-log.md",
|
|
36
|
-
"research": "research.md",
|
|
37
|
-
"plan": "plan.md",
|
|
38
|
-
"contracts": "contracts",
|
|
39
|
-
"tasks": "tasks.md",
|
|
40
|
-
"review-tasks": "review-log.md",
|
|
41
|
-
"implement": None,
|
|
42
|
-
"review-impl": "review-log.md",
|
|
43
|
-
"docs": "docs.md",
|
|
44
|
-
}
|
|
5
|
+
SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
|
|
45
6
|
|
|
46
|
-
|
|
7
|
+
ACTIVITY_LINEAR_MAPPING = {
|
|
8
|
+
"assess": "Backlog",
|
|
9
|
+
"prepare": "Plan",
|
|
10
|
+
"risk-assessment": "Research",
|
|
47
11
|
"spec": "Spec",
|
|
48
|
-
"clarify": "Spec",
|
|
49
|
-
"review-spec": "Review",
|
|
50
12
|
"research": "Research",
|
|
51
|
-
"plan": "Plan",
|
|
52
13
|
"contracts": "Plan",
|
|
14
|
+
"plan": "Plan",
|
|
53
15
|
"tasks": "Tasks",
|
|
54
|
-
"review-tasks": "Review",
|
|
55
16
|
"implement": "In Progress",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
17
|
+
"verify": "Review",
|
|
18
|
+
"review": "Review",
|
|
19
|
+
"rollback-check": "Review",
|
|
58
20
|
"done": "Done",
|
|
59
21
|
}
|
|
60
22
|
|
|
61
|
-
|
|
62
|
-
# Un statut ne doit jamais régresser : si l'index courant >= index cible, on ne met pas à jour.
|
|
63
|
-
STEP_LINEAR_ORDER = [
|
|
23
|
+
LINEAR_STATUS_ORDER = [
|
|
64
24
|
"Backlog",
|
|
65
25
|
"Spec",
|
|
66
26
|
"Research",
|
|
67
27
|
"Plan",
|
|
68
28
|
"Tasks",
|
|
69
|
-
"Review",
|
|
70
29
|
"In Progress",
|
|
71
|
-
"
|
|
30
|
+
"Review",
|
|
72
31
|
"Done",
|
|
73
32
|
]
|
|
74
33
|
|
|
75
|
-
SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
|
|
76
|
-
|
|
77
|
-
# Mapping speckit → devflow pour la migration
|
|
78
|
-
SPECKIT_STEP_MAP = {
|
|
79
|
-
"specify": "spec",
|
|
80
|
-
"clarify": "clarify",
|
|
81
|
-
"plan": "plan",
|
|
82
|
-
"tasks": "tasks",
|
|
83
|
-
"implement": "implement",
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
SPECKIT_IGNORED_STEPS = {"checklist", "analyze"}
|
|
87
|
-
|
|
88
34
|
ARTIFACTS = [
|
|
35
|
+
"risk-assessment.md",
|
|
89
36
|
"spec.md",
|
|
90
|
-
"clarify-log.md",
|
|
91
37
|
"research.md",
|
|
38
|
+
"contracts.md",
|
|
92
39
|
"plan.md",
|
|
93
|
-
"contracts/",
|
|
94
|
-
"data-model.md",
|
|
95
|
-
"quickstart.md",
|
|
96
40
|
"tasks.md",
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
41
|
+
"worklog.md",
|
|
42
|
+
"activity-contract.json",
|
|
43
|
+
"activity-result.json",
|
|
44
|
+
"verification.json",
|
|
101
45
|
]
|
|
102
46
|
|
|
103
|
-
|
|
104
|
-
_steps_set = set(STEPS)
|
|
105
|
-
assert OPTIONAL_STEPS <= _steps_set, f"OPTIONAL_STEPS not subset of STEPS: {OPTIONAL_STEPS - _steps_set}"
|
|
106
|
-
assert REVIEW_GATES <= _steps_set, f"REVIEW_GATES not subset of STEPS: {REVIEW_GATES - _steps_set}"
|
|
107
|
-
assert set(STEP_ARTIFACTS.keys()) <= _steps_set, f"STEP_ARTIFACTS has unknown keys: {set(STEP_ARTIFACTS.keys()) - _steps_set}"
|
|
108
|
-
assert set(STEP_LINEAR_MAPPING.keys()) <= _steps_set, f"STEP_LINEAR_MAPPING has unknown keys: {set(STEP_LINEAR_MAPPING.keys()) - _steps_set}"
|
|
109
|
-
assert set(STEP_LINEAR_MAPPING.values()) <= set(STEP_LINEAR_ORDER), (
|
|
110
|
-
f"STEP_LINEAR_MAPPING values not in STEP_LINEAR_ORDER: "
|
|
111
|
-
f"{set(STEP_LINEAR_MAPPING.values()) - set(STEP_LINEAR_ORDER)}"
|
|
112
|
-
)
|
|
113
|
-
assert set(SPECKIT_STEP_MAP.values()) <= _steps_set, f"SPECKIT_STEP_MAP maps to unknown steps: {set(SPECKIT_STEP_MAP.values()) - _steps_set}"
|
|
47
|
+
assert set(ACTIVITY_LINEAR_MAPPING.values()) <= set(LINEAR_STATUS_ORDER)
|
devflow_cli/adapters/agents.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
import subprocess
|
|
6
7
|
import re
|
|
7
8
|
from abc import ABC, abstractmethod
|
|
@@ -9,6 +10,9 @@ from dataclasses import dataclass
|
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Literal
|
|
11
12
|
|
|
13
|
+
from jsonschema.exceptions import SchemaError, ValidationError
|
|
14
|
+
from jsonschema.validators import validator_for
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
PermissionMode = Literal["auto", "acceptEdits", "manual"]
|
|
14
18
|
SandboxMode = Literal["read-only", "workspace-write"]
|
|
@@ -101,11 +105,29 @@ class AgentAdapter(ABC):
|
|
|
101
105
|
capture_output=True,
|
|
102
106
|
shell=False,
|
|
103
107
|
)
|
|
104
|
-
|
|
108
|
+
execution = AgentExecution(
|
|
105
109
|
result.returncode,
|
|
106
110
|
result.stdout or "",
|
|
107
111
|
result.stderr or "",
|
|
108
112
|
)
|
|
113
|
+
if execution.returncode != 0:
|
|
114
|
+
return execution
|
|
115
|
+
try:
|
|
116
|
+
payload = self._structured_payload(execution.stdout, output_file)
|
|
117
|
+
_validate_structured_payload(payload, schema_file)
|
|
118
|
+
output_file.write_text(
|
|
119
|
+
json.dumps(payload, ensure_ascii=False), encoding="utf-8"
|
|
120
|
+
)
|
|
121
|
+
except (
|
|
122
|
+
OSError, ValueError, json.JSONDecodeError, SchemaError, ValidationError
|
|
123
|
+
) as exc:
|
|
124
|
+
output_file.unlink(missing_ok=True)
|
|
125
|
+
return AgentExecution(
|
|
126
|
+
1,
|
|
127
|
+
execution.stdout,
|
|
128
|
+
f"Sortie structuree invalide ({self.name}) : {exc}",
|
|
129
|
+
)
|
|
130
|
+
return execution
|
|
109
131
|
except subprocess.TimeoutExpired as exc:
|
|
110
132
|
return AgentExecution(
|
|
111
133
|
124,
|
|
@@ -116,6 +138,11 @@ class AgentAdapter(ABC):
|
|
|
116
138
|
except FileNotFoundError:
|
|
117
139
|
return AgentExecution(127, stderr=f"Executable '{self.executable}' introuvable")
|
|
118
140
|
|
|
141
|
+
def _structured_payload(self, stdout: str, output_file: Path) -> object:
|
|
142
|
+
if output_file.is_file():
|
|
143
|
+
return json.loads(output_file.read_text(encoding="utf-8"))
|
|
144
|
+
return json.loads(stdout)
|
|
145
|
+
|
|
119
146
|
|
|
120
147
|
class ClaudeCodeAdapter(AgentAdapter):
|
|
121
148
|
name = "claude-code"
|
|
@@ -133,6 +160,40 @@ class ClaudeCodeAdapter(AgentAdapter):
|
|
|
133
160
|
def build_named_agent_command(self, agent_name: str, prompt: str) -> list[str]:
|
|
134
161
|
return [self.executable, "--print", "--agent", agent_name, prompt]
|
|
135
162
|
|
|
163
|
+
def build_structured_command(
|
|
164
|
+
self,
|
|
165
|
+
prompt: str,
|
|
166
|
+
permission_mode: PermissionMode,
|
|
167
|
+
schema_file: Path,
|
|
168
|
+
output_file: Path,
|
|
169
|
+
sandbox_mode: SandboxMode = "workspace-write",
|
|
170
|
+
) -> list[str]:
|
|
171
|
+
del output_file
|
|
172
|
+
schema = schema_file.read_text(encoding="utf-8")
|
|
173
|
+
effective_mode = "plan" if sandbox_mode == "read-only" else permission_mode
|
|
174
|
+
return [
|
|
175
|
+
self.executable,
|
|
176
|
+
"--print",
|
|
177
|
+
"--permission-mode",
|
|
178
|
+
effective_mode,
|
|
179
|
+
"--output-format",
|
|
180
|
+
"json",
|
|
181
|
+
"--json-schema",
|
|
182
|
+
schema,
|
|
183
|
+
prompt,
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
def _structured_payload(self, stdout: str, output_file: Path) -> object:
|
|
187
|
+
del output_file
|
|
188
|
+
envelope = json.loads(stdout)
|
|
189
|
+
if not isinstance(envelope, dict):
|
|
190
|
+
raise ValueError("enveloppe JSON Claude absente")
|
|
191
|
+
if envelope.get("subtype") not in {None, "success"}:
|
|
192
|
+
raise ValueError(f"execution Claude terminee avec {envelope.get('subtype')}")
|
|
193
|
+
if "structured_output" not in envelope:
|
|
194
|
+
raise ValueError("champ structured_output absent")
|
|
195
|
+
return envelope["structured_output"]
|
|
196
|
+
|
|
136
197
|
|
|
137
198
|
class CodexAdapter(AgentAdapter):
|
|
138
199
|
name = "codex"
|
|
@@ -181,6 +242,40 @@ class CursorAdapter(AgentAdapter):
|
|
|
181
242
|
command.append(_portable_prompt(prompt))
|
|
182
243
|
return command
|
|
183
244
|
|
|
245
|
+
def build_structured_command(
|
|
246
|
+
self,
|
|
247
|
+
prompt: str,
|
|
248
|
+
permission_mode: PermissionMode,
|
|
249
|
+
schema_file: Path,
|
|
250
|
+
output_file: Path,
|
|
251
|
+
sandbox_mode: SandboxMode = "workspace-write",
|
|
252
|
+
) -> list[str]:
|
|
253
|
+
del output_file
|
|
254
|
+
schema = schema_file.read_text(encoding="utf-8")
|
|
255
|
+
structured_prompt = (
|
|
256
|
+
f"{prompt}\n\n"
|
|
257
|
+
"Retourne comme reponse finale uniquement un objet JSON valide "
|
|
258
|
+
"respectant exactement ce JSON Schema, sans bloc Markdown :\n"
|
|
259
|
+
f"{schema}"
|
|
260
|
+
)
|
|
261
|
+
command = [self.executable, "--print", "--output-format", "json"]
|
|
262
|
+
if permission_mode in {"auto", "acceptEdits"} and sandbox_mode == "workspace-write":
|
|
263
|
+
command.append("--force")
|
|
264
|
+
command.append(structured_prompt)
|
|
265
|
+
return command
|
|
266
|
+
|
|
267
|
+
def _structured_payload(self, stdout: str, output_file: Path) -> object:
|
|
268
|
+
del output_file
|
|
269
|
+
envelope = json.loads(stdout)
|
|
270
|
+
if not isinstance(envelope, dict):
|
|
271
|
+
raise ValueError("enveloppe JSON Cursor absente")
|
|
272
|
+
if envelope.get("is_error") is True or envelope.get("subtype") not in {None, "success"}:
|
|
273
|
+
raise ValueError("execution Cursor terminee en erreur")
|
|
274
|
+
result = envelope.get("result")
|
|
275
|
+
if not isinstance(result, str):
|
|
276
|
+
raise ValueError("champ result Cursor absent")
|
|
277
|
+
return json.loads(_strip_json_fence(result))
|
|
278
|
+
|
|
184
279
|
|
|
185
280
|
_AGENT_ADAPTERS = {
|
|
186
281
|
"claude": ClaudeCodeAdapter,
|
|
@@ -218,3 +313,16 @@ def _text(value: str | bytes | None) -> str:
|
|
|
218
313
|
if value is None:
|
|
219
314
|
return ""
|
|
220
315
|
return value.decode(errors="replace") if isinstance(value, bytes) else value
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _strip_json_fence(value: str) -> str:
|
|
319
|
+
text = value.strip()
|
|
320
|
+
match = re.fullmatch(r"```(?:json)?\s*([\s\S]*?)\s*```", text, re.IGNORECASE)
|
|
321
|
+
return match.group(1).strip() if match else text
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _validate_structured_payload(payload: object, schema_file: Path) -> None:
|
|
325
|
+
schema = json.loads(schema_file.read_text(encoding="utf-8"))
|
|
326
|
+
validator_type = validator_for(schema)
|
|
327
|
+
validator_type.check_schema(schema)
|
|
328
|
+
validator_type(schema).validate(payload)
|
devflow_cli/adapters/linear.py
CHANGED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import Literal, Protocol
|
|
7
7
|
|
|
8
|
-
from devflow_cli import
|
|
8
|
+
from devflow_cli import ACTIVITY_LINEAR_MAPPING, LINEAR_STATUS_ORDER
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
LinearAction = Literal["update", "keep", "skip"]
|
|
@@ -22,7 +22,7 @@ class LinearAdapter(Protocol):
|
|
|
22
22
|
@dataclass(frozen=True)
|
|
23
23
|
class LinearDecision:
|
|
24
24
|
action: LinearAction
|
|
25
|
-
|
|
25
|
+
activity: str
|
|
26
26
|
current_status: str | None
|
|
27
27
|
target_status: str | None
|
|
28
28
|
reason: str
|
|
@@ -34,7 +34,7 @@ class LinearDecision:
|
|
|
34
34
|
def to_dict(self) -> dict[str, str | bool | None]:
|
|
35
35
|
return {
|
|
36
36
|
"action": self.action,
|
|
37
|
-
"
|
|
37
|
+
"activity": self.activity,
|
|
38
38
|
"currentStatus": self.current_status,
|
|
39
39
|
"targetStatus": self.target_status,
|
|
40
40
|
"shouldUpdate": self.should_update,
|
|
@@ -42,38 +42,42 @@ class LinearDecision:
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def decide_linear_transition(
|
|
45
|
+
def decide_linear_transition(activity: str, current_status: str | None) -> LinearDecision:
|
|
46
46
|
"""Applique le mapping et la règle de non-régression canonique."""
|
|
47
|
-
target =
|
|
47
|
+
target = ACTIVITY_LINEAR_MAPPING.get(activity)
|
|
48
48
|
if target is None:
|
|
49
|
-
return LinearDecision(
|
|
50
|
-
|
|
49
|
+
return LinearDecision(
|
|
50
|
+
"skip", activity, current_status, None, "Activité sans statut Linear"
|
|
51
|
+
)
|
|
52
|
+
if current_status not in LINEAR_STATUS_ORDER:
|
|
51
53
|
return LinearDecision(
|
|
52
54
|
"skip",
|
|
53
|
-
|
|
55
|
+
activity,
|
|
54
56
|
current_status,
|
|
55
57
|
target,
|
|
56
58
|
"Statut Linear courant absent de l'ordre canonique",
|
|
57
59
|
)
|
|
58
|
-
current_index =
|
|
59
|
-
target_index =
|
|
60
|
+
current_index = LINEAR_STATUS_ORDER.index(current_status)
|
|
61
|
+
target_index = LINEAR_STATUS_ORDER.index(target)
|
|
60
62
|
if current_index >= target_index:
|
|
61
63
|
return LinearDecision(
|
|
62
64
|
"keep",
|
|
63
|
-
|
|
65
|
+
activity,
|
|
64
66
|
current_status,
|
|
65
67
|
target,
|
|
66
68
|
"Statut courant identique ou plus avancé",
|
|
67
69
|
)
|
|
68
|
-
return LinearDecision(
|
|
70
|
+
return LinearDecision(
|
|
71
|
+
"update", activity, current_status, target, "Progression autorisée"
|
|
72
|
+
)
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
def sync_linear_status(
|
|
72
|
-
adapter: LinearAdapter, issue_id: str,
|
|
76
|
+
adapter: LinearAdapter, issue_id: str, activity: str
|
|
73
77
|
) -> LinearDecision:
|
|
74
78
|
"""Synchronise via un transport injecté lorsque la politique l'autorise."""
|
|
75
79
|
current_status = adapter.get_status(issue_id)
|
|
76
|
-
decision = decide_linear_transition(
|
|
80
|
+
decision = decide_linear_transition(activity, current_status)
|
|
77
81
|
if decision.should_update and decision.target_status is not None:
|
|
78
82
|
adapter.update_status(issue_id, decision.target_status)
|
|
79
83
|
return decision
|
devflow_cli/cli.py
CHANGED
|
@@ -15,15 +15,10 @@ from devflow_cli.commands.export_cmd import export
|
|
|
15
15
|
from devflow_cli.commands.stale import stale
|
|
16
16
|
from devflow_cli.commands.migrate_cmd import migrate
|
|
17
17
|
from devflow_cli.commands.upgrade_cmd import upgrade
|
|
18
|
-
from devflow_cli.commands.rollback import rollback
|
|
19
|
-
from devflow_cli.commands.migrate_speckit import migrate_speckit
|
|
20
18
|
from devflow_cli.commands import extension
|
|
21
19
|
from devflow_cli.commands import hooks_cmd
|
|
22
20
|
from devflow_cli.commands import linear
|
|
23
|
-
from devflow_cli.commands import review
|
|
24
|
-
from devflow_cli.commands import step
|
|
25
21
|
from devflow_cli.commands import adaptive
|
|
26
|
-
from devflow_cli.commands.run import run
|
|
27
22
|
from devflow_cli.commands.docs_sync import docs_sync
|
|
28
23
|
from devflow_cli.commands.traceability import traceability
|
|
29
24
|
from devflow_cli.commands.verify import verify
|
|
@@ -60,15 +55,10 @@ app.command(name="export")(export)
|
|
|
60
55
|
app.command()(stale)
|
|
61
56
|
app.command()(migrate)
|
|
62
57
|
app.command()(upgrade)
|
|
63
|
-
app.command()(rollback)
|
|
64
|
-
app.command(name="migrate-speckit")(migrate_speckit)
|
|
65
58
|
app.add_typer(extension.app, name="extension")
|
|
66
59
|
app.add_typer(hooks_cmd.app, name="hooks")
|
|
67
60
|
app.add_typer(linear.app, name="linear")
|
|
68
|
-
app.add_typer(step.app, name="step")
|
|
69
|
-
app.add_typer(review.app, name="review")
|
|
70
61
|
app.add_typer(adaptive.app, name="adaptive")
|
|
71
|
-
app.command()(run)
|
|
72
62
|
app.command(name="docs-sync")(docs_sync)
|
|
73
63
|
app.command()(traceability)
|
|
74
64
|
app.command()(verify)
|