@cleocode/cleo 2026.4.157 → 2026.4.161

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/cleo",
3
- "version": "2026.4.157",
3
+ "version": "2026.4.161",
4
4
  "description": "CLEO CLI — the assembled product consuming @cleocode/core",
5
5
  "type": "module",
6
6
  "main": "./dist/cli/index.js",
@@ -29,16 +29,16 @@
29
29
  "tree-sitter-ruby": "^0.23.1",
30
30
  "tree-sitter-rust": "0.23.1",
31
31
  "tree-sitter-typescript": "^0.23.2",
32
- "@cleocode/cant": "2026.4.157",
33
- "@cleocode/caamp": "2026.4.157",
34
- "@cleocode/contracts": "2026.4.157",
35
- "@cleocode/lafs": "2026.4.157",
36
- "@cleocode/nexus": "2026.4.157",
37
- "@cleocode/playbooks": "2026.4.157",
38
- "@cleocode/runtime": "2026.4.157"
32
+ "@cleocode/caamp": "2026.4.161",
33
+ "@cleocode/lafs": "2026.4.161",
34
+ "@cleocode/cant": "2026.4.161",
35
+ "@cleocode/nexus": "2026.4.161",
36
+ "@cleocode/contracts": "2026.4.161",
37
+ "@cleocode/playbooks": "2026.4.161",
38
+ "@cleocode/runtime": "2026.4.161"
39
39
  },
40
40
  "peerDependencies": {
41
- "@cleocode/core": "2026.4.157"
41
+ "@cleocode/core": "2026.4.161"
42
42
  },
