@deftai/directive-content 0.55.1 → 0.56.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.
Files changed (220) hide show
  1. package/.githooks/pre-commit +143 -0
  2. package/.githooks/pre-push +121 -0
  3. package/QUICK-START.md +13 -3
  4. package/Taskfile.yml +934 -0
  5. package/UPGRADING.md +82 -11
  6. package/events/README.md +3 -3
  7. package/package.json +5 -4
  8. package/packs/skills/skills-pack-0.1.json +22 -22
  9. package/scripts/_agents_md.py +494 -0
  10. package/scripts/_cache_fetch.py +635 -0
  11. package/scripts/_cache_quota.py +529 -0
  12. package/scripts/_cache_refresh.py +163 -0
  13. package/scripts/_cache_validate.py +209 -0
  14. package/scripts/_content_root.py +42 -0
  15. package/scripts/_doctor_state.py +277 -0
  16. package/scripts/_event_detect.py +305 -0
  17. package/scripts/_events.py +514 -0
  18. package/scripts/_lifecycle_hygiene.py +568 -0
  19. package/scripts/_pathspec.py +91 -0
  20. package/scripts/_policy_show_cli.py +266 -0
  21. package/scripts/_precutover.py +92 -0
  22. package/scripts/_project_context.py +224 -0
  23. package/scripts/_project_definition_io.py +164 -0
  24. package/scripts/_relocate_snapshot.py +209 -0
  25. package/scripts/_relocate_states.py +343 -0
  26. package/scripts/_resolve_preflight_path.py +152 -0
  27. package/scripts/_safe_subprocess.py +167 -0
  28. package/scripts/_session_start_hook.py +205 -0
  29. package/scripts/_sor_gate_diff.py +365 -0
  30. package/scripts/_stdio_utf8.py +59 -0
  31. package/scripts/_triage_bootstrap_gitignore.py +904 -0
  32. package/scripts/_triage_classify_cli.py +122 -0
  33. package/scripts/_triage_queue_cli.py +625 -0
  34. package/scripts/_triage_scope_cli.py +343 -0
  35. package/scripts/_triage_scope_drift_cli.py +121 -0
  36. package/scripts/_triage_scope_ignores.py +286 -0
  37. package/scripts/_triage_scope_milestone.py +432 -0
  38. package/scripts/_triage_scope_mutations.py +337 -0
  39. package/scripts/_triage_scope_renderers.py +207 -0
  40. package/scripts/_triage_smoketest_stages.py +674 -0
  41. package/scripts/_triage_subscribe_cli.py +140 -0
  42. package/scripts/_triage_welcome_cli.py +421 -0
  43. package/scripts/_vbrief_build.py +239 -0
  44. package/scripts/_vbrief_fidelity.py +479 -0
  45. package/scripts/_vbrief_legacy.py +589 -0
  46. package/scripts/_vbrief_reconciliation.py +883 -0
  47. package/scripts/_vbrief_routing.py +277 -0
  48. package/scripts/_vbrief_safety.py +778 -0
  49. package/scripts/_vbrief_sources.py +312 -0
  50. package/scripts/_vbrief_speckit.py +262 -0
  51. package/scripts/_vbrief_story_quality.py +353 -0
  52. package/scripts/_vbrief_validation.py +299 -0
  53. package/scripts/build_dist.py +412 -0
  54. package/scripts/cache.py +1078 -0
  55. package/scripts/cache_scanner.py +745 -0
  56. package/scripts/candidates_log.py +432 -0
  57. package/scripts/capacity_backfill.py +680 -0
  58. package/scripts/capacity_show.py +653 -0
  59. package/scripts/ci_local.py +689 -0
  60. package/scripts/code_structure_validate.py +765 -0
  61. package/scripts/codebase_default_extractor.py +495 -0
  62. package/scripts/codebase_map.py +304 -0
  63. package/scripts/codebase_map_fresh.py +104 -0
  64. package/scripts/codebase_projection_registry.py +94 -0
  65. package/scripts/codebase_provider.py +582 -0
  66. package/scripts/doctor.py +2257 -0
  67. package/scripts/framework_commands.py +505 -0
  68. package/scripts/gh_rest.py +882 -0
  69. package/scripts/github_auth_modes.py +437 -0
  70. package/scripts/github_body.py +292 -0
  71. package/scripts/ip_risk.py +531 -0
  72. package/scripts/issue_emit.py +670 -0
  73. package/scripts/issue_ingest.py +1064 -0
  74. package/scripts/migrate_preflight.py +418 -0
  75. package/scripts/migrate_vbrief.py +2677 -0
  76. package/scripts/monitor_pr.py +401 -0
  77. package/scripts/pack_migrate_lessons.py +336 -0
  78. package/scripts/pack_migrate_patterns.py +254 -0
  79. package/scripts/pack_migrate_rules.py +350 -0
  80. package/scripts/pack_migrate_skills.py +423 -0
  81. package/scripts/pack_migrate_strategies.py +311 -0
  82. package/scripts/pack_migrate_swarm_spec.py +250 -0
  83. package/scripts/pack_render.py +434 -0
  84. package/scripts/packs_slice.py +712 -0
  85. package/scripts/platform_capabilities.py +336 -0
  86. package/scripts/policy.py +2826 -0
  87. package/scripts/policy_set.py +324 -0
  88. package/scripts/pr_check_closing_keywords.py +524 -0
  89. package/scripts/pr_check_protected_issues.py +267 -0
  90. package/scripts/pr_merge_readiness.py +1004 -0
  91. package/scripts/pr_wait_mergeable.py +669 -0
  92. package/scripts/prd_render.py +159 -0
  93. package/scripts/preflight_architecture_sor.py +974 -0
  94. package/scripts/preflight_branch.py +289 -0
  95. package/scripts/preflight_cache.py +974 -0
  96. package/scripts/preflight_gh.py +721 -0
  97. package/scripts/preflight_implementation.py +272 -0
  98. package/scripts/preflight_story_start.py +838 -0
  99. package/scripts/preflight_wip_cap.py +149 -0
  100. package/scripts/probe_session.py +545 -0
  101. package/scripts/project_render.py +293 -0
  102. package/scripts/quarantine_ext.py +237 -0
  103. package/scripts/reconcile_issues.py +1442 -0
  104. package/scripts/refresh-path.ps1 +107 -0
  105. package/scripts/release.py +2030 -0
  106. package/scripts/release_e2e.py +1011 -0
  107. package/scripts/release_publish.py +486 -0
  108. package/scripts/release_rollback.py +980 -0
  109. package/scripts/relocate.py +1034 -0
  110. package/scripts/resolve_changelog_unreleased.py +667 -0
  111. package/scripts/resolve_version.py +490 -0
  112. package/scripts/resume_conditions.py +706 -0
  113. package/scripts/ritual_sentinel.py +609 -0
  114. package/scripts/roadmap_render.py +635 -0
  115. package/scripts/rule_ownership_lint.py +325 -0
  116. package/scripts/scm.py +591 -0
  117. package/scripts/scope_audit_log.py +387 -0
  118. package/scripts/scope_decompose.py +654 -0
  119. package/scripts/scope_demote.py +509 -0
  120. package/scripts/scope_lifecycle.py +1126 -0
  121. package/scripts/scope_undo.py +772 -0
  122. package/scripts/session_start.py +406 -0
  123. package/scripts/setup_ghx.py +339 -0
  124. package/scripts/setup_windows.ps1 +220 -0
  125. package/scripts/slice_audit.py +585 -0
  126. package/scripts/slice_record.py +530 -0
  127. package/scripts/slice_record_existing.py +692 -0
  128. package/scripts/slug_normalize.py +178 -0
  129. package/scripts/spec_render.py +477 -0
  130. package/scripts/spec_validate.py +238 -0
  131. package/scripts/subagent_monitor.py +658 -0
  132. package/scripts/swarm_complete_cohort.py +644 -0
  133. package/scripts/swarm_launch.py +1206 -0
  134. package/scripts/swarm_readiness.py +554 -0
  135. package/scripts/swarm_verify_review_clean.py +438 -0
  136. package/scripts/swarm_worktrees.py +497 -0
  137. package/scripts/toolchain-check.py +52 -0
  138. package/scripts/triage_actions.py +871 -0
  139. package/scripts/triage_bootstrap.py +1153 -0
  140. package/scripts/triage_bulk.py +630 -0
  141. package/scripts/triage_classify.py +932 -0
  142. package/scripts/triage_help.py +1685 -0
  143. package/scripts/triage_queue.py +1944 -0
  144. package/scripts/triage_reconcile.py +581 -0
  145. package/scripts/triage_refresh.py +643 -0
  146. package/scripts/triage_scope.py +999 -0
  147. package/scripts/triage_scope_drift.py +575 -0
  148. package/scripts/triage_smoketest.py +396 -0
  149. package/scripts/triage_subscribe.py +399 -0
  150. package/scripts/triage_summary.py +1011 -0
  151. package/scripts/triage_welcome.py +1178 -0
  152. package/scripts/ts_check_lane.py +86 -0
  153. package/scripts/validate-links.py +64 -0
  154. package/scripts/validate_strategy_output.py +212 -0
  155. package/scripts/vbrief_activate.py +228 -0
  156. package/scripts/vbrief_migrate_conformance.py +368 -0
  157. package/scripts/vbrief_reconcile_graph.py +306 -0
  158. package/scripts/vbrief_reconcile_labels.py +460 -0
  159. package/scripts/vbrief_reconcile_umbrellas.py +741 -0
  160. package/scripts/vbrief_validate.py +1195 -0
  161. package/scripts/verify-stubs.py +61 -0
  162. package/scripts/verify_capacity.py +160 -0
  163. package/scripts/verify_encoding.py +699 -0
  164. package/scripts/verify_hooks_installed.py +206 -0
  165. package/scripts/verify_investigation.py +360 -0
  166. package/scripts/verify_judgment_gates.py +827 -0
  167. package/scripts/verify_no_task_runtime.py +171 -0
  168. package/scripts/verify_scm_boundary.py +509 -0
  169. package/scripts/verify_session_ritual.py +389 -0
  170. package/scripts/verify_tools.py +426 -0
  171. package/scripts/verify_vbrief_conformance.py +478 -0
  172. package/skills/deft-directive-swarm/SKILL.md +7 -26
  173. package/skills/deft-directive-sync/SKILL.md +1 -1
  174. package/tasks/architecture.yml +13 -0
  175. package/tasks/cache.yml +69 -0
  176. package/tasks/capacity.yml +38 -0
  177. package/tasks/change.yml +46 -0
  178. package/tasks/changelog.yml +24 -0
  179. package/tasks/ci.yml +49 -0
  180. package/tasks/codebase.yml +47 -0
  181. package/tasks/commit.yml +30 -0
  182. package/tasks/core.yml +126 -0
  183. package/tasks/deployments.yml +54 -0
  184. package/tasks/framework.yml +74 -0
  185. package/tasks/install.yml +60 -0
  186. package/tasks/issue.yml +50 -0
  187. package/tasks/migrate.yml +73 -0
  188. package/tasks/packs.yml +92 -0
  189. package/tasks/policy.yml +75 -0
  190. package/tasks/pr.yml +89 -0
  191. package/tasks/prd.yml +39 -0
  192. package/tasks/project.yml +27 -0
  193. package/tasks/reconcile.yml +32 -0
  194. package/tasks/relocate.yml +56 -0
  195. package/tasks/roadmap.yml +28 -0
  196. package/tasks/scm.yml +126 -0
  197. package/tasks/scope-undo.yml +36 -0
  198. package/tasks/scope.yml +141 -0
  199. package/tasks/session.yml +19 -0
  200. package/tasks/setup.yml +37 -0
  201. package/tasks/slice.yml +69 -0
  202. package/tasks/spec.yml +41 -0
  203. package/tasks/swarm.yml +85 -0
  204. package/tasks/toolchain.yml +13 -0
  205. package/tasks/triage-actions.yml +94 -0
  206. package/tasks/triage-bootstrap.yml +43 -0
  207. package/tasks/triage-bulk.yml +75 -0
  208. package/tasks/triage-classify.yml +30 -0
  209. package/tasks/triage-queue.yml +50 -0
  210. package/tasks/triage-reconcile.yml +29 -0
  211. package/tasks/triage-scope-drift.yml +29 -0
  212. package/tasks/triage-scope.yml +31 -0
  213. package/tasks/triage-smoketest.yml +33 -0
  214. package/tasks/triage-subscribe.yml +36 -0
  215. package/tasks/triage-summary.yml +29 -0
  216. package/tasks/triage-welcome.yml +32 -0
  217. package/tasks/ts.yml +328 -0
  218. package/tasks/vbrief.yml +206 -0
  219. package/tasks/verify.yml +292 -0
  220. package/templates/agents-entry.md +2 -2
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env sh
2
+ # .githooks/pre-commit -- detection-bound branch-protection gate (#747).
3
+ #
4
+ # Activated by `git config core.hooksPath .githooks` -- idempotent setup
5
+ # performed by `task setup` (the directive repo itself) OR by the deft
6
+ # installer, which copies this hook to the consumer root .githooks/ and sets
7
+ # core.hooksPath for vendored consumer projects (#1463). Verify with:
8
+ #
9
+ # task verify:hooks-installed
10
+ #
11
+ # Pure POSIX-shell so this runs from MSYS / Git for Windows without bash
12
+ # being on PATH. The Python script itself is stdlib-only so it does NOT
13
+ # require `uv` or a virtualenv to be active.
14
+
15
+ # Resolve repo root (this hook lives at <core.hooksPath>/pre-commit).
16
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
17
+ if [ -z "$REPO_ROOT" ]; then
18
+ echo "deft pre-commit: unable to resolve repo root via 'git rev-parse'." >&2
19
+ exit 1
20
+ fi
21
+
22
+ # Layout-aware helper resolution (#1463). In the directive repo the gate
23
+ # scripts live at $REPO_ROOT/scripts/; in a vendored consumer the framework
24
+ # payload (and its scripts/) lives at $REPO_ROOT/.deft/core/ (canonical) or
25
+ # $REPO_ROOT/deft/ (legacy). Because the installer copies this hook to the
26
+ # consumer root .githooks/, it MUST locate the helpers relative to the install
27
+ # root rather than assuming $REPO_ROOT/scripts/. Probe the known layouts and
28
+ # use the first that resolves so the hook works in BOTH layouts.
29
+ SCRIPTS_DIR=""
30
+ for candidate in "$REPO_ROOT/scripts" "$REPO_ROOT/.deft/core/scripts" "$REPO_ROOT/deft/scripts"; do
31
+ if [ -f "$candidate/preflight_branch.py" ]; then
32
+ SCRIPTS_DIR="$candidate"
33
+ break
34
+ fi
35
+ done
36
+ if [ -z "$SCRIPTS_DIR" ]; then
37
+ echo "deft pre-commit: unable to locate the deft scripts directory." >&2
38
+ echo " Looked in scripts/, .deft/core/scripts/, deft/scripts/ under $REPO_ROOT." >&2
39
+ echo " Re-run the deft installer or 'task setup' to wire the hooks correctly." >&2
40
+ exit 1
41
+ fi
42
+
43
+ # Resolve a usable Python interpreter (#1668). On Windows `python3` is usually
44
+ # absent (only `python.exe` / the `py` launcher exist), and a bare `python3` on
45
+ # PATH is often the Microsoft Store App-Execution-Alias stub that prints
46
+ # "Python was not found" and exits non-zero. Probe DEFT_PYTHON -> python3 ->
47
+ # python -> the `py -3` launcher, validating each candidate by running
48
+ # `--version`, rejecting the alias stub, AND enforcing the framework's Python
49
+ # 3.11+ floor (#1676 -- framework scripts import `from datetime import UTC`,
50
+ # added in 3.11). PY_PREFIX carries the launcher selector (`-3`) when needed.
51
+ PYTHON_BIN=""
52
+ PY_PREFIX=""
53
+
54
+ # _deft_try BIN [PREFIX] -- probe a candidate. Succeeds (and sets PYTHON_BIN /
55
+ # PY_PREFIX) only when the candidate runs, is not the Windows alias stub, and
56
+ # reports a Python version >= 3.11.
57
+ _deft_try() {
58
+ _bin="$1"
59
+ _pre="${2:-}"
60
+ command -v "$_bin" >/dev/null 2>&1 || return 1
61
+ if [ -n "$_pre" ]; then
62
+ _out=$("$_bin" "$_pre" --version 2>&1) || return 1
63
+ else
64
+ _out=$("$_bin" --version 2>&1) || return 1
65
+ fi
66
+ case "$_out" in
67
+ *"Microsoft Store"*|*"was not found"*|*"execution alias"*) return 1 ;;
68
+ esac
69
+ case "$_out" in
70
+ *Python\ [0-9]*) ;;
71
+ *) return 1 ;;
72
+ esac
73
+ # Enforce the 3.11+ floor using POSIX parameter expansion (no sed/awk
74
+ # dependency). Parse major.minor from the "Python X.Y.Z" banner.
75
+ _rest=${_out#*Python }
76
+ _major=${_rest%%.*}
77
+ _rest=${_rest#*.}
78
+ _minor=${_rest%%.*}
79
+ case "$_major" in ''|*[!0-9]*) return 1 ;; esac
80
+ case "$_minor" in ''|*[!0-9]*) return 1 ;; esac
81
+ if [ "$_major" -gt 3 ]; then
82
+ PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
83
+ fi
84
+ if [ "$_major" -eq 3 ] && [ "$_minor" -ge 11 ]; then
85
+ PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
86
+ fi
87
+ return 1
88
+ }
89
+
90
+ if [ -n "${DEFT_PYTHON:-}" ]; then
91
+ # DEFT_PYTHON is the explicit override, but still version-checked so the
92
+ # hooks enforce the same 3.11+ floor as the installer (#1676).
93
+ _deft_try "$DEFT_PYTHON" || true
94
+ if [ -z "$PYTHON_BIN" ]; then
95
+ echo "deft pre-commit: DEFT_PYTHON ($DEFT_PYTHON) is not a usable Python 3.11+ interpreter." >&2
96
+ echo " Point DEFT_PYTHON at a Python 3.11+ executable." >&2
97
+ exit 1
98
+ fi
99
+ else
100
+ _deft_try python3 || _deft_try python || _deft_try py "-3" || true
101
+ fi
102
+
103
+ if [ -z "$PYTHON_BIN" ]; then
104
+ echo "deft pre-commit: no usable Python 3.11+ interpreter found." >&2
105
+ echo " Probed python3, python, and the 'py' launcher (rejecting the Windows" >&2
106
+ echo " App-Execution-Alias stub and interpreters older than 3.11)." >&2
107
+ echo " Set DEFT_PYTHON=<absolute python 3.11+ path> or install Python 3.11+." >&2
108
+ exit 1
109
+ fi
110
+
111
+ # deft_py forwards args to the resolved interpreter, preserving quoting for
112
+ # paths that may contain spaces (common on Windows).
113
+ deft_py() {
114
+ if [ -n "$PY_PREFIX" ]; then
115
+ "$PYTHON_BIN" "$PY_PREFIX" "$@"
116
+ else
117
+ "$PYTHON_BIN" "$@"
118
+ fi
119
+ }
120
+
121
+ # Step 1: branch-protection gate (#747).
122
+ deft_py "$SCRIPTS_DIR/preflight_branch.py" --project-root "$REPO_ROOT" || exit $?
123
+
124
+ # Step 2: PS 5.1 non-ASCII round-trip corruption gate (#798). Scans staged
125
+ # files for U+FFFD, CP1252/CP437-as-UTF-8 mojibake, and unexpected BOM.
126
+ # Three-state exit propagates: 0 allowed / 1 corruption blocked with diagnostic
127
+ # / 2 config error. Recurrence chain documented in scripts/verify_encoding.py.
128
+ deft_py "$SCRIPTS_DIR/verify_encoding.py" --project-root "$REPO_ROOT" --staged || exit $?
129
+
130
+ # Step 3: vBRIEF 0.6 conformance gate (#1620). Scans staged vbrief/**/*.vbrief.json
131
+ # for bare keys that are neither 0.6 spec-core nor x-directive/ / x-vbrief/
132
+ # namespaced. Three-state exit propagates: 0 clean / 1 bare key blocked with
133
+ # diagnostic / 2 config error. Rule body lives in scripts/verify_vbrief_conformance.py.
134
+ # Guarded on a present vbrief/ corpus so a fresh consumer repo that has not yet
135
+ # created the vBRIEF lifecycle folders is not blocked by the gate's intentional
136
+ # exit-2-on-missing-vbrief/ config-error contract. Also guarded on the gate
137
+ # script being present so a vendored consumer whose .deft/core/scripts/ payload
138
+ # predates this gate (stale-payload upgrade window) is not blocked by a missing
139
+ # script; payload-staleness is surfaced separately by `task doctor`.
140
+ if [ -d "$REPO_ROOT/vbrief" ] && [ -f "$SCRIPTS_DIR/verify_vbrief_conformance.py" ]; then
141
+ deft_py "$SCRIPTS_DIR/verify_vbrief_conformance.py" --project-root "$REPO_ROOT" --staged
142
+ exit $?
143
+ fi
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env sh
2
+ # .githooks/pre-push -- detection-bound branch-protection gate (#747).
3
+ #
4
+ # Activated by `git config core.hooksPath .githooks` -- idempotent setup
5
+ # performed by `task setup` (the directive repo itself) OR by the deft
6
+ # installer, which copies this hook to the consumer root .githooks/ and sets
7
+ # core.hooksPath for vendored consumer projects (#1463). Mirrors the
8
+ # pre-commit shape: pre-push defends against the case where a user opted out
9
+ # at commit time (e.g. DEFT_ALLOW_DEFAULT_BRANCH_COMMIT in a single shell)
10
+ # but still tries to push to the default branch in a fresh shell. The same
11
+ # scripts run in both hooks so behavior stays consistent.
12
+
13
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
14
+ if [ -z "$REPO_ROOT" ]; then
15
+ echo "deft pre-push: unable to resolve repo root via 'git rev-parse'." >&2
16
+ exit 1
17
+ fi
18
+
19
+ # Layout-aware helper resolution (#1463) -- see .githooks/pre-commit for the
20
+ # rationale. The installer copies this hook to the consumer root .githooks/,
21
+ # so the gate scripts must be located relative to the install root (own-repo
22
+ # scripts/, canonical .deft/core/scripts/, or legacy deft/scripts/) rather
23
+ # than assuming $REPO_ROOT/scripts/.
24
+ SCRIPTS_DIR=""
25
+ for candidate in "$REPO_ROOT/scripts" "$REPO_ROOT/.deft/core/scripts" "$REPO_ROOT/deft/scripts"; do
26
+ if [ -f "$candidate/preflight_branch.py" ]; then
27
+ SCRIPTS_DIR="$candidate"
28
+ break
29
+ fi
30
+ done
31
+ if [ -z "$SCRIPTS_DIR" ]; then
32
+ echo "deft pre-push: unable to locate the deft scripts directory." >&2
33
+ echo " Looked in scripts/, .deft/core/scripts/, deft/scripts/ under $REPO_ROOT." >&2
34
+ echo " Re-run the deft installer or 'task setup' to wire the hooks correctly." >&2
35
+ exit 1
36
+ fi
37
+
38
+ # Resolve a usable Python interpreter (#1668) -- see .githooks/pre-commit for
39
+ # the rationale. On Windows `python3` is usually absent (only `python.exe` /
40
+ # the `py` launcher exist), and a bare `python3` on PATH is often the Microsoft
41
+ # Store App-Execution-Alias stub. Probe DEFT_PYTHON -> python3 -> python -> the
42
+ # `py -3` launcher, validating each candidate by running `--version`, rejecting
43
+ # the alias stub, AND enforcing the framework's Python 3.11+ floor (#1676).
44
+ PYTHON_BIN=""
45
+ PY_PREFIX=""
46
+
47
+ # _deft_try BIN [PREFIX] -- probe a candidate. Succeeds (and sets PYTHON_BIN /
48
+ # PY_PREFIX) only when the candidate runs, is not the Windows alias stub, and
49
+ # reports a Python version >= 3.11.
50
+ _deft_try() {
51
+ _bin="$1"
52
+ _pre="${2:-}"
53
+ command -v "$_bin" >/dev/null 2>&1 || return 1
54
+ if [ -n "$_pre" ]; then
55
+ _out=$("$_bin" "$_pre" --version 2>&1) || return 1
56
+ else
57
+ _out=$("$_bin" --version 2>&1) || return 1
58
+ fi
59
+ case "$_out" in
60
+ *"Microsoft Store"*|*"was not found"*|*"execution alias"*) return 1 ;;
61
+ esac
62
+ case "$_out" in
63
+ *Python\ [0-9]*) ;;
64
+ *) return 1 ;;
65
+ esac
66
+ # Enforce the 3.11+ floor using POSIX parameter expansion (no sed/awk
67
+ # dependency). Parse major.minor from the "Python X.Y.Z" banner.
68
+ _rest=${_out#*Python }
69
+ _major=${_rest%%.*}
70
+ _rest=${_rest#*.}
71
+ _minor=${_rest%%.*}
72
+ case "$_major" in ''|*[!0-9]*) return 1 ;; esac
73
+ case "$_minor" in ''|*[!0-9]*) return 1 ;; esac
74
+ if [ "$_major" -gt 3 ]; then
75
+ PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
76
+ fi
77
+ if [ "$_major" -eq 3 ] && [ "$_minor" -ge 11 ]; then
78
+ PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
79
+ fi
80
+ return 1
81
+ }
82
+
83
+ if [ -n "${DEFT_PYTHON:-}" ]; then
84
+ # DEFT_PYTHON is the explicit override, but still version-checked so the
85
+ # hooks enforce the same 3.11+ floor as the installer (#1676).
86
+ _deft_try "$DEFT_PYTHON" || true
87
+ if [ -z "$PYTHON_BIN" ]; then
88
+ echo "deft pre-push: DEFT_PYTHON ($DEFT_PYTHON) is not a usable Python 3.11+ interpreter." >&2
89
+ echo " Point DEFT_PYTHON at a Python 3.11+ executable." >&2
90
+ exit 1
91
+ fi
92
+ else
93
+ _deft_try python3 || _deft_try python || _deft_try py "-3" || true
94
+ fi
95
+
96
+ if [ -z "$PYTHON_BIN" ]; then
97
+ echo "deft pre-push: no usable Python 3.11+ interpreter found." >&2
98
+ echo " Probed python3, python, and the 'py' launcher (rejecting the Windows" >&2
99
+ echo " App-Execution-Alias stub and interpreters older than 3.11)." >&2
100
+ echo " Set DEFT_PYTHON=<absolute python 3.11+ path> or install Python 3.11+." >&2
101
+ exit 1
102
+ fi
103
+
104
+ # deft_py forwards args to the resolved interpreter, preserving quoting for
105
+ # paths that may contain spaces (common on Windows).
106
+ deft_py() {
107
+ if [ -n "$PY_PREFIX" ]; then
108
+ "$PYTHON_BIN" "$PY_PREFIX" "$@"
109
+ else
110
+ "$PYTHON_BIN" "$@"
111
+ fi
112
+ }
113
+
114
+ deft_py "$SCRIPTS_DIR/preflight_branch.py" --project-root "$REPO_ROOT" || exit $?
115
+
116
+ # Step 2: destructive-gh-verb gate (#1019). Reads per-ref stdin lines from
117
+ # git pre-push (one line per ref: <local_ref> <local_oid> <remote_ref> <remote_oid>)
118
+ # and refuses pushes touching the default branch (force-push or otherwise).
119
+ # DEFT_ALLOW_DESTRUCTIVE_GH_VERBS=1 is the per-shell emergency bypass.
120
+ deft_py "$SCRIPTS_DIR/preflight_gh.py" --pre-push-stdin
121
+ exit $?
package/QUICK-START.md CHANGED
@@ -4,9 +4,19 @@ You are reading this because a user told you to, or because a stale `AGENTS.md`
4
4
 
