@deftai/directive-content 0.58.0 → 0.60.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-push +10 -9
- package/Taskfile.yml +57 -67
- package/UPGRADING.md +1 -1
- 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/rules/rules-pack-0.1.json +3 -3
- package/packs/skills/skills-pack-0.1.json +22 -22
- package/scm/github.md +20 -2
- 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 +16 -0
- package/tasks/relocate.yml +18 -48
- package/tasks/toolchain.yml +15 -5
- package/tasks/vbrief.yml +4 -3
- package/tasks/verify.yml +12 -14
- package/templates/agents-entry.md +1 -2
- 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 -2551
- 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/build_dist.py
DELETED
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""build_dist.py -- cross-platform release-archive builder (#736).
|
|
3
|
-
|
|
4
|
-
Replaces the prior ``tasks/core.yml::build`` shape which dispatched
|
|
5
|
-
``tar -czf ... --exclude=...`` on Linux / macOS but
|
|
6
|
-
``Compress-Archive -Path . -DestinationPath dist\\deft-X.Y.Z.zip -Force``
|
|
7
|
-
on Windows. The Windows branch had no exclude list, so the resulting zip
|
|
8
|
-
included ``.git/`` (often hundreds of MB of history), ``backup/``,
|
|
9
|
-
``node_modules/``, AND -- because ``Compress-Archive -Path .`` walks the
|
|
10
|
-
output directory recursively -- the prior ``dist/`` artifact, leading to
|
|
11
|
-
unbounded growth on every re-run.
|
|
12
|
-
|
|
13
|
-
The fix is a single Python helper using stdlib ``tarfile`` and ``zipfile``
|
|
14
|
-
with one canonical exclude list, dispatched as a single command from
|
|
15
|
-
``tasks/core.yml::build`` (no platform split). Format selection is
|
|
16
|
-
data-driven by ``sys.platform`` (overridable via ``--format`` for tests).
|
|
17
|
-
|
|
18
|
-
The script is intentionally stdlib-only -- it ships with the framework
|
|
19
|
-
distribution itself, so any external dependency would be a chicken-and-egg
|
|
20
|
-
problem during release.
|
|
21
|
-
|
|
22
|
-
Windows installer manifest resources (#1441)
|
|
23
|
-
--------------------------------------------
|
|
24
|
-
This script packages the *framework* archive; it does NOT build the Go
|
|
25
|
-
``deft-install`` binaries (the release workflow's ``go build`` matrix does).
|
|
26
|
-
The Windows binaries embed an ``asInvoker`` application manifest so Windows'
|
|
27
|
-
installer-detection heuristic does not auto-elevate the ``install-*.exe``
|
|
28
|
-
asset (which would pop a UAC prompt and break headless ``--yes`` runs). The
|
|
29
|
-
manifest is carried by the committed per-arch resource objects
|
|
30
|
-
``cmd/deft-install/resource_windows_{amd64,arm64}.syso``; ``go build`` links
|
|
31
|
-
them automatically for ``GOOS=windows`` and ignores them elsewhere, so no
|
|
32
|
-
step here (and no extra release tooling) is required. To regenerate them
|
|
33
|
-
after editing ``cmd/deft-install/deft-install.manifest`` or
|
|
34
|
-
``versioninfo.json``, run ``go generate ./cmd/deft-install/``.
|
|
35
|
-
|
|
36
|
-
Usage
|
|
37
|
-
-----
|
|
38
|
-
uv run python scripts/build_dist.py --version 0.22.0
|
|
39
|
-
uv run python scripts/build_dist.py --version 0.22.0 --format zip
|
|
40
|
-
uv run python scripts/build_dist.py --version 0.22.0 \\
|
|
41
|
-
--exclude-extra .venv,htmlcov
|
|
42
|
-
|
|
43
|
-
Exit codes
|
|
44
|
-
----------
|
|
45
|
-
0 -- archive written
|
|
46
|
-
1 -- runtime failure (filesystem error, archive write failure)
|
|
47
|
-
2 -- configuration error (missing --version / missing root)
|
|
48
|
-
|
|
49
|
-
Refs #736.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
from __future__ import annotations
|
|
53
|
-
|
|
54
|
-
import argparse
|
|
55
|
-
import os
|
|
56
|
-
import re
|
|
57
|
-
import sys
|
|
58
|
-
import tarfile
|
|
59
|
-
import zipfile
|
|
60
|
-
from pathlib import Path
|
|
61
|
-
|
|
62
|
-
# Make sibling helpers importable both when run as __main__ and when imported
|
|
63
|
-
# by tests via importlib.util.spec_from_file_location.
|
|
64
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
65
|
-
|
|
66
|
-
from _stdio_utf8 import reconfigure_stdio # noqa: E402
|
|
67
|
-
|
|
68
|
-
reconfigure_stdio()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# ---- Constants --------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
# Canonical exclude list. Top-level directory names that MUST never be in
|
|
74
|
-
# the archive per the #736 acceptance criteria + the broader .gitignore
|
|
75
|
-
# conventions in this repo. The match is purely by path-component name --
|
|
76
|
-
# any directory anywhere under the root whose basename matches one of these
|
|
77
|
-
# is pruned during the os.walk traversal.
|
|
78
|
-
#
|
|
79
|
-
# The first four entries (.git, dist, backup, node_modules) mirror the
|
|
80
|
-
# previous Linux/macOS tar exclude list verbatim so the cross-platform
|
|
81
|
-
# parity test in tests/content/test_taskfile_zip_parity.py can assert the
|
|
82
|
-
# exclude set is preserved as the task contract changes shape. The
|
|
83
|
-
# additional entries (__pycache__ ... .ruff_cache) extend the list for
|
|
84
|
-
# Python-tooling artifacts that appear in development checkouts and would
|
|
85
|
-
# otherwise bloat the archive.
|
|
86
|
-
DEFAULT_EXCLUDES: tuple[str, ...] = (
|
|
87
|
-
".git",
|
|
88
|
-
"dist",
|
|
89
|
-
"backup",
|
|
90
|
-
"node_modules",
|
|
91
|
-
"__pycache__",
|
|
92
|
-
".venv",
|
|
93
|
-
"htmlcov",
|
|
94
|
-
".pytest_cache",
|
|
95
|
-
".mypy_cache",
|
|
96
|
-
".ruff_cache",
|
|
97
|
-
".coverage",
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
# Repo-relative prefixes that should not be shipped in consumer framework
|
|
101
|
-
# payloads. These are project-management / forensic artifacts for this source
|
|
102
|
-
# repository, not runtime framework files. Keep vbrief/schemas/** and other
|
|
103
|
-
# runtime surfaces by pruning only the historical lifecycle folders.
|
|
104
|
-
DEFAULT_EXCLUDED_PATH_PREFIXES: tuple[str, ...] = (
|
|
105
|
-
"history/archive",
|
|
106
|
-
"vbrief/completed",
|
|
107
|
-
"vbrief/cancelled",
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
EXIT_OK = 0
|
|
111
|
-
EXIT_RUNTIME_ERROR = 1
|
|
112
|
-
EXIT_CONFIG_ERROR = 2
|
|
113
|
-
|
|
114
|
-
# Vendored TypeScript engine test-file exclusion (#1878). The dist artifact
|
|
115
|
-
# ships the TypeScript engine sources under packages/{cli,core}/. The engine's
|
|
116
|
-
# own *.test.* / *.spec.* files would be discovered by a vitest-based consumer's
|
|
117
|
-
# default include glob (**/*.{test,spec}.?(c|m)[jt]s?(x)) and fail their CI, so
|
|
118
|
-
# they are excluded from the produced artifact -- keeping the tarball/zip
|
|
119
|
-
# consistent with the installer-side prune (pruneVendoredTSTests in
|
|
120
|
-
# cmd/deft-install/deposit.go). Only test SOURCE files are dropped; non-test
|
|
121
|
-
# engine sources (e.g. index.ts) are retained.
|
|
122
|
-
_VENDORED_TS_TEST_RE = re.compile(r"(?i)\.(test|spec)\.(c|m)?[jt]sx?$")
|
|
123
|
-
|
|
124
|
-
# Archive root directory inside the produced artifact. Consumers extracting
|
|
125
|
-
# the tarball / zip get a single top-level ``deft/`` directory rather than
|
|
126
|
-
# a sea of top-level files, matching the previous tar(1) behaviour where
|
|
127
|
-
# ``tar -czf foo.tar.gz .`` produces ``./`` entries that extract into the
|
|
128
|
-
# current directory but most tools display as ``./<name>``.
|
|
129
|
-
ARCHIVE_ROOT = "deft"
|
|
130
|
-
|
|
131
|
-
# C1 flatten (#1875 / #1669 Wave-1 LockedDecisions). The #1875 move relocated
|
|
132
|
-
# every shippable asset under a single ``content/`` root in the SOURCE repo.
|
|
133
|
-
# The consumer-facing deposit layout (``.deft/core/<x>``) MUST stay byte-stable,
|
|
134
|
-
# so the archive strips the ``content/`` prefix when packaging: a source file at
|
|
135
|
-
# ``content/coding/coding.md`` ships as ``deft/coding/coding.md`` and deposits to
|
|
136
|
-
# ``.deft/core/coding/coding.md`` exactly as before the move. Non-content entries
|
|
137
|
-
# (engine / harness / repo-dev) and the named root harness-entry files
|
|
138
|
-
# (AGENTS.md / main.md / SKILL.md / REFERENCES.md) are unaffected. This is the
|
|
139
|
-
# single flatten point -- the Go installer (cmd/deft-install) needs no change.
|
|
140
|
-
CONTENT_PREFIX = "content/"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _flatten_content_prefix(rel_posix: str) -> str:
|
|
144
|
-
"""Strip the leading ``content/`` prefix so the deposit stays byte-stable."""
|
|
145
|
-
if rel_posix == "content":
|
|
146
|
-
return rel_posix
|
|
147
|
-
if rel_posix.startswith(CONTENT_PREFIX):
|
|
148
|
-
return rel_posix[len(CONTENT_PREFIX) :]
|
|
149
|
-
return rel_posix
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# ---- Path filtering ---------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _iter_source_files(
|
|
156
|
-
root: Path,
|
|
157
|
-
excludes: frozenset[str],
|
|
158
|
-
excluded_prefixes: tuple[str, ...] = DEFAULT_EXCLUDED_PATH_PREFIXES,
|
|
159
|
-
) -> list[tuple[Path, str]]:
|
|
160
|
-
"""Return a sorted list of ``(absolute_path, archive_relative_posix)``.
|
|
161
|
-
|
|
162
|
-
Walks ``root`` skipping any directory or file whose basename matches
|
|
163
|
-
an entry in ``excludes``. Returns deterministic ordering so the
|
|
164
|
-
produced archive is reproducible across runs and across platforms
|
|
165
|
-
(the task contract ``test_idempotent_rerun`` depends on this).
|
|
166
|
-
|
|
167
|
-
Three pruning paths apply:
|
|
168
|
-
|
|
169
|
-
1. Directory pruning -- ``dirnames`` is mutated in place so any
|
|
170
|
-
directory whose basename matches an exclude is skipped along with
|
|
171
|
-
its entire subtree. The dist/ output dir is implicitly pruned by
|
|
172
|
-
being in the canonical exclude list, which delivers the
|
|
173
|
-
idempotency guarantee called out in the #736 acceptance criteria.
|
|
174
|
-
2. File pruning -- bare filenames whose basename matches an exclude
|
|
175
|
-
(e.g. ``.coverage`` is written as a single regular file at the
|
|
176
|
-
repo root by coverage.py, NOT a directory) are skipped. Without
|
|
177
|
-
this branch the directory-only prune would silently fail to honor
|
|
178
|
-
the documented intent for file-shaped artifacts (Greptile P1
|
|
179
|
-
review on PR #773).
|
|
180
|
-
3. Path-prefix pruning -- directories and files whose repo-relative POSIX
|
|
181
|
-
path starts with an entry in ``excluded_prefixes`` are dropped, keeping
|
|
182
|
-
consumer archives free of source-repo forensic history while preserving
|
|
183
|
-
runtime siblings such as ``vbrief/schemas/**``.
|
|
184
|
-
4. Vendored TS test-file pruning -- files under ``packages/`` whose basename
|
|
185
|
-
matches the vitest test glob (``*.test.*`` / ``*.spec.*``) are dropped so
|
|
186
|
-
a vitest-based consumer never discovers the framework's own tests, while
|
|
187
|
-
non-test engine sources are retained (#1878).
|
|
188
|
-
"""
|
|
189
|
-
entries: list[tuple[Path, str]] = []
|
|
190
|
-
for dirpath, dirnames, filenames in os.walk(root):
|
|
191
|
-
# Mutate dirnames in place to prune the walk -- canonical os.walk
|
|
192
|
-
# idiom. Sort for determinism.
|
|
193
|
-
kept_dirnames: list[str] = []
|
|
194
|
-
for dirname in sorted(dirnames):
|
|
195
|
-
if dirname in excludes:
|
|
196
|
-
continue
|
|
197
|
-
child = Path(dirpath) / dirname
|
|
198
|
-
try:
|
|
199
|
-
child_rel = child.relative_to(root).as_posix()
|
|
200
|
-
except ValueError:
|
|
201
|
-
continue
|
|
202
|
-
if _matches_excluded_prefix(child_rel, excluded_prefixes):
|
|
203
|
-
continue
|
|
204
|
-
kept_dirnames.append(dirname)
|
|
205
|
-
dirnames[:] = kept_dirnames
|
|
206
|
-
for fname in sorted(filenames):
|
|
207
|
-
if fname in excludes:
|
|
208
|
-
# File-level pruning -- catches single-file artifacts
|
|
209
|
-
# like .coverage that os.walk surfaces in `filenames`,
|
|
210
|
-
# not `dirnames`.
|
|
211
|
-
continue
|
|
212
|
-
abs_path = Path(dirpath) / fname
|
|
213
|
-
try:
|
|
214
|
-
rel = abs_path.relative_to(root)
|
|
215
|
-
except ValueError:
|
|
216
|
-
# Defensive: os.walk should never yield a path outside
|
|
217
|
-
# root, but symlinked traversals can in theory.
|
|
218
|
-
continue
|
|
219
|
-
rel_posix = rel.as_posix()
|
|
220
|
-
if _matches_excluded_prefix(rel_posix, excluded_prefixes):
|
|
221
|
-
continue
|
|
222
|
-
if _is_vendored_ts_test(rel_posix):
|
|
223
|
-
# Drop the vendored TS engine's own test files so a
|
|
224
|
-
# vitest-based consumer does not discover and fail on them,
|
|
225
|
-
# mirroring the installer-side prune (#1878).
|
|
226
|
-
continue
|
|
227
|
-
# C1 flatten: ship content/<x> as <x> so the deposit is byte-stable.
|
|
228
|
-
entries.append((abs_path, _flatten_content_prefix(rel_posix)))
|
|
229
|
-
return entries
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _matches_excluded_prefix(rel_posix: str, prefixes: tuple[str, ...]) -> bool:
|
|
233
|
-
"""Return True when ``rel_posix`` is at or below an excluded path prefix."""
|
|
234
|
-
return any(rel_posix == prefix or rel_posix.startswith(f"{prefix}/") for prefix in prefixes)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def _is_vendored_ts_test(rel_posix: str) -> bool:
|
|
238
|
-
"""Return True for a vendored TypeScript engine test SOURCE file (#1878).
|
|
239
|
-
|
|
240
|
-
True only when the repo-relative POSIX path is under ``packages/`` AND its
|
|
241
|
-
basename matches the vitest-discoverable test glob (``*.test.*`` /
|
|
242
|
-
``*.spec.*`` with a ``[jt]s``/``x``/``c``/``m`` extension). Non-test engine
|
|
243
|
-
sources under ``packages/`` (e.g. ``packages/core/src/index.ts``) return
|
|
244
|
-
False so only the framework's own tests are dropped from the artifact.
|
|
245
|
-
"""
|
|
246
|
-
if rel_posix != "packages" and not rel_posix.startswith("packages/"):
|
|
247
|
-
return False
|
|
248
|
-
basename = rel_posix.rsplit("/", 1)[-1]
|
|
249
|
-
return _VENDORED_TS_TEST_RE.search(basename) is not None
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
# ---- Archive writers --------------------------------------------------------
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def _write_tar_gz(root: Path, output: Path, entries: list[tuple[Path, str]]) -> int:
|
|
256
|
-
"""Write a gzipped tar archive of ``entries`` to ``output``.
|
|
257
|
-
|
|
258
|
-
Each entry is added under the ``ARCHIVE_ROOT`` prefix so extraction
|
|
259
|
-
yields a single top-level directory.
|
|
260
|
-
"""
|
|
261
|
-
output.parent.mkdir(parents=True, exist_ok=True)
|
|
262
|
-
count = 0
|
|
263
|
-
with tarfile.open(output, "w:gz") as tar:
|
|
264
|
-
for abs_path, rel in entries:
|
|
265
|
-
tar.add(abs_path, arcname=f"{ARCHIVE_ROOT}/{rel}", recursive=False)
|
|
266
|
-
count += 1
|
|
267
|
-
return count
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def _write_zip(root: Path, output: Path, entries: list[tuple[Path, str]]) -> int:
|
|
271
|
-
"""Write a deflate-compressed zip archive of ``entries`` to ``output``."""
|
|
272
|
-
output.parent.mkdir(parents=True, exist_ok=True)
|
|
273
|
-
count = 0
|
|
274
|
-
with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
275
|
-
for abs_path, rel in entries:
|
|
276
|
-
zf.write(abs_path, arcname=f"{ARCHIVE_ROOT}/{rel}")
|
|
277
|
-
count += 1
|
|
278
|
-
return count
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
# ---- Format / path resolution ----------------------------------------------
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
def select_format(arg: str | None) -> str:
|
|
285
|
-
"""Return the archive format token (``tar`` or ``zip``).
|
|
286
|
-
|
|
287
|
-
When ``arg`` is None the platform default applies: ``zip`` on Windows
|
|
288
|
-
(``sys.platform`` startswith ``win``), ``tar`` everywhere else.
|
|
289
|
-
"""
|
|
290
|
-
if arg:
|
|
291
|
-
return arg.lower()
|
|
292
|
-
if sys.platform.startswith("win"):
|
|
293
|
-
return "zip"
|
|
294
|
-
return "tar"
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def output_path(root: Path, version: str, fmt: str) -> Path:
|
|
298
|
-
"""Return the final artifact path for ``version`` + ``fmt`` under root."""
|
|
299
|
-
suffix = "zip" if fmt == "zip" else "tar.gz"
|
|
300
|
-
return root / "dist" / f"deft-{version}.{suffix}"
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
# ---- Public build entry point ----------------------------------------------
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def build(
|
|
307
|
-
root: Path,
|
|
308
|
-
version: str,
|
|
309
|
-
fmt: str,
|
|
310
|
-
extra_excludes: tuple[str, ...] = (),
|
|
311
|
-
) -> Path:
|
|
312
|
-
"""Produce the release archive and return its path.
|
|
313
|
-
|
|
314
|
-
Idempotency: the canonical exclude list contains ``dist`` so the
|
|
315
|
-
output directory is pruned during traversal -- a stale prior artifact
|
|
316
|
-
sitting at ``dist/deft-<version>.<ext>`` cannot be ingested into the
|
|
317
|
-
new archive. As a belt-and-suspenders guard we also unlink the target
|
|
318
|
-
output file if it already exists so the new archive is fresh.
|
|
319
|
-
"""
|
|
320
|
-
excludes = frozenset((*DEFAULT_EXCLUDES, *extra_excludes))
|
|
321
|
-
output = output_path(root, version, fmt)
|
|
322
|
-
if output.exists():
|
|
323
|
-
output.unlink()
|
|
324
|
-
entries = _iter_source_files(root, excludes)
|
|
325
|
-
if fmt == "zip":
|
|
326
|
-
_write_zip(root, output, entries)
|
|
327
|
-
else:
|
|
328
|
-
_write_tar_gz(root, output, entries)
|
|
329
|
-
return output
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# ---- CLI --------------------------------------------------------------------
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def _build_parser() -> argparse.ArgumentParser:
|
|
336
|
-
parser = argparse.ArgumentParser(
|
|
337
|
-
prog="build_dist.py",
|
|
338
|
-
description=(
|
|
339
|
-
"Build a cross-platform release archive (#736). Default format "
|
|
340
|
-
"is tar.gz on Linux/macOS and zip on Windows; override with "
|
|
341
|
-
"--format. Excludes .git, dist, backup, node_modules, and "
|
|
342
|
-
"Python-tooling caches by default."
|
|
343
|
-
),
|
|
344
|
-
)
|
|
345
|
-
parser.add_argument(
|
|
346
|
-
"--version",
|
|
347
|
-
required=True,
|
|
348
|
-
help=(
|
|
349
|
-
"Version string used as the archive filename suffix "
|
|
350
|
-
"(e.g. 0.22.0). Passed by tasks/core.yml::build via "
|
|
351
|
-
"{{.VERSION}}, which itself resolves through "
|
|
352
|
-
"scripts/resolve_version.py's priority chain."
|
|
353
|
-
),
|
|
354
|
-
)
|
|
355
|
-
parser.add_argument(
|
|
356
|
-
"--format",
|
|
357
|
-
choices=("tar", "zip"),
|
|
358
|
-
default=None,
|
|
359
|
-
help=(
|
|
360
|
-
"Archive format override. tar=tar.gz, zip=zip. Default is "
|
|
361
|
-
"platform-driven (zip on Windows, tar.gz elsewhere)."
|
|
362
|
-
),
|
|
363
|
-
)
|
|
364
|
-
parser.add_argument(
|
|
365
|
-
"--root",
|
|
366
|
-
type=Path,
|
|
367
|
-
default=None,
|
|
368
|
-
help=("Repository root to package (default: parent of the scripts/ directory)."),
|
|
369
|
-
)
|
|
370
|
-
parser.add_argument(
|
|
371
|
-
"--exclude-extra",
|
|
372
|
-
default="",
|
|
373
|
-
help=(
|
|
374
|
-
"Comma-separated extra directory basenames to exclude in "
|
|
375
|
-
"addition to the canonical list."
|
|
376
|
-
),
|
|
377
|
-
)
|
|
378
|
-
return parser
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def _parse_extras(raw: str) -> tuple[str, ...]:
|
|
382
|
-
"""Split ``raw`` (comma-separated) into a tuple, stripping empties."""
|
|
383
|
-
return tuple(p.strip() for p in raw.split(",") if p.strip())
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
def main(argv: list[str] | None = None) -> int:
|
|
387
|
-
parser = _build_parser()
|
|
388
|
-
args = parser.parse_args(argv)
|
|
389
|
-
if not args.version:
|
|
390
|
-
print("error: --version is required", file=sys.stderr)
|
|
391
|
-
return EXIT_CONFIG_ERROR
|
|
392
|
-
root = (args.root or Path(__file__).resolve().parent.parent).resolve()
|
|
393
|
-
if not root.is_dir():
|
|
394
|
-
print(f"error: root not found: {root}", file=sys.stderr)
|
|
395
|
-
return EXIT_CONFIG_ERROR
|
|
396
|
-
fmt = select_format(args.format)
|
|
397
|
-
extras = _parse_extras(args.exclude_extra)
|
|
398
|
-
try:
|
|
399
|
-
out = build(root, args.version, fmt, extras)
|
|
400
|
-
except OSError as exc:
|
|
401
|
-
print(f"error: {exc}", file=sys.stderr)
|
|
402
|
-
return EXIT_RUNTIME_ERROR
|
|
403
|
-
try:
|
|
404
|
-
printable = out.relative_to(root)
|
|
405
|
-
except ValueError:
|
|
406
|
-
printable = out
|
|
407
|
-
print(f"Created {printable}")
|
|
408
|
-
return EXIT_OK
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if __name__ == "__main__":
|
|
412
|
-
sys.exit(main())
|