43
43
  "peerDependenciesMeta": {
44
44
  "@cleocode/core": {
@@ -0,0 +1,37 @@
1
+ # STALE — DO NOT READ THIS FILE FOR STATE
2
+
3
+ **This file is deprecated as canonical state per T1593 (shipped in v2026.4.157).**
4
+
5
+ The current state of the project lives in **TASKS + BRAIN** — never in markdown.
6
+
7
+ ## What you must do instead
8
+
9
+ ```bash
10
+ cleo briefing
11
+ ```
12
+
13
+ That command returns:
14
+ - The structured `lastSession.handoff.note` — guaranteed current, written at session-end
15
+ - `nextTasks` ranked by score — the system's recommendation for what to work on
16
+ - `blockedTasks` showing dependency chains — what cannot start yet
17
+ - `memoryContext` with relevant BRAIN observations
18
+ - `activeEpics` with completion percentages
19
+
20
+ ## If you are seeing this and you ALREADY started reading instead of running `cleo briefing`
21
+
22
+ Stop. Run `cleo briefing`. Then `cleo memory find "IRONCLAD-ROADMAP"` if needed.
23
+ The system has explicit instructions for the next orchestrator that do NOT live in this file.
24
+
25
+ ## Verification
26
+
27
+ Run any of these to verify state from the canonical source:
28
+
29
+ ```bash
30
+ cleo briefing # next-session handoff (canonical)
31
+ cleo dash # task counts
32
+ cleo memory find "roadmap" # roadmap observations in BRAIN
33
+ ```
34
+
35
+ ---
36
+
37
+ *This file deliberately contains no state. Reading it cannot mislead you. Run `cleo briefing`.*
@@ -1,11 +1,18 @@
1
1
  #!/bin/sh
2
2
  # CLEO_MANAGED_HOOK v1
3
3
  # T1588 — project-agnostic T-ID enforcement for every commit subject.
4
+ # T1608 — diff-scope validation: warn when staged files drift from task scope.
4
5
  #
5
6
  # Rule: subject MUST contain `T<digits>` somewhere, OR be a merge/revert
6
7
  # (which preserves the git merge --no-ff path established in T1587 and the
7
8
  # stock `git revert` flow).
8
9
  #
10
+ # Diff-scope check (T1608): if `cleo` is on the PATH and the referenced task
11
+ # has a non-empty files[] array, compare the staged diff against that scope.
12
+ # If >50 % of staged files fall outside the task's declared scope, emit a
13
+ # WARNING on stderr — but still exit 0 (hard-block is intentionally omitted
14
+ # to avoid rejecting valid refactors; the warning feeds audit tooling).
15
+ #
9
16
  # Override: `git commit --no-verify` bypasses (standard git behaviour).
10
17
  # A best-effort audit of `--no-verify` lives in the git shim (see T1591) —
11
18
  # hooks themselves cannot observe `--no-verify`.
@@ -13,6 +20,7 @@
13
20
  # This script is POSIX `/bin/sh` only (no bash/zsh-isms). It MUST work in
14
21
  # any environment cleo init runs in: node-less projects, Rust, Python,
15
22
  # bare repos, etc. Do not introduce node/pnpm dependencies here.
23
+ # The diff-scope check degrades gracefully when cleo or python3 is absent.
16
24
  set -e
17
25
 
18
26
  MSG_FILE="$1"
@@ -50,11 +58,8 @@ esac
50
58
 
51
59
  # Match `T` followed by 1+ digits anywhere in the subject.
52
60
  # POSIX BRE — `[0-9][0-9]*` is `\d+` equivalent.
53
- if printf '%s' "$SUBJECT" | grep -Eq 'T[0-9]+'; then
54
- exit 0
55
- fi
56
-
57
- cat >&2 <<EOF
61
+ if ! printf '%s' "$SUBJECT" | grep -Eq 'T[0-9]+'; then
62
+ cat >&2 <<EOF
58
63
  cleo commit-msg hook: commit subject is missing a task ID.
59
64
 
60
65
  subject: $SUBJECT
@@ -70,4 +75,139 @@ Override (audited via git shim — see T1591):
70
75
  git commit --no-verify
71
76
 
72
77
  EOF
73
- exit 1
78
+ exit 1
79
+ fi
80
+
81
+ # -----------------------------------------------------------------------
82
+ # T1608: diff-scope validation (warning-only — exits 0 on all paths below)
83
+ # -----------------------------------------------------------------------
84
+ # Extract the first T-ID from the subject (e.g. "feat(T1608): ..." → T1608).
85
+ TASK_ID=$(printf '%s' "$SUBJECT" | grep -Eo 'T[0-9]+' | head -n 1)
86
+
87
+ # Resolve cleo binary: prefer explicit CLEO_BIN env, then PATH lookup.
88
+ CLEO_BIN="${CLEO_BIN:-}"
89
+ if [ -z "$CLEO_BIN" ]; then
90
+ CLEO_BIN=$(command -v cleo 2>/dev/null || true)
91
+ fi
92
+
93
+ if [ -z "$CLEO_BIN" ] || [ ! -x "$CLEO_BIN" ] || [ -z "$TASK_ID" ]; then
94
+ # cleo not available or no T-ID extracted — skip diff-scope check.
95
+ exit 0
96
+ fi
97
+
98
+ # Resolve python3 (used for JSON parsing — lightweight, no npm required).
99
+ PYTHON3_BIN=$(command -v python3 2>/dev/null || true)
100
+ if [ -z "$PYTHON3_BIN" ]; then
101
+ # python3 unavailable — skip diff-scope check gracefully.
102
+ exit 0
103
+ fi
104
+
105
+ # Fetch task.files[] via cleo show. Suppress errors (task may not exist
106
+ # in cleo DB for the target project; non-zero cleo exit → skip silently).
107
+ TASK_JSON=$("$CLEO_BIN" show "$TASK_ID" 2>/dev/null) || true
108
+ if [ -z "$TASK_JSON" ]; then
109
+ exit 0
110
+ fi
111
+
112
+ # Extract the files[] array as one path per line via python3 -c.
113
+ # Using -c avoids the stdin-conflict that arises with heredoc + pipe.
114
+ TASK_FILES=$("$PYTHON3_BIN" -c "
115
+ import json, sys
116
+ try:
117
+ d = json.loads(sys.argv[1])
118
+ files = d.get('data', {}).get('task', {}).get('files', [])
119
+ print('\n'.join(f for f in files if f))
120
+ except Exception:
121
+ pass
122
+ " "$TASK_JSON" 2>/dev/null) || true
123
+
124
+ if [ -z "$TASK_FILES" ]; then
125
+ # Task has no files[] scope declared — nothing to validate against.
126
+ exit 0
127
+ fi
128
+
129
+ # Get staged file list (diff-scope is only meaningful for staged changes).
130
+ # git diff --cached exits 0 even when empty, so this is always safe.
131
+ STAGED_FILES=$(git diff --cached --name-only 2>/dev/null) || true
132
+
133
+ if [ -z "$STAGED_FILES" ]; then
134
+ # No staged files (e.g. amend of message only) — skip check.
135
+ exit 0
136
+ fi
137
+
138
+ # Count staged files and how many are in-scope (path prefix or exact match).
139
+ # A staged file is "in-scope" when it matches any task file path OR any
140
+ # task file path is a path-prefix of the staged file (directory scope).
141
+ # We delegate the maths to python3 to avoid shell integer-division quirks.
142
+ # Both TASK_FILES and STAGED_FILES are passed as argv to avoid stdin conflict.
143
+ DRIFT_RESULT=$("$PYTHON3_BIN" -c "
144
+ import sys
145
+
146
+ # argv[1] = newline-separated task files; argv[2] = newline-separated staged files
147
+ task_files = [f.strip() for f in sys.argv[1].splitlines() if f.strip()]
148
+ staged = [f.strip() for f in sys.argv[2].splitlines() if f.strip()]
149
+
150
+ def in_scope(staged_path, task_files):
151
+ for tf in task_files:
152
+ # Exact match.
153
+ if staged_path == tf:
154
+ return True
155
+ # task file is a directory prefix of the staged file.
156
+ if staged_path.startswith(tf.rstrip('/') + '/'):
157
+ return True
158
+ # staged file is a directory prefix of a task file (task file
159
+ # is deeper; e.g. task scope is src/foo.ts, staged is src/).
160
+ if tf.startswith(staged_path.rstrip('/') + '/'):
161
+ return True
162
+ return False
163
+
164
+ if not staged or not task_files:
165
+ print('SKIP')
166
+ sys.exit(0)
167
+
168
+ out_of_scope = [f for f in staged if not in_scope(f, task_files)]
169
+ total = len(staged)
170
+ drift_count = len(out_of_scope)
171
+ drift_pct = (drift_count * 100) // total if total > 0 else 0
172
+
173
+ print('TOTAL=' + str(total))
174
+ print('DRIFT_COUNT=' + str(drift_count))
175
+ print('DRIFT_PCT=' + str(drift_pct))
176
+ for f in out_of_scope:
177
+ print('OUT=' + f)
178
+ " "$TASK_FILES" "$STAGED_FILES" 2>/dev/null) || true
179
+
180
+ # Parse drift result.
181
+ if [ -z "$DRIFT_RESULT" ] || printf '%s' "$DRIFT_RESULT" | grep -q '^SKIP$'; then
182
+ exit 0
183
+ fi
184
+
185
+ DRIFT_PCT=$(printf '%s' "$DRIFT_RESULT" | grep '^DRIFT_PCT=' | sed 's/^DRIFT_PCT=//')
186
+ TOTAL=$(printf '%s' "$DRIFT_RESULT" | grep '^TOTAL=' | sed 's/^TOTAL=//')
187
+ DRIFT_COUNT=$(printf '%s' "$DRIFT_RESULT" | grep '^DRIFT_COUNT=' | sed 's/^DRIFT_COUNT=//')
188
+ OUT_FILES=$(printf '%s' "$DRIFT_RESULT" | grep '^OUT=' | sed 's/^OUT=//')
189
+
190
+ # Threshold: warn when drift exceeds 50 % (i.e. drift_pct > 50).
191
+ THRESHOLD=50
192
+ if [ -n "$DRIFT_PCT" ] && [ "$DRIFT_PCT" -gt "$THRESHOLD" ] 2>/dev/null; then
193
+ cat >&2 <<EOF
194
+ cleo commit-msg hook [T1608]: diff-scope drift WARNING
195
+
196
+ Task : $TASK_ID
197
+ Staged : $TOTAL file(s) — $DRIFT_COUNT ($DRIFT_PCT%) are outside $TASK_ID scope
198
+
199
+ Out-of-scope staged files:
200
+ $(printf '%s' "$OUT_FILES" | sed 's/^/ /')
201
+
202
+ Task scope (files[]):
203
+ $(printf '%s' "$TASK_FILES" | sed 's/^/ /')
204
+
205
+ This is a WARNING, not a hard block. The commit will proceed.
206
+ To silence: ensure staged files align with the task scope,
207
+ or add the files to the task via \`cleo update $TASK_ID --files ...\`.
208
+
209
+ EOF
210
+ # Exit 0 — warning only.
211
+ fi
212
+
213
+ exit 0