@deftai/directive-content 0.60.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.
@@ -1,143 +1,25 @@
1
1
  #!/usr/bin/env sh
2
- # .githooks/pre-commit -- detection-bound branch-protection gate (#747).
2
+ # .githooks/pre-commit -- deft CLI gates (#2049 / #747 / #798 / #1620).
3
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.
4
+ # Activated by `git config core.hooksPath .githooks` via `task setup` or the
5
+ # deft installer (#1463). Pure POSIX shell; requires `deft` on PATH (no Python).
14
6
 
15
- # Resolve repo root (this hook lives at <core.hooksPath>/pre-commit).
16
7
  REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
17
8
  if [ -z "$REPO_ROOT" ]; then
18
9
  echo "deft pre-commit: unable to resolve repo root via 'git rev-parse'." >&2
19
10
  exit 1
20
11
  fi
21
12
 
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
13
+ if ! command -v deft >/dev/null 2>&1; then
14
+ echo "deft pre-commit: 'deft' not found on PATH." >&2
15
+ echo " Install: npm i -g @deftai/directive" >&2
108
16
  exit 1
109
17
  fi
110
18
 
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 $?
19
+ deft verify:branch --project-root "$REPO_ROOT" || exit $?
123
20
 
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 $?
21
+ deft verify:encoding --staged --project-root "$REPO_ROOT" || exit $?
129
22
 
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 $?
23
+ if [ -d "$REPO_ROOT/vbrief" ]; then
24
+ deft verify:vbrief-conformance --staged --project-root "$REPO_ROOT" || exit $?
143
25
  fi
@@ -1,17 +1,9 @@
1
1
  #!/usr/bin/env sh
2
- # .githooks/pre-push -- refspec-aware default-branch push gate (#1019 / #1814).
2
+ # .githooks/pre-push -- refspec-aware default-branch push gate (#2049 / #1019 / #1814).
3
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).
8
- #
9
- # Pre-push does NOT invoke preflight_branch.py (#747 gate #1). That gate
10
- # inspects HEAD only, so benign operations checked out on the default branch
11
- # (deleting a merged feature branch, pushing a non-default ref) false-positive
12
- # before the refspec-aware gate can approve them (#1814 Option A). HEAD-only
13
- # default-branch protection remains on pre-commit; pre-push relies on
14
- # preflight_gh --pre-push-stdin (gate #2) for refspec-aware protection.
4
+ # Pre-push does NOT invoke verify:branch (#747). HEAD-only default-branch
5
+ # protection remains on pre-commit; pre-push relies on preflight-gh for
6
+ # refspec-aware protection (#1814 Option A). Pure POSIX shell; `deft` only.
15
7
 
16
8
  REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
17
9
  if [ -z "$REPO_ROOT" ]; then
@@ -19,104 +11,11 @@ if [ -z "$REPO_ROOT" ]; then
19
11
  exit 1
20
12
  fi
21
13
 
22
- # Layout-aware helper resolution (#1463) -- see .githooks/pre-commit for the
23
- # rationale. The installer copies this hook to the consumer root .githooks/,
24
- # so the gate scripts must be located relative to the install root (own-repo
25
- # scripts/, canonical .deft/core/scripts/, or legacy deft/scripts/) rather
26
- # than assuming $REPO_ROOT/scripts/.
27
- SCRIPTS_DIR=""
28
- for candidate in "$REPO_ROOT/scripts" "$REPO_ROOT/.deft/core/scripts" "$REPO_ROOT/deft/scripts"; do
29
- if [ -f "$candidate/preflight_branch.py" ]; then
30
- SCRIPTS_DIR="$candidate"
31
- break
32
- fi
33
- done
34
- if [ -z "$SCRIPTS_DIR" ]; then
35
- echo "deft pre-push: unable to locate the deft scripts directory." >&2
36
- echo " Looked in scripts/, .deft/core/scripts/, deft/scripts/ under $REPO_ROOT." >&2
37
- echo " Re-run the deft installer or 'task setup' to wire the hooks correctly." >&2
14
+ if ! command -v deft >/dev/null 2>&1; then
15
+ echo "deft pre-push: 'deft' not found on PATH." >&2
16
+ echo " Install: npm i -g @deftai/directive" >&2
38
17
  exit 1
39
18
  fi
40
19
 
