@deftai/directive-content 0.55.2 → 0.56.1
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 +143 -0
- package/.githooks/pre-push +121 -0
- package/QUICK-START.md +2 -2
- package/Taskfile.yml +934 -0
- package/UPGRADING.md +47 -1
- package/events/README.md +3 -3
- package/package.json +5 -4
- package/scripts/_agents_md.py +494 -0
- package/scripts/_cache_fetch.py +635 -0
- package/scripts/_cache_quota.py +529 -0
- package/scripts/_cache_refresh.py +163 -0
- package/scripts/_cache_validate.py +209 -0
- package/scripts/_content_root.py +42 -0
- package/scripts/_doctor_state.py +277 -0
- package/scripts/_event_detect.py +305 -0
- package/scripts/_events.py +514 -0
- package/scripts/_lifecycle_hygiene.py +568 -0
- package/scripts/_pathspec.py +91 -0
- package/scripts/_policy_show_cli.py +266 -0
- package/scripts/_precutover.py +92 -0
- package/scripts/_project_context.py +224 -0
- package/scripts/_project_definition_io.py +164 -0
- package/scripts/_relocate_snapshot.py +209 -0
- package/scripts/_relocate_states.py +343 -0
- package/scripts/_resolve_preflight_path.py +152 -0
- package/scripts/_safe_subprocess.py +167 -0
- package/scripts/_session_start_hook.py +205 -0
- package/scripts/_sor_gate_diff.py +365 -0
- package/scripts/_stdio_utf8.py +59 -0
- package/scripts/_triage_bootstrap_gitignore.py +904 -0
- package/scripts/_triage_classify_cli.py +122 -0
- package/scripts/_triage_queue_cli.py +625 -0
- package/scripts/_triage_scope_cli.py +343 -0
- package/scripts/_triage_scope_drift_cli.py +121 -0
- package/scripts/_triage_scope_ignores.py +286 -0
- package/scripts/_triage_scope_milestone.py +432 -0
- package/scripts/_triage_scope_mutations.py +337 -0
- package/scripts/_triage_scope_renderers.py +207 -0
- package/scripts/_triage_smoketest_stages.py +674 -0
- package/scripts/_triage_subscribe_cli.py +140 -0
- package/scripts/_triage_welcome_cli.py +421 -0
- package/scripts/_vbrief_build.py +239 -0
- package/scripts/_vbrief_fidelity.py +479 -0
- package/scripts/_vbrief_legacy.py +589 -0
- package/scripts/_vbrief_reconciliation.py +883 -0
- package/scripts/_vbrief_routing.py +277 -0
- package/scripts/_vbrief_safety.py +778 -0
- package/scripts/_vbrief_sources.py +312 -0
- package/scripts/_vbrief_speckit.py +262 -0
- package/scripts/_vbrief_story_quality.py +353 -0
- package/scripts/_vbrief_validation.py +299 -0
- package/scripts/build_dist.py +412 -0
- package/scripts/cache.py +1078 -0
- package/scripts/cache_scanner.py +745 -0
- package/scripts/candidates_log.py +432 -0
- package/scripts/capacity_backfill.py +680 -0
- package/scripts/capacity_show.py +653 -0
- package/scripts/ci_local.py +689 -0
- package/scripts/code_structure_validate.py +765 -0
- package/scripts/codebase_default_extractor.py +495 -0
- package/scripts/codebase_map.py +304 -0
- package/scripts/codebase_map_fresh.py +104 -0
- package/scripts/codebase_projection_registry.py +94 -0
- package/scripts/codebase_provider.py +582 -0
- package/scripts/doctor.py +2257 -0
- package/scripts/framework_commands.py +505 -0
- package/scripts/gh_rest.py +882 -0
- package/scripts/github_auth_modes.py +437 -0
- package/scripts/github_body.py +292 -0
- package/scripts/ip_risk.py +531 -0
- package/scripts/issue_emit.py +670 -0
- package/scripts/issue_ingest.py +1064 -0
- package/scripts/migrate_preflight.py +418 -0
- package/scripts/migrate_vbrief.py +2677 -0
- package/scripts/monitor_pr.py +401 -0
- package/scripts/pack_migrate_lessons.py +336 -0
- package/scripts/pack_migrate_patterns.py +254 -0
- package/scripts/pack_migrate_rules.py +350 -0
- package/scripts/pack_migrate_skills.py +423 -0
- package/scripts/pack_migrate_strategies.py +311 -0
- package/scripts/pack_migrate_swarm_spec.py +250 -0
- package/scripts/pack_render.py +434 -0
- package/scripts/packs_slice.py +712 -0
- package/scripts/platform_capabilities.py +336 -0
- package/scripts/policy.py +2826 -0
- package/scripts/policy_set.py +324 -0
- package/scripts/pr_check_closing_keywords.py +524 -0
- package/scripts/pr_check_protected_issues.py +267 -0
- package/scripts/pr_merge_readiness.py +1004 -0
- package/scripts/pr_wait_mergeable.py +669 -0
- package/scripts/prd_render.py +159 -0
- package/scripts/preflight_architecture_sor.py +974 -0
- package/scripts/preflight_branch.py +289 -0
- package/scripts/preflight_cache.py +974 -0
- package/scripts/preflight_gh.py +721 -0
- package/scripts/preflight_implementation.py +272 -0
- package/scripts/preflight_story_start.py +838 -0
- package/scripts/preflight_wip_cap.py +149 -0
- package/scripts/probe_session.py +545 -0
- package/scripts/project_render.py +293 -0
- package/scripts/quarantine_ext.py +237 -0
- package/scripts/reconcile_issues.py +1442 -0
- package/scripts/refresh-path.ps1 +107 -0
- package/scripts/release.py +2030 -0
- package/scripts/release_e2e.py +1011 -0
- package/scripts/release_publish.py +486 -0
- package/scripts/release_rollback.py +980 -0
- package/scripts/relocate.py +1034 -0
- package/scripts/resolve_changelog_unreleased.py +667 -0
- package/scripts/resolve_version.py +490 -0
- package/scripts/resume_conditions.py +706 -0
- package/scripts/ritual_sentinel.py +609 -0
- package/scripts/roadmap_render.py +635 -0
- package/scripts/rule_ownership_lint.py +325 -0
- package/scripts/scm.py +591 -0
- package/scripts/scope_audit_log.py +387 -0
- package/scripts/scope_decompose.py +654 -0
- package/scripts/scope_demote.py +509 -0
- package/scripts/scope_lifecycle.py +1126 -0
- package/scripts/scope_undo.py +772 -0
- package/scripts/session_start.py +406 -0
- package/scripts/setup_ghx.py +339 -0
- package/scripts/setup_windows.ps1 +220 -0
- package/scripts/slice_audit.py +585 -0
- package/scripts/slice_record.py +530 -0
- package/scripts/slice_record_existing.py +692 -0
- package/scripts/slug_normalize.py +178 -0
- package/scripts/spec_render.py +477 -0
- package/scripts/spec_validate.py +238 -0
- package/scripts/subagent_monitor.py +658 -0
- package/scripts/swarm_complete_cohort.py +644 -0
- package/scripts/swarm_launch.py +1206 -0
- package/scripts/swarm_readiness.py +554 -0
- package/scripts/swarm_verify_review_clean.py +438 -0
- package/scripts/swarm_worktrees.py +497 -0
- package/scripts/toolchain-check.py +52 -0
- package/scripts/triage_actions.py +871 -0
- package/scripts/triage_bootstrap.py +1153 -0
- package/scripts/triage_bulk.py +630 -0
- package/scripts/triage_classify.py +932 -0
- package/scripts/triage_help.py +1685 -0
- package/scripts/triage_queue.py +1944 -0
- package/scripts/triage_reconcile.py +581 -0
- package/scripts/triage_refresh.py +643 -0
- package/scripts/triage_scope.py +999 -0
- package/scripts/triage_scope_drift.py +575 -0
- package/scripts/triage_smoketest.py +396 -0
- package/scripts/triage_subscribe.py +399 -0
- package/scripts/triage_summary.py +1011 -0
- package/scripts/triage_welcome.py +1178 -0
- package/scripts/ts_check_lane.py +86 -0
- package/scripts/validate-links.py +64 -0
- package/scripts/validate_strategy_output.py +212 -0
- package/scripts/vbrief_activate.py +228 -0
- package/scripts/vbrief_migrate_conformance.py +368 -0
- package/scripts/vbrief_reconcile_graph.py +306 -0
- package/scripts/vbrief_reconcile_labels.py +460 -0
- package/scripts/vbrief_reconcile_umbrellas.py +741 -0
- package/scripts/vbrief_validate.py +1195 -0
- package/scripts/verify-stubs.py +61 -0
- package/scripts/verify_capacity.py +160 -0
- package/scripts/verify_encoding.py +699 -0
- package/scripts/verify_hooks_installed.py +206 -0
- package/scripts/verify_investigation.py +360 -0
- package/scripts/verify_judgment_gates.py +827 -0
- package/scripts/verify_no_task_runtime.py +171 -0
- package/scripts/verify_scm_boundary.py +509 -0
- package/scripts/verify_session_ritual.py +389 -0
- package/scripts/verify_tools.py +426 -0
- package/scripts/verify_vbrief_conformance.py +478 -0
- package/tasks/architecture.yml +13 -0
- package/tasks/cache.yml +69 -0
- package/tasks/capacity.yml +38 -0
- package/tasks/change.yml +46 -0
- package/tasks/changelog.yml +24 -0
- package/tasks/ci.yml +49 -0
- package/tasks/codebase.yml +47 -0
- package/tasks/commit.yml +30 -0
- package/tasks/core.yml +126 -0
- package/tasks/deployments.yml +54 -0
- package/tasks/framework.yml +74 -0
- package/tasks/install.yml +60 -0
- package/tasks/issue.yml +50 -0
- package/tasks/migrate.yml +73 -0
- package/tasks/packs.yml +92 -0
- package/tasks/policy.yml +75 -0
- package/tasks/pr.yml +89 -0
- package/tasks/prd.yml +39 -0
- package/tasks/project.yml +27 -0
- package/tasks/reconcile.yml +32 -0
- package/tasks/relocate.yml +56 -0
- package/tasks/roadmap.yml +28 -0
- package/tasks/scm.yml +126 -0
- package/tasks/scope-undo.yml +36 -0
- package/tasks/scope.yml +141 -0
- package/tasks/session.yml +19 -0
- package/tasks/setup.yml +37 -0
- package/tasks/slice.yml +69 -0
- package/tasks/spec.yml +41 -0
- package/tasks/swarm.yml +85 -0
- package/tasks/toolchain.yml +13 -0
- package/tasks/triage-actions.yml +94 -0
- package/tasks/triage-bootstrap.yml +43 -0
- package/tasks/triage-bulk.yml +75 -0
- package/tasks/triage-classify.yml +30 -0
- package/tasks/triage-queue.yml +50 -0
- package/tasks/triage-reconcile.yml +29 -0
- package/tasks/triage-scope-drift.yml +29 -0
- package/tasks/triage-scope.yml +31 -0
- package/tasks/triage-smoketest.yml +33 -0
- package/tasks/triage-subscribe.yml +36 -0
- package/tasks/triage-summary.yml +29 -0
- package/tasks/triage-welcome.yml +32 -0
- package/tasks/ts.yml +328 -0
- package/tasks/vbrief.yml +206 -0
- package/tasks/verify.yml +292 -0
- package/templates/agents-entry.md +1 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared no-task dispatcher for Deft framework verbs (#1659).
|
|
3
|
+
|
|
4
|
+
This module is the compatibility rail between the old Taskfile verb names
|
|
5
|
+
(``triage:bootstrap``, ``verify:cache-fresh``, etc.) and the Python entrypoints
|
|
6
|
+
that actually implement them. Package-manager installs can route ``deft
|
|
7
|
+
<verb>`` through this module without requiring go-task on PATH, while Taskfile
|
|
8
|
+
consumers can keep using ``task deft:<verb>`` as a thin wrapper.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import contextlib
|
|
14
|
+
import importlib
|
|
15
|
+
import importlib.util
|
|
16
|
+
import inspect
|
|
17
|
+
import io
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from collections.abc import Callable, Sequence
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from types import ModuleType
|
|
25
|
+
from typing import Literal
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
28
|
+
FRAMEWORK_ROOT = SCRIPT_DIR.parent
|
|
29
|
+
if str(SCRIPT_DIR) not in sys.path:
|
|
30
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
31
|
+
|
|
32
|
+
RootMode = Literal["project", "framework"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class CommandResult:
|
|
37
|
+
code: int
|
|
38
|
+
stdout: str = ""
|
|
39
|
+
stderr: str = ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class CommandSpec:
|
|
44
|
+
name: str
|
|
45
|
+
entrypoint: str | None = None
|
|
46
|
+
default_args: tuple[str, ...] = ()
|
|
47
|
+
project_root_arg: str | None = None
|
|
48
|
+
framework_root_arg: str | None = None
|
|
49
|
+
vbrief_dir_arg: str | None = None
|
|
50
|
+
root_arg: str | None = None
|
|
51
|
+
cwd: RootMode = "project"
|
|
52
|
+
no_argv: bool = False
|
|
53
|
+
aggregate: tuple[str, ...] = ()
|
|
54
|
+
description: str = ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _spec(
|
|
58
|
+
name: str,
|
|
59
|
+
entrypoint: str,
|
|
60
|
+
*,
|
|
61
|
+
default_args: Sequence[str] = (),
|
|
62
|
+
project_root_arg: str | None = None,
|
|
63
|
+
framework_root_arg: str | None = None,
|
|
64
|
+
vbrief_dir_arg: str | None = None,
|
|
65
|
+
root_arg: str | None = None,
|
|
66
|
+
cwd: RootMode = "project",
|
|
67
|
+
no_argv: bool = False,
|
|
68
|
+
description: str = "",
|
|
69
|
+
) -> CommandSpec:
|
|
70
|
+
return CommandSpec(
|
|
71
|
+
name=name,
|
|
72
|
+
entrypoint=entrypoint,
|
|
73
|
+
default_args=tuple(default_args),
|
|
74
|
+
project_root_arg=project_root_arg,
|
|
75
|
+
framework_root_arg=framework_root_arg,
|
|
76
|
+
vbrief_dir_arg=vbrief_dir_arg,
|
|
77
|
+
root_arg=root_arg,
|
|
78
|
+
cwd=cwd,
|
|
79
|
+
no_argv=no_argv,
|
|
80
|
+
description=description,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _aggregate(name: str, commands: Sequence[str], *, description: str = "") -> CommandSpec:
|
|
85
|
+
return CommandSpec(name=name, aggregate=tuple(commands), description=description)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
COMMANDS: dict[str, CommandSpec] = {
|
|
89
|
+
"core:validate": _spec(
|
|
90
|
+
"core:validate", "framework_commands:_cmd_core_validate", cwd="framework"
|
|
91
|
+
),
|
|
92
|
+
"core:lint": _spec("core:lint", "framework_commands:_cmd_core_lint", cwd="framework"),
|
|
93
|
+
"core:test": _spec("core:test", "framework_commands:_cmd_core_test", cwd="framework"),
|
|
94
|
+
"doctor": _spec("doctor", "doctor:cmd_doctor"),
|
|
95
|
+
"session:start": _spec(
|
|
96
|
+
"session:start", "session_start:main", project_root_arg="--project-root"
|
|
97
|
+
),
|
|
98
|
+
"triage:welcome": _spec(
|
|
99
|
+
"triage:welcome", "triage_welcome:main", project_root_arg="--project-root"
|
|
100
|
+
),
|
|
101
|
+
"triage:bootstrap": _spec(
|
|
102
|
+
"triage:bootstrap", "triage_bootstrap:main", project_root_arg="--project-root"
|
|
103
|
+
),
|
|
104
|
+
"triage:summary": _spec(
|
|
105
|
+
"triage:summary", "triage_summary:main", project_root_arg="--project-root"
|
|
106
|
+
),
|
|
107
|
+
"triage:queue": _spec(
|
|
108
|
+
"triage:queue",
|
|
109
|
+
"triage_queue:main",
|
|
110
|
+
default_args=("queue",),
|
|
111
|
+
project_root_arg="--project-root",
|
|
112
|
+
),
|
|
113
|
+
"triage:show": _spec(
|
|
114
|
+
"triage:show",
|
|
115
|
+
"triage_queue:main",
|
|
116
|
+
default_args=("show",),
|
|
117
|
+
project_root_arg="--project-root",
|
|
118
|
+
),
|
|
119
|
+
"triage:audit": _spec(
|
|
120
|
+
"triage:audit",
|
|
121
|
+
"triage_queue:main",
|
|
122
|
+
default_args=("audit",),
|
|
123
|
+
project_root_arg="--project-root",
|
|
124
|
+
),
|
|
125
|
+
"triage:accept": _spec("triage:accept", "triage_actions:main", default_args=("accept",)),
|
|
126
|
+
"triage:status": _spec("triage:status", "triage_actions:main", default_args=("status",)),
|
|
127
|
+
"triage:scope": _spec("triage:scope", "triage_scope:main"),
|
|
128
|
+
"migrate:vbrief": _spec("migrate:vbrief", "framework_commands:_cmd_migrate_vbrief"),
|
|
129
|
+
"cache:fetch-all": _spec("cache:fetch-all", "cache:main", default_args=("fetch-all",)),
|
|
130
|
+
"capacity:show": _spec(
|
|
131
|
+
"capacity:show", "capacity_show:main", project_root_arg="--project-root"
|
|
132
|
+
),
|
|
133
|
+
"scope:demote": _spec("scope:demote", "scope_demote:main", project_root_arg="--project-root"),
|
|
134
|
+
"toolchain:check": _spec("toolchain:check", "toolchain-check.py:main", no_argv=True),
|
|
135
|
+
"verify:stubs": _spec("verify:stubs", "verify-stubs.py:main", no_argv=True),
|
|
136
|
+
"verify:links": _spec("verify:links", "validate-links.py:main", no_argv=True),
|
|
137
|
+
"verify:rule-ownership": _spec(
|
|
138
|
+
"verify:rule-ownership",
|
|
139
|
+
"rule_ownership_lint:main",
|
|
140
|
+
root_arg="--root",
|
|
141
|
+
cwd="framework",
|
|
142
|
+
),
|
|
143
|
+
"verify:branch": _spec(
|
|
144
|
+
"verify:branch",
|
|
145
|
+
"preflight_branch:main",
|
|
146
|
+
default_args=("--allow-missing-project-definition",),
|
|
147
|
+
project_root_arg="--project-root",
|
|
148
|
+
),
|
|
149
|
+
"verify:encoding": _spec(
|
|
150
|
+
"verify:encoding", "verify_encoding:main", project_root_arg="--project-root"
|
|
151
|
+
),
|
|
152
|
+
"verify:vbrief-conformance": _spec(
|
|
153
|
+
"verify:vbrief-conformance",
|
|
154
|
+
"verify_vbrief_conformance:main",
|
|
155
|
+
project_root_arg="--project-root",
|
|
156
|
+
),
|
|
157
|
+
"verify:destructive-gh-verbs": _spec(
|
|
158
|
+
"verify:destructive-gh-verbs",
|
|
159
|
+
"preflight_gh:main",
|
|
160
|
+
default_args=("--self-test",),
|
|
161
|
+
project_root_arg="--project-root",
|
|
162
|
+
),
|
|
163
|
+
"verify:scm-boundary": _spec(
|
|
164
|
+
"verify:scm-boundary",
|
|
165
|
+
"verify_scm_boundary:main",
|
|
166
|
+
project_root_arg="--project-root",
|
|
167
|
+
),
|
|
168
|
+
"verify:no-task-runtime": _spec(
|
|
169
|
+
"verify:no-task-runtime",
|
|
170
|
+
"verify_no_task_runtime:main",
|
|
171
|
+
no_argv=True,
|
|
172
|
+
cwd="framework",
|
|
173
|
+
),
|
|
174
|
+
"verify:cache-fresh": _spec(
|
|
175
|
+
"verify:cache-fresh",
|
|
176
|
+
"preflight_cache:main",
|
|
177
|
+
default_args=("--allow-missing-bootstrap",),
|
|
178
|
+
project_root_arg="--project-root",
|
|
179
|
+
),
|
|
180
|
+
"verify:wip-cap": _spec(
|
|
181
|
+
"verify:wip-cap", "preflight_wip_cap:main", project_root_arg="--project-root"
|
|
182
|
+
),
|
|
183
|
+
"verify:pack-drift": _spec(
|
|
184
|
+
"verify:pack-drift", "pack_render:main", default_args=("--check",), cwd="framework"
|
|
185
|
+
),
|
|
186
|
+
"verify-strategy-output": _spec(
|
|
187
|
+
"verify-strategy-output",
|
|
188
|
+
"validate_strategy_output:main",
|
|
189
|
+
project_root_arg="--project-root",
|
|
190
|
+
),
|
|
191
|
+
"vbrief:validate": _spec(
|
|
192
|
+
"vbrief:validate", "vbrief_validate:main", vbrief_dir_arg="--vbrief-dir"
|
|
193
|
+
),
|
|
194
|
+
"build": _spec(
|
|
195
|
+
"build",
|
|
196
|
+
"build_dist:main",
|
|
197
|
+
default_args=("--version", "__DEFT_VERSION__"),
|
|
198
|
+
cwd="framework",
|
|
199
|
+
),
|
|
200
|
+
"check:consumer": _aggregate(
|
|
201
|
+
"check:consumer",
|
|
202
|
+
(
|
|
203
|
+
"doctor",
|
|
204
|
+
"toolchain:check",
|
|
205
|
+
"verify:branch",
|
|
206
|
+
"verify:cache-fresh",
|
|
207
|
+
"verify:wip-cap",
|
|
208
|
+
"vbrief:validate",
|
|
209
|
+
"verify-strategy-output",
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
"check:framework-source": _aggregate(
|
|
213
|
+
"check:framework-source",
|
|
214
|
+
(
|
|
215
|
+
"core:validate",
|
|
216
|
+
"core:lint",
|
|
217
|
+
"core:test",
|
|
218
|
+
"toolchain:check",
|
|
219
|
+
"verify:stubs",
|
|
220
|
+
"verify:links",
|
|
221
|
+
"verify:rule-ownership",
|
|
222
|
+
"verify:branch",
|
|
223
|
+
"verify:encoding",
|
|
224
|
+
"verify:vbrief-conformance",
|
|
225
|
+
"verify:destructive-gh-verbs",
|
|
226
|
+
"verify:scm-boundary",
|
|
227
|
+
"verify:no-task-runtime",
|
|
228
|
+
"verify:cache-fresh",
|
|
229
|
+
"verify:pack-drift",
|
|
230
|
+
"verify:wip-cap",
|
|
231
|
+
"vbrief:validate",
|
|
232
|
+
"verify-strategy-output",
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def available_commands() -> tuple[str, ...]:
|
|
239
|
+
return tuple(sorted(COMMANDS))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def has_command(name: str) -> bool:
|
|
243
|
+
return name in COMMANDS
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def normalize_task_separator(argv: Sequence[str]) -> list[str]:
|
|
247
|
+
"""Tolerate Taskfile-style pass-through: ``deft verb -- --flag``."""
|
|
248
|
+
args = list(argv)
|
|
249
|
+
if args[:1] == ["--"]:
|
|
250
|
+
return args[1:]
|
|
251
|
+
return args
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def format_framework_command(
|
|
255
|
+
args: Sequence[str],
|
|
256
|
+
*,
|
|
257
|
+
surface: str = "deft",
|
|
258
|
+
task_prefix: str | None = None,
|
|
259
|
+
) -> str:
|
|
260
|
+
"""Render an operator-facing command for the selected invocation surface."""
|
|
261
|
+
parts = list(args)
|
|
262
|
+
if surface == "task":
|
|
263
|
+
prefix = (task_prefix or "").strip()
|
|
264
|
+
if prefix and not prefix.endswith(":"):
|
|
265
|
+
prefix = f"{prefix}:"
|
|
266
|
+
if parts:
|
|
267
|
+
parts[0] = f"{prefix}{parts[0]}"
|
|
268
|
+
return " ".join(["task", *parts])
|
|
269
|
+
return " ".join([surface, *parts])
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _version() -> str:
|
|
273
|
+
try:
|
|
274
|
+
import resolve_version # noqa: PLC0415
|
|
275
|
+
|
|
276
|
+
return resolve_version.resolve_version()
|
|
277
|
+
except Exception: # noqa: BLE001 -- build should still produce a dev artifact
|
|
278
|
+
return "0.0.0-dev"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _load_module(module_ref: str) -> ModuleType:
|
|
282
|
+
if module_ref.endswith(".py"):
|
|
283
|
+
path = SCRIPT_DIR / module_ref
|
|
284
|
+
module_name = f"_deft_command_{path.stem.replace('-', '_')}"
|
|
285
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
286
|
+
if spec is None or spec.loader is None:
|
|
287
|
+
raise ImportError(f"cannot load {path}")
|
|
288
|
+
module = importlib.util.module_from_spec(spec)
|
|
289
|
+
sys.modules[module_name] = module
|
|
290
|
+
spec.loader.exec_module(module)
|
|
291
|
+
return module
|
|
292
|
+
return importlib.import_module(module_ref)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _reject_args(argv: Sequence[str], command: str) -> int:
|
|
296
|
+
if argv:
|
|
297
|
+
print(
|
|
298
|
+
f"error: {command} does not accept arguments: {' '.join(argv)}",
|
|
299
|
+
file=sys.stderr,
|
|
300
|
+
)
|
|
301
|
+
return 2
|
|
302
|
+
return 0
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _cmd_core_validate(argv: list[str] | None = None) -> int:
|
|
306
|
+
if _reject_args(argv or [], "core:validate") != 0:
|
|
307
|
+
return 2
|
|
308
|
+
files = [
|
|
309
|
+
path
|
|
310
|
+
for path in sorted(Path(".").rglob("*.md"))
|
|
311
|
+
if ".git" not in path.parts and "backup" not in path.parts
|
|
312
|
+
]
|
|
313
|
+
for path in files:
|
|
314
|
+
print(f"✓ {path}")
|
|
315
|
+
print(f"✓ All {len(files)} markdown files validated")
|
|
316
|
+
return 0
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _run_uv(args: Sequence[str]) -> int:
|
|
320
|
+
return subprocess.run(["uv", "--project", str(FRAMEWORK_ROOT), "run", *args]).returncode
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _cmd_core_lint(argv: list[str] | None = None) -> int:
|
|
324
|
+
if _reject_args(argv or [], "core:lint") != 0:
|
|
325
|
+
return 2
|
|
326
|
+
ruff_code = _run_uv(["ruff", "check", "."])
|
|
327
|
+
if ruff_code != 0:
|
|
328
|
+
return ruff_code
|
|
329
|
+
targets = ["run.py"]
|
|
330
|
+
if Path("tests").exists():
|
|
331
|
+
targets.append("tests")
|
|
332
|
+
return _run_uv(["python", "-m", "mypy", *targets])
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _cmd_core_test(argv: list[str] | None = None) -> int:
|
|
336
|
+
if _reject_args(argv or [], "core:test") != 0:
|
|
337
|
+
return 2
|
|
338
|
+
if not Path("tests").exists():
|
|
339
|
+
print("no tests/ (vendored consumer) -- skipping")
|
|
340
|
+
return 0
|
|
341
|
+
return subprocess.run([sys.executable, "-m", "pytest", "tests"]).returncode
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _cmd_migrate_vbrief(argv: list[str] | None = None) -> int:
|
|
345
|
+
import migrate_preflight # noqa: PLC0415
|
|
346
|
+
import migrate_vbrief # noqa: PLC0415
|
|
347
|
+
|
|
348
|
+
project_root = Path.cwd().resolve()
|
|
349
|
+
preflight_code = migrate_preflight.main(
|
|
350
|
+
[
|
|
351
|
+
"--project-root",
|
|
352
|
+
str(project_root),
|
|
353
|
+
"--deft-root",
|
|
354
|
+
str(FRAMEWORK_ROOT),
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
if preflight_code != 0:
|
|
358
|
+
return preflight_code
|
|
359
|
+
return migrate_vbrief.main([str(project_root), *(argv or [])])
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _load_callable(entrypoint: str) -> Callable[..., int | None]:
|
|
363
|
+
module_ref, _, func_name = entrypoint.partition(":")
|
|
364
|
+
if not module_ref or not func_name:
|
|
365
|
+
raise ValueError(f"invalid framework entrypoint: {entrypoint!r}")
|
|
366
|
+
module = _load_module(module_ref)
|
|
367
|
+
func = getattr(module, func_name)
|
|
368
|
+
if not callable(func):
|
|
369
|
+
raise TypeError(f"framework entrypoint is not callable: {entrypoint}")
|
|
370
|
+
return func
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _argv_for_spec(
|
|
374
|
+
spec: CommandSpec,
|
|
375
|
+
argv: Sequence[str],
|
|
376
|
+
*,
|
|
377
|
+
project_root: Path,
|
|
378
|
+
framework_root: Path,
|
|
379
|
+
) -> list[str]:
|
|
380
|
+
resolved: list[str] = []
|
|
381
|
+
for item in spec.default_args:
|
|
382
|
+
resolved.append(_version() if item == "__DEFT_VERSION__" else item)
|
|
383
|
+
if spec.project_root_arg:
|
|
384
|
+
resolved.extend((spec.project_root_arg, str(project_root)))
|
|
385
|
+
if spec.framework_root_arg:
|
|
386
|
+
resolved.extend((spec.framework_root_arg, str(framework_root)))
|
|
387
|
+
if spec.vbrief_dir_arg:
|
|
388
|
+
resolved.extend((spec.vbrief_dir_arg, str(project_root / "vbrief")))
|
|
389
|
+
if spec.root_arg:
|
|
390
|
+
resolved.extend((spec.root_arg, str(framework_root)))
|
|
391
|
+
resolved.extend(normalize_task_separator(argv))
|
|
392
|
+
return resolved
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _invoke(func: Callable[..., int | None], argv: list[str], *, no_argv: bool) -> int:
|
|
396
|
+
try:
|
|
397
|
+
if no_argv:
|
|
398
|
+
if argv:
|
|
399
|
+
print(
|
|
400
|
+
f"error: this framework command does not accept arguments: {' '.join(argv)}",
|
|
401
|
+
file=sys.stderr,
|
|
402
|
+
)
|
|
403
|
+
return 2
|
|
404
|
+
code = func()
|
|
405
|
+
else:
|
|
406
|
+
signature = inspect.signature(func)
|
|
407
|
+
code = func() if len(signature.parameters) == 0 else func(argv)
|
|
408
|
+
except SystemExit as exc:
|
|
409
|
+
raw = exc.code
|
|
410
|
+
return raw if isinstance(raw, int) else (0 if raw is None else 1)
|
|
411
|
+
return int(code or 0)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def run_framework_command(
|
|
415
|
+
name: str,
|
|
416
|
+
argv: Sequence[str] = (),
|
|
417
|
+
*,
|
|
418
|
+
project_root: Path | None = None,
|
|
419
|
+
framework_root: Path | None = None,
|
|
420
|
+
capture: bool = False,
|
|
421
|
+
output_fn: Callable[[str], None] | None = None,
|
|
422
|
+
) -> CommandResult:
|
|
423
|
+
root = (project_root or Path.cwd()).resolve()
|
|
424
|
+
framework = (framework_root or FRAMEWORK_ROOT).resolve()
|
|
425
|
+
spec = COMMANDS.get(name)
|
|
426
|
+
if spec is None:
|
|
427
|
+
return CommandResult(2, "", f"unknown framework command: {name}")
|
|
428
|
+
|
|
429
|
+
if spec.aggregate:
|
|
430
|
+
stdout_parts: list[str] = []
|
|
431
|
+
stderr_parts: list[str] = []
|
|
432
|
+
for child in spec.aggregate:
|
|
433
|
+
if output_fn is not None:
|
|
434
|
+
output_fn(f"[deft] {child}")
|
|
435
|
+
result = run_framework_command(
|
|
436
|
+
child,
|
|
437
|
+
(),
|
|
438
|
+
project_root=root,
|
|
439
|
+
framework_root=framework,
|
|
440
|
+
capture=capture,
|
|
441
|
+
output_fn=output_fn,
|
|
442
|
+
)
|
|
443
|
+
stdout_parts.append(result.stdout)
|
|
444
|
+
stderr_parts.append(result.stderr)
|
|
445
|
+
if result.code != 0:
|
|
446
|
+
return CommandResult(
|
|
447
|
+
result.code,
|
|
448
|
+
"".join(stdout_parts),
|
|
449
|
+
"".join(stderr_parts),
|
|
450
|
+
)
|
|
451
|
+
return CommandResult(0, "".join(stdout_parts), "".join(stderr_parts))
|
|
452
|
+
|
|
453
|
+
if spec.entrypoint is None:
|
|
454
|
+
return CommandResult(2, "", f"framework command has no entrypoint: {name}")
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
func = _load_callable(spec.entrypoint)
|
|
458
|
+
command_argv = _argv_for_spec(
|
|
459
|
+
spec,
|
|
460
|
+
argv,
|
|
461
|
+
project_root=root,
|
|
462
|
+
framework_root=framework,
|
|
463
|
+
)
|
|
464
|
+
except Exception as exc: # noqa: BLE001 -- produce CLI-shaped failure
|
|
465
|
+
return CommandResult(2, "", f"{type(exc).__name__}: {exc}")
|
|
466
|
+
|
|
467
|
+
previous_cwd = Path.cwd()
|
|
468
|
+
cwd = root if spec.cwd == "project" else framework
|
|
469
|
+
stdout = io.StringIO()
|
|
470
|
+
stderr = io.StringIO()
|
|
471
|
+
try:
|
|
472
|
+
os.chdir(cwd)
|
|
473
|
+
if capture:
|
|
474
|
+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
475
|
+
code = _invoke(func, command_argv, no_argv=spec.no_argv)
|
|
476
|
+
else:
|
|
477
|
+
code = _invoke(func, command_argv, no_argv=spec.no_argv)
|
|
478
|
+
except Exception as exc: # noqa: BLE001 -- dispatcher is a command boundary
|
|
479
|
+
print(f"{type(exc).__name__}: {exc}", file=stderr)
|
|
480
|
+
code = 2
|
|
481
|
+
finally:
|
|
482
|
+
os.chdir(previous_cwd)
|
|
483
|
+
return CommandResult(code, stdout.getvalue(), stderr.getvalue())
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def main(argv: list[str] | None = None) -> int:
|
|
487
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
488
|
+
if not args or args[0] in {"-h", "--help", "help"}:
|
|
489
|
+
print("Usage: framework_commands.py <verb> [args...]")
|
|
490
|
+
print()
|
|
491
|
+
print("Available framework verbs:")
|
|
492
|
+
for name in available_commands():
|
|
493
|
+
print(f" {name}")
|
|
494
|
+
return 0
|
|
495
|
+
command, *rest = args
|
|
496
|
+
result = run_framework_command(command, rest)
|
|
497
|
+
if result.stdout:
|
|
498
|
+
print(result.stdout, end="")
|
|
499
|
+
if result.stderr:
|
|
500
|
+
print(result.stderr, end="", file=sys.stderr)
|
|
501
|
+
return result.code
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
if __name__ == "__main__":
|
|
505
|
+
raise SystemExit(main())
|