devflow-cli 2.0.0__py3-none-any.whl → 2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """devflow CLI — Spec-Driven Development workflow."""
2
2
 
3
- VERSION = "1.0.0"
3
+ VERSION = "2.2.0"
4
4
 
5
5
  STEPS = [
6
6
  "constitution", # Pré-requis projet (1/projet). Auto-complétée si .specify/memory/constitution.md existe.
@@ -51,6 +51,17 @@ STEP_LINEAR_MAPPING = {
51
51
 
52
52
  SUPPORTED_AGENTS = ["claude-code", "cursor"]
53
53
 
54
+ # Mapping speckit → devflow pour la migration
55
+ SPECKIT_STEP_MAP = {
56
+ "specify": "spec",
57
+ "clarify": "clarify",
58
+ "plan": "plan",
59
+ "tasks": "tasks",
60
+ "implement": "implement",
61
+ }
62
+
63
+ SPECKIT_IGNORED_STEPS = {"checklist", "analyze"}
64
+
54
65
  ARTIFACTS = [
55
66
  "spec.md",
56
67
  "clarify-log.md",
devflow_cli/cli.py CHANGED
@@ -15,6 +15,7 @@ from devflow_cli.commands.regen import regen
15
15
  from devflow_cli.commands.migrate_cmd import migrate
16
16
  from devflow_cli.commands.upgrade_cmd import upgrade
17
17
  from devflow_cli.commands.rollback import rollback
18
+ from devflow_cli.commands.migrate_speckit import migrate_speckit
18
19
  from devflow_cli.commands import extension
19
20
 
20
21
  app = typer.Typer(
@@ -47,6 +48,7 @@ app.command()(regen)
47
48
  app.command()(migrate)
48
49
  app.command()(upgrade)
49
50
  app.command()(rollback)
51
+ app.command(name="migrate-speckit")(migrate_speckit)
50
52
  app.add_typer(extension.app, name="extension")
51
53
 
52
54
 
@@ -0,0 +1,219 @@
1
+ """devflow migrate-speckit — Migration des projets speckit vers devflow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ import typer
10
+
11
+ from devflow_cli.core.state import (
12
+ is_speckit_state, convert_speckit_state, write_state,
13
+ )
14
+ from devflow_cli.core.manifest import compute_file_hash
15
+ from devflow_cli.utils.console import console, ok, info, warn, header
16
+ from devflow_cli.utils.paths import get_devflow_root, get_claude_dir, get_specs_root
17
+
18
+
19
+ def migrate_speckit(
20
+ dry_run: bool = typer.Option(False, "--dry-run", help="Affiche le plan de migration sans modifier de fichiers"),
21
+ clean_speckit: bool = typer.Option(False, "--clean-speckit", help="Supprime les commandes speckit et templates non personnalises"),
22
+ ) -> None:
23
+ """Migration des projets speckit vers le workflow devflow."""
24
+ specs_root = get_specs_root()
25
+
26
+ if not specs_root.is_dir():
27
+ info("Rien a migrer : .specify/specs/ n'existe pas.")
28
+ return
29
+
30
+ # Scanner les features
31
+ features = sorted(
32
+ d for d in specs_root.iterdir()
33
+ if d.is_dir() and (d / "state.json").is_file()
34
+ )
35
+
36
+ if not features:
37
+ info("Aucune feature speckit detectee.")
38
+ if clean_speckit:
39
+ _clean_speckit(dry_run)
40
+ else:
41
+ _suggest_clean_speckit()
42
+ return
43
+
44
+ # Filtrer : seulement les speckit states
45
+ speckit_features = []
46
+ devflow_features = []
47
+ error_features = []
48
+
49
+ for feat_dir in features:
50
+ state_file = feat_dir / "state.json"
51
+ if is_speckit_state(state_file):
52
+ speckit_features.append(feat_dir)
53
+ else:
54
+ # Verifier si c'est un devflow valide ou corrompu
55
+ try:
56
+ data = json.loads(state_file.read_text(encoding="utf-8"))
57
+ if "reviewIterations" in data:
58
+ devflow_features.append(feat_dir)
59
+ else:
60
+ error_features.append((feat_dir, "Format non reconnu"))
61
+ except (json.JSONDecodeError, OSError) as e:
62
+ error_features.append((feat_dir, str(e)))
63
+
64
+ if not speckit_features and not error_features:
65
+ info("Aucune feature speckit detectee.")
66
+ if devflow_features:
67
+ info(f" {len(devflow_features)} feature(s) deja au format devflow.")
68
+ if clean_speckit:
69
+ _clean_speckit(dry_run)
70
+ else:
71
+ _suggest_clean_speckit()
72
+ return
73
+
74
+ header(f"devflow migrate-speckit — {len(speckit_features)} feature(s) speckit")
75
+ console.print()
76
+
77
+ if dry_run:
78
+ console.print("[dim]Mode dry-run : aucun fichier ne sera modifie.[/dim]")
79
+ console.print()
80
+
81
+ migrated = 0
82
+
83
+ for feat_dir in speckit_features:
84
+ state_file = feat_dir / "state.json"
85
+ try:
86
+ raw_data = json.loads(state_file.read_text(encoding="utf-8"))
87
+ except (json.JSONDecodeError, OSError) as e:
88
+ warn(f" {feat_dir.name} : erreur lecture — {e}")
89
+ error_features.append((feat_dir, str(e)))
90
+ continue
91
+
92
+ issue_id = raw_data.get("issueId", feat_dir.name)
93
+ from_step = raw_data.get("currentStep", "?")
94
+ converted = convert_speckit_state(raw_data)
95
+ to_step = converted.currentStep
96
+
97
+ if dry_run:
98
+ console.print(f" [bold]{feat_dir.name}[/bold] ({issue_id})")
99
+ console.print(f" Etape : {from_step} → {to_step}")
100
+ console.print(f" Champs ajoutes : reviewIterations, mode, skippedSteps")
101
+ else:
102
+ # Backup
103
+ shutil.copy2(state_file, state_file.with_suffix(".json.bak"))
104
+ # Ecrire le state converti
105
+ write_state(state_file, converted)
106
+ ok(f" {feat_dir.name} ({issue_id}) : {from_step} → {to_step}")
107
+
108
+ migrated += 1
109
+
110
+ # Constitution
111
+ if not dry_run:
112
+ _ensure_constitution()
113
+
114
+ # Rapport
115
+ console.print()
116
+ console.rule("Recapitulatif")
117
+ console.print(f" Migrees : {migrated}")
118
+ if devflow_features:
119
+ console.print(f" Ignorees : {len(devflow_features)} (deja devflow)")
120
+ if error_features:
121
+ console.print(f" Erreurs : {len(error_features)}")
122
+ for feat_dir, msg in error_features:
123
+ warn(f" {feat_dir.name} : {msg}")
124
+
125
+ if dry_run:
126
+ console.print()
127
+ console.print("[dim]Mode dry-run : aucun fichier modifie.[/dim]")
128
+
129
+ # Nettoyage speckit
130
+ if clean_speckit:
131
+ _clean_speckit(dry_run)
132
+ elif not dry_run:
133
+ _suggest_clean_speckit()
134
+
135
+
136
+ def _ensure_constitution() -> None:
137
+ """Cree une constitution starter si absente."""
138
+ const_dest = Path.cwd() / ".specify" / "memory" / "constitution.md"
139
+ if const_dest.is_file():
140
+ return
141
+
142
+ try:
143
+ const_src = get_devflow_root() / "templates" / "constitution-template.md"
144
+ if const_src.is_file():
145
+ const_dest.parent.mkdir(parents=True, exist_ok=True)
146
+ shutil.copy2(const_src, const_dest)
147
+ ok("Constitution starter creee dans .specify/memory/constitution.md")
148
+ info(" Pensez a la personnaliser pour votre projet.")
149
+ else:
150
+ warn("Template constitution non trouve — creation ignoree.")
151
+ except Exception:
152
+ warn("Impossible de creer la constitution starter.")
153
+
154
+
155
+ def _clean_speckit(dry_run: bool) -> None:
156
+ """Supprime les commandes speckit et templates non personnalises."""
157
+ console.print()
158
+ header("Nettoyage speckit")
159
+ console.print()
160
+
161
+ # 1. Commandes speckit dans ~/.claude/commands/
162
+ claude_dir = get_claude_dir()
163
+ cmd_dir = claude_dir / "commands"
164
+ removed_cmds = 0
165
+ if cmd_dir.is_dir():
166
+ for f in sorted(cmd_dir.glob("speckit.*.md")):
167
+ if dry_run:
168
+ console.print(f" [dim](dry-run) Supprimerait {f.name}[/dim]")
169
+ else:
170
+ f.unlink()
171
+ console.print(f" Supprime : {f.name}")
172
+ removed_cmds += 1
173
+
174
+ # 2. Templates dans .specify/templates/
175
+ tpl_dir = Path.cwd() / ".specify" / "templates"
176
+ removed_tpls = 0
177
+ preserved_tpls = 0
178
+ if tpl_dir.is_dir():
179
+ try:
180
+ devflow_tpl_dir = get_devflow_root() / "templates"
181
+ except Exception:
182
+ devflow_tpl_dir = None
183
+
184
+ for f in sorted(tpl_dir.glob("*")):
185
+ if not f.is_file():
186
+ continue
187
+ # Comparer avec le template devflow source
188
+ if devflow_tpl_dir and (devflow_tpl_dir / f.name).is_file():
189
+ src_hash = compute_file_hash(devflow_tpl_dir / f.name)
190
+ installed_hash = compute_file_hash(f)
191
+ if src_hash == installed_hash:
192
+ if dry_run:
193
+ console.print(f" [dim](dry-run) Supprimerait {f.name}[/dim]")
194
+ else:
195
+ f.unlink()
196
+ console.print(f" Supprime : {f.name}")
197
+ removed_tpls += 1
198
+ else:
199
+ warn(f" Conserve (personnalise) : {f.name}")
200
+ preserved_tpls += 1
201
+ else:
202
+ # Pas de reference → conserver par securite
203
+ warn(f" Conserve (pas de reference) : {f.name}")
204
+ preserved_tpls += 1
205
+
206
+ console.print()
207
+ console.print(f" Commandes supprimees : {removed_cmds}")
208
+ console.print(f" Templates supprimes : {removed_tpls}")
209
+ if preserved_tpls:
210
+ console.print(f" Templates conserves : {preserved_tpls}")
211
+
212
+
213
+ def _suggest_clean_speckit() -> None:
214
+ """Suggere --clean-speckit si des commandes speckit sont detectees."""
215
+ claude_dir = get_claude_dir()
216
+ cmd_dir = claude_dir / "commands"
217
+ if cmd_dir.is_dir() and list(cmd_dir.glob("speckit.*.md")):
218
+ info("Des commandes speckit sont encore presentes dans ~/.claude/commands/.")
219
+ info("Utilisez --clean-speckit pour les supprimer.")
devflow_cli/core/state.py CHANGED
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field, asdict
7
7
  from datetime import datetime, timezone
8
8
  from pathlib import Path
9
9
 
10
- from devflow_cli import STEPS, OPTIONAL_STEPS, STEP_ARTIFACTS
10
+ from devflow_cli import STEPS, OPTIONAL_STEPS, STEP_ARTIFACTS, SPECKIT_STEP_MAP, SPECKIT_IGNORED_STEPS
11
11
 
12
12
 
13
13
  @dataclass
@@ -262,6 +262,74 @@ def rollback_step(state: FeatureState) -> FeatureState | None:
262
262
  return state
263
263
 
264
264
 
265
+ def is_speckit_state(state_file: Path) -> bool:
266
+ """Detecte si un state.json est au format speckit (pas devflow).
267
+
268
+ Lit le JSON brut et verifie l'absence du champ 'reviewIterations'.
269
+ """
270
+ try:
271
+ data = json.loads(state_file.read_text(encoding="utf-8"))
272
+ return isinstance(data, dict) and "reviewIterations" not in data
273
+ except (json.JSONDecodeError, OSError):
274
+ return False
275
+
276
+
277
+ def convert_speckit_state(raw_data: dict) -> FeatureState:
278
+ """Convertit un state.json speckit brut en FeatureState devflow.
279
+
280
+ - Mappe les etapes via SPECKIT_STEP_MAP (specify→spec, etc.)
281
+ - Filtre les etapes ignorees (checklist, analyze)
282
+ - Injecte les etapes devflow intermediaires comme auto-validees
283
+ si l'utilisateur les a deja depassees
284
+ """
285
+ issue_id = raw_data.get("issueId", "")
286
+
287
+ # Mapper currentStep
288
+ raw_current = raw_data.get("currentStep", "spec")
289
+ current_step = SPECKIT_STEP_MAP.get(raw_current, raw_current)
290
+
291
+ # Mapper completedSteps : convertir et filtrer
292
+ raw_completed = raw_data.get("completedSteps", [])
293
+ mapped_completed: list[str] = []
294
+ for step in raw_completed:
295
+ if step in SPECKIT_IGNORED_STEPS:
296
+ continue
297
+ mapped = SPECKIT_STEP_MAP.get(step, step)
298
+ if mapped in STEPS and mapped not in mapped_completed:
299
+ mapped_completed.append(mapped)
300
+
301
+ # Injecter les etapes devflow intermediaires auto-validees
302
+ # Pour chaque etape devflow, si elle precede une etape deja completee
303
+ # et n'est pas encore dans completed, l'injecter
304
+ review_iterations: dict[str, int] = {}
305
+ if current_step in STEPS:
306
+ current_idx = STEPS.index(current_step)
307
+ final_completed: list[str] = []
308
+ for i, step in enumerate(STEPS):
309
+ if step == current_step:
310
+ break
311
+ if step in mapped_completed:
312
+ final_completed.append(step)
313
+ elif i < current_idx:
314
+ # Etape intermediaire manquante → auto-valider
315
+ final_completed.append(step)
316
+ if step in ("review-spec", "review-tasks", "review-impl"):
317
+ review_iterations[step] = 0
318
+ mapped_completed = final_completed
319
+
320
+ return FeatureState(
321
+ issueId=issue_id,
322
+ currentStep=current_step,
323
+ completedSteps=mapped_completed,
324
+ stepTimestamps={},
325
+ reviewIterations=review_iterations,
326
+ mode="full",
327
+ skippedSteps=[],
328
+ createdAt=raw_data.get("createdAt", ""),
329
+ updatedAt=raw_data.get("updatedAt", ""),
330
+ )
331
+
332
+
265
333
  def validate_and_advance(
266
334
  state: FeatureState,
267
335
  completed_step: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devflow-cli
3
- Version: 2.0.0
3
+ Version: 2.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
@@ -117,7 +117,7 @@ devflow status
117
117
  Consultez la [documentation complète](docs/index.md) pour :
118
118
 
119
119
  - [Pipeline détaillé](docs/pipeline.md) — les 13 étapes expliquées
120
- - [Commandes CLI](docs/cli-reference.md) — référence des 11 commandes terminal
120
+ - [Commandes CLI](docs/cli-reference.md) — référence des 12 commandes terminal
121
121
  - [Commandes slash](docs/commands-reference.md) — référence des 23 commandes Claude Code
122
122
  - [Guides par type de projet](docs/index.md#guides-par-type-de-projet) — brownfield, greenfield, microservices
123
123
 
@@ -1,5 +1,5 @@
1
- devflow_cli/__init__.py,sha256=4C2mALrQiTmJMZx99CT2fViXvKO5_IFzMStcTLumeeg,1400
2
- devflow_cli/cli.py,sha256=UxuYGV0TgsnoVV_-vp2Ce1sUTkc61Viyr13X_2HEP-o,1458
1
+ devflow_cli/__init__.py,sha256=VCGzz5uVlYBpsURHQZexi_70jMAIuYAHQWhxTfVjDjs,1643
2
+ devflow_cli/cli.py,sha256=HxxFDISXAYvv5dMvI_xNx1mhUdhQSsa_fqvFtzsN_bM,1576
3
3
  devflow_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  devflow_cli/commands/check.py,sha256=sb5zkSXXwl91o8yn1-TRL5wH89MWX-kY2CAHYYjMxq4,4170
5
5
  devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
@@ -8,6 +8,7 @@ devflow_cli/commands/extension.py,sha256=gL33YS8EuBTADmqKGDqw_0gJXFwccwg1uJmCNtv
8
8
  devflow_cli/commands/feature.py,sha256=EceMCxYEif4P6Ir-1XZml4txLH9iqm3jEAeS6dJ8LpA,4098
9
9
  devflow_cli/commands/init_cmd.py,sha256=LLUJx_Kdl-YCEuy1corvaYe65pZ6R5-VnVBal6wZSa4,4513
10
10
  devflow_cli/commands/migrate_cmd.py,sha256=y2_yWyBabMgDZ0e2CsX2nYtb33yQRyr4zcrj858Ieoo,3274
11
+ devflow_cli/commands/migrate_speckit.py,sha256=NbHgTbrWi9vtvD1OQz0VLSSY5hv68KlU_Fsh-ovlHx8,7870
11
12
  devflow_cli/commands/regen.py,sha256=UyB7_0oBxDs_CqaAQ6yE2Yoz4IcpUr2bqkIEmm0e1iM,3198
12
13
  devflow_cli/commands/rollback.py,sha256=B-Qnmem_sAwmSwyFdzpzglbeX4uLd0t4ohsLYyHngl4,1789
13
14
  devflow_cli/commands/status.py,sha256=nPuudZPqCFWE2JqiCFOMKlN_CM_uDwD-gq9MbHBhg20,4775
@@ -20,14 +21,14 @@ devflow_cli/core/installer.py,sha256=XGuugkWAV7McTnpdNZ2JX4NtEAE8CI73mfrYnKlb-JQ
20
21
  devflow_cli/core/manifest.py,sha256=5hu2w6PPGo4raGbgKzx6qpAtt653SrzpRkbWngHM9qw,5770
21
22
  devflow_cli/core/scanner.py,sha256=bcoC91Nc0Hw2P7aHpL-9ITxynLZhLTEKpOXo553a-18,4395
22
23
  devflow_cli/core/staleness.py,sha256=ASe-rlYN17AtpI_S4QlidaHwbtv7E7L4fhRGYtyTlPE,5586
23
- devflow_cli/core/state.py,sha256=M7pldLzXekgQWwPXFYg62CQ-q_4C9Es1lza74qAmCGM,9433
24
+ devflow_cli/core/state.py,sha256=EvfX-s3mBLIaOI6OzpRkeQQfdb2mB7S8UZ2EGJ0ZSYk,12038
24
25
  devflow_cli/core/validators.py,sha256=3PtqC-R2kqNbh4HK_l_Yl8RylQsFdtCXhopt0yT065k,6194
25
26
  devflow_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
27
  devflow_cli/utils/console.py,sha256=t7cpyeEluWj4v_O_KFSI59iqXK4GiSXPuxlsr8q5zoQ,771
27
28
  devflow_cli/utils/paths.py,sha256=9J2Qhq2Ot6p7HQwRAfdK3V5bc5sme6FnnnAg9dhXcP4,4212
28
29
  devflow_cli/utils/strings.py,sha256=bfFXCwBDnFxshgZOYfy6mtysnjiaWO1wFnqBf7HLg4o,351
29
- devflow_cli-2.0.0.dist-info/METADATA,sha256=1nrITovXCXduwSNOfg0dRgHecrAbZoCO6fiWoe8tyeA,4896
30
- devflow_cli-2.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
31
- devflow_cli-2.0.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
32
- devflow_cli-2.0.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
33
- devflow_cli-2.0.0.dist-info/RECORD,,
30
+ devflow_cli-2.2.0.dist-info/METADATA,sha256=I2tCqZ3vTDs_DjcIcOwGbgG3LHlze_x1BtxmIm8uoQg,4896
31
+ devflow_cli-2.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
32
+ devflow_cli-2.2.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
33
+ devflow_cli-2.2.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
34
+ devflow_cli-2.2.0.dist-info/RECORD,,