41
- # Resolve a usable Python interpreter (#1668) -- see .githooks/pre-commit for
42
- # the rationale. On Windows `python3` is usually absent (only `python.exe` /
43
- # the `py` launcher exist), and a bare `python3` on PATH is often the Microsoft
44
- # Store App-Execution-Alias stub. Probe DEFT_PYTHON -> python3 -> python -> the
45
- # `py -3` launcher, validating each candidate by running `--version`, rejecting
46
- # the alias stub, AND enforcing the framework's Python 3.11+ floor (#1676).
47
- PYTHON_BIN=""
48
- PY_PREFIX=""
49
-
50
- # _deft_try BIN [PREFIX] -- probe a candidate. Succeeds (and sets PYTHON_BIN /
51
- # PY_PREFIX) only when the candidate runs, is not the Windows alias stub, and
52
- # reports a Python version >= 3.11.
53
- _deft_try() {
54
- _bin="$1"
55
- _pre="${2:-}"
56
- command -v "$_bin" >/dev/null 2>&1 || return 1
57
- if [ -n "$_pre" ]; then
58
- _out=$("$_bin" "$_pre" --version 2>&1) || return 1
59
- else
60
- _out=$("$_bin" --version 2>&1) || return 1
61
- fi
62
- case "$_out" in
63
- *"Microsoft Store"*|*"was not found"*|*"execution alias"*) return 1 ;;
64
- esac
65
- case "$_out" in
66
- *Python\ [0-9]*) ;;
67
- *) return 1 ;;
68
- esac
69
- # Enforce the 3.11+ floor using POSIX parameter expansion (no sed/awk
70
- # dependency). Parse major.minor from the "Python X.Y.Z" banner.
71
- _rest=${_out#*Python }
72
- _major=${_rest%%.*}
73
- _rest=${_rest#*.}
74
- _minor=${_rest%%.*}
75
- case "$_major" in ''|*[!0-9]*) return 1 ;; esac
76
- case "$_minor" in ''|*[!0-9]*) return 1 ;; esac
77
- if [ "$_major" -gt 3 ]; then
78
- PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
79
- fi
80
- if [ "$_major" -eq 3 ] && [ "$_minor" -ge 11 ]; then
81
- PYTHON_BIN="$_bin"; PY_PREFIX="$_pre"; return 0
82
- fi
83
- return 1
84
- }
85
-
86
- if [ -n "${DEFT_PYTHON:-}" ]; then
87
- # DEFT_PYTHON is the explicit override, but still version-checked so the
88
- # hooks enforce the same 3.11+ floor as the installer (#1676).
89
- _deft_try "$DEFT_PYTHON" || true
90
- if [ -z "$PYTHON_BIN" ]; then
91
- echo "deft pre-push: DEFT_PYTHON ($DEFT_PYTHON) is not a usable Python 3.11+ interpreter." >&2
92
- echo " Point DEFT_PYTHON at a Python 3.11+ executable." >&2
93
- exit 1
94
- fi
95
- else
96
- _deft_try python3 || _deft_try python || _deft_try py "-3" || true
97
- fi
98
-
99
- if [ -z "$PYTHON_BIN" ]; then
100
- echo "deft pre-push: no usable Python 3.11+ interpreter found." >&2
101
- echo " Probed python3, python, and the 'py' launcher (rejecting the Windows" >&2
102
- echo " App-Execution-Alias stub and interpreters older than 3.11)." >&2
103
- echo " Set DEFT_PYTHON=<absolute python 3.11+ path> or install Python 3.11+." >&2
104
- exit 1
105
- fi
106
-
107
- # deft_py forwards args to the resolved interpreter, preserving quoting for
108
- # paths that may contain spaces (common on Windows).
109
- deft_py() {
110
- if [ -n "$PY_PREFIX" ]; then
111
- "$PYTHON_BIN" "$PY_PREFIX" "$@"
112
- else
113
- "$PYTHON_BIN" "$@"
114
- fi
115
- }
116
-
117
- # Refspec-aware default-branch push gate (#1019). Reads per-ref stdin lines from
118
- # git pre-push (one line per ref: <local_ref> <local_oid> <remote_ref> <remote_oid>)
119
- # and refuses pushes touching the default branch (force-push or otherwise).
120
- # DEFT_ALLOW_DESTRUCTIVE_GH_VERBS=1 is the per-shell emergency bypass.
121
- deft_py "$SCRIPTS_DIR/preflight_gh.py" --pre-push-stdin
20
+ deft preflight-gh --pre-push-stdin
122
21
  exit $?
package/UPGRADING.md CHANGED
@@ -16,7 +16,7 @@ From v0.55.1 onwards `@deftai/directive` is published on npm. The canonical upgr
16
16
  npm i -g @deftai/directive@latest
