@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/dist/cli/index.js +33 -6
- package/dist/cli/index.js.map +2 -2
- package/package.json +9 -9
- package/templates/HANDOFF-REDIRECT-STUB.md +37 -0
- package/templates/hooks/commit-msg +146 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/cleo",
|
|
3
|
-
"version": "2026.4.
|
|
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/
|
|
33
|
-
"@cleocode/
|
|
34
|
-
"@cleocode/
|
|
35
|
-
"@cleocode/
|
|
36
|
-
"@cleocode/
|
|
37
|
-
"@cleocode/playbooks": "2026.4.
|
|
38
|
-
"@cleocode/runtime": "2026.4.
|
|
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.
|
|
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
|
-
|
|
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
|