@cleocode/cleo 2026.4.154 → 2026.4.158
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 +2681 -1648
- package/dist/cli/index.js.map +4 -4
- package/package.json +9 -9
- package/templates/hooks/commit-msg +73 -0
- package/templates/hooks/pre-push +87 -0
- package/templates/hooks/pre-push.t1595-extension.sh +140 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/cleo",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.158",
|
|
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/caamp": "2026.4.
|
|
33
|
-
"@cleocode/
|
|
34
|
-
"@cleocode/
|
|
35
|
-
"@cleocode/
|
|
36
|
-
"@cleocode/
|
|
37
|
-
"@cleocode/playbooks": "2026.4.
|
|
38
|
-
"@cleocode/runtime": "2026.4.
|
|
32
|
+
"@cleocode/caamp": "2026.4.158",
|
|
33
|
+
"@cleocode/cant": "2026.4.158",
|
|
34
|
+
"@cleocode/lafs": "2026.4.158",
|
|
35
|
+
"@cleocode/nexus": "2026.4.158",
|
|
36
|
+
"@cleocode/contracts": "2026.4.158",
|
|
37
|
+
"@cleocode/playbooks": "2026.4.158",
|
|
38
|
+
"@cleocode/runtime": "2026.4.158"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@cleocode/core": "2026.4.
|
|
41
|
+
"@cleocode/core": "2026.4.158"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@cleocode/core": {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# CLEO_MANAGED_HOOK v1
|
|
3
|
+
# T1588 — project-agnostic T-ID enforcement for every commit subject.
|
|
4
|
+
#
|
|
5
|
+
# Rule: subject MUST contain `T<digits>` somewhere, OR be a merge/revert
|
|
6
|
+
# (which preserves the git merge --no-ff path established in T1587 and the
|
|
7
|
+
# stock `git revert` flow).
|
|
8
|
+
#
|
|
9
|
+
# Override: `git commit --no-verify` bypasses (standard git behaviour).
|
|
10
|
+
# A best-effort audit of `--no-verify` lives in the git shim (see T1591) —
|
|
11
|
+
# hooks themselves cannot observe `--no-verify`.
|
|
12
|
+
#
|
|
13
|
+
# This script is POSIX `/bin/sh` only (no bash/zsh-isms). It MUST work in
|
|
14
|
+
# any environment cleo init runs in: node-less projects, Rust, Python,
|
|
15
|
+
# bare repos, etc. Do not introduce node/pnpm dependencies here.
|
|
16
|
+
set -e
|
|
17
|
+
|
|
18
|
+
MSG_FILE="$1"
|
|
19
|
+
if [ -z "$MSG_FILE" ] || [ ! -f "$MSG_FILE" ]; then
|
|
20
|
+
echo "cleo commit-msg hook: missing message file argument" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# First non-empty, non-comment line = the subject.
|
|
25
|
+
SUBJECT=""
|
|
26
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
27
|
+
case "$line" in
|
|
28
|
+
'#'*) continue ;;
|
|
29
|
+
esac
|
|
30
|
+
trimmed=$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
31
|
+
if [ -n "$trimmed" ]; then
|
|
32
|
+
SUBJECT="$trimmed"
|
|
33
|
+
break
|
|
34
|
+
fi
|
|
35
|
+
done < "$MSG_FILE"
|
|
36
|
+
|
|
37
|
+
if [ -z "$SUBJECT" ]; then
|
|
38
|
+
echo "cleo commit-msg hook: empty commit subject — refusing." >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Bypass merge / revert / fixup / squash / amend-only metadata commits.
|
|
43
|
+
case "$SUBJECT" in
|
|
44
|
+
'Merge '*) exit 0 ;;
|
|
45
|
+
'Revert '*) exit 0 ;;
|
|
46
|
+
'fixup! '*) exit 0 ;;
|
|
47
|
+
'squash! '*) exit 0 ;;
|
|
48
|
+
'amend! '*) exit 0 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# Match `T` followed by 1+ digits anywhere in the subject.
|
|
52
|
+
# 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
|
|
58
|
+
cleo commit-msg hook: commit subject is missing a task ID.
|
|
59
|
+
|
|
60
|
+
subject: $SUBJECT
|
|
61
|
+
|
|
62
|
+
Every commit MUST reference at least one CLEO task. Examples:
|
|
63
|
+
|
|
64
|
+
feat(T1588): ship POSIX commit-msg hook
|
|
65
|
+
T1588 — wire hooks-install into cleo init
|
|
66
|
+
fix: T1588 typo
|
|
67
|
+
|
|
68
|
+
Override (audited via git shim — see T1591):
|
|
69
|
+
|
|
70
|
+
git commit --no-verify
|
|
71
|
+
|
|
72
|
+
EOF
|
|
73
|
+
exit 1
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# CLEO_MANAGED_HOOK v1
|
|
3
|
+
# T1588 — project-agnostic T-ID enforcement for every commit being pushed.
|
|
4
|
+
#
|
|
5
|
+
# git invokes this with `<remote> <url>` as args and feeds ref updates on
|
|
6
|
+
# stdin: `<local-ref> <local-sha> <remote-ref> <remote-sha>` (one per line).
|
|
7
|
+
#
|
|
8
|
+
# For each new commit being pushed (commits in `local-sha` that are NOT
|
|
9
|
+
# already in any remote ref), require a T-ID in the subject. Same allow-
|
|
10
|
+
# list as commit-msg: merge / revert / fixup / squash / amend.
|
|
11
|
+
#
|
|
12
|
+
# Override: `git push --no-verify` (standard git override).
|
|
13
|
+
#
|
|
14
|
+
# T1595:reconcile-extension-point
|
|
15
|
+
# Pre-push reconcile gate hooks here (see T1595 worker).
|
|
16
|
+
# Reserved range below — DO NOT remove these markers; T1595 extends here.
|
|
17
|
+
# T1595:reconcile-extension-point-end
|
|
18
|
+
#
|
|
19
|
+
# POSIX `/bin/sh` only. No node/pnpm dependency.
|
|
20
|
+
set -e
|
|
21
|
+
|
|
22
|
+
ZERO_SHA="0000000000000000000000000000000000000000"
|
|
23
|
+
EMPTY_TREE_SHA=""
|
|
24
|
+
FAIL=0
|
|
25
|
+
FAIL_LOG=""
|
|
26
|
+
|
|
27
|
+
check_subject() {
|
|
28
|
+
subject="$1"
|
|
29
|
+
case "$subject" in
|
|
30
|
+
'Merge '*) return 0 ;;
|
|
31
|
+
'Revert '*) return 0 ;;
|
|
32
|
+
'fixup! '*) return 0 ;;
|
|
33
|
+
'squash! '*) return 0 ;;
|
|
34
|
+
'amend! '*) return 0 ;;
|
|
35
|
+
esac
|
|
36
|
+
if printf '%s' "$subject" | grep -Eq 'T[0-9]+'; then
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Read each ref-update line from stdin.
|
|
43
|
+
while read -r local_ref local_sha remote_ref remote_sha; do
|
|
44
|
+
# Branch deletion (push :branch) — nothing to validate.
|
|
45
|
+
if [ "$local_sha" = "$ZERO_SHA" ]; then
|
|
46
|
+
continue
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
if [ "$remote_sha" = "$ZERO_SHA" ]; then
|
|
50
|
+
# New branch: validate every commit reachable from local_sha that is
|
|
51
|
+
# NOT reachable from any other remote ref.
|
|
52
|
+
range="$local_sha --not --remotes"
|
|
53
|
+
else
|
|
54
|
+
range="$remote_sha..$local_sha"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Subject extraction: %s = subject. NUL-delimit for safety.
|
|
58
|
+
# shellcheck disable=SC2086
|
|
59
|
+
commits=$(git rev-list $range 2>/dev/null || true)
|
|
60
|
+
for sha in $commits; do
|
|
61
|
+
subject=$(git log -1 --pretty=%s "$sha" 2>/dev/null || true)
|
|
62
|
+
if [ -z "$subject" ]; then
|
|
63
|
+
continue
|
|
64
|
+
fi
|
|
65
|
+
if ! check_subject "$subject"; then
|
|
66
|
+
FAIL=1
|
|
67
|
+
FAIL_LOG="${FAIL_LOG} ${sha} ${subject}
|
|
68
|
+
"
|
|
69
|
+
fi
|
|
70
|
+
done
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if [ "$FAIL" -ne 0 ]; then
|
|
74
|
+
cat >&2 <<EOF
|
|
75
|
+
cleo pre-push hook: refusing push — commits are missing task IDs.
|
|
76
|
+
|
|
77
|
+
${FAIL_LOG}
|
|
78
|
+
Every commit MUST reference at least one CLEO task (e.g. \`T1588\`).
|
|
79
|
+
Fix the subjects (\`git rebase -i\` + reword), or override with:
|
|
80
|
+
|
|
81
|
+
git push --no-verify
|
|
82
|
+
|
|
83
|
+
EOF
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exit 0
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# T1595 — pre-push reconcile gate (extension)
|
|
3
|
+
#
|
|
4
|
+
# This file is the reconcile-gate extension for the project's pre-push
|
|
5
|
+
# hook. T1588 will produce a unified pre-push hook that contains a
|
|
6
|
+
# sentinel block:
|
|
7
|
+
#
|
|
8
|
+
# # T1595:reconcile-extension-point
|
|
9
|
+
# # Pre-push reconcile gate hooks here (see T1595 worker)
|
|
10
|
+
#
|
|
11
|
+
# When T1588 lands, the contents of `reconcile_gate()` below MUST be
|
|
12
|
+
# inlined at that sentinel. Until then, this file is sourced as-is by
|
|
13
|
+
# any pre-push hook that wants the reconcile gate (see installer in
|
|
14
|
+
# `packages/core/src/hooks/install-pre-push.ts`, future).
|
|
15
|
+
#
|
|
16
|
+
# CONTRACT
|
|
17
|
+
# - POSIX shell (`/bin/sh`); no bashisms.
|
|
18
|
+
# - Reads pending tag from `git tag --sort=-v:refname | head -1`
|
|
19
|
+
# (tag-shape agnostic — works for CalVer or SemVer).
|
|
20
|
+
# - Calls `cleo reconcile release --tag <pending> --dry-run --json`
|
|
21
|
+
# and parses the aggregate `reconciled` count.
|
|
22
|
+
# - Drift > 0 → exit 1 with task-ID list.
|
|
23
|
+
# - Drift == 0 → return 0.
|
|
24
|
+
# - Override: env `CLEO_ALLOW_DRIFT_PUSH=1` bypasses the gate AND
|
|
25
|
+
# appends an audit entry to
|
|
26
|
+
# `${XDG_DATA_HOME:-$HOME/.local/share}/cleo/audit/drift-push-bypass.jsonl`.
|
|
27
|
+
# - Project-agnostic: no hardcoded branch name; default branch is
|
|
28
|
+
# resolved via `git symbolic-ref refs/remotes/origin/HEAD` when
|
|
29
|
+
# needed.
|
|
30
|
+
#
|
|
31
|
+
# EXIT CODES
|
|
32
|
+
# 0 — no drift, push allowed
|
|
33
|
+
# 1 — drift detected, push refused (or cleo CLI unavailable in
|
|
34
|
+
# strict mode; see CLEO_RECONCILE_STRICT below)
|
|
35
|
+
#
|
|
36
|
+
# CONFIGURATION (env)
|
|
37
|
+
# CLEO_ALLOW_DRIFT_PUSH=1 bypass the gate (audited)
|
|
38
|
+
# CLEO_RECONCILE_STRICT=1 if `cleo` CLI is missing, refuse push
|
|
39
|
+
# (default: warn-and-allow so first-time
|
|
40
|
+
# clones without cleo installed still work)
|
|
41
|
+
# CLEO_RECONCILE_BIN=<path> override the cleo binary path (testing)
|
|
42
|
+
|
|
43
|
+
set -eu
|
|
44
|
+
|
|
45
|
+
reconcile_gate() {
|
|
46
|
+
# Locate cleo CLI ------------------------------------------------------
|
|
47
|
+
cleo_bin="${CLEO_RECONCILE_BIN:-cleo}"
|
|
48
|
+
if ! command -v "$cleo_bin" >/dev/null 2>&1; then
|
|
49
|
+
if [ "${CLEO_RECONCILE_STRICT:-0}" = "1" ]; then
|
|
50
|
+
echo "ERROR: cleo CLI not found on PATH (strict mode)" >&2
|
|
51
|
+
echo " install cleo or unset CLEO_RECONCILE_STRICT" >&2
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
# Soft-fail: warn but allow push. Avoids blocking fresh clones.
|
|
55
|
+
echo "warn: cleo CLI not found; skipping reconcile gate" >&2
|
|
56
|
+
return 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Resolve the pending release tag -------------------------------------
|
|
60
|
+
# We use the most recent tag as the "pending" anchor. Reconcile is
|
|
61
|
+
# project-agnostic — it walks tasks released_in this tag's range
|
|
62
|
+
# regardless of CalVer vs SemVer shape.
|
|
63
|
+
pending_tag="$(git tag --sort=-v:refname 2>/dev/null | head -n 1 || true)"
|
|
64
|
+
if [ -z "$pending_tag" ]; then
|
|
65
|
+
# No tags yet → no release to reconcile.
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Override path -------------------------------------------------------
|
|
70
|
+
if [ "${CLEO_ALLOW_DRIFT_PUSH:-0}" = "1" ]; then
|
|
71
|
+
audit_dir="${XDG_DATA_HOME:-$HOME/.local/share}/cleo/audit"
|
|
72
|
+
mkdir -p "$audit_dir"
|
|
73
|
+
audit_log="$audit_dir/drift-push-bypass.jsonl"
|
|
74
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
75
|
+
user="${USER:-${LOGNAME:-unknown}}"
|
|
76
|
+
repo="$(git rev-parse --show-toplevel 2>/dev/null || echo unknown)"
|
|
77
|
+
head_sha="$(git rev-parse HEAD 2>/dev/null || echo unknown)"
|
|
78
|
+
# Write a JSONL line. Stay project-agnostic: no jq dependency.
|
|
79
|
+
printf '{"ts":"%s","user":"%s","repo":"%s","head":"%s","tag":"%s","reason":"CLEO_ALLOW_DRIFT_PUSH=1"}\n' \
|
|
80
|
+
"$ts" "$user" "$repo" "$head_sha" "$pending_tag" >> "$audit_log"
|
|
81
|
+
echo "warn: pre-push reconcile gate bypassed (CLEO_ALLOW_DRIFT_PUSH=1)" >&2
|
|
82
|
+
echo " audit: $audit_log" >&2
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Run reconcile in dry-run JSON mode ----------------------------------
|
|
87
|
+
# We swallow non-zero exit codes from the CLI here because reconcile
|
|
88
|
+
# exits 2 when drift exists; we want to read the JSON regardless.
|
|
89
|
+
json_out="$("$cleo_bin" reconcile release --tag "$pending_tag" --dry-run --json 2>/dev/null || true)"
|
|
90
|
+
if [ -z "$json_out" ]; then
|
|
91
|
+
if [ "${CLEO_RECONCILE_STRICT:-0}" = "1" ]; then
|
|
92
|
+
echo "ERROR: cleo reconcile release returned empty output (strict mode)" >&2
|
|
93
|
+
return 1
|
|
94
|
+
fi
|
|
95
|
+
echo "warn: cleo reconcile release returned empty output; skipping gate" >&2
|
|
96
|
+
return 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Parse the aggregate `reconciled` count without jq.
|
|
100
|
+
# The InvariantReport JSON has a top-level `"reconciled": N` integer.
|
|
101
|
+
# We grep the first such key (top-level always emitted before per-result
|
|
102
|
+
# entries). Per-result entries are nested under `results[*].details`,
|
|
103
|
+
# so the first match is the aggregate.
|
|
104
|
+
drift_count="$(printf '%s' "$json_out" \
|
|
105
|
+
| grep -o '"reconciled"[[:space:]]*:[[:space:]]*[0-9]\+' \
|
|
106
|
+
| head -n 1 \
|
|
107
|
+
| grep -o '[0-9]\+' \
|
|
108
|
+
|| true)"
|
|
109
|
+
drift_count="${drift_count:-0}"
|
|
110
|
+
|
|
111
|
+
if [ "$drift_count" -gt 0 ] 2>/dev/null; then
|
|
112
|
+
echo "ERROR: pre-push reconcile gate detected drift" >&2
|
|
113
|
+
echo " tag: $pending_tag" >&2
|
|
114
|
+
echo " drift count: $drift_count shipped-but-pending task(s)" >&2
|
|
115
|
+
# Best-effort: extract reconciled task IDs from results[*].details.reconciled
|
|
116
|
+
# arrays. Strings look like "T1411" (project-agnostic prefix).
|
|
117
|
+
drifted_ids="$(printf '%s' "$json_out" \
|
|
118
|
+
| tr -d '\n' \
|
|
119
|
+
| grep -o '"reconciled"[[:space:]]*:[[:space:]]*\[[^]]*\]' \
|
|
120
|
+
| grep -o '"[A-Za-z][A-Za-z0-9_-]*"' \
|
|
121
|
+
| sort -u \
|
|
122
|
+
| tr '\n' ' ' \
|
|
123
|
+
|| true)"
|
|
124
|
+
if [ -n "$drifted_ids" ]; then
|
|
125
|
+
echo " drifted tasks: $drifted_ids" >&2
|
|
126
|
+
fi
|
|
127
|
+
echo "" >&2
|
|
128
|
+
echo "Refuse push: run 'cleo reconcile release --tag $pending_tag' to" >&2
|
|
129
|
+
echo "reconcile, or set CLEO_ALLOW_DRIFT_PUSH=1 (audited bypass)." >&2
|
|
130
|
+
return 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
return 0
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# When sourced from the unified pre-push hook the function is invoked
|
|
137
|
+
# at the sentinel point. When run standalone (testing), invoke directly.
|
|
138
|
+
if [ "${T1595_SOURCED:-0}" != "1" ]; then
|
|
139
|
+
reconcile_gate
|
|
140
|
+
fi
|