devflow-toolkit 0.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/__init__.py +0 -0
- devflow/adapters/__init__.py +47 -0
- devflow/adapters/agents.py +49 -0
- devflow/adapters/base.py +81 -0
- devflow/adapters/claude_code.py +120 -0
- devflow/adapters/codex.py +111 -0
- devflow/adapters/detector.py +118 -0
- devflow/adapters/instructions.py +263 -0
- devflow/adapters/kiro.py +211 -0
- devflow/adapters/opencode.py +237 -0
- devflow/adapters/registry.py +41 -0
- devflow/cli.py +39 -0
- devflow/commands/__init__.py +6 -0
- devflow/commands/approve.py +98 -0
- devflow/commands/harness.py +98 -0
- devflow/commands/init.py +544 -0
- devflow/commands/spec.py +514 -0
- devflow/commands/status.py +156 -0
- devflow/core/__init__.py +15 -0
- devflow/core/architecture.py +469 -0
- devflow/core/artifacts/__init__.py +29 -0
- devflow/core/artifacts/errors.py +19 -0
- devflow/core/artifacts/models.py +82 -0
- devflow/core/artifacts/store.py +130 -0
- devflow/core/clock.py +67 -0
- devflow/core/config.py +706 -0
- devflow/core/constitution.py +166 -0
- devflow/core/discovery.py +1304 -0
- devflow/core/ports.py +159 -0
- devflow/core/profile_manager.py +311 -0
- devflow/core/project.py +533 -0
- devflow/core/spec_workflow.py +932 -0
- devflow/core/specification.py +2443 -0
- devflow/core/state/__init__.py +75 -0
- devflow/core/state/errors.py +39 -0
- devflow/core/state/events.py +725 -0
- devflow/core/state/locator.py +116 -0
- devflow/core/state/lock.py +69 -0
- devflow/core/state/manager.py +960 -0
- devflow/core/state/models.py +164 -0
- devflow/core/state/transitions.py +114 -0
- devflow/models/__init__.py +6 -0
- devflow/models/profile.py +63 -0
- devflow/models/project.py +47 -0
- devflow/profiles/ai-rag/constitution/ai-rag-standards.md +49 -0
- devflow/profiles/ai-rag/profile.yaml +28 -0
- devflow/profiles/base/constitution/architecture.md +58 -0
- devflow/profiles/base/constitution/code-standards.md +85 -0
- devflow/profiles/base/constitution/constitution.md +85 -0
- devflow/profiles/base/profile.yaml +15 -0
- devflow/profiles/django-backend/constitution/django-standards.md +64 -0
- devflow/profiles/django-backend/profile.yaml +24 -0
- devflow/profiles/fastapi-backend/constitution/fastapi-standards.md +59 -0
- devflow/profiles/fastapi-backend/profile.yaml +23 -0
- devflow/prompts/__init__.py +200 -0
- devflow/prompts/discovery.md +119 -0
- devflow/prompts/spec.greenfield.md +149 -0
- devflow/prompts/spec.md +116 -0
- devflow_toolkit-0.3.0.dist-info/METADATA +628 -0
- devflow_toolkit-0.3.0.dist-info/RECORD +63 -0
- devflow_toolkit-0.3.0.dist-info/WHEEL +4 -0
- devflow_toolkit-0.3.0.dist-info/entry_points.txt +2 -0
- devflow_toolkit-0.3.0.dist-info/licenses/LICENSE +21 -0
devflow/commands/init.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
"""Implementation of the devflow init command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from devflow.adapters.agents import AgentsFileWriter
|
|
13
|
+
from devflow.commands.harness import (
|
|
14
|
+
adapter_for,
|
|
15
|
+
print_setup_result,
|
|
16
|
+
resolve_harness,
|
|
17
|
+
validate_harness_name,
|
|
18
|
+
)
|
|
19
|
+
from devflow.core.architecture import (
|
|
20
|
+
ARCHITECTURE_RELATIVE_PATH,
|
|
21
|
+
ArchitectureError,
|
|
22
|
+
create_architecture_template,
|
|
23
|
+
)
|
|
24
|
+
from devflow.core.config import (
|
|
25
|
+
EXECUTION_MODES,
|
|
26
|
+
ConfigError,
|
|
27
|
+
ConfigManager,
|
|
28
|
+
DevFlowConfig,
|
|
29
|
+
canonical_execution_mode,
|
|
30
|
+
)
|
|
31
|
+
from devflow.core.constitution import Constitution, ConstitutionLoader
|
|
32
|
+
from devflow.core.ports import (
|
|
33
|
+
FileChange,
|
|
34
|
+
SetupResult,
|
|
35
|
+
build_setup_context,
|
|
36
|
+
)
|
|
37
|
+
from devflow.core.profile_manager import ProfileError, ProfileManager
|
|
38
|
+
from devflow.core.project import ProjectDetector
|
|
39
|
+
from devflow.models.profile import Profile
|
|
40
|
+
from devflow.models.project import (
|
|
41
|
+
PROJECT_MODES,
|
|
42
|
+
Project,
|
|
43
|
+
canonical_project_mode,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
console = Console()
|
|
47
|
+
error_console = Console(stderr=True)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def init(
|
|
51
|
+
path: Path = typer.Argument(
|
|
52
|
+
Path("."),
|
|
53
|
+
help="Project root to initialize.",
|
|
54
|
+
),
|
|
55
|
+
name: Optional[str] = typer.Option(
|
|
56
|
+
None,
|
|
57
|
+
"--name",
|
|
58
|
+
"-n",
|
|
59
|
+
help="Override the detected project name.",
|
|
60
|
+
),
|
|
61
|
+
profile: Optional[list[str]] = typer.Option(
|
|
62
|
+
None,
|
|
63
|
+
"--profile",
|
|
64
|
+
"-p",
|
|
65
|
+
help="Enable a profile explicitly. Can be repeated.",
|
|
66
|
+
),
|
|
67
|
+
yes: bool = typer.Option(
|
|
68
|
+
False,
|
|
69
|
+
"--yes",
|
|
70
|
+
"-y",
|
|
71
|
+
help="Do not prompt when no profile or harness is detected.",
|
|
72
|
+
),
|
|
73
|
+
harness: Optional[str] = typer.Option(
|
|
74
|
+
None,
|
|
75
|
+
"--harness",
|
|
76
|
+
help=(
|
|
77
|
+
"Agent harness: auto, codex, opencode, claude-code, kiro or none."
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
mode: Optional[str] = typer.Option(
|
|
81
|
+
None,
|
|
82
|
+
"--mode",
|
|
83
|
+
help="Agent execution mode: interactive or non-interactive.",
|
|
84
|
+
),
|
|
85
|
+
no_agent: bool = typer.Option(
|
|
86
|
+
False,
|
|
87
|
+
"--no-agent",
|
|
88
|
+
help="Disable agent harness integration.",
|
|
89
|
+
),
|
|
90
|
+
project_mode: Optional[str] = typer.Option(
|
|
91
|
+
None,
|
|
92
|
+
"--project-mode",
|
|
93
|
+
help=(
|
|
94
|
+
"Default specification context: existing or greenfield."
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Initialize DevFlow in a project."""
|
|
99
|
+
try:
|
|
100
|
+
_initialize(
|
|
101
|
+
path,
|
|
102
|
+
name,
|
|
103
|
+
profile or [],
|
|
104
|
+
yes,
|
|
105
|
+
harness,
|
|
106
|
+
mode,
|
|
107
|
+
no_agent,
|
|
108
|
+
project_mode,
|
|
109
|
+
)
|
|
110
|
+
except (
|
|
111
|
+
ArchitectureError,
|
|
112
|
+
ConfigError,
|
|
113
|
+
ProfileError,
|
|
114
|
+
FileNotFoundError,
|
|
115
|
+
NotADirectoryError,
|
|
116
|
+
OSError,
|
|
117
|
+
) as error:
|
|
118
|
+
error_console.print(f"[red]Error:[/red] {error}")
|
|
119
|
+
raise typer.Exit(code=1) from error
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _initialize(
|
|
123
|
+
path: Path,
|
|
124
|
+
name: Optional[str],
|
|
125
|
+
explicit_profiles: list[str],
|
|
126
|
+
yes: bool,
|
|
127
|
+
harness: Optional[str],
|
|
128
|
+
mode: Optional[str],
|
|
129
|
+
no_agent: bool,
|
|
130
|
+
project_mode: Optional[str],
|
|
131
|
+
) -> None:
|
|
132
|
+
requested_harness = _requested_harness(harness, no_agent)
|
|
133
|
+
execution_mode = _requested_mode(mode)
|
|
134
|
+
requested_project_mode = _requested_project_mode(project_mode)
|
|
135
|
+
|
|
136
|
+
root = path.resolve()
|
|
137
|
+
if not root.exists():
|
|
138
|
+
msg = (
|
|
139
|
+
f"Project path does not exist: {root}. "
|
|
140
|
+
"Create the directory or pass an existing project."
|
|
141
|
+
)
|
|
142
|
+
raise FileNotFoundError(msg)
|
|
143
|
+
if not root.is_dir():
|
|
144
|
+
msg = f"Project path is not a directory: {root}"
|
|
145
|
+
raise NotADirectoryError(msg)
|
|
146
|
+
|
|
147
|
+
detector = ProjectDetector()
|
|
148
|
+
project = detector.detect(root)
|
|
149
|
+
if name is not None:
|
|
150
|
+
trimmed_name = name.strip()
|
|
151
|
+
if not trimmed_name:
|
|
152
|
+
msg = "Project name cannot be empty."
|
|
153
|
+
raise ConfigError(msg)
|
|
154
|
+
project.name = trimmed_name
|
|
155
|
+
|
|
156
|
+
profile_manager = ProfileManager()
|
|
157
|
+
matches = profile_manager.detect(project)
|
|
158
|
+
detected_names = [match.profile.name for match in matches]
|
|
159
|
+
|
|
160
|
+
config_manager = ConfigManager(root)
|
|
161
|
+
if config_manager.exists():
|
|
162
|
+
config = config_manager.load()
|
|
163
|
+
project.name = config.project_name
|
|
164
|
+
project.project_type = config.project_type
|
|
165
|
+
project.profiles = list(config.profiles)
|
|
166
|
+
_refresh_existing(
|
|
167
|
+
config=config,
|
|
168
|
+
config_manager=config_manager,
|
|
169
|
+
profile_manager=profile_manager,
|
|
170
|
+
project=project,
|
|
171
|
+
detected_names=detected_names,
|
|
172
|
+
explicit_profiles=explicit_profiles,
|
|
173
|
+
requested_harness=requested_harness,
|
|
174
|
+
execution_mode=execution_mode,
|
|
175
|
+
harness_override=harness is not None or no_agent,
|
|
176
|
+
mode_override=mode is not None,
|
|
177
|
+
project_mode=requested_project_mode,
|
|
178
|
+
project_mode_override=project_mode is not None,
|
|
179
|
+
allow_prompt=not yes and sys.stdin.isatty(),
|
|
180
|
+
)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
selected = list(explicit_profiles) or detected_names
|
|
184
|
+
selected = [
|
|
185
|
+
profile_name
|
|
186
|
+
for profile_name in selected
|
|
187
|
+
if profile_name != "base"
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
if not selected and not yes and not explicit_profiles:
|
|
191
|
+
selected = _prompt_for_profiles(profile_manager)
|
|
192
|
+
selected = [
|
|
193
|
+
profile_name
|
|
194
|
+
for profile_name in selected
|
|
195
|
+
if profile_name != "base"
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
profiles = profile_manager.resolve(selected)
|
|
199
|
+
selected = [
|
|
200
|
+
profile.name for profile in profiles if profile.name != "base"
|
|
201
|
+
]
|
|
202
|
+
installed = profile_manager.install_constitution(root, profiles)
|
|
203
|
+
constitution_files = profile_manager.constitution_documents(profiles)
|
|
204
|
+
|
|
205
|
+
config = config_manager.create(
|
|
206
|
+
project=project,
|
|
207
|
+
profiles=selected,
|
|
208
|
+
constitution_files=constitution_files,
|
|
209
|
+
project_mode=requested_project_mode,
|
|
210
|
+
agent_harness=requested_harness,
|
|
211
|
+
execution_mode=execution_mode,
|
|
212
|
+
)
|
|
213
|
+
config_manager.ensure_directories(config)
|
|
214
|
+
_ensure_architecture_template(config, requested_project_mode)
|
|
215
|
+
constitution = ConstitutionLoader(profile_manager).load(
|
|
216
|
+
config,
|
|
217
|
+
profiles,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
selected_harness, setup_result, harness_warnings = _apply_harness_setup(
|
|
221
|
+
config=config,
|
|
222
|
+
project=project,
|
|
223
|
+
profiles=profiles,
|
|
224
|
+
constitution=constitution,
|
|
225
|
+
requested_harness=requested_harness,
|
|
226
|
+
allow_prompt=not yes and sys.stdin.isatty(),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
console.print(f"[bold]Project:[/bold] {project.name}")
|
|
230
|
+
console.print(f"[bold]Root:[/bold] {config.root}")
|
|
231
|
+
console.print(
|
|
232
|
+
f"[bold]Project mode:[/bold] {config.project_mode}"
|
|
233
|
+
)
|
|
234
|
+
console.print(
|
|
235
|
+
"[bold]Detected profiles:[/bold] "
|
|
236
|
+
+ (", ".join(detected_names) or "none")
|
|
237
|
+
)
|
|
238
|
+
for match in matches:
|
|
239
|
+
console.print(
|
|
240
|
+
f" [dim]{match.profile.name}: "
|
|
241
|
+
f"{', '.join(match.reasons)}[/dim]"
|
|
242
|
+
)
|
|
243
|
+
if not selected:
|
|
244
|
+
console.print(
|
|
245
|
+
"[yellow]No technology profile was detected. The base profile "
|
|
246
|
+
"was installed. Use --profile to enable one explicitly.[/yellow]"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if installed.copied:
|
|
250
|
+
console.print(
|
|
251
|
+
"[green]Constitution files created:[/green] "
|
|
252
|
+
+ ", ".join(installed.copied)
|
|
253
|
+
)
|
|
254
|
+
if installed.skipped:
|
|
255
|
+
console.print(
|
|
256
|
+
"[yellow]Constitution files kept:[/yellow] "
|
|
257
|
+
+ ", ".join(installed.skipped)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
console.print(
|
|
261
|
+
f"[green]Configuration created:[/green] "
|
|
262
|
+
f"{config.path.relative_to(root)}"
|
|
263
|
+
)
|
|
264
|
+
_report_harness(
|
|
265
|
+
requested_harness,
|
|
266
|
+
selected_harness,
|
|
267
|
+
setup_result,
|
|
268
|
+
harness_warnings,
|
|
269
|
+
config,
|
|
270
|
+
)
|
|
271
|
+
console.print(
|
|
272
|
+
f"[green]Specifications directory:[/green] "
|
|
273
|
+
f"{config.specifications_path.relative_to(root)}"
|
|
274
|
+
)
|
|
275
|
+
console.print(
|
|
276
|
+
"\nNext: [bold]devflow spec \"Describe the change\"[/bold]"
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _refresh_existing(
|
|
281
|
+
config: DevFlowConfig,
|
|
282
|
+
config_manager: ConfigManager,
|
|
283
|
+
profile_manager: ProfileManager,
|
|
284
|
+
project: Project,
|
|
285
|
+
detected_names: list[str],
|
|
286
|
+
explicit_profiles: list[str],
|
|
287
|
+
requested_harness: str,
|
|
288
|
+
execution_mode: str,
|
|
289
|
+
harness_override: bool,
|
|
290
|
+
mode_override: bool,
|
|
291
|
+
project_mode: str,
|
|
292
|
+
project_mode_override: bool,
|
|
293
|
+
allow_prompt: bool,
|
|
294
|
+
) -> None:
|
|
295
|
+
if explicit_profiles:
|
|
296
|
+
msg = (
|
|
297
|
+
"DevFlow is already initialized. Edit .devflow/config.yaml "
|
|
298
|
+
"to change profiles, or remove it to reinitialize."
|
|
299
|
+
)
|
|
300
|
+
raise ConfigError(msg)
|
|
301
|
+
|
|
302
|
+
available = set(profile_manager.names())
|
|
303
|
+
configured = list(config.profiles)
|
|
304
|
+
unknown = sorted(set(configured) - available)
|
|
305
|
+
if unknown:
|
|
306
|
+
msg = (
|
|
307
|
+
"Configured profile(s) are no longer available: "
|
|
308
|
+
+ ", ".join(unknown)
|
|
309
|
+
)
|
|
310
|
+
raise ProfileError(msg)
|
|
311
|
+
|
|
312
|
+
if harness_override or mode_override:
|
|
313
|
+
config = config_manager.set_agent_harness(
|
|
314
|
+
requested_harness if harness_override else config.agent_harness,
|
|
315
|
+
execution_mode if mode_override else None,
|
|
316
|
+
)
|
|
317
|
+
if project_mode_override:
|
|
318
|
+
config = config_manager.set_project_mode(project_mode)
|
|
319
|
+
|
|
320
|
+
profiles = profile_manager.resolve(configured)
|
|
321
|
+
installed = profile_manager.install_constitution(
|
|
322
|
+
config.root,
|
|
323
|
+
profiles,
|
|
324
|
+
destination=config.constitution.path,
|
|
325
|
+
)
|
|
326
|
+
config_manager.ensure_directories(config)
|
|
327
|
+
_ensure_architecture_template(config, config.project_mode)
|
|
328
|
+
|
|
329
|
+
constitution = ConstitutionLoader(profile_manager).load(
|
|
330
|
+
config,
|
|
331
|
+
profiles,
|
|
332
|
+
)
|
|
333
|
+
selected_harness, setup_result, harness_warnings = _apply_harness_setup(
|
|
334
|
+
config=config,
|
|
335
|
+
project=project,
|
|
336
|
+
profiles=profiles,
|
|
337
|
+
constitution=constitution,
|
|
338
|
+
requested_harness=config.agent_harness,
|
|
339
|
+
allow_prompt=allow_prompt,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
console.print("[yellow]DevFlow is already initialized.[/yellow]")
|
|
343
|
+
console.print(f"[bold]Root:[/bold] {config.root}")
|
|
344
|
+
console.print(
|
|
345
|
+
f"[bold]Project mode:[/bold] {config.project_mode}"
|
|
346
|
+
)
|
|
347
|
+
if installed.copied:
|
|
348
|
+
console.print(
|
|
349
|
+
"[green]Missing constitution files restored:[/green] "
|
|
350
|
+
+ ", ".join(installed.copied)
|
|
351
|
+
)
|
|
352
|
+
if installed.skipped:
|
|
353
|
+
console.print(
|
|
354
|
+
"[dim]Existing constitution files left untouched:[/dim] "
|
|
355
|
+
+ ", ".join(installed.skipped)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
missing = sorted(set(detected_names) - set(configured))
|
|
359
|
+
if missing:
|
|
360
|
+
console.print(
|
|
361
|
+
"[yellow]Detected but not configured:[/yellow] "
|
|
362
|
+
+ ", ".join(missing)
|
|
363
|
+
+ ". Edit .devflow/config.yaml to enable them."
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
for warning in constitution.warnings:
|
|
367
|
+
console.print(f"[yellow]Warning:[/yellow] {warning}")
|
|
368
|
+
|
|
369
|
+
_report_harness(
|
|
370
|
+
config.agent_harness,
|
|
371
|
+
selected_harness,
|
|
372
|
+
setup_result,
|
|
373
|
+
harness_warnings,
|
|
374
|
+
config,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _requested_harness(
|
|
379
|
+
harness: Optional[str],
|
|
380
|
+
no_agent: bool,
|
|
381
|
+
) -> str:
|
|
382
|
+
if no_agent:
|
|
383
|
+
return "none"
|
|
384
|
+
return validate_harness_name(harness or "auto")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _requested_project_mode(project_mode: Optional[str]) -> str:
|
|
388
|
+
if project_mode is None:
|
|
389
|
+
return "existing"
|
|
390
|
+
normalized = canonical_project_mode(project_mode)
|
|
391
|
+
if normalized not in PROJECT_MODES:
|
|
392
|
+
msg = (
|
|
393
|
+
f"Unknown project mode: {project_mode}. "
|
|
394
|
+
f"Supported values: {', '.join(PROJECT_MODES)}."
|
|
395
|
+
)
|
|
396
|
+
raise ConfigError(msg)
|
|
397
|
+
return normalized
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _ensure_architecture_template(
|
|
401
|
+
config: DevFlowConfig,
|
|
402
|
+
project_mode: str,
|
|
403
|
+
) -> None:
|
|
404
|
+
if project_mode != "greenfield":
|
|
405
|
+
return
|
|
406
|
+
created = create_architecture_template(config.root)
|
|
407
|
+
if created is None:
|
|
408
|
+
console.print(
|
|
409
|
+
"[dim]Architecture manifest kept:[/dim] "
|
|
410
|
+
f"{ARCHITECTURE_RELATIVE_PATH.as_posix()}"
|
|
411
|
+
)
|
|
412
|
+
else:
|
|
413
|
+
console.print(
|
|
414
|
+
"[green]Architecture manifest template created:[/green] "
|
|
415
|
+
f"{ARCHITECTURE_RELATIVE_PATH.as_posix()}"
|
|
416
|
+
)
|
|
417
|
+
console.print(
|
|
418
|
+
"[dim]Edit it before running a Greenfield specification.[/dim]"
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _requested_mode(mode: Optional[str]) -> str:
|
|
423
|
+
if mode is None:
|
|
424
|
+
return "interactive"
|
|
425
|
+
normalized = canonical_execution_mode(mode)
|
|
426
|
+
if normalized not in EXECUTION_MODES:
|
|
427
|
+
msg = (
|
|
428
|
+
f"Unknown execution mode: {mode}. "
|
|
429
|
+
f"Supported values: {', '.join(EXECUTION_MODES)}."
|
|
430
|
+
)
|
|
431
|
+
raise ConfigError(msg)
|
|
432
|
+
return normalized
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _apply_harness_setup(
|
|
436
|
+
config: DevFlowConfig,
|
|
437
|
+
project: Project,
|
|
438
|
+
profiles: list[Profile],
|
|
439
|
+
constitution: Constitution,
|
|
440
|
+
requested_harness: str,
|
|
441
|
+
allow_prompt: bool,
|
|
442
|
+
) -> tuple[str | None, SetupResult | None, list[str]]:
|
|
443
|
+
selected, warnings = resolve_harness(
|
|
444
|
+
requested_harness,
|
|
445
|
+
allow_prompt=allow_prompt,
|
|
446
|
+
require_installed=False,
|
|
447
|
+
)
|
|
448
|
+
if selected is not None:
|
|
449
|
+
adapter = adapter_for(selected)
|
|
450
|
+
if not adapter.is_available():
|
|
451
|
+
executable = getattr(adapter, "executable", selected)
|
|
452
|
+
warnings.append(
|
|
453
|
+
f"Agent harness '{selected}' is configured, but its executable "
|
|
454
|
+
f"'{executable}' is not installed or not on PATH. DevFlow will "
|
|
455
|
+
"use deterministic analysis until it is available."
|
|
456
|
+
)
|
|
457
|
+
result = adapter.setup(
|
|
458
|
+
build_setup_context(
|
|
459
|
+
config=config,
|
|
460
|
+
project=project,
|
|
461
|
+
profiles=profiles,
|
|
462
|
+
constitution=constitution,
|
|
463
|
+
)
|
|
464
|
+
)
|
|
465
|
+
return selected, result, warnings
|
|
466
|
+
|
|
467
|
+
if validate_harness_name(requested_harness) == "none":
|
|
468
|
+
return None, None, warnings
|
|
469
|
+
|
|
470
|
+
writer = AgentsFileWriter()
|
|
471
|
+
result = writer.write(
|
|
472
|
+
root=config.root,
|
|
473
|
+
content=writer.render(
|
|
474
|
+
project=project,
|
|
475
|
+
profiles=profiles,
|
|
476
|
+
constitution_files=list(config.constitution.files),
|
|
477
|
+
config=config,
|
|
478
|
+
),
|
|
479
|
+
)
|
|
480
|
+
generic = SetupResult(
|
|
481
|
+
changes=(FileChange(path=result.path, status=result.status),)
|
|
482
|
+
)
|
|
483
|
+
warnings.append(
|
|
484
|
+
"No specific harness was selected. A generic AGENTS.md fallback "
|
|
485
|
+
"was generated."
|
|
486
|
+
)
|
|
487
|
+
return None, generic, warnings
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _report_harness(
|
|
491
|
+
requested_harness: str,
|
|
492
|
+
selected_harness: str | None,
|
|
493
|
+
setup_result: SetupResult | None,
|
|
494
|
+
warnings: list[str],
|
|
495
|
+
config: DevFlowConfig,
|
|
496
|
+
) -> None:
|
|
497
|
+
if selected_harness is not None:
|
|
498
|
+
label = selected_harness
|
|
499
|
+
if requested_harness == "auto":
|
|
500
|
+
label += " (auto-detected)"
|
|
501
|
+
console.print(f"[bold]Agent harness:[/bold] {label}")
|
|
502
|
+
elif requested_harness == "none":
|
|
503
|
+
console.print("[yellow]Agent integration disabled.[/yellow]")
|
|
504
|
+
else:
|
|
505
|
+
console.print(
|
|
506
|
+
"[yellow]No specific harness selected; using the generic "
|
|
507
|
+
"AGENTS.md fallback.[/yellow]"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
console.print(
|
|
511
|
+
f"[bold]Execution mode:[/bold] {config.execution_mode}"
|
|
512
|
+
)
|
|
513
|
+
for warning in warnings:
|
|
514
|
+
console.print(f"[yellow]Warning:[/yellow] {warning}")
|
|
515
|
+
if setup_result is not None:
|
|
516
|
+
print_setup_result(setup_result, config.root, console)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _prompt_for_profiles(profile_manager: ProfileManager) -> list[str]:
|
|
520
|
+
if not sys.stdin.isatty():
|
|
521
|
+
return []
|
|
522
|
+
|
|
523
|
+
available = [
|
|
524
|
+
profile_name
|
|
525
|
+
for profile_name in profile_manager.names()
|
|
526
|
+
if profile_name != "base"
|
|
527
|
+
]
|
|
528
|
+
if not available:
|
|
529
|
+
return []
|
|
530
|
+
|
|
531
|
+
console.print(
|
|
532
|
+
"[yellow]No project profile was detected automatically.[/yellow]"
|
|
533
|
+
)
|
|
534
|
+
console.print("Available profiles: " + ", ".join(available))
|
|
535
|
+
answer = typer.prompt(
|
|
536
|
+
"Profiles to enable (comma-separated, leave empty for base only)",
|
|
537
|
+
default="",
|
|
538
|
+
show_default=False,
|
|
539
|
+
)
|
|
540
|
+
return [
|
|
541
|
+
value.strip()
|
|
542
|
+
for value in answer.split(",")
|
|
543
|
+
if value.strip()
|
|
544
|
+
]
|