devflow-cli 7.1.1__py3-none-any.whl → 7.2.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 +9 -12
- devflow_cli/adapters/agents.py +4 -0
- devflow_cli/adapters/linear.py +163 -3
- devflow_cli/commands/adaptive.py +10 -2
- devflow_cli/commands/check.py +11 -2
- devflow_cli/commands/hooks_cmd.py +4 -0
- devflow_cli/core/feedback.py +2 -0
- devflow_cli/core/git.py +8 -1
- devflow_cli/core/hooks.py +2 -0
- devflow_cli/core/verification.py +2 -0
- devflow_cli/core/worktree_snapshot.py +4 -0
- devflow_cli/utils/paths.py +2 -0
- {devflow_cli-7.1.1.dist-info → devflow_cli-7.2.0.dist-info}/METADATA +17 -1
- {devflow_cli-7.1.1.dist-info → devflow_cli-7.2.0.dist-info}/RECORD +17 -17
- {devflow_cli-7.1.1.dist-info → devflow_cli-7.2.0.dist-info}/WHEEL +0 -0
- {devflow_cli-7.1.1.dist-info → devflow_cli-7.2.0.dist-info}/entry_points.txt +0 -0
- {devflow_cli-7.1.1.dist-info → devflow_cli-7.2.0.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
"""devflow CLI — workflow de développement adaptatif."""
|
|
2
2
|
|
|
3
|
-
VERSION = "7.
|
|
3
|
+
VERSION = "7.2.0"
|
|
4
4
|
|
|
5
5
|
SUPPORTED_AGENTS = ["claude-code", "codex", "cursor"]
|
|
6
6
|
|
|
7
7
|
ACTIVITY_LINEAR_MAPPING = {
|
|
8
8
|
"assess": "Backlog",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"spec": "
|
|
12
|
-
"contracts": "
|
|
13
|
-
"plan": "
|
|
14
|
-
"tasks": "
|
|
15
|
-
"preflight-evidence": "
|
|
9
|
+
"risk-assessment": "Design",
|
|
10
|
+
"prepare": "Design",
|
|
11
|
+
"spec": "Design",
|
|
12
|
+
"contracts": "Design",
|
|
13
|
+
"plan": "Design",
|
|
14
|
+
"tasks": "Design",
|
|
15
|
+
"preflight-evidence": "Design",
|
|
16
16
|
"implement": "In Progress",
|
|
17
17
|
"verify": "Review",
|
|
18
18
|
"review": "Review",
|
|
@@ -22,10 +22,7 @@ ACTIVITY_LINEAR_MAPPING = {
|
|
|
22
22
|
|
|
23
23
|
LINEAR_STATUS_ORDER = [
|
|
24
24
|
"Backlog",
|
|
25
|
-
"
|
|
26
|
-
"Research",
|
|
27
|
-
"Plan",
|
|
28
|
-
"Tasks",
|
|
25
|
+
"Design",
|
|
29
26
|
"In Progress",
|
|
30
27
|
"Review",
|
|
31
28
|
"Done",
|
devflow_cli/adapters/agents.py
CHANGED
|
@@ -16,6 +16,8 @@ from typing import Literal
|
|
|
16
16
|
from jsonschema.exceptions import SchemaError, ValidationError
|
|
17
17
|
from jsonschema.validators import validator_for
|
|
18
18
|
|
|
19
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
PermissionMode = Literal["auto", "acceptEdits", "manual"]
|
|
21
23
|
SandboxMode = Literal["read-only", "workspace-write"]
|
|
@@ -123,6 +125,7 @@ class AgentAdapter(ABC):
|
|
|
123
125
|
capture_output=capture_output,
|
|
124
126
|
stdin=subprocess.DEVNULL,
|
|
125
127
|
shell=False,
|
|
128
|
+
env=environment_without_linear_key(),
|
|
126
129
|
)
|
|
127
130
|
return AgentExecution(
|
|
128
131
|
result.returncode,
|
|
@@ -162,6 +165,7 @@ class AgentAdapter(ABC):
|
|
|
162
165
|
capture_output=True,
|
|
163
166
|
stdin=subprocess.DEVNULL,
|
|
164
167
|
shell=False,
|
|
168
|
+
env=environment_without_linear_key(),
|
|
165
169
|
)
|
|
166
170
|
execution = AgentExecution(
|
|
167
171
|
result.returncode,
|
devflow_cli/adapters/linear.py
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
"""Politique de synchronisation Linear
|
|
1
|
+
"""Politique de synchronisation Linear et transport GraphQL."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
5
9
|
from dataclasses import dataclass
|
|
6
|
-
from typing import Literal, Protocol
|
|
10
|
+
from typing import Literal, Protocol, TextIO
|
|
7
11
|
|
|
8
|
-
from devflow_cli import ACTIVITY_LINEAR_MAPPING, LINEAR_STATUS_ORDER
|
|
12
|
+
from devflow_cli import ACTIVITY_LINEAR_MAPPING, LINEAR_STATUS_ORDER, VERSION
|
|
13
|
+
from devflow_cli.core.run_progress import emit_progress
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
LINEAR_API_KEY_ENV = "LINEAR_API_KEY"
|
|
17
|
+
LINEAR_API_URL = "https://api.linear.app/graphql"
|
|
18
|
+
LINEAR_TIMEOUT_SECONDS = 5
|
|
19
|
+
|
|
20
|
+
_ISSUE_QUERY = (
|
|
21
|
+
"query($id: String!) { issue(id: $id) { id state { name } "
|
|
22
|
+
"team { states { nodes { id name } } } } }"
|
|
23
|
+
)
|
|
24
|
+
_UPDATE_MUTATION = (
|
|
25
|
+
"mutation($id: String!, $stateId: String!) { "
|
|
26
|
+
"issueUpdate(id: $id, input: { stateId: $stateId }) { success } }"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
_missing_key_warned = False
|
|
9
30
|
|
|
10
31
|
|
|
11
32
|
LinearAction = Literal["update", "keep", "skip"]
|
|
@@ -81,3 +102,142 @@ def sync_linear_status(
|
|
|
81
102
|
if decision.should_update and decision.target_status is not None:
|
|
82
103
|
adapter.update_status(issue_id, decision.target_status)
|
|
83
104
|
return decision
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def environment_without_linear_key() -> dict[str, str]:
|
|
108
|
+
"""Environnement transmis aux processus lancés par la CLI, sans la clé Linear."""
|
|
109
|
+
return {key: value for key, value in os.environ.items() if key != LINEAR_API_KEY_ENV}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class _NoRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|
113
|
+
"""Refuse de suivre une redirection : l'en-tête Authorization ne doit pas la suivre."""
|
|
114
|
+
|
|
115
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class LinearSyncError(Exception):
|
|
120
|
+
"""Échec de synchronisation ; le libellé ne contient aucune donnée externe."""
|
|
121
|
+
|
|
122
|
+
def __init__(self, label: str) -> None:
|
|
123
|
+
super().__init__(label)
|
|
124
|
+
self.label = label
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class LinearGraphQLAdapter:
|
|
128
|
+
"""Transport par l'API GraphQL de Linear, en bibliothèque standard."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, api_key: str, timeout: float = LINEAR_TIMEOUT_SECONDS) -> None:
|
|
131
|
+
self._api_key = api_key
|
|
132
|
+
self._timeout = timeout
|
|
133
|
+
self._issue_uuid: str | None = None
|
|
134
|
+
self._state_ids: dict[str, str] = {}
|
|
135
|
+
|
|
136
|
+
def get_status(self, issue_id: str) -> str | None:
|
|
137
|
+
data = self._request(_ISSUE_QUERY, {"id": issue_id})
|
|
138
|
+
issue = data.get("issue")
|
|
139
|
+
if issue is None:
|
|
140
|
+
raise LinearSyncError("issue inconnue de Linear")
|
|
141
|
+
try:
|
|
142
|
+
self._issue_uuid = str(issue["id"])
|
|
143
|
+
self._state_ids = {
|
|
144
|
+
str(node["name"]): str(node["id"])
|
|
145
|
+
for node in issue["team"]["states"]["nodes"]
|
|
146
|
+
}
|
|
147
|
+
return str(issue["state"]["name"])
|
|
148
|
+
except (KeyError, TypeError):
|
|
149
|
+
raise LinearSyncError("reponse Linear invalide") from None
|
|
150
|
+
|
|
151
|
+
def update_status(self, issue_id: str, status: str) -> None:
|
|
152
|
+
state_id = self._state_ids.get(status)
|
|
153
|
+
if self._issue_uuid is None or state_id is None:
|
|
154
|
+
raise LinearSyncError(f"statut '{status}' absent de l'equipe Linear")
|
|
155
|
+
data = self._request(
|
|
156
|
+
_UPDATE_MUTATION, {"id": self._issue_uuid, "stateId": state_id}
|
|
157
|
+
)
|
|
158
|
+
result = data.get("issueUpdate")
|
|
159
|
+
if not isinstance(result, dict) or result.get("success") is not True:
|
|
160
|
+
raise LinearSyncError("mise a jour refusee par Linear")
|
|
161
|
+
|
|
162
|
+
def _request(self, query: str, variables: dict[str, str]) -> dict:
|
|
163
|
+
request = urllib.request.Request(
|
|
164
|
+
LINEAR_API_URL,
|
|
165
|
+
data=json.dumps({"query": query, "variables": variables}).encode("utf-8"),
|
|
166
|
+
headers={
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
"Authorization": self._api_key,
|
|
169
|
+
"User-Agent": f"devflow-cli/{VERSION}",
|
|
170
|
+
},
|
|
171
|
+
method="POST",
|
|
172
|
+
)
|
|
173
|
+
opener = urllib.request.build_opener(_NoRedirectHandler())
|
|
174
|
+
try:
|
|
175
|
+
with opener.open(request, timeout=self._timeout) as response:
|
|
176
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
177
|
+
except urllib.error.HTTPError as exc:
|
|
178
|
+
raise LinearSyncError(f"erreur HTTP {exc.code}") from None
|
|
179
|
+
except TimeoutError:
|
|
180
|
+
raise LinearSyncError("delai depasse") from None
|
|
181
|
+
except urllib.error.URLError as exc:
|
|
182
|
+
if isinstance(exc.reason, TimeoutError):
|
|
183
|
+
raise LinearSyncError("delai depasse") from None
|
|
184
|
+
raise LinearSyncError("erreur reseau") from None
|
|
185
|
+
except OSError:
|
|
186
|
+
raise LinearSyncError("erreur reseau") from None
|
|
187
|
+
except ValueError:
|
|
188
|
+
raise LinearSyncError("reponse Linear invalide") from None
|
|
189
|
+
if not isinstance(payload, dict):
|
|
190
|
+
raise LinearSyncError("reponse Linear invalide")
|
|
191
|
+
if payload.get("errors"):
|
|
192
|
+
raise LinearSyncError("erreur GraphQL")
|
|
193
|
+
data = payload.get("data")
|
|
194
|
+
if not isinstance(data, dict):
|
|
195
|
+
raise LinearSyncError("reponse Linear invalide")
|
|
196
|
+
return data
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def sync_activity_status(
|
|
200
|
+
issue_id: str, activity: str, stream: TextIO
|
|
201
|
+
) -> LinearDecision | None:
|
|
202
|
+
"""Synchronise le statut au démarrage d'une activité, sans jamais bloquer."""
|
|
203
|
+
global _missing_key_warned
|
|
204
|
+
api_key = os.environ.get(LINEAR_API_KEY_ENV, "").strip()
|
|
205
|
+
if not api_key:
|
|
206
|
+
if not _missing_key_warned:
|
|
207
|
+
_missing_key_warned = True
|
|
208
|
+
emit_progress(
|
|
209
|
+
f"Linear : avertissement, {LINEAR_API_KEY_ENV} absente ; "
|
|
210
|
+
"statuts non synchronises",
|
|
211
|
+
stream,
|
|
212
|
+
)
|
|
213
|
+
return None
|
|
214
|
+
try:
|
|
215
|
+
decision = sync_linear_status(LinearGraphQLAdapter(api_key), issue_id, activity)
|
|
216
|
+
except LinearSyncError as exc:
|
|
217
|
+
label = exc.label
|
|
218
|
+
except Exception:
|
|
219
|
+
label = "erreur inattendue"
|
|
220
|
+
else:
|
|
221
|
+
emit_progress(_describe_decision(issue_id, decision), stream)
|
|
222
|
+
return decision
|
|
223
|
+
emit_progress(
|
|
224
|
+
f"Linear : avertissement, {issue_id} non synchronisee ({label})", stream
|
|
225
|
+
)
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _describe_decision(issue_id: str, decision: LinearDecision) -> str:
|
|
230
|
+
if decision.action == "update":
|
|
231
|
+
return (
|
|
232
|
+
f"Linear : {issue_id} {decision.current_status} -> "
|
|
233
|
+
f"{decision.target_status} (activite {decision.activity})"
|
|
234
|
+
)
|
|
235
|
+
if decision.action == "keep":
|
|
236
|
+
return (
|
|
237
|
+
f"Linear : {issue_id} reste en {decision.current_status} "
|
|
238
|
+
f"(activite {decision.activity}, {decision.reason})"
|
|
239
|
+
)
|
|
240
|
+
return (
|
|
241
|
+
f"Linear : {issue_id} ignoree, statut {decision.current_status or 'inconnu'} "
|
|
242
|
+
f"({decision.reason})"
|
|
243
|
+
)
|
devflow_cli/commands/adaptive.py
CHANGED
|
@@ -14,6 +14,7 @@ from typing import Literal, Optional
|
|
|
14
14
|
import typer
|
|
15
15
|
|
|
16
16
|
from devflow_cli import VERSION
|
|
17
|
+
from devflow_cli.adapters.linear import sync_activity_status
|
|
17
18
|
from devflow_cli.core.adaptive_workflow import (
|
|
18
19
|
AdaptiveWorkflowState,
|
|
19
20
|
complete_activity,
|
|
@@ -226,6 +227,8 @@ def complete(
|
|
|
226
227
|
raise typer.Exit(int(ExitCode.BLOCKED)) from exc
|
|
227
228
|
write_adaptive_state(feature_dir / "workflow-state.json", state)
|
|
228
229
|
safe_refresh_worklog(feature_dir)
|
|
230
|
+
if activity == "done":
|
|
231
|
+
sync_activity_status(state.issueId, "done", run_progress.progress_stream(False))
|
|
229
232
|
if state.status == "completed":
|
|
230
233
|
ok(f"Workflow {state.profile} termine.")
|
|
231
234
|
_print_completion_summary(feature_dir, state)
|
|
@@ -388,6 +391,9 @@ def run_adaptive(
|
|
|
388
391
|
f"agent {agent}, delai {timeout} s",
|
|
389
392
|
stream,
|
|
390
393
|
)
|
|
394
|
+
sync_activity_status(
|
|
395
|
+
current_state.issueId, activity_contract.activity, stream
|
|
396
|
+
)
|
|
391
397
|
with run_progress.heartbeat(
|
|
392
398
|
activity_contract.activity,
|
|
393
399
|
agent,
|
|
@@ -809,10 +815,12 @@ def _execute_local_verification(
|
|
|
809
815
|
feature_dir: Path, project_root: Path, issue_id: str, *,
|
|
810
816
|
refresh: bool = False, json_output: bool = False,
|
|
811
817
|
) -> None:
|
|
818
|
+
stream = run_progress.progress_stream(json_output)
|
|
812
819
|
run_progress.emit_progress(
|
|
813
|
-
"Activite 'verify' demarree : verification locale",
|
|
814
|
-
run_progress.progress_stream(json_output),
|
|
820
|
+
"Activite 'verify' demarree : verification locale", stream
|
|
815
821
|
)
|
|
822
|
+
if not refresh:
|
|
823
|
+
sync_activity_status(issue_id, "verify", stream)
|
|
816
824
|
outcome = run_automatic_verification(feature_dir, project_root, refresh=refresh)
|
|
817
825
|
if json_output:
|
|
818
826
|
_print_execution_json(
|
devflow_cli/commands/check.py
CHANGED
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
from devflow_cli.adapters.agents import get_agent_adapter
|
|
11
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
11
12
|
from devflow_cli.core.agent_failure import diagnose_agent_failure
|
|
12
13
|
from devflow_cli.utils.console import console, ok, warn, fail
|
|
13
14
|
from devflow_cli.utils.logging import get_logger
|
|
@@ -78,7 +79,12 @@ def check(
|
|
|
78
79
|
# git
|
|
79
80
|
git_path = _cmd_exists("git")
|
|
80
81
|
if git_path:
|
|
81
|
-
result = subprocess.run(
|
|
82
|
+
result = subprocess.run(
|
|
83
|
+
["git", "--version"],
|
|
84
|
+
capture_output=True,
|
|
85
|
+
text=True,
|
|
86
|
+
env=environment_without_linear_key(),
|
|
87
|
+
)
|
|
82
88
|
version = result.stdout.strip().replace("git version ", "")
|
|
83
89
|
ok(f"git -- {git_path} (version {version})")
|
|
84
90
|
ok_count += 1
|
|
@@ -90,7 +96,10 @@ def check(
|
|
|
90
96
|
codex_path = _cmd_exists("codex")
|
|
91
97
|
if codex_path:
|
|
92
98
|
result = subprocess.run(
|
|
93
|
-
["codex", "--version"],
|
|
99
|
+
["codex", "--version"],
|
|
100
|
+
capture_output=True,
|
|
101
|
+
text=True,
|
|
102
|
+
env=environment_without_linear_key(),
|
|
94
103
|
)
|
|
95
104
|
version = (result.stdout or result.stderr).strip()
|
|
96
105
|
if result.returncode == 0:
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
11
12
|
from devflow_cli.core.git import is_git_repo
|
|
12
13
|
from devflow_cli.core.hooks import (
|
|
13
14
|
DEFAULT_HOOK_TIMEOUT,
|
|
@@ -28,6 +29,7 @@ def _configure_hooks_path(repo: Path) -> None:
|
|
|
28
29
|
["git", "-C", str(repo), "config", "core.hooksPath", HOOKS_DIR_NAME],
|
|
29
30
|
check=True,
|
|
30
31
|
capture_output=True,
|
|
32
|
+
env=environment_without_linear_key(),
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
|
|
@@ -36,6 +38,7 @@ def _current_hooks_path(repo: Path) -> str | None:
|
|
|
36
38
|
["git", "-C", str(repo), "config", "--get", "core.hooksPath"],
|
|
37
39
|
capture_output=True,
|
|
38
40
|
text=True,
|
|
41
|
+
env=environment_without_linear_key(),
|
|
39
42
|
)
|
|
40
43
|
if result.returncode != 0:
|
|
41
44
|
return None
|
|
@@ -46,6 +49,7 @@ def _unset_hooks_path(repo: Path) -> None:
|
|
|
46
49
|
subprocess.run(
|
|
47
50
|
["git", "-C", str(repo), "config", "--unset", "core.hooksPath"],
|
|
48
51
|
capture_output=True,
|
|
52
|
+
env=environment_without_linear_key(),
|
|
49
53
|
)
|
|
50
54
|
|
|
51
55
|
|
devflow_cli/core/feedback.py
CHANGED
|
@@ -21,6 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
from typing import Any
|
|
22
22
|
|
|
23
23
|
from devflow_cli import VERSION
|
|
24
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
24
25
|
from devflow_cli.core.contract_schemas import LegacySchemaError, check_schema_version
|
|
25
26
|
from devflow_cli.core.redaction import redact_text
|
|
26
27
|
from devflow_cli.core.telemetry import (
|
|
@@ -167,6 +168,7 @@ def _git_root(cwd: Path) -> str:
|
|
|
167
168
|
text=True,
|
|
168
169
|
timeout=5,
|
|
169
170
|
check=False,
|
|
171
|
+
env=environment_without_linear_key(),
|
|
170
172
|
)
|
|
171
173
|
except (OSError, subprocess.SubprocessError):
|
|
172
174
|
return str(cwd.resolve())
|
devflow_cli/core/git.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
8
9
|
from devflow_cli.utils.logging import get_logger
|
|
9
10
|
|
|
10
11
|
logger = get_logger(__name__)
|
|
@@ -31,6 +32,7 @@ def ensure_git_available() -> None:
|
|
|
31
32
|
capture_output=True,
|
|
32
33
|
text=True,
|
|
33
34
|
timeout=5,
|
|
35
|
+
env=environment_without_linear_key(),
|
|
34
36
|
)
|
|
35
37
|
_git_checked = True
|
|
36
38
|
except FileNotFoundError:
|
|
@@ -45,7 +47,12 @@ def _run(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProces
|
|
|
45
47
|
ensure_git_available()
|
|
46
48
|
try:
|
|
47
49
|
return subprocess.run(
|
|
48
|
-
args,
|
|
50
|
+
args,
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
cwd=cwd,
|
|
54
|
+
timeout=_GIT_TIMEOUT,
|
|
55
|
+
env=environment_without_linear_key(),
|
|
49
56
|
)
|
|
50
57
|
except subprocess.TimeoutExpired:
|
|
51
58
|
logger.warning("git command timed out: %s", args, exc_info=True)
|
devflow_cli/core/hooks.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
import yaml
|
|
12
12
|
|
|
13
13
|
from devflow_cli.adapters.agents import ClaudeCodeAdapter
|
|
14
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
14
15
|
|
|
15
16
|
from devflow_cli.utils.logging import get_logger
|
|
16
17
|
|
|
@@ -187,6 +188,7 @@ def run_hook_point(
|
|
|
187
188
|
text=True,
|
|
188
189
|
timeout=timeout,
|
|
189
190
|
shell=False,
|
|
191
|
+
env=environment_without_linear_key(),
|
|
190
192
|
)
|
|
191
193
|
executions.append(HookExecution(
|
|
192
194
|
hook_point=hook_point,
|
devflow_cli/core/verification.py
CHANGED
|
@@ -9,6 +9,7 @@ from dataclasses import dataclass
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Collection, Mapping
|
|
11
11
|
|
|
12
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
12
13
|
from devflow_cli.core.redaction import redact_command, redact_text
|
|
13
14
|
|
|
14
15
|
|
|
@@ -237,6 +238,7 @@ def run_verifications(
|
|
|
237
238
|
text=True,
|
|
238
239
|
timeout=validation.timeout,
|
|
239
240
|
shell=False,
|
|
241
|
+
env=environment_without_linear_key(),
|
|
240
242
|
)
|
|
241
243
|
results.append(VerificationResult(
|
|
242
244
|
id=validation.id,
|
|
@@ -9,6 +9,8 @@ import subprocess
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
@dataclass(frozen=True)
|
|
14
16
|
class WorktreeSnapshot:
|
|
@@ -99,6 +101,7 @@ def _git_text(project_root: Path, args: list[str], *, allow_failure: bool = Fals
|
|
|
99
101
|
capture_output=True,
|
|
100
102
|
text=True,
|
|
101
103
|
timeout=30,
|
|
104
|
+
env=environment_without_linear_key(),
|
|
102
105
|
)
|
|
103
106
|
if result.returncode != 0:
|
|
104
107
|
if allow_failure:
|
|
@@ -115,6 +118,7 @@ def _git_paths(
|
|
|
115
118
|
cwd=project_root,
|
|
116
119
|
capture_output=True,
|
|
117
120
|
timeout=30,
|
|
121
|
+
env=environment_without_linear_key(),
|
|
118
122
|
)
|
|
119
123
|
if result.returncode != 0:
|
|
120
124
|
if allow_failure:
|
devflow_cli/utils/paths.py
CHANGED
|
@@ -8,6 +8,7 @@ import re
|
|
|
8
8
|
import subprocess
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
+
from devflow_cli.adapters.linear import environment_without_linear_key
|
|
11
12
|
from devflow_cli.utils.logging import get_logger
|
|
12
13
|
|
|
13
14
|
logger = get_logger(__name__)
|
|
@@ -206,6 +207,7 @@ def next_feature_number(project_dir: Path | None = None) -> int:
|
|
|
206
207
|
text=True,
|
|
207
208
|
cwd=str(base),
|
|
208
209
|
timeout=5,
|
|
210
|
+
env=environment_without_linear_key(),
|
|
209
211
|
)
|
|
210
212
|
if result.returncode == 0:
|
|
211
213
|
for line in result.stdout.splitlines():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: devflow-cli
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.2.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
|
|
@@ -129,6 +129,22 @@ mise à jour. Voir [ADR-0002](docs/adr/0002-v5-rupture-and-provenance.md).
|
|
|
129
129
|
Claude connues vers `~/.claude/.devflow-legacy-backup/` sans toucher aux
|
|
130
130
|
commandes personnalisées.
|
|
131
131
|
|
|
132
|
+
## Synchronisation Linear
|
|
133
|
+
|
|
134
|
+
Si `LINEAR_API_KEY` contient une clé d'API personnelle Linear, la CLI met à jour
|
|
135
|
+
le statut de l'issue du parcours au démarrage de chaque activité (`adaptive
|
|
136
|
+
run`, y compris avec `--until`, et clôture `done`). L'équipe Linear doit avoir
|
|
137
|
+
les statuts `Backlog`, `Design`, `In Progress`, `Review` et `Done`. Un statut
|
|
138
|
+
n'est jamais ramené en arrière ; un statut hors de cette liste (`Canceled`,
|
|
139
|
+
`Duplicate`) n'est jamais modifié.
|
|
140
|
+
|
|
141
|
+
Sans clé, ou si Linear échoue (issue inconnue, statut absent, erreur réseau ou
|
|
142
|
+
délai de 5 s dépassé), la CLI affiche un avertissement et le parcours continue
|
|
143
|
+
à l'identique. La clé est retirée de l'environnement de tout processus lancé
|
|
144
|
+
par Devflow (agents, commandes de validation, hooks, commandes git) ; elle reste
|
|
145
|
+
lisible par un agent si elle est définie dans un fichier que son shell charge (`~/.zshrc`). Détails :
|
|
146
|
+
[Référence CLI](docs/cli-reference.md#synchronisation-linear).
|
|
147
|
+
|
|
132
148
|
## Documentation
|
|
133
149
|
|
|
134
150
|
- [Workflow adaptatif](docs/pipeline.md)
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
devflow_cli/__init__.py,sha256=
|
|
1
|
+
devflow_cli/__init__.py,sha256=6uOANM6Vfa9M0S7zoErecm-UQvyc77gqsxxEdBRDmHw,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
|
-
devflow_cli/adapters/agents.py,sha256=
|
|
5
|
-
devflow_cli/adapters/linear.py,sha256=
|
|
4
|
+
devflow_cli/adapters/agents.py,sha256=lq3OjcaT3Nt9-WV9Po4Dj1fag4getb7DEt9Y3Zp1js4,26683
|
|
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=6wNnVgMklIpiFgxqzQfCFMrmwcRZE6EzkrxpDvkWtB8,46640
|
|
8
8
|
devflow_cli/commands/assess.py,sha256=lyj6TI5hXUWX55dY589fG4bKJSMztvMPxZ0HZjo0O-w,5230
|
|
9
|
-
devflow_cli/commands/check.py,sha256=
|
|
9
|
+
devflow_cli/commands/check.py,sha256=hsfeOg9yDOTAIvNm6XhRmTIFgv7w9wC8zvrwm4ojfZs,6923
|
|
10
10
|
devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
|
|
11
11
|
devflow_cli/commands/docs_sync.py,sha256=Xc0mulpNTjDyeUDumKL7VNG0eumAf3016wEM_dqXpSU,1021
|
|
12
12
|
devflow_cli/commands/export_cmd.py,sha256=_DVgQru8z7UM9br-ntzMkNPcDL9OpY1G6S3cMPJ4Gqo,3259
|
|
13
13
|
devflow_cli/commands/extension.py,sha256=W2bc1xntJmDUrvLt8GFgg3lgTcJSwmGGTDPUkrxonBo,4494
|
|
14
14
|
devflow_cli/commands/feature.py,sha256=gomtMUUJl-0YfZZIx0PHNHBX-Rq5GhmMZE-XkP55SwE,4200
|
|
15
15
|
devflow_cli/commands/feedback.py,sha256=pKG4jCsyVcfTA7Df_qe8tYF1VzveLJ-WfR9BcV9ZtMM,10268
|
|
16
|
-
devflow_cli/commands/hooks_cmd.py,sha256=
|
|
16
|
+
devflow_cli/commands/hooks_cmd.py,sha256=BMzNBKHIpQkMj5y-2bxtCNqAa8cncGfGTWMgx6SUeeQ,6133
|
|
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
|
|
@@ -32,9 +32,9 @@ devflow_cli/core/automatic_evidence.py,sha256=Pjjy8yi_72Qo0-joKeWeZx6Zt0gutkFNaF
|
|
|
32
32
|
devflow_cli/core/catalog.py,sha256=nLPc78JkbISaSxJnHlWiMi_lvepaumbfwcjgLuVy7Zc,3102
|
|
33
33
|
devflow_cli/core/contract_schemas.py,sha256=4e29MPBpjKTeLm_SWd8LkjP_fWn7DHJQwQ86ujwUmos,5326
|
|
34
34
|
devflow_cli/core/docgen.py,sha256=47JoVDWetkcctgHwJZC67AVebOKgl10VB9hZ3Xr63mo,3908
|
|
35
|
-
devflow_cli/core/feedback.py,sha256=
|
|
36
|
-
devflow_cli/core/git.py,sha256=
|
|
37
|
-
devflow_cli/core/hooks.py,sha256=
|
|
35
|
+
devflow_cli/core/feedback.py,sha256=5eePFprl51THrhFyE4uM4lPq630Z6_v9fODZPFTxqhk,18580
|
|
36
|
+
devflow_cli/core/git.py,sha256=gsDfT-hDNtMQFRUl1WzZSqltrdwZQ2BJBcCIh4TwDkA,2780
|
|
37
|
+
devflow_cli/core/hooks.py,sha256=nFPaziFJgeZgAPKPxbTibrB_0MJAST3cAmg3gtcvDBQ,8067
|
|
38
38
|
devflow_cli/core/installer.py,sha256=MfcGZKoOcQc2qviNnGShoD0P_MqOgRuVHX7YwCrG2vw,6259
|
|
39
39
|
devflow_cli/core/manifest.py,sha256=h058_m5-HJrYsNUdgfwnKts2riHiYPXUwMzPImHOs2g,7023
|
|
40
40
|
devflow_cli/core/operational.py,sha256=ZEWh6DKJfHYLpV9r3vZwaDFREQwlJF0wTtgB1VleXog,1204
|
|
@@ -47,10 +47,10 @@ devflow_cli/core/telemetry.py,sha256=emcYo6Qikc86TYKT5Mc_9-3Thkt3nztkPZrpTy9VlvA
|
|
|
47
47
|
devflow_cli/core/traceability.py,sha256=jfNE9MZzGkmaPer9Wa6Y18yA9Y3sqdeR-nzH9h_TB-8,2968
|
|
48
48
|
devflow_cli/core/validation_approvals.py,sha256=_uwUYGclqSWfhKoQ0GVkf-iXnkeU2houD14uyn96teg,8409
|
|
49
49
|
devflow_cli/core/validators.py,sha256=fREjEx2fBW36q3vB6WJNqoGUM9ejYlQUXie5A9lNuPs,7440
|
|
50
|
-
devflow_cli/core/verification.py,sha256=
|
|
50
|
+
devflow_cli/core/verification.py,sha256=gVPCkYfzG_v0MKFnvvpEhnpv5OHftrM3v5R9eEiQGA8,19983
|
|
51
51
|
devflow_cli/core/waivers.py,sha256=xpfSiC4K51ZVlBOZTRNJ5ftS05-bsLyMW-W9CdQHqH4,11595
|
|
52
52
|
devflow_cli/core/worklog.py,sha256=72neQL33LPRouj4R6ZJwZpidyYVgQk3r3hdaBWujAek,12090
|
|
53
|
-
devflow_cli/core/worktree_snapshot.py,sha256=
|
|
53
|
+
devflow_cli/core/worktree_snapshot.py,sha256=FTmpSn-O42PP1vOlnwG0nb9SUkzSCuVygb8edB2uKiw,4833
|
|
54
54
|
devflow_cli/schemas/activity-contract-v4.schema.json,sha256=IeduWDiJ5IV6EL7FDZWnHBjzyUt1uwig2rB-SMbcpOU,2006
|
|
55
55
|
devflow_cli/schemas/activity-result-v1.schema.json,sha256=xQuyD5fwHGIdeoJoDJxJn26qBGJX74lD6Dc5gi5CLQo,2772
|
|
56
56
|
devflow_cli/schemas/assessment-v4.schema.json,sha256=byFBaoG_Vu-qS7vERREKThtpQa3dzNtPiFCMyEpWFVQ,2254
|
|
@@ -58,7 +58,7 @@ devflow_cli/schemas/workflow-state-v5.schema.json,sha256=I18nTSi52XDTJhMIDO_AZJB
|
|
|
58
58
|
devflow_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
59
|
devflow_cli/utils/console.py,sha256=t7cpyeEluWj4v_O_KFSI59iqXK4GiSXPuxlsr8q5zoQ,771
|
|
60
60
|
devflow_cli/utils/logging.py,sha256=I7U9_IVvYAFnUAWi7xs5F_WGR0uSz6DmRvZ_B3xLFbs,1769
|
|
61
|
-
devflow_cli/utils/paths.py,sha256=
|
|
61
|
+
devflow_cli/utils/paths.py,sha256=CH81dZZTI2vjgl8mck6QcAChxoBpujmKZ0vVKhKmXEY,8176
|
|
62
62
|
devflow_cli/utils/strings.py,sha256=bfFXCwBDnFxshgZOYfy6mtysnjiaWO1wFnqBf7HLg4o,351
|
|
63
63
|
devflow_cli/resources/commands/devflow.context.md,sha256=SdvZ286cJKkgQIKYwnjd30v5zDeEXTFBhpEEsEOInoc,2048
|
|
64
64
|
devflow_cli/resources/commands/devflow.extension.md,sha256=w7hoFfxRS0y-3rsvUmQDx1fvZhtE9KhANmRgFDwYVaA,2373
|
|
@@ -86,8 +86,8 @@ devflow_cli/resources/templates/contracts/api-template.md,sha256=rC5v7JBz71PznCn
|
|
|
86
86
|
devflow_cli/resources/templates/contracts/components-template.md,sha256=XIWfEXfC72sNmL5OA3V-7xIywkNDU2DBPLfyiagOWcs,484
|
|
87
87
|
devflow_cli/resources/templates/contracts/interfaces-template.md,sha256=kf8B7b6HsmN7jyRlLNU7RnZl0rTMlI5s97yVRyI-6yc,336
|
|
88
88
|
devflow_cli/resources/templates/contracts/services-template.md,sha256=cFafKCyLmq_a7aR7G0LXAaMO2z4Fuflq6iKdxfVg0vg,477
|
|
89
|
-
devflow_cli-7.
|
|
90
|
-
devflow_cli-7.
|
|
91
|
-
devflow_cli-7.
|
|
92
|
-
devflow_cli-7.
|
|
93
|
-
devflow_cli-7.
|
|
89
|
+
devflow_cli-7.2.0.dist-info/METADATA,sha256=KbyPQIqAxLJO8bWSrJBVcJhYKKp1aaxWnzFPZvFxyCs,6700
|
|
90
|
+
devflow_cli-7.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
91
|
+
devflow_cli-7.2.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
|
|
92
|
+
devflow_cli-7.2.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
|
|
93
|
+
devflow_cli-7.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|