@deftai/directive-content 0.59.0 → 0.61.0
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.
- package/.githooks/pre-commit +10 -128
- package/.githooks/pre-push +8 -108
- package/Taskfile.yml +48 -58
- package/UPGRADING.md +19 -3
- package/docs/assets/directive-lifecycle-diagram.png +0 -0
- package/docs/directive-lifecycle.md +73 -0
- package/docs/getting-started.md +5 -1
- package/package.json +3 -3
- package/packs/skills/skills-pack-0.1.json +1 -1
- package/packs/strategies/strategies-pack-0.1.json +19 -19
- package/scm/github.md +37 -6
- package/skills/deft-directive-setup/SKILL.md +24 -15
- package/strategies/speckit.md +14 -14
- package/strategies/v0-20-contract.md +12 -1
- package/tasks/change.yml +16 -31
- package/tasks/ci.yml +8 -0
- package/tasks/commit.yml +12 -19
- package/tasks/core.yml +10 -0
- package/tasks/engine.yml +42 -0
- package/tasks/framework.yml +3 -0
- package/tasks/install.yml +20 -19
- package/tasks/migrate.yml +26 -15
- package/tasks/project.yml +26 -0
- package/tasks/toolchain.yml +15 -5
- package/tasks/vbrief.yml +4 -3
- package/tasks/verify.yml +12 -14
- package/templates/agents-entry.md +1 -1
- package/scripts/_agents_md.py +0 -494
- package/scripts/_cache_fetch.py +0 -635
- package/scripts/_cache_quota.py +0 -529
- package/scripts/_cache_refresh.py +0 -163
- package/scripts/_cache_validate.py +0 -209
- package/scripts/_content_root.py +0 -42
- package/scripts/_doctor_state.py +0 -277
- package/scripts/_event_detect.py +0 -305
- package/scripts/_events.py +0 -514
- package/scripts/_lifecycle_hygiene.py +0 -568
- package/scripts/_pathspec.py +0 -91
- package/scripts/_policy_show_cli.py +0 -266
- package/scripts/_precutover.py +0 -92
- package/scripts/_project_context.py +0 -224
- package/scripts/_project_definition_io.py +0 -164
- package/scripts/_relocate_snapshot.py +0 -209
- package/scripts/_relocate_states.py +0 -343
- package/scripts/_resolve_preflight_path.py +0 -152
- package/scripts/_safe_subprocess.py +0 -167
- package/scripts/_session_start_hook.py +0 -205
- package/scripts/_sor_gate_diff.py +0 -365
- package/scripts/_stdio_utf8.py +0 -59
- package/scripts/_triage_bootstrap_gitignore.py +0 -904
- package/scripts/_triage_classify_cli.py +0 -122
- package/scripts/_triage_queue_cli.py +0 -625
- package/scripts/_triage_scope_cli.py +0 -343
- package/scripts/_triage_scope_drift_cli.py +0 -121
- package/scripts/_triage_scope_ignores.py +0 -286
- package/scripts/_triage_scope_milestone.py +0 -432
- package/scripts/_triage_scope_mutations.py +0 -337
- package/scripts/_triage_scope_renderers.py +0 -207
- package/scripts/_triage_smoketest_stages.py +0 -674
- package/scripts/_triage_subscribe_cli.py +0 -140
- package/scripts/_triage_welcome_cli.py +0 -421
- package/scripts/_vbrief_build.py +0 -239
- package/scripts/_vbrief_fidelity.py +0 -479
- package/scripts/_vbrief_legacy.py +0 -589
- package/scripts/_vbrief_reconciliation.py +0 -883
- package/scripts/_vbrief_routing.py +0 -277
- package/scripts/_vbrief_safety.py +0 -778
- package/scripts/_vbrief_sources.py +0 -312
- package/scripts/_vbrief_speckit.py +0 -262
- package/scripts/_vbrief_story_quality.py +0 -353
- package/scripts/_vbrief_validation.py +0 -299
- package/scripts/build_dist.py +0 -412
- package/scripts/cache.py +0 -1078
- package/scripts/cache_scanner.py +0 -745
- package/scripts/candidates_log.py +0 -432
- package/scripts/capacity_backfill.py +0 -680
- package/scripts/capacity_show.py +0 -653
- package/scripts/ci_local.py +0 -689
- package/scripts/code_structure_validate.py +0 -765
- package/scripts/codebase_default_extractor.py +0 -495
- package/scripts/codebase_map.py +0 -304
- package/scripts/codebase_map_fresh.py +0 -104
- package/scripts/codebase_projection_registry.py +0 -94
- package/scripts/codebase_provider.py +0 -582
- package/scripts/doctor.py +0 -2552
- package/scripts/framework_commands.py +0 -505
- package/scripts/gh_rest.py +0 -882
- package/scripts/github_auth_modes.py +0 -437
- package/scripts/github_body.py +0 -292
- package/scripts/ip_risk.py +0 -531
- package/scripts/issue_emit.py +0 -670
- package/scripts/issue_ingest.py +0 -1064
- package/scripts/migrate_preflight.py +0 -418
- package/scripts/migrate_vbrief.py +0 -2677
- package/scripts/monitor_pr.py +0 -401
- package/scripts/pack_migrate_lessons.py +0 -336
- package/scripts/pack_migrate_patterns.py +0 -254
- package/scripts/pack_migrate_rules.py +0 -350
- package/scripts/pack_migrate_skills.py +0 -423
- package/scripts/pack_migrate_strategies.py +0 -311
- package/scripts/pack_migrate_swarm_spec.py +0 -250
- package/scripts/pack_render.py +0 -434
- package/scripts/packs_slice.py +0 -712
- package/scripts/platform_capabilities.py +0 -336
- package/scripts/policy.py +0 -2826
- package/scripts/policy_set.py +0 -324
- package/scripts/pr_check_closing_keywords.py +0 -524
- package/scripts/pr_check_protected_issues.py +0 -267
- package/scripts/pr_merge_readiness.py +0 -1004
- package/scripts/pr_wait_mergeable.py +0 -669
- package/scripts/prd_render.py +0 -159
- package/scripts/preflight_architecture_sor.py +0 -974
- package/scripts/preflight_branch.py +0 -289
- package/scripts/preflight_cache.py +0 -974
- package/scripts/preflight_gh.py +0 -721
- package/scripts/preflight_implementation.py +0 -272
- package/scripts/preflight_story_start.py +0 -838
- package/scripts/preflight_wip_cap.py +0 -149
- package/scripts/probe_session.py +0 -545
- package/scripts/project_render.py +0 -293
- package/scripts/quarantine_ext.py +0 -237
- package/scripts/reconcile_issues.py +0 -1442
- package/scripts/refresh-path.ps1 +0 -107
- package/scripts/release.py +0 -2030
- package/scripts/release_e2e.py +0 -1011
- package/scripts/release_publish.py +0 -486
- package/scripts/release_rollback.py +0 -980
- package/scripts/relocate.py +0 -1034
- package/scripts/resolve_changelog_unreleased.py +0 -667
- package/scripts/resolve_version.py +0 -490
- package/scripts/resume_conditions.py +0 -706
- package/scripts/ritual_sentinel.py +0 -609
- package/scripts/roadmap_render.py +0 -635
- package/scripts/rule_ownership_lint.py +0 -325
- package/scripts/scm.py +0 -591
- package/scripts/scope_audit_log.py +0 -387
- package/scripts/scope_decompose.py +0 -654
- package/scripts/scope_demote.py +0 -509
- package/scripts/scope_lifecycle.py +0 -1126
- package/scripts/scope_undo.py +0 -772
- package/scripts/session_start.py +0 -406
- package/scripts/setup_ghx.py +0 -339
- package/scripts/setup_windows.ps1 +0 -220
- package/scripts/slice_audit.py +0 -585
- package/scripts/slice_record.py +0 -530
- package/scripts/slice_record_existing.py +0 -692
- package/scripts/slug_normalize.py +0 -178
- package/scripts/spec_render.py +0 -477
- package/scripts/spec_validate.py +0 -238
- package/scripts/subagent_monitor.py +0 -658
- package/scripts/swarm_complete_cohort.py +0 -644
- package/scripts/swarm_launch.py +0 -1206
- package/scripts/swarm_readiness.py +0 -554
- package/scripts/swarm_verify_review_clean.py +0 -438
- package/scripts/swarm_worktrees.py +0 -497
- package/scripts/toolchain-check.py +0 -52
- package/scripts/triage_actions.py +0 -871
- package/scripts/triage_bootstrap.py +0 -1153
- package/scripts/triage_bulk.py +0 -630
- package/scripts/triage_classify.py +0 -932
- package/scripts/triage_help.py +0 -1685
- package/scripts/triage_queue.py +0 -1944
- package/scripts/triage_reconcile.py +0 -581
- package/scripts/triage_refresh.py +0 -643
- package/scripts/triage_scope.py +0 -999
- package/scripts/triage_scope_drift.py +0 -575
- package/scripts/triage_smoketest.py +0 -396
- package/scripts/triage_subscribe.py +0 -399
- package/scripts/triage_summary.py +0 -1011
- package/scripts/triage_welcome.py +0 -1178
- package/scripts/ts_check_lane.py +0 -86
- package/scripts/validate-links.py +0 -64
- package/scripts/validate_strategy_output.py +0 -212
- package/scripts/vbrief_activate.py +0 -228
- package/scripts/vbrief_migrate_conformance.py +0 -368
- package/scripts/vbrief_reconcile_graph.py +0 -306
- package/scripts/vbrief_reconcile_labels.py +0 -460
- package/scripts/vbrief_reconcile_umbrellas.py +0 -741
- package/scripts/vbrief_validate.py +0 -1144
- package/scripts/verify-stubs.py +0 -61
- package/scripts/verify_capacity.py +0 -160
- package/scripts/verify_encoding.py +0 -699
- package/scripts/verify_hooks_installed.py +0 -206
- package/scripts/verify_investigation.py +0 -360
- package/scripts/verify_judgment_gates.py +0 -827
- package/scripts/verify_no_task_runtime.py +0 -171
- package/scripts/verify_scm_boundary.py +0 -509
- package/scripts/verify_session_ritual.py +0 -389
- package/scripts/verify_tools.py +0 -426
- package/scripts/verify_vbrief_conformance.py +0 -478
package/scripts/verify_tools.py
DELETED
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Verify Deft's required host tooling and print setup guidance (#1187)."""
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import argparse
|
|
7
|
-
import json
|
|
8
|
-
import platform
|
|
9
|
-
import shutil
|
|
10
|
-
import subprocess
|
|
11
|
-
import sys
|
|
12
|
-
from collections.abc import Callable, Sequence
|
|
13
|
-
from dataclasses import dataclass, replace
|
|
14
|
-
|
|
15
|
-
ProbeFn = Callable[[str], str | None]
|
|
16
|
-
InputFn = Callable[[str], str]
|
|
17
|
-
OutputFn = Callable[[str], None]
|
|
18
|
-
RunFn = Callable[[Sequence[str]], subprocess.CompletedProcess[str]]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass(frozen=True)
|
|
22
|
-
class ToolSpec:
|
|
23
|
-
name: str
|
|
24
|
-
commands: tuple[str, ...]
|
|
25
|
-
url: str
|
|
26
|
-
manual_commands: dict[str, str]
|
|
27
|
-
packages: dict[str, dict[str, tuple[str, ...]]]
|
|
28
|
-
foundational: bool = False
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@dataclass(frozen=True)
|
|
32
|
-
class ToolStatus:
|
|
33
|
-
name: str
|
|
34
|
-
installed: bool
|
|
35
|
-
command: str | None = None
|
|
36
|
-
installable: bool = False
|
|
37
|
-
install_command: tuple[str, ...] | None = None
|
|
38
|
-
manual_command: str | None = None
|
|
39
|
-
url: str | None = None
|
|
40
|
-
installed_after_offer: bool = False
|
|
41
|
-
declined: bool = False
|
|
42
|
-
install_error: str | None = None
|
|
43
|
-
foundational: bool = False
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def unresolved(self) -> bool:
|
|
47
|
-
return not self.installed and not self.installed_after_offer
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@dataclass(frozen=True)
|
|
51
|
-
class VerificationResult:
|
|
52
|
-
statuses: tuple[ToolStatus, ...]
|
|
53
|
-
platform_id: str
|
|
54
|
-
package_manager: str | None
|
|
55
|
-
|
|
56
|
-
@property
|
|
57
|
-
def missing(self) -> tuple[ToolStatus, ...]:
|
|
58
|
-
return tuple(status for status in self.statuses if status.unresolved)
|
|
59
|
-
|
|
60
|
-
@property
|
|
61
|
-
def exit_code(self) -> int:
|
|
62
|
-
if any(status.foundational and status.unresolved for status in self.statuses):
|
|
63
|
-
return 2
|
|
64
|
-
return 1 if self.missing else 0
|
|
65
|
-
|
|
66
|
-
def to_json(self) -> str:
|
|
67
|
-
payload = {
|
|
68
|
-
"platform": self.platform_id,
|
|
69
|
-
"package_manager": self.package_manager,
|
|
70
|
-
"exit_code": self.exit_code,
|
|
71
|
-
"tools": [
|
|
72
|
-
{
|
|
73
|
-
"name": status.name,
|
|
74
|
-
"installed": status.installed or status.installed_after_offer,
|
|
75
|
-
"command": status.command,
|
|
76
|
-
"installable": status.installable,
|
|
77
|
-
"install_command": list(status.install_command or ()),
|
|
78
|
-
"manual_command": status.manual_command,
|
|
79
|
-
"url": status.url,
|
|
80
|
-
"declined": status.declined,
|
|
81
|
-
"install_error": status.install_error,
|
|
82
|
-
"foundational": status.foundational,
|
|
83
|
-
}
|
|
84
|
-
for status in self.statuses
|
|
85
|
-
],
|
|
86
|
-
}
|
|
87
|
-
return json.dumps(payload, sort_keys=True)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
TOOL_SPECS: tuple[ToolSpec, ...] = (
|
|
91
|
-
ToolSpec(
|
|
92
|
-
name="git",
|
|
93
|
-
commands=("git",),
|
|
94
|
-
url="https://git-scm.com/downloads",
|
|
95
|
-
manual_commands={
|
|
96
|
-
"windows": "winget install --id Git.Git -e",
|
|
97
|
-
"macos": "brew install git",
|
|
98
|
-
"linux": "sudo apt-get install git",
|
|
99
|
-
"unknown": "Install Git from https://git-scm.com/downloads",
|
|
100
|
-
},
|
|
101
|
-
packages={},
|
|
102
|
-
foundational=True,
|
|
103
|
-
),
|
|
104
|
-
ToolSpec(
|
|
105
|
-
name="task",
|
|
106
|
-
commands=("task",),
|
|
107
|
-
url="https://taskfile.dev/installation/",
|
|
108
|
-
manual_commands={
|
|
109
|
-
"windows": "winget install --id Task.Task -e",
|
|
110
|
-
"macos": "brew install go-task",
|
|
111
|
-
"linux": "sudo apt-get install go-task",
|
|
112
|
-
"unknown": "Install Task from https://taskfile.dev/installation/",
|
|
113
|
-
},
|
|
114
|
-
packages={
|
|
115
|
-
"windows": {
|
|
116
|
-
"winget": ("winget", "install", "--id", "Task.Task", "-e"),
|
|
117
|
-
"scoop": ("scoop", "install", "go-task"),
|
|
118
|
-
"choco": ("choco", "install", "go-task", "-y"),
|
|
119
|
-
},
|
|
120
|
-
"macos": {"brew": ("brew", "install", "go-task")},
|
|
121
|
-
"linux": {
|
|
122
|
-
"apt-get": ("sudo", "apt-get", "install", "-y", "go-task"),
|
|
123
|
-
"dnf": ("sudo", "dnf", "install", "-y", "go-task"),
|
|
124
|
-
"pacman": ("sudo", "pacman", "-S", "--noconfirm", "go-task"),
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
),
|
|
128
|
-
ToolSpec(
|
|
129
|
-
name="uv",
|
|
130
|
-
commands=("uv",),
|
|
131
|
-
url="https://docs.astral.sh/uv/getting-started/installation/",
|
|
132
|
-
manual_commands={
|
|
133
|
-
"windows": "winget install --id astral-sh.uv -e",
|
|
134
|
-
"macos": "brew install uv",
|
|
135
|
-
"linux": "sudo apt-get install uv",
|
|
136
|
-
"unknown": "Install uv from https://docs.astral.sh/uv/getting-started/installation/",
|
|
137
|
-
},
|
|
138
|
-
packages={
|
|
139
|
-
"windows": {
|
|
140
|
-
"winget": ("winget", "install", "--id", "astral-sh.uv", "-e"),
|
|
141
|
-
"scoop": ("scoop", "install", "uv"),
|
|
142
|
-
"choco": ("choco", "install", "uv", "-y"),
|
|
143
|
-
},
|
|
144
|
-
"macos": {"brew": ("brew", "install", "uv")},
|
|
145
|
-
"linux": {
|
|
146
|
-
"apt-get": ("sudo", "apt-get", "install", "-y", "uv"),
|
|
147
|
-
"dnf": ("sudo", "dnf", "install", "-y", "uv"),
|
|
148
|
-
"pacman": ("sudo", "pacman", "-S", "--noconfirm", "uv"),
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
),
|
|
152
|
-
ToolSpec(
|
|
153
|
-
name="python",
|
|
154
|
-
commands=("python3", "python"),
|
|
155
|
-
url="https://www.python.org/downloads/",
|
|
156
|
-
manual_commands={
|
|
157
|
-
"windows": "winget install --id Python.Python.3 -e",
|
|
158
|
-
"macos": "brew install python",
|
|
159
|
-
"linux": "sudo apt-get install python3",
|
|
160
|
-
"unknown": "Install Python from https://www.python.org/downloads/",
|
|
161
|
-
},
|
|
162
|
-
packages={
|
|
163
|
-
"windows": {
|
|
164
|
-
"winget": ("winget", "install", "--id", "Python.Python.3", "-e"),
|
|
165
|
-
"scoop": ("scoop", "install", "python"),
|
|
166
|
-
"choco": ("choco", "install", "python", "-y"),
|
|
167
|
-
},
|
|
168
|
-
"macos": {"brew": ("brew", "install", "python")},
|
|
169
|
-
"linux": {
|
|
170
|
-
"apt-get": ("sudo", "apt-get", "install", "-y", "python3"),
|
|
171
|
-
"dnf": ("sudo", "dnf", "install", "-y", "python3"),
|
|
172
|
-
"pacman": ("sudo", "pacman", "-S", "--noconfirm", "python"),
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
),
|
|
176
|
-
ToolSpec(
|
|
177
|
-
name="gh",
|
|
178
|
-
commands=("gh",),
|
|
179
|
-
url="https://cli.github.com/",
|
|
180
|
-
manual_commands={
|
|
181
|
-
"windows": "winget install --id GitHub.cli -e",
|
|
182
|
-
"macos": "brew install gh",
|
|
183
|
-
"linux": "sudo apt-get install gh",
|
|
184
|
-
"unknown": "Install GitHub CLI from https://cli.github.com/",
|
|
185
|
-
},
|
|
186
|
-
packages={
|
|
187
|
-
"windows": {
|
|
188
|
-
"winget": ("winget", "install", "--id", "GitHub.cli", "-e"),
|
|
189
|
-
"scoop": ("scoop", "install", "gh"),
|
|
190
|
-
"choco": ("choco", "install", "gh", "-y"),
|
|
191
|
-
},
|
|
192
|
-
"macos": {"brew": ("brew", "install", "gh")},
|
|
193
|
-
"linux": {
|
|
194
|
-
"apt-get": ("sudo", "apt-get", "install", "-y", "gh"),
|
|
195
|
-
"dnf": ("sudo", "dnf", "install", "-y", "gh"),
|
|
196
|
-
"pacman": ("sudo", "pacman", "-S", "--noconfirm", "github-cli"),
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
),
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
PACKAGE_MANAGERS: dict[str, tuple[str, ...]] = {
|
|
204
|
-
"windows": ("winget", "scoop", "choco"),
|
|
205
|
-
"macos": ("brew",),
|
|
206
|
-
"linux": ("apt-get", "dnf", "pacman"),
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def detect_platform() -> str:
|
|
211
|
-
system = platform.system().lower()
|
|
212
|
-
if system == "windows":
|
|
213
|
-
return "windows"
|
|
214
|
-
if system == "darwin":
|
|
215
|
-
return "macos"
|
|
216
|
-
if system == "linux":
|
|
217
|
-
return "linux"
|
|
218
|
-
return "unknown"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def detect_package_manager(platform_id: str, *, probe: ProbeFn = shutil.which) -> str | None:
|
|
222
|
-
for manager in PACKAGE_MANAGERS.get(platform_id, ()):
|
|
223
|
-
if probe(manager):
|
|
224
|
-
return manager
|
|
225
|
-
return None
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _installed_command(spec: ToolSpec, probe: ProbeFn) -> str | None:
|
|
229
|
-
for command in spec.commands:
|
|
230
|
-
if probe(command):
|
|
231
|
-
return command
|
|
232
|
-
return None
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _install_command(
|
|
236
|
-
spec: ToolSpec,
|
|
237
|
-
*,
|
|
238
|
-
platform_id: str,
|
|
239
|
-
package_manager: str | None,
|
|
240
|
-
) -> tuple[str, ...] | None:
|
|
241
|
-
if package_manager is None:
|
|
242
|
-
return None
|
|
243
|
-
return spec.packages.get(platform_id, {}).get(package_manager)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _default_run(command: Sequence[str]) -> subprocess.CompletedProcess[str]:
|
|
247
|
-
return subprocess.run(
|
|
248
|
-
list(command),
|
|
249
|
-
capture_output=True,
|
|
250
|
-
text=True,
|
|
251
|
-
encoding="utf-8",
|
|
252
|
-
errors="replace",
|
|
253
|
-
check=False,
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def verify_required_tools(
|
|
258
|
-
*,
|
|
259
|
-
install: bool = False,
|
|
260
|
-
assume_yes: bool = False,
|
|
261
|
-
include_task: bool = False,
|
|
262
|
-
platform_id: str | None = None,
|
|
263
|
-
probe: ProbeFn = shutil.which,
|
|
264
|
-
input_fn: InputFn = input,
|
|
265
|
-
run_fn: RunFn = _default_run,
|
|
266
|
-
output_fn: OutputFn | None = None,
|
|
267
|
-
) -> VerificationResult:
|
|
268
|
-
resolved_platform = platform_id or detect_platform()
|
|
269
|
-
package_manager = detect_package_manager(resolved_platform, probe=probe)
|
|
270
|
-
statuses: list[ToolStatus] = []
|
|
271
|
-
lines: list[str] = []
|
|
272
|
-
|
|
273
|
-
selected_specs = (
|
|
274
|
-
TOOL_SPECS
|
|
275
|
-
if include_task
|
|
276
|
-
else tuple(spec for spec in TOOL_SPECS if spec.name != "task")
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
for spec in selected_specs:
|
|
280
|
-
found = _installed_command(spec, probe)
|
|
281
|
-
if found:
|
|
282
|
-
statuses.append(
|
|
283
|
-
ToolStatus(
|
|
284
|
-
name=spec.name,
|
|
285
|
-
installed=True,
|
|
286
|
-
command=found,
|
|
287
|
-
foundational=spec.foundational,
|
|
288
|
-
)
|
|
289
|
-
)
|
|
290
|
-
continue
|
|
291
|
-
|
|
292
|
-
manual_command = spec.manual_commands.get(
|
|
293
|
-
resolved_platform,
|
|
294
|
-
spec.manual_commands["unknown"],
|
|
295
|
-
)
|
|
296
|
-
install_command = _install_command(
|
|
297
|
-
spec,
|
|
298
|
-
platform_id=resolved_platform,
|
|
299
|
-
package_manager=package_manager,
|
|
300
|
-
)
|
|
301
|
-
base = ToolStatus(
|
|
302
|
-
name=spec.name,
|
|
303
|
-
installed=False,
|
|
304
|
-
installable=install_command is not None and not spec.foundational,
|
|
305
|
-
install_command=install_command,
|
|
306
|
-
manual_command=manual_command,
|
|
307
|
-
url=spec.url,
|
|
308
|
-
foundational=spec.foundational,
|
|
309
|
-
)
|
|
310
|
-
will_prompt = install and not assume_yes
|
|
311
|
-
lines.extend(_guidance_lines(base, will_prompt=will_prompt))
|
|
312
|
-
if not install or spec.foundational or install_command is None:
|
|
313
|
-
statuses.append(base)
|
|
314
|
-
continue
|
|
315
|
-
|
|
316
|
-
approved = assume_yes
|
|
317
|
-
if not assume_yes:
|
|
318
|
-
prompt = f"{spec.name} is not installed on this machine. Install it now? (Y/n) "
|
|
319
|
-
answer = input_fn(prompt)
|
|
320
|
-
approved = answer.strip().lower() in {"", "y", "yes"}
|
|
321
|
-
if not approved:
|
|
322
|
-
statuses.append(replace(base, declined=True))
|
|
323
|
-
continue
|
|
324
|
-
|
|
325
|
-
proc = run_fn(install_command)
|
|
326
|
-
rechecked = _installed_command(spec, probe)
|
|
327
|
-
if proc.returncode == 0 and rechecked:
|
|
328
|
-
statuses.append(replace(base, installed_after_offer=True, command=rechecked))
|
|
329
|
-
else:
|
|
330
|
-
error = (proc.stderr or proc.stdout or "installer did not put tool on PATH").strip()
|
|
331
|
-
statuses.append(replace(base, install_error=error))
|
|
332
|
-
|
|
333
|
-
result = VerificationResult(
|
|
334
|
-
statuses=tuple(statuses),
|
|
335
|
-
platform_id=resolved_platform,
|
|
336
|
-
package_manager=package_manager,
|
|
337
|
-
)
|
|
338
|
-
if result.missing:
|
|
339
|
-
unresolved = ", ".join(status.name for status in result.missing)
|
|
340
|
-
lines.append(f"[deft tools] Unresolved required tools: {unresolved}.")
|
|
341
|
-
elif lines:
|
|
342
|
-
lines.append("[deft tools] Required tools are now available.")
|
|
343
|
-
else:
|
|
344
|
-
lines.append("[deft tools] Required tools are available.")
|
|
345
|
-
|
|
346
|
-
if output_fn is not None:
|
|
347
|
-
for line in lines:
|
|
348
|
-
output_fn(line)
|
|
349
|
-
return result
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
def _guidance_lines(status: ToolStatus, *, will_prompt: bool = False) -> list[str]:
|
|
353
|
-
if status.foundational:
|
|
354
|
-
return [
|
|
355
|
-
(
|
|
356
|
-
f"[deft tools] Required foundational tool `{status.name}` is missing; "
|
|
357
|
-
"install it before continuing."
|
|
358
|
-
),
|
|
359
|
-
f"[deft tools] Manual install: {status.manual_command}",
|
|
360
|
-
f"[deft tools] Canonical install URL: {status.url}",
|
|
361
|
-
]
|
|
362
|
-
if status.install_command:
|
|
363
|
-
if will_prompt:
|
|
364
|
-
headline = (
|
|
365
|
-
f"[deft tools] `{status.name}` is not installed on this machine. "
|
|
366
|
-
"Install it now? (Y/n)"
|
|
367
|
-
)
|
|
368
|
-
else:
|
|
369
|
-
headline = (
|
|
370
|
-
f"[deft tools] `{status.name}` is not installed on this machine; "
|
|
371
|
-
"re-run with `--install` to set it up."
|
|
372
|
-
)
|
|
373
|
-
return [
|
|
374
|
-
headline,
|
|
375
|
-
f"[deft tools] Auto-install command: {' '.join(status.install_command)}",
|
|
376
|
-
f"[deft tools] Manual install: {status.manual_command}",
|
|
377
|
-
f"[deft tools] Canonical install URL: {status.url}",
|
|
378
|
-
]
|
|
379
|
-
return [
|
|
380
|
-
(
|
|
381
|
-
f"[deft tools] `{status.name}` is not installed and no safe automated "
|
|
382
|
-
"installer was detected."
|
|
383
|
-
),
|
|
384
|
-
f"[deft tools] Manual install: {status.manual_command}",
|
|
385
|
-
f"[deft tools] Canonical install URL: {status.url}",
|
|
386
|
-
]
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def _build_parser() -> argparse.ArgumentParser:
|
|
390
|
-
parser = argparse.ArgumentParser(description="Verify required Deft host tools.")
|
|
391
|
-
parser.add_argument("--install", action="store_true", help="Offer to run installers.")
|
|
392
|
-
parser.add_argument("--yes", action="store_true", help="Approve installer prompts.")
|
|
393
|
-
parser.add_argument("--json", action="store_true", dest="emit_json")
|
|
394
|
-
parser.add_argument(
|
|
395
|
-
"--include-task",
|
|
396
|
-
action="store_true",
|
|
397
|
-
help="Also require go-task for Taskfile-source workflows.",
|
|
398
|
-
)
|
|
399
|
-
parser.add_argument(
|
|
400
|
-
"--platform",
|
|
401
|
-
choices=("windows", "macos", "linux", "unknown"),
|
|
402
|
-
help="Override platform detection for tests or diagnostics.",
|
|
403
|
-
)
|
|
404
|
-
return parser
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def main(argv: list[str] | None = None) -> int:
|
|
408
|
-
args = _build_parser().parse_args(argv)
|
|
409
|
-
captured: list[str] = []
|
|
410
|
-
result = verify_required_tools(
|
|
411
|
-
install=args.install,
|
|
412
|
-
assume_yes=args.yes,
|
|
413
|
-
include_task=args.include_task,
|
|
414
|
-
platform_id=args.platform,
|
|
415
|
-
output_fn=captured.append,
|
|
416
|
-
)
|
|
417
|
-
if args.emit_json:
|
|
418
|
-
print(result.to_json())
|
|
419
|
-
else:
|
|
420
|
-
for line in captured:
|
|
421
|
-
print(line)
|
|
422
|
-
return result.exit_code
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if __name__ == "__main__":
|
|
426
|
-
sys.exit(main())
|