17
17
  ```
18
18
 
19
- Run from any shell with Node ≥ 20. After upgrading, start a new agent session so the refreshed AGENTS.md and skills load from a clean context. Run `directive doctor` to confirm the install state.
19
+ Run from any shell with Node ≥ 20. After upgrading, run `deft update` from your project root to refresh the vendored `.deft/core/` payload and project-root `.githooks/` (#2049), then start a new agent session so the refreshed AGENTS.md and skills load from a clean context. Run `deft doctor` to confirm the install state.
20
20
 
21
21
  ### One-time migration from the Go installer (legacy → npm)
22
22
 
@@ -94,6 +94,7 @@ runs the doctor first gets pointed at this exact two-step before touching `init`
94
94
  - **From v0.25.x — manual (breaking).** The deft-cache on-disk layout changed: [From v0.25.x → v0.26.0](#from-v025x--v0260-deft-cache-unified-layer-breaking).
95
95
  - **From v0.26.x — auto-handled (interactive).** Run the triage adoption ritual: [From v0.26.x → v0.27](#from-v026x---v027-triage-adoption-via-task-triagewelcome).
96
96
  - **From v0.27.x — mostly auto-handled.** Pick up the install manifest and the `deft/` → `.deft/core/` layout: [From v0.27.x → v0.28](#from-v027x---v028-canonical-install-manifest-at-installversion), [From deft/ → .deft/core/](#from-deft---deftcore), and [From drifted AGENTS.md → current install](#from-drifted-agentsmd---current-install-task-upgrade-repair-path-1061).
97
+ - **From v0.60.x — manual (hook refresh).** After #2049, consumer `.githooks/` dispatch through the `deft` CLI only. Run [From v0.60.0 → v0.61.x (refresh project-root git hooks, #2049)](#from-v0600--v061x-refresh-project-root-git-hooks-2049) after every framework upgrade that touches hook templates.
97
98
  - **From v0.28–v0.36 (and the final hop to current) — auto-handled.** If still on the Go-installer layout, follow the [One-time migration from the Go installer](#one-time-migration-from-the-go-installer-legacy--npm) above, then `npm i -g @deftai/directive@latest` for all future upgrades.
98
99
 
99
100
  **Final step for every bucket.** Finish on the canonical upgrade command, then let the doctor confirm you are current:
@@ -102,7 +103,22 @@ runs the doctor first gets pointed at this exact two-step before touching `init`
102
103
  npm i -g @deftai/directive@latest
103
104
  ```
104
105
 
105
- Then run `directive doctor`: it checks install integrity and tells you whether any further hop is still due. If still on a Go-installer layout, follow the [One-time migration from the Go installer](#one-time-migration-from-the-go-installer-legacy--npm) first.
106
+ Then run `deft update` in your project root (refreshes `.deft/core/` and `.githooks/`), then `directive doctor`: it checks install integrity and tells you whether any further hop is still due. If still on a Go-installer layout, follow the [One-time migration from the Go installer](#one-time-migration-from-the-go-installer-legacy--npm) first.
107
+
108
+ ---
109
+
110
+ ## From v0.60.0 → v0.61.x (refresh project-root git hooks, #2049)
111
+
112
+ - **Applies when:** your project was on deft v0.60.x (or earlier) with `.githooks/` installed via `deft setup` / the npm deposit path, and hooks still invoke legacy Python scripts (`scripts/preflight_branch.py`, `scripts/preflight_gh.py`, etc.). Detection: `deft verify:hooks-installed` fails with "still dispatches through Python scripts (expected deft CLI only, #2049)" or pre-commit/pre-push errors mentioning missing `scripts/*.py` on Python-free installs.
113
+ - **Safe to auto-run:** Yes. `deft update` from the project root re-deposits the current `.githooks/` templates and refreshes the vendored `.deft/core/` payload. No manual hook editing required when the update completes cleanly.
114
+ - **Restart required:** No for hook wiring — but start a **new agent session** after update so AGENTS.md and skills reflect the refreshed managed section.
115
+ - **Commands:**
116
+ - `npm i -g @deftai/directive@latest` (upgrade the global CLI/engine)
117
+ - `deft update` (from project root — refreshes `.deft/core/` + `.githooks/`)
118
+ - `deft verify:hooks-installed` (confirm pre-commit/pre-push dispatch via `deft verify:branch`, `deft verify:encoding`, `deft preflight-gh`)
119
+ - `deft doctor` (install integrity + managed-section freshness)
120
+
121
+ If `deft update` is unavailable (older deposit), fall back to `deft setup` to re-install the hooks path and copy current hook templates.
106
122
 
107
123
  ---
108
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-content",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "description": "Shippable Directive framework content in the consumer .deft/core/ layout (C1 flatten), plus the engine surfaces (.githooks/, Taskfile.yml, tasks/) the deposit wires. Python-free per #2022 Phase 3. Refs #11, #1669, #1967.",
5
5
  "type": "module",
6
6
  "files": [