devflow-cli 2.0.0__py3-none-any.whl → 2.3.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 +22 -1
- devflow_cli/cli.py +7 -2
- devflow_cli/commands/check.py +4 -1
- devflow_cli/commands/feature.py +4 -0
- devflow_cli/commands/init_cmd.py +5 -0
- devflow_cli/commands/migrate_cmd.py +4 -0
- devflow_cli/commands/migrate_speckit.py +227 -0
- devflow_cli/commands/{regen.py → stale.py} +6 -6
- devflow_cli/core/catalog.py +4 -0
- devflow_cli/core/git.py +6 -0
- devflow_cli/core/hooks.py +5 -0
- devflow_cli/core/manifest.py +4 -0
- devflow_cli/core/state.py +79 -2
- devflow_cli/core/validators.py +6 -0
- devflow_cli/utils/logging.py +62 -0
- devflow_cli/utils/paths.py +6 -2
- {devflow_cli-2.0.0.dist-info → devflow_cli-2.3.0.dist-info}/METADATA +2 -2
- devflow_cli-2.3.0.dist-info/RECORD +35 -0
- devflow_cli-2.0.0.dist-info/RECORD +0 -33
- {devflow_cli-2.0.0.dist-info → devflow_cli-2.3.0.dist-info}/WHEEL +0 -0
- {devflow_cli-2.0.0.dist-info → devflow_cli-2.3.0.dist-info}/entry_points.txt +0 -0
- {devflow_cli-2.0.0.dist-info → devflow_cli-2.3.0.dist-info}/licenses/LICENSE +0 -0
devflow_cli/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""devflow CLI — Spec-Driven Development workflow."""
|
|
2
2
|
|
|
3
|
-
VERSION = "
|
|
3
|
+
VERSION = "2.3.0"
|
|
4
4
|
|
|
5
5
|
STEPS = [
|
|
6
6
|
"constitution", # Pré-requis projet (1/projet). Auto-complétée si .specify/memory/constitution.md existe.
|
|
@@ -20,6 +20,8 @@ STEPS = [
|
|
|
20
20
|
|
|
21
21
|
OPTIONAL_STEPS = {"clarify", "research", "contracts", "docs"}
|
|
22
22
|
|
|
23
|
+
REVIEW_GATES = {"review-spec", "review-tasks", "review-impl"}
|
|
24
|
+
|
|
23
25
|
STEP_ARTIFACTS = {
|
|
24
26
|
"spec": "spec.md",
|
|
25
27
|
"clarify": "clarify-log.md",
|
|
@@ -51,6 +53,17 @@ STEP_LINEAR_MAPPING = {
|
|
|
51
53
|
|
|
52
54
|
SUPPORTED_AGENTS = ["claude-code", "cursor"]
|
|
53
55
|
|
|
56
|
+
# Mapping speckit → devflow pour la migration
|
|
57
|
+
SPECKIT_STEP_MAP = {
|
|
58
|
+
"specify": "spec",
|
|
59
|
+
"clarify": "clarify",
|
|
60
|
+
"plan": "plan",
|
|
61
|
+
"tasks": "tasks",
|
|
62
|
+
"implement": "implement",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
SPECKIT_IGNORED_STEPS = {"checklist", "analyze"}
|
|
66
|
+
|
|
54
67
|
ARTIFACTS = [
|
|
55
68
|
"spec.md",
|
|
56
69
|
"clarify-log.md",
|
|
@@ -65,3 +78,11 @@ ARTIFACTS = [
|
|
|
65
78
|
"analysis.md",
|
|
66
79
|
"checklist.md",
|
|
67
80
|
]
|
|
81
|
+
|
|
82
|
+
# --- Pipeline constants coherence checks ---
|
|
83
|
+
_steps_set = set(STEPS)
|
|
84
|
+
assert OPTIONAL_STEPS <= _steps_set, f"OPTIONAL_STEPS not subset of STEPS: {OPTIONAL_STEPS - _steps_set}"
|
|
85
|
+
assert REVIEW_GATES <= _steps_set, f"REVIEW_GATES not subset of STEPS: {REVIEW_GATES - _steps_set}"
|
|
86
|
+
assert set(STEP_ARTIFACTS.keys()) <= _steps_set, f"STEP_ARTIFACTS has unknown keys: {set(STEP_ARTIFACTS.keys()) - _steps_set}"
|
|
87
|
+
assert set(STEP_LINEAR_MAPPING.keys()) <= _steps_set, f"STEP_LINEAR_MAPPING has unknown keys: {set(STEP_LINEAR_MAPPING.keys()) - _steps_set}"
|
|
88
|
+
assert set(SPECKIT_STEP_MAP.values()) <= _steps_set, f"SPECKIT_STEP_MAP maps to unknown steps: {set(SPECKIT_STEP_MAP.values()) - _steps_set}"
|
devflow_cli/cli.py
CHANGED
|
@@ -5,16 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from devflow_cli import VERSION
|
|
8
|
+
from devflow_cli.utils.logging import setup_logging
|
|
8
9
|
from devflow_cli.commands.init_cmd import init
|
|
9
10
|
from devflow_cli.commands.check import check
|
|
10
11
|
from devflow_cli.commands.status import status
|
|
11
12
|
from devflow_cli.commands.feature import feature
|
|
12
13
|
from devflow_cli.commands.context import context
|
|
13
14
|
from devflow_cli.commands.export_cmd import export
|
|
14
|
-
from devflow_cli.commands.
|
|
15
|
+
from devflow_cli.commands.stale import stale
|
|
15
16
|
from devflow_cli.commands.migrate_cmd import migrate
|
|
16
17
|
from devflow_cli.commands.upgrade_cmd import upgrade
|
|
17
18
|
from devflow_cli.commands.rollback import rollback
|
|
19
|
+
from devflow_cli.commands.migrate_speckit import migrate_speckit
|
|
18
20
|
from devflow_cli.commands import extension
|
|
19
21
|
|
|
20
22
|
app = typer.Typer(
|
|
@@ -33,8 +35,10 @@ def version_callback(value: bool) -> None:
|
|
|
33
35
|
@app.callback()
|
|
34
36
|
def main(
|
|
35
37
|
version: bool = typer.Option(False, "--version", callback=version_callback, is_eager=True, help="Affiche la version"),
|
|
38
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Active les logs dans stderr"),
|
|
36
39
|
) -> None:
|
|
37
40
|
"""devflow — Spec-Driven Development workflow CLI."""
|
|
41
|
+
setup_logging(verbose=verbose)
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
app.command()(init)
|
|
@@ -43,10 +47,11 @@ app.command()(status)
|
|
|
43
47
|
app.command()(feature)
|
|
44
48
|
app.command()(context)
|
|
45
49
|
app.command(name="export")(export)
|
|
46
|
-
app.command()(
|
|
50
|
+
app.command()(stale)
|
|
47
51
|
app.command()(migrate)
|
|
48
52
|
app.command()(upgrade)
|
|
49
53
|
app.command()(rollback)
|
|
54
|
+
app.command(name="migrate-speckit")(migrate_speckit)
|
|
50
55
|
app.add_typer(extension.app, name="extension")
|
|
51
56
|
|
|
52
57
|
|
devflow_cli/commands/check.py
CHANGED
|
@@ -8,6 +8,9 @@ from pathlib import Path
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
from devflow_cli.utils.console import console, ok, warn, fail
|
|
11
|
+
from devflow_cli.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
11
14
|
from devflow_cli.utils.paths import get_claude_dir
|
|
12
15
|
|
|
13
16
|
|
|
@@ -133,7 +136,7 @@ def check() -> None:
|
|
|
133
136
|
linear_configured = True
|
|
134
137
|
break
|
|
135
138
|
except OSError:
|
|
136
|
-
|
|
139
|
+
logger.warning("Failed to read config %s", cfg_file, exc_info=True)
|
|
137
140
|
if linear_configured:
|
|
138
141
|
ok("Linear MCP -- configure")
|
|
139
142
|
ok_count += 1
|
devflow_cli/commands/feature.py
CHANGED
|
@@ -9,6 +9,9 @@ from typing import Optional
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
11
|
from devflow_cli.core.git import branch_exists, create_branch, checkout_branch, GitNotFoundError
|
|
12
|
+
from devflow_cli.utils.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
12
15
|
from devflow_cli.core.state import create_state
|
|
13
16
|
from devflow_cli.utils.console import console, ok, info
|
|
14
17
|
from devflow_cli.utils.paths import get_specs_root, next_feature_number
|
|
@@ -28,6 +31,7 @@ def _ensure_git() -> None:
|
|
|
28
31
|
from devflow_cli.core.git import ensure_git_available
|
|
29
32
|
ensure_git_available()
|
|
30
33
|
except GitNotFoundError as e:
|
|
34
|
+
logger.error("Git not found: %s", e, exc_info=True)
|
|
31
35
|
console.print(f"[red]Erreur : {e}[/red]")
|
|
32
36
|
raise typer.Exit(1)
|
|
33
37
|
|
devflow_cli/commands/init_cmd.py
CHANGED
|
@@ -8,6 +8,10 @@ from typing import Literal, Optional
|
|
|
8
8
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
|
+
from devflow_cli.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
11
15
|
from devflow_cli import SUPPORTED_AGENTS
|
|
12
16
|
from devflow_cli.core.installer import install_devflow
|
|
13
17
|
from devflow_cli.core.git import is_git_repo
|
|
@@ -110,6 +114,7 @@ def init(
|
|
|
110
114
|
save_manifest(get_claude_dir(), manifest_data)
|
|
111
115
|
ok("Manifeste d'installation cree")
|
|
112
116
|
except Exception:
|
|
117
|
+
logger.error("Failed to create manifest", exc_info=True)
|
|
113
118
|
warn("Impossible de creer le manifeste d'installation")
|
|
114
119
|
|
|
115
120
|
console.print()
|
|
@@ -8,6 +8,9 @@ from pathlib import Path
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
from devflow_cli.core.state import read_state
|
|
11
|
+
from devflow_cli.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
11
14
|
from devflow_cli.utils.console import console, ok, info, warn, fail, header
|
|
12
15
|
from devflow_cli.utils.paths import get_specs_root, next_feature_number
|
|
13
16
|
from devflow_cli.utils.strings import clean_short_name
|
|
@@ -68,6 +71,7 @@ def migrate(
|
|
|
68
71
|
default=default_name,
|
|
69
72
|
)
|
|
70
73
|
except (KeyboardInterrupt, typer.Abort):
|
|
74
|
+
logger.warning("Migration interrupted for %s", issue_id)
|
|
71
75
|
console.print(f" [yellow]Ignore (annule)[/yellow]")
|
|
72
76
|
skipped += 1
|
|
73
77
|
continue
|
|
@@ -0,0 +1,227 @@
|
|
|
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.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
from devflow_cli.core.state import (
|
|
16
|
+
is_speckit_state, convert_speckit_state, write_state,
|
|
17
|
+
)
|
|
18
|
+
from devflow_cli.core.manifest import compute_file_hash
|
|
19
|
+
from devflow_cli.utils.console import console, ok, info, warn, header
|
|
20
|
+
from devflow_cli.utils.paths import get_devflow_root, get_claude_dir, get_specs_root
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def migrate_speckit(
|
|
24
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Affiche le plan de migration sans modifier de fichiers"),
|
|
25
|
+
clean_speckit: bool = typer.Option(False, "--clean-speckit", help="Supprime les commandes speckit et templates non personnalises"),
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Migration des projets speckit vers le workflow devflow."""
|
|
28
|
+
specs_root = get_specs_root()
|
|
29
|
+
|
|
30
|
+
if not specs_root.is_dir():
|
|
31
|
+
info("Rien a migrer : .specify/specs/ n'existe pas.")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# Scanner les features
|
|
35
|
+
features = sorted(
|
|
36
|
+
d for d in specs_root.iterdir()
|
|
37
|
+
if d.is_dir() and (d / "state.json").is_file()
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not features:
|
|
41
|
+
info("Aucune feature speckit detectee.")
|
|
42
|
+
if clean_speckit:
|
|
43
|
+
_clean_speckit(dry_run)
|
|
44
|
+
else:
|
|
45
|
+
_suggest_clean_speckit()
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
# Filtrer : seulement les speckit states
|
|
49
|
+
speckit_features = []
|
|
50
|
+
devflow_features = []
|
|
51
|
+
error_features = []
|
|
52
|
+
|
|
53
|
+
for feat_dir in features:
|
|
54
|
+
state_file = feat_dir / "state.json"
|
|
55
|
+
if is_speckit_state(state_file):
|
|
56
|
+
speckit_features.append(feat_dir)
|
|
57
|
+
else:
|
|
58
|
+
# Verifier si c'est un devflow valide ou corrompu
|
|
59
|
+
try:
|
|
60
|
+
data = json.loads(state_file.read_text(encoding="utf-8"))
|
|
61
|
+
if "reviewIterations" in data:
|
|
62
|
+
devflow_features.append(feat_dir)
|
|
63
|
+
else:
|
|
64
|
+
error_features.append((feat_dir, "Format non reconnu"))
|
|
65
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
66
|
+
logger.warning("Failed to read state %s", state_file, exc_info=True)
|
|
67
|
+
error_features.append((feat_dir, str(e)))
|
|
68
|
+
|
|
69
|
+
if not speckit_features and not error_features:
|
|
70
|
+
info("Aucune feature speckit detectee.")
|
|
71
|
+
if devflow_features:
|
|
72
|
+
info(f" {len(devflow_features)} feature(s) deja au format devflow.")
|
|
73
|
+
if clean_speckit:
|
|
74
|
+
_clean_speckit(dry_run)
|
|
75
|
+
else:
|
|
76
|
+
_suggest_clean_speckit()
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
header(f"devflow migrate-speckit — {len(speckit_features)} feature(s) speckit")
|
|
80
|
+
console.print()
|
|
81
|
+
|
|
82
|
+
if dry_run:
|
|
83
|
+
console.print("[dim]Mode dry-run : aucun fichier ne sera modifie.[/dim]")
|
|
84
|
+
console.print()
|
|
85
|
+
|
|
86
|
+
migrated = 0
|
|
87
|
+
|
|
88
|
+
for feat_dir in speckit_features:
|
|
89
|
+
state_file = feat_dir / "state.json"
|
|
90
|
+
try:
|
|
91
|
+
raw_data = json.loads(state_file.read_text(encoding="utf-8"))
|
|
92
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
93
|
+
logger.warning("Failed to read speckit state %s", state_file, exc_info=True)
|
|
94
|
+
warn(f" {feat_dir.name} : erreur lecture — {e}")
|
|
95
|
+
error_features.append((feat_dir, str(e)))
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
issue_id = raw_data.get("issueId", feat_dir.name)
|
|
99
|
+
from_step = raw_data.get("currentStep", "?")
|
|
100
|
+
converted = convert_speckit_state(raw_data)
|
|
101
|
+
to_step = converted.currentStep
|
|
102
|
+
|
|
103
|
+
if dry_run:
|
|
104
|
+
console.print(f" [bold]{feat_dir.name}[/bold] ({issue_id})")
|
|
105
|
+
console.print(f" Etape : {from_step} → {to_step}")
|
|
106
|
+
console.print(f" Champs ajoutes : reviewIterations, mode, skippedSteps")
|
|
107
|
+
else:
|
|
108
|
+
# Backup
|
|
109
|
+
shutil.copy2(state_file, state_file.with_suffix(".json.bak"))
|
|
110
|
+
# Ecrire le state converti
|
|
111
|
+
write_state(state_file, converted)
|
|
112
|
+
ok(f" {feat_dir.name} ({issue_id}) : {from_step} → {to_step}")
|
|
113
|
+
|
|
114
|
+
migrated += 1
|
|
115
|
+
|
|
116
|
+
# Constitution
|
|
117
|
+
if not dry_run:
|
|
118
|
+
_ensure_constitution()
|
|
119
|
+
|
|
120
|
+
# Rapport
|
|
121
|
+
console.print()
|
|
122
|
+
console.rule("Recapitulatif")
|
|
123
|
+
console.print(f" Migrees : {migrated}")
|
|
124
|
+
if devflow_features:
|
|
125
|
+
console.print(f" Ignorees : {len(devflow_features)} (deja devflow)")
|
|
126
|
+
if error_features:
|
|
127
|
+
console.print(f" Erreurs : {len(error_features)}")
|
|
128
|
+
for feat_dir, msg in error_features:
|
|
129
|
+
warn(f" {feat_dir.name} : {msg}")
|
|
130
|
+
|
|
131
|
+
if dry_run:
|
|
132
|
+
console.print()
|
|
133
|
+
console.print("[dim]Mode dry-run : aucun fichier modifie.[/dim]")
|
|
134
|
+
|
|
135
|
+
# Nettoyage speckit
|
|
136
|
+
if clean_speckit:
|
|
137
|
+
_clean_speckit(dry_run)
|
|
138
|
+
elif not dry_run:
|
|
139
|
+
_suggest_clean_speckit()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _ensure_constitution() -> None:
|
|
143
|
+
"""Cree une constitution starter si absente."""
|
|
144
|
+
const_dest = Path.cwd() / ".specify" / "memory" / "constitution.md"
|
|
145
|
+
if const_dest.is_file():
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
const_src = get_devflow_root() / "templates" / "constitution-template.md"
|
|
150
|
+
if const_src.is_file():
|
|
151
|
+
const_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
shutil.copy2(const_src, const_dest)
|
|
153
|
+
ok("Constitution starter creee dans .specify/memory/constitution.md")
|
|
154
|
+
info(" Pensez a la personnaliser pour votre projet.")
|
|
155
|
+
else:
|
|
156
|
+
warn("Template constitution non trouve — creation ignoree.")
|
|
157
|
+
except Exception:
|
|
158
|
+
logger.error("Failed to create constitution starter", exc_info=True)
|
|
159
|
+
warn("Impossible de creer la constitution starter.")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _clean_speckit(dry_run: bool) -> None:
|
|
163
|
+
"""Supprime les commandes speckit et templates non personnalises."""
|
|
164
|
+
console.print()
|
|
165
|
+
header("Nettoyage speckit")
|
|
166
|
+
console.print()
|
|
167
|
+
|
|
168
|
+
# 1. Commandes speckit dans ~/.claude/commands/
|
|
169
|
+
claude_dir = get_claude_dir()
|
|
170
|
+
cmd_dir = claude_dir / "commands"
|
|
171
|
+
removed_cmds = 0
|
|
172
|
+
if cmd_dir.is_dir():
|
|
173
|
+
for f in sorted(cmd_dir.glob("speckit.*.md")):
|
|
174
|
+
if dry_run:
|
|
175
|
+
console.print(f" [dim](dry-run) Supprimerait {f.name}[/dim]")
|
|
176
|
+
else:
|
|
177
|
+
f.unlink()
|
|
178
|
+
console.print(f" Supprime : {f.name}")
|
|
179
|
+
removed_cmds += 1
|
|
180
|
+
|
|
181
|
+
# 2. Templates dans .specify/templates/
|
|
182
|
+
tpl_dir = Path.cwd() / ".specify" / "templates"
|
|
183
|
+
removed_tpls = 0
|
|
184
|
+
preserved_tpls = 0
|
|
185
|
+
if tpl_dir.is_dir():
|
|
186
|
+
try:
|
|
187
|
+
devflow_tpl_dir = get_devflow_root() / "templates"
|
|
188
|
+
except Exception:
|
|
189
|
+
logger.error("Failed to resolve devflow template dir", exc_info=True)
|
|
190
|
+
devflow_tpl_dir = None
|
|
191
|
+
|
|
192
|
+
for f in sorted(tpl_dir.glob("*")):
|
|
193
|
+
if not f.is_file():
|
|
194
|
+
continue
|
|
195
|
+
# Comparer avec le template devflow source
|
|
196
|
+
if devflow_tpl_dir and (devflow_tpl_dir / f.name).is_file():
|
|
197
|
+
src_hash = compute_file_hash(devflow_tpl_dir / f.name)
|
|
198
|
+
installed_hash = compute_file_hash(f)
|
|
199
|
+
if src_hash == installed_hash:
|
|
200
|
+
if dry_run:
|
|
201
|
+
console.print(f" [dim](dry-run) Supprimerait {f.name}[/dim]")
|
|
202
|
+
else:
|
|
203
|
+
f.unlink()
|
|
204
|
+
console.print(f" Supprime : {f.name}")
|
|
205
|
+
removed_tpls += 1
|
|
206
|
+
else:
|
|
207
|
+
warn(f" Conserve (personnalise) : {f.name}")
|
|
208
|
+
preserved_tpls += 1
|
|
209
|
+
else:
|
|
210
|
+
# Pas de reference → conserver par securite
|
|
211
|
+
warn(f" Conserve (pas de reference) : {f.name}")
|
|
212
|
+
preserved_tpls += 1
|
|
213
|
+
|
|
214
|
+
console.print()
|
|
215
|
+
console.print(f" Commandes supprimees : {removed_cmds}")
|
|
216
|
+
console.print(f" Templates supprimes : {removed_tpls}")
|
|
217
|
+
if preserved_tpls:
|
|
218
|
+
console.print(f" Templates conserves : {preserved_tpls}")
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _suggest_clean_speckit() -> None:
|
|
222
|
+
"""Suggere --clean-speckit si des commandes speckit sont detectees."""
|
|
223
|
+
claude_dir = get_claude_dir()
|
|
224
|
+
cmd_dir = claude_dir / "commands"
|
|
225
|
+
if cmd_dir.is_dir() and list(cmd_dir.glob("speckit.*.md")):
|
|
226
|
+
info("Des commandes speckit sont encore presentes dans ~/.claude/commands/.")
|
|
227
|
+
info("Utilisez --clean-speckit pour les supprimer.")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""devflow
|
|
1
|
+
"""devflow stale — Detection des artefacts desuets."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -14,11 +14,11 @@ from devflow_cli.utils.console import console, ok, info, header
|
|
|
14
14
|
from devflow_cli.utils.paths import resolve_feature_dir
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
only: Optional[str] = typer.Option(None, "--only", help="
|
|
17
|
+
def stale(
|
|
18
|
+
only: Optional[str] = typer.Option(None, "--only", help="Filtrer uniquement cet artefact (plan, tasks)"),
|
|
19
19
|
json_output: bool = typer.Option(False, "--json", help="Sortie JSON pour integration Claude Code"),
|
|
20
20
|
) -> None:
|
|
21
|
-
"""Detecte et
|
|
21
|
+
"""Detecte les artefacts desuets et affiche les commandes de regeneration."""
|
|
22
22
|
branch = current_branch()
|
|
23
23
|
feature_dir = resolve_feature_dir(branch) if branch else None
|
|
24
24
|
if feature_dir is None:
|
|
@@ -62,7 +62,7 @@ def regen(
|
|
|
62
62
|
print(json.dumps(output, indent=2))
|
|
63
63
|
return
|
|
64
64
|
|
|
65
|
-
header(f"devflow
|
|
65
|
+
header(f"devflow stale — {feature_dir.name}")
|
|
66
66
|
console.print()
|
|
67
67
|
|
|
68
68
|
if not stale_list:
|
|
@@ -80,7 +80,7 @@ def regen(
|
|
|
80
80
|
console.print(f" {i}. {cmd}")
|
|
81
81
|
|
|
82
82
|
console.print()
|
|
83
|
-
if typer.confirm("
|
|
83
|
+
if typer.confirm("Voir les commandes de regeneration ?", default=False):
|
|
84
84
|
console.print()
|
|
85
85
|
info("Lancez les commandes suivantes dans l'ordre :")
|
|
86
86
|
for s in stale_list:
|
devflow_cli/core/catalog.py
CHANGED
|
@@ -6,8 +6,11 @@ import json
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from devflow_cli.utils.logging import get_logger
|
|
9
10
|
from devflow_cli.utils.paths import get_devflow_root, get_claude_dir
|
|
10
11
|
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
@dataclass
|
|
13
16
|
class ExtensionInclude:
|
|
@@ -40,6 +43,7 @@ def load_catalog(path: Path, source: str = "principal") -> Catalog | None:
|
|
|
40
43
|
try:
|
|
41
44
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
42
45
|
except (json.JSONDecodeError, OSError) as e:
|
|
46
|
+
logger.warning("Failed to load catalog %s", path, exc_info=True)
|
|
43
47
|
from devflow_cli.utils.console import warn
|
|
44
48
|
warn(f"Catalogue corrompu ou illisible : {path} ({type(e).__name__}: {e})")
|
|
45
49
|
return None
|
devflow_cli/core/git.py
CHANGED
|
@@ -5,6 +5,10 @@ from __future__ import annotations
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
+
from devflow_cli.utils.logging import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
class GitNotFoundError(Exception):
|
|
10
14
|
"""Levee quand git n'est pas installe ou pas dans le PATH."""
|
|
@@ -30,6 +34,7 @@ def ensure_git_available() -> None:
|
|
|
30
34
|
)
|
|
31
35
|
_git_checked = True
|
|
32
36
|
except FileNotFoundError:
|
|
37
|
+
logger.error("git not found in PATH", exc_info=True)
|
|
33
38
|
raise GitNotFoundError() from None
|
|
34
39
|
|
|
35
40
|
|
|
@@ -43,6 +48,7 @@ def _run(args: list[str], cwd: Path | None = None) -> subprocess.CompletedProces
|
|
|
43
48
|
args, capture_output=True, text=True, cwd=cwd, timeout=_GIT_TIMEOUT,
|
|
44
49
|
)
|
|
45
50
|
except subprocess.TimeoutExpired:
|
|
51
|
+
logger.warning("git command timed out: %s", args, exc_info=True)
|
|
46
52
|
return subprocess.CompletedProcess(
|
|
47
53
|
args, returncode=1, stdout="", stderr="git command timed out",
|
|
48
54
|
)
|
devflow_cli/core/hooks.py
CHANGED
|
@@ -7,6 +7,10 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
|
+
from devflow_cli.utils.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
VALID_HOOK_POINTS = {"after_tasks", "before_implement", "after_implement"}
|
|
12
16
|
VALID_HOOK_TYPES = {"shell", "claude", "agent"}
|
|
@@ -34,6 +38,7 @@ def validate_hooks_file(hooks_file: Path) -> ValidationResult:
|
|
|
34
38
|
try:
|
|
35
39
|
data = yaml.safe_load(hooks_file.read_text(encoding="utf-8"))
|
|
36
40
|
except yaml.YAMLError as e:
|
|
41
|
+
logger.warning("Failed to parse hooks YAML %s", hooks_file, exc_info=True)
|
|
37
42
|
result.errors.append(f"Erreur de syntaxe YAML : {e}")
|
|
38
43
|
return result
|
|
39
44
|
|
devflow_cli/core/manifest.py
CHANGED
|
@@ -9,6 +9,9 @@ from datetime import datetime, timezone
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
11
|
from devflow_cli import VERSION
|
|
12
|
+
from devflow_cli.utils.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
@dataclass
|
|
@@ -82,6 +85,7 @@ def load_manifest(claude_dir: Path) -> dict | None:
|
|
|
82
85
|
try:
|
|
83
86
|
return json.loads(manifest_file.read_text(encoding="utf-8"))
|
|
84
87
|
except (json.JSONDecodeError, OSError) as e:
|
|
88
|
+
logger.warning("Failed to read manifest %s", manifest_file, exc_info=True)
|
|
85
89
|
from devflow_cli.utils.console import warn
|
|
86
90
|
warn(f"Manifeste corrompu ou illisible : {manifest_file} ({type(e).__name__}: {e})")
|
|
87
91
|
return None
|
devflow_cli/core/state.py
CHANGED
|
@@ -7,7 +7,10 @@ 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, REVIEW_GATES
|
|
11
|
+
from devflow_cli.utils.logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
@dataclass
|
|
@@ -82,7 +85,8 @@ def read_state(state_file: Path) -> FeatureState | None:
|
|
|
82
85
|
if k in FeatureState.__dataclass_fields__
|
|
83
86
|
})
|
|
84
87
|
return _validate_state(state)
|
|
85
|
-
except (json.JSONDecodeError, TypeError, KeyError, OSError):
|
|
88
|
+
except (json.JSONDecodeError, TypeError, KeyError, OSError, UnicodeDecodeError):
|
|
89
|
+
logger.warning("Failed to read state %s", state_file, exc_info=True)
|
|
86
90
|
return None
|
|
87
91
|
|
|
88
92
|
|
|
@@ -205,6 +209,10 @@ def advance_step(
|
|
|
205
209
|
else:
|
|
206
210
|
state.stepTimestamps[completed_step]["completedAt"] = now
|
|
207
211
|
|
|
212
|
+
# Incrementer reviewIterations pour les gates de review
|
|
213
|
+
if completed_step in REVIEW_GATES:
|
|
214
|
+
state.reviewIterations[completed_step] = state.reviewIterations.get(completed_step, 0) + 1
|
|
215
|
+
|
|
208
216
|
# Trouver la prochaine etape non-skippee
|
|
209
217
|
next_idx = idx + 1
|
|
210
218
|
while next_idx < len(STEPS) and STEPS[next_idx] in state.skippedSteps:
|
|
@@ -262,6 +270,75 @@ def rollback_step(state: FeatureState) -> FeatureState | None:
|
|
|
262
270
|
return state
|
|
263
271
|
|
|
264
272
|
|
|
273
|
+
def is_speckit_state(state_file: Path) -> bool:
|
|
274
|
+
"""Detecte si un state.json est au format speckit (pas devflow).
|
|
275
|
+
|
|
276
|
+
Lit le JSON brut et verifie l'absence du champ 'reviewIterations'.
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
data = json.loads(state_file.read_text(encoding="utf-8"))
|
|
280
|
+
return isinstance(data, dict) and "reviewIterations" not in data
|
|
281
|
+
except (json.JSONDecodeError, OSError):
|
|
282
|
+
logger.warning("Failed to detect speckit state %s", state_file, exc_info=True)
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def convert_speckit_state(raw_data: dict) -> FeatureState:
|
|
287
|
+
"""Convertit un state.json speckit brut en FeatureState devflow.
|
|
288
|
+
|
|
289
|
+
- Mappe les etapes via SPECKIT_STEP_MAP (specify→spec, etc.)
|
|
290
|
+
- Filtre les etapes ignorees (checklist, analyze)
|
|
291
|
+
- Injecte les etapes devflow intermediaires comme auto-validees
|
|
292
|
+
si l'utilisateur les a deja depassees
|
|
293
|
+
"""
|
|
294
|
+
issue_id = raw_data.get("issueId", "")
|
|
295
|
+
|
|
296
|
+
# Mapper currentStep
|
|
297
|
+
raw_current = raw_data.get("currentStep", "spec")
|
|
298
|
+
current_step = SPECKIT_STEP_MAP.get(raw_current, raw_current)
|
|
299
|
+
|
|
300
|
+
# Mapper completedSteps : convertir et filtrer
|
|
301
|
+
raw_completed = raw_data.get("completedSteps", [])
|
|
302
|
+
mapped_completed: list[str] = []
|
|
303
|
+
for step in raw_completed:
|
|
304
|
+
if step in SPECKIT_IGNORED_STEPS:
|
|
305
|
+
continue
|
|
306
|
+
mapped = SPECKIT_STEP_MAP.get(step, step)
|
|
307
|
+
if mapped in STEPS and mapped not in mapped_completed:
|
|
308
|
+
mapped_completed.append(mapped)
|
|
309
|
+
|
|
310
|
+
# Injecter les etapes devflow intermediaires auto-validees
|
|
311
|
+
# Pour chaque etape devflow, si elle precede une etape deja completee
|
|
312
|
+
# et n'est pas encore dans completed, l'injecter
|
|
313
|
+
review_iterations: dict[str, int] = {}
|
|
314
|
+
if current_step in STEPS:
|
|
315
|
+
current_idx = STEPS.index(current_step)
|
|
316
|
+
final_completed: list[str] = []
|
|
317
|
+
for i, step in enumerate(STEPS):
|
|
318
|
+
if step == current_step:
|
|
319
|
+
break
|
|
320
|
+
if step in mapped_completed:
|
|
321
|
+
final_completed.append(step)
|
|
322
|
+
elif i < current_idx:
|
|
323
|
+
# Etape intermediaire manquante → auto-valider
|
|
324
|
+
final_completed.append(step)
|
|
325
|
+
if step in ("review-spec", "review-tasks", "review-impl"):
|
|
326
|
+
review_iterations[step] = 0
|
|
327
|
+
mapped_completed = final_completed
|
|
328
|
+
|
|
329
|
+
return FeatureState(
|
|
330
|
+
issueId=issue_id,
|
|
331
|
+
currentStep=current_step,
|
|
332
|
+
completedSteps=mapped_completed,
|
|
333
|
+
stepTimestamps={},
|
|
334
|
+
reviewIterations=review_iterations,
|
|
335
|
+
mode="full",
|
|
336
|
+
skippedSteps=[],
|
|
337
|
+
createdAt=raw_data.get("createdAt", ""),
|
|
338
|
+
updatedAt=raw_data.get("updatedAt", ""),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
|
|
265
342
|
def validate_and_advance(
|
|
266
343
|
state: FeatureState,
|
|
267
344
|
completed_step: str,
|
devflow_cli/core/validators.py
CHANGED
|
@@ -6,6 +6,10 @@ import re
|
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
+
from devflow_cli.utils.logging import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
@dataclass
|
|
11
15
|
class ValidationIssue:
|
|
@@ -85,6 +89,7 @@ def validate_artifact(step: str, artifact_path: Path) -> ValidationResult:
|
|
|
85
89
|
try:
|
|
86
90
|
content = artifact_path.read_text(encoding="utf-8")
|
|
87
91
|
except (UnicodeDecodeError, OSError) as exc:
|
|
92
|
+
logger.warning("Failed to read artifact %s", artifact_path, exc_info=True)
|
|
88
93
|
issues.append(ValidationIssue(
|
|
89
94
|
type="encoding_error",
|
|
90
95
|
message=f"Impossible de lire {artifact_path.name}: {exc}",
|
|
@@ -159,6 +164,7 @@ def _validate_contracts(
|
|
|
159
164
|
try:
|
|
160
165
|
content = artifact_path.read_text(encoding="utf-8")
|
|
161
166
|
except (UnicodeDecodeError, OSError) as exc:
|
|
167
|
+
logger.warning("Failed to read artifact %s", artifact_path, exc_info=True)
|
|
162
168
|
issues.append(ValidationIssue(
|
|
163
169
|
type="encoding_error",
|
|
164
170
|
message=f"Impossible de lire {artifact_path.name}: {exc}",
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Configuration du logging pour la CLI devflow."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
from logging.handlers import RotatingFileHandler
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
_LOG_DIR = ".devflow"
|
|
11
|
+
_LOG_FILE = "devflow.log"
|
|
12
|
+
_MAX_BYTES = 1_048_576 # 1 Mo
|
|
13
|
+
_BACKUP_COUNT = 1
|
|
14
|
+
_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
|
15
|
+
|
|
16
|
+
_configured = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup_logging(verbose: bool = False) -> None:
|
|
20
|
+
"""Configure le logging une seule fois.
|
|
21
|
+
|
|
22
|
+
- Fichier : .devflow/devflow.log (RotatingFileHandler, 1 Mo, 1 backup)
|
|
23
|
+
- Console : stderr uniquement si verbose=True
|
|
24
|
+
- Fallback : NullHandler si le fichier ne peut pas etre cree
|
|
25
|
+
"""
|
|
26
|
+
global _configured
|
|
27
|
+
if _configured:
|
|
28
|
+
return
|
|
29
|
+
_configured = True
|
|
30
|
+
|
|
31
|
+
root = logging.getLogger("devflow_cli")
|
|
32
|
+
root.setLevel(logging.DEBUG)
|
|
33
|
+
|
|
34
|
+
formatter = logging.Formatter(_FORMAT)
|
|
35
|
+
|
|
36
|
+
# File handler avec rotation
|
|
37
|
+
try:
|
|
38
|
+
log_dir = Path(_LOG_DIR)
|
|
39
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
file_handler = RotatingFileHandler(
|
|
41
|
+
log_dir / _LOG_FILE,
|
|
42
|
+
maxBytes=_MAX_BYTES,
|
|
43
|
+
backupCount=_BACKUP_COUNT,
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
)
|
|
46
|
+
file_handler.setLevel(logging.DEBUG)
|
|
47
|
+
file_handler.setFormatter(formatter)
|
|
48
|
+
root.addHandler(file_handler)
|
|
49
|
+
except OSError:
|
|
50
|
+
root.addHandler(logging.NullHandler())
|
|
51
|
+
|
|
52
|
+
# Console handler (stderr) si verbose
|
|
53
|
+
if verbose:
|
|
54
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
|
55
|
+
console_handler.setLevel(logging.DEBUG)
|
|
56
|
+
console_handler.setFormatter(formatter)
|
|
57
|
+
root.addHandler(console_handler)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_logger(name: str) -> logging.Logger:
|
|
61
|
+
"""Retourne un logger enfant de devflow_cli."""
|
|
62
|
+
return logging.getLogger(name)
|
devflow_cli/utils/paths.py
CHANGED
|
@@ -7,6 +7,10 @@ import re
|
|
|
7
7
|
import subprocess
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
from devflow_cli.utils.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
def get_devflow_root() -> Path:
|
|
12
16
|
"""Racine du projet devflow (templates, commands, agents, extensions)."""
|
|
@@ -116,7 +120,7 @@ def next_feature_number(project_dir: Path | None = None) -> int:
|
|
|
116
120
|
if num > highest:
|
|
117
121
|
highest = num
|
|
118
122
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
119
|
-
|
|
123
|
+
logger.warning("Failed to scan git branches for feature numbers", exc_info=True)
|
|
120
124
|
|
|
121
125
|
return highest + 1
|
|
122
126
|
|
|
@@ -132,5 +136,5 @@ def next_feature_number(project_dir: Path | None = None) -> int:
|
|
|
132
136
|
fcntl.flock(f, fcntl.LOCK_EX)
|
|
133
137
|
return _scan()
|
|
134
138
|
except ImportError:
|
|
135
|
-
|
|
139
|
+
logger.warning("fcntl not available (Windows) — running without file lock")
|
|
136
140
|
return _scan()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devflow-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
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
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
devflow_cli/__init__.py,sha256=mraf4w1I_7HJ4qRRyQ62xTVfVDHUSCTqmxuYeP4Px4M,2394
|
|
2
|
+
devflow_cli/cli.py,sha256=4FdmMntfA7Uxpm45XUjx5LjOS6l_BpaukwLTHGGRRps,1759
|
|
3
|
+
devflow_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
devflow_cli/commands/check.py,sha256=84eXY1Oc2OXk-oNvDKS-FVXN954ykDJ6hnSHb30C3j0,4313
|
|
5
|
+
devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
|
|
6
|
+
devflow_cli/commands/export_cmd.py,sha256=Mdvoh8oyainm0ulaaLd3pcx9m2s-Go--fFBeLS6i9Ts,4871
|
|
7
|
+
devflow_cli/commands/extension.py,sha256=gL33YS8EuBTADmqKGDqw_0gJXFwccwg1uJmCNtvexhE,4373
|
|
8
|
+
devflow_cli/commands/feature.py,sha256=GP_tZ7B3jMKb-cxXY3RC6yiv8mj5ZLyhHsXfIS1Ffhg,4238
|
|
9
|
+
devflow_cli/commands/init_cmd.py,sha256=5hKBYKFCklTvVVQxF5Qx4gjqIlUQqxePY4nWct3HYJc,4659
|
|
10
|
+
devflow_cli/commands/migrate_cmd.py,sha256=Kxz_Ml78UvDzjL1-4Gp3SJG_DrVDWGn9Ie2oc0in1N4,3423
|
|
11
|
+
devflow_cli/commands/migrate_speckit.py,sha256=zs-6aCeLLePRj9mSxfr_VrMYs84JrEHUYIgheIERHVk,8284
|
|
12
|
+
devflow_cli/commands/rollback.py,sha256=B-Qnmem_sAwmSwyFdzpzglbeX4uLd0t4ohsLYyHngl4,1789
|
|
13
|
+
devflow_cli/commands/stale.py,sha256=xInmH5QywgxHCeAbpumwvUKqnk-XnY8FcURkXXPGic0,3208
|
|
14
|
+
devflow_cli/commands/status.py,sha256=nPuudZPqCFWE2JqiCFOMKlN_CM_uDwD-gq9MbHBhg20,4775
|
|
15
|
+
devflow_cli/commands/upgrade_cmd.py,sha256=xLOoaLXpyyJO4UuWLHjkXSGSX2IcL65-PQnoZAmwW9M,5029
|
|
16
|
+
devflow_cli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
devflow_cli/core/catalog.py,sha256=nLPc78JkbISaSxJnHlWiMi_lvepaumbfwcjgLuVy7Zc,3102
|
|
18
|
+
devflow_cli/core/git.py,sha256=o7Vk_n8b3-cAQa3CR1FblTZUvMDez7Ik4D9lrYI0njY,2561
|
|
19
|
+
devflow_cli/core/hooks.py,sha256=KhG6BECui7IeoAERJAI6EWnRThqmSdeJCOxuk_xowe8,3189
|
|
20
|
+
devflow_cli/core/installer.py,sha256=XGuugkWAV7McTnpdNZ2JX4NtEAE8CI73mfrYnKlb-JQ,4329
|
|
21
|
+
devflow_cli/core/manifest.py,sha256=J7g2SzhXy-Vi4w9-rEPmARSZXo8KTOxfd2pEc9jNUME,5933
|
|
22
|
+
devflow_cli/core/scanner.py,sha256=bcoC91Nc0Hw2P7aHpL-9ITxynLZhLTEKpOXo553a-18,4395
|
|
23
|
+
devflow_cli/core/staleness.py,sha256=ASe-rlYN17AtpI_S4QlidaHwbtv7E7L4fhRGYtyTlPE,5586
|
|
24
|
+
devflow_cli/core/state.py,sha256=tkMScnTSMp8nQm_AElDJPqV_RtcXWpq29CJYfdNpBXI,12515
|
|
25
|
+
devflow_cli/core/validators.py,sha256=rW8JCveYjf8KtOR2Q7v2mdvhYfB0qQnC3FBuWYLmytE,6445
|
|
26
|
+
devflow_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
devflow_cli/utils/console.py,sha256=t7cpyeEluWj4v_O_KFSI59iqXK4GiSXPuxlsr8q5zoQ,771
|
|
28
|
+
devflow_cli/utils/logging.py,sha256=I7U9_IVvYAFnUAWi7xs5F_WGR0uSz6DmRvZ_B3xLFbs,1769
|
|
29
|
+
devflow_cli/utils/paths.py,sha256=C_0Jmiz4LRoelyFYWA42ehGTg6R2UnBxllBo5_LZ1HI,4400
|
|
30
|
+
devflow_cli/utils/strings.py,sha256=bfFXCwBDnFxshgZOYfy6mtysnjiaWO1wFnqBf7HLg4o,351
|
|
31
|
+
devflow_cli-2.3.0.dist-info/METADATA,sha256=1GDvGXx3RXvmsxOq-Ldc-9vgwS13lAHrxQaBU1x7jt4,4896
|
|
32
|
+
devflow_cli-2.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
33
|
+
devflow_cli-2.3.0.dist-info/entry_points.txt,sha256=GVJ6bMvFpqyO5589iC4nFKeGTpGIW8aKn_QPPpSZ6kU,48
|
|
34
|
+
devflow_cli-2.3.0.dist-info/licenses/LICENSE,sha256=v79PvnmqOdKhQiOa_QcWxng4q8GuGscLOwi7ygUpwpo,1070
|
|
35
|
+
devflow_cli-2.3.0.dist-info/RECORD,,
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
devflow_cli/__init__.py,sha256=4C2mALrQiTmJMZx99CT2fViXvKO5_IFzMStcTLumeeg,1400
|
|
2
|
-
devflow_cli/cli.py,sha256=UxuYGV0TgsnoVV_-vp2Ce1sUTkc61Viyr13X_2HEP-o,1458
|
|
3
|
-
devflow_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
devflow_cli/commands/check.py,sha256=sb5zkSXXwl91o8yn1-TRL5wH89MWX-kY2CAHYYjMxq4,4170
|
|
5
|
-
devflow_cli/commands/context.py,sha256=hwTitOHxIpJMx1X8R1jHHy9CpE4HVZpJKkyeOujxAio,6246
|
|
6
|
-
devflow_cli/commands/export_cmd.py,sha256=Mdvoh8oyainm0ulaaLd3pcx9m2s-Go--fFBeLS6i9Ts,4871
|
|
7
|
-
devflow_cli/commands/extension.py,sha256=gL33YS8EuBTADmqKGDqw_0gJXFwccwg1uJmCNtvexhE,4373
|
|
8
|
-
devflow_cli/commands/feature.py,sha256=EceMCxYEif4P6Ir-1XZml4txLH9iqm3jEAeS6dJ8LpA,4098
|
|
9
|
-
devflow_cli/commands/init_cmd.py,sha256=LLUJx_Kdl-YCEuy1corvaYe65pZ6R5-VnVBal6wZSa4,4513
|
|
10
|
-
devflow_cli/commands/migrate_cmd.py,sha256=y2_yWyBabMgDZ0e2CsX2nYtb33yQRyr4zcrj858Ieoo,3274
|
|
11
|
-
devflow_cli/commands/regen.py,sha256=UyB7_0oBxDs_CqaAQ6yE2Yoz4IcpUr2bqkIEmm0e1iM,3198
|
|
12
|
-
devflow_cli/commands/rollback.py,sha256=B-Qnmem_sAwmSwyFdzpzglbeX4uLd0t4ohsLYyHngl4,1789
|
|
13
|
-
devflow_cli/commands/status.py,sha256=nPuudZPqCFWE2JqiCFOMKlN_CM_uDwD-gq9MbHBhg20,4775
|
|
14
|
-
devflow_cli/commands/upgrade_cmd.py,sha256=xLOoaLXpyyJO4UuWLHjkXSGSX2IcL65-PQnoZAmwW9M,5029
|
|
15
|
-
devflow_cli/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
devflow_cli/core/catalog.py,sha256=oMBZRigOtpAQceu3slV8UpuKSZpkxQcr4xtjZXyFHYY,2949
|
|
17
|
-
devflow_cli/core/git.py,sha256=WL8tU3f5KcNyF5aL669Gzy73OIvuC_CCGZdt8TzWvXc,2346
|
|
18
|
-
devflow_cli/core/hooks.py,sha256=EHIlpwmDYHshaDijiLCEoPDbnV9T7jDttOCnpRRx0HU,3025
|
|
19
|
-
devflow_cli/core/installer.py,sha256=XGuugkWAV7McTnpdNZ2JX4NtEAE8CI73mfrYnKlb-JQ,4329
|
|
20
|
-
devflow_cli/core/manifest.py,sha256=5hu2w6PPGo4raGbgKzx6qpAtt653SrzpRkbWngHM9qw,5770
|
|
21
|
-
devflow_cli/core/scanner.py,sha256=bcoC91Nc0Hw2P7aHpL-9ITxynLZhLTEKpOXo553a-18,4395
|
|
22
|
-
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/validators.py,sha256=3PtqC-R2kqNbh4HK_l_Yl8RylQsFdtCXhopt0yT065k,6194
|
|
25
|
-
devflow_cli/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
devflow_cli/utils/console.py,sha256=t7cpyeEluWj4v_O_KFSI59iqXK4GiSXPuxlsr8q5zoQ,771
|
|
27
|
-
devflow_cli/utils/paths.py,sha256=9J2Qhq2Ot6p7HQwRAfdK3V5bc5sme6FnnnAg9dhXcP4,4212
|
|
28
|
-
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|