5
5
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
6
 
7
- ## Installing Deft from GitHub (fetch-and-run one-liner)
7
+ ## Installing Deft (canonical npm)
8
8
 
9
- ! If Deft is **not yet installed** and you were told to "download and install Deft from GitHub into this directory," do NOT build from a source checkout and do NOT guess a path. Run the canonical per-platform fetch-and-run one-liner below. Each downloads the correct release binary from `https://github.com/deftai/directive/releases/latest/download/<asset>`, makes it executable, then runs it headless against the current directory (`--yes --repo-root . --json`).
9
+ ! If Deft is **not yet installed** and you were told to "install Deft into this directory," the canonical install is npm (Node 20 required):
10
+
11
+ ```bash
12
+ npm i -g @deftai/directive
13
+ ```
14
+
15
+ Once installed, run `directive` (or the `deft` alias) from your project directory. Continue with Step 1 below.
16
+
17
+ ### Legacy / offline install (frozen Go installer, #1912)
18
+
19
+ Node ≥ 20 is required to **run** Deft regardless of how it's installed — the Go installer only deposits files on disk, so install Node first ([nvm](https://github.com/nvm-sh/nvm) / your OS package manager / [nodejs.org](https://nodejs.org/)) and prefer the npm command above. Use the Go installer only for an **offline / air-gapped** deposit or to **migrate a legacy on-disk layout**. Do NOT build from a source checkout and do NOT guess a path. Each one-liner downloads the correct release binary from `releases/latest/download`, makes it executable, then runs it headless.
10
20
 
11
21
  **macOS (Intel + Apple Silicon):**
12
22
 
@@ -26,7 +36,7 @@ curl -fsSL -o /tmp/deft-install https://github.com/deftai/directive/releases/lat
26
36
  Invoke-WebRequest -Uri https://github.com/deftai/directive/releases/latest/download/install-windows-amd64.exe -OutFile $env:TEMP\deft-install.exe; & $env:TEMP\deft-install.exe --yes --repo-root . --json
27
37
  ```
28
38
 
29
- ⊗ Do NOT `go build` from a source checkout or a developer-specific path (e.g. a hardcoded `/Users/<name>/...` clone) — the canonical fetch path above never assumes a local clone. Once the installer finishes, continue with Step 1 below.
39
+ ⊗ Do NOT `go build` from a source checkout or a developer-specific path (e.g. a hardcoded `/Users/<name>/...` clone). Once the installer finishes, continue with Step 1 below.
30
40
 
31
41
  ## Step 1 — Who are you?
32
42