luciazero 1.5.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.
- package/CHANGELOG.md +423 -0
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/README.th.md +215 -0
- package/bin/luciazero.js +35 -0
- package/claude/agents/reviewer.md +43 -0
- package/claude/hooks/hooks.json +33 -0
- package/claude/hooks/luciazero-statusline.sh +81 -0
- package/claude/hooks/luciazero-verify.sh +252 -0
- package/claude/luciazero.md +24 -0
- package/install-codex.sh +84 -0
- package/install.sh +249 -0
- package/package.json +34 -0
- package/skills/debug/SKILL.md +51 -0
- package/skills/done/SKILL.md +57 -0
- package/skills/done/scripts/revert-probe.sh +95 -0
- package/skills/experiment/SKILL.md +44 -0
- package/skills/handoff/SKILL.md +56 -0
- package/skills/luciazero-bootstrap/SKILL.md +109 -0
- package/skills/luciazero-bootstrap/scripts/detect.sh +84 -0
- package/skills/retro/SKILL.md +74 -0
- package/uninstall-codex.sh +50 -0
- package/uninstall.sh +139 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Enforcement-pack statusline (Claude Code only; installed by `install.sh
|
|
3
|
+
# --with-hooks`). Shows: model | git branch | verify status from the state
|
|
4
|
+
# that luciazero-verify.sh maintains — so the doctrine's ground truth is visible
|
|
5
|
+
# at a glance instead of remembered.
|
|
6
|
+
#
|
|
7
|
+
# ✅ verify 3m last verify-ish command succeeded, 3 minutes ago
|
|
8
|
+
# ❌ verify RED last verify-ish command failed
|
|
9
|
+
# ✳ verify ran a verify ran but its result was unreadable
|
|
10
|
+
# ✎ unverified edits happened after the last verify run
|
|
11
|
+
# — no verify yet nothing tracked this session
|
|
12
|
+
#
|
|
13
|
+
# Requires python3. Fails open to a minimal line.
|
|
14
|
+
set -u
|
|
15
|
+
|
|
16
|
+
IN="$(cat 2>/dev/null || true)"
|
|
17
|
+
|
|
18
|
+
LINE="$(printf '%s' "${IN}" | python3 -c '
|
|
19
|
+
import json, os, sys, time, hashlib
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
d = json.load(sys.stdin)
|
|
23
|
+
except Exception:
|
|
24
|
+
d = {}
|
|
25
|
+
|
|
26
|
+
model = ((d.get("model") or {}).get("display_name")) or "claude"
|
|
27
|
+
cwd = ((d.get("workspace") or {}).get("current_dir")) or d.get("cwd") or os.getcwd()
|
|
28
|
+
|
|
29
|
+
key = hashlib.md5(cwd.encode()).hexdigest()[:12]
|
|
30
|
+
state = os.path.join(os.environ.get("TMPDIR", "/tmp"), "luciazero-verify-state", key)
|
|
31
|
+
|
|
32
|
+
def mtime(name):
|
|
33
|
+
try:
|
|
34
|
+
return os.path.getmtime(os.path.join(state, name))
|
|
35
|
+
except OSError:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def age(ts):
|
|
39
|
+
s = int(time.time() - ts)
|
|
40
|
+
if s < 60:
|
|
41
|
+
return f"{s}s"
|
|
42
|
+
if s < 3600:
|
|
43
|
+
return f"{s // 60}m"
|
|
44
|
+
return f"{s // 3600}h"
|
|
45
|
+
|
|
46
|
+
ve, ed = mtime("last_verify"), mtime("last_edit")
|
|
47
|
+
if ve is None and ed is None:
|
|
48
|
+
verify = "— no verify yet"
|
|
49
|
+
elif ve is None or (ed is not None and ed > ve):
|
|
50
|
+
verify = "✎ unverified"
|
|
51
|
+
else:
|
|
52
|
+
try:
|
|
53
|
+
status = open(os.path.join(state, "last_verify")).read().strip()
|
|
54
|
+
except OSError:
|
|
55
|
+
status = "ran"
|
|
56
|
+
if status == "ok":
|
|
57
|
+
verify = f"✅ verify {age(ve)}"
|
|
58
|
+
elif status == "fail":
|
|
59
|
+
verify = f"❌ verify RED {age(ve)}"
|
|
60
|
+
else:
|
|
61
|
+
verify = f"✳ verify ran {age(ve)}"
|
|
62
|
+
|
|
63
|
+
print(model + "\t" + cwd + "\t" + verify)' 2>/dev/null)" || LINE=""
|
|
64
|
+
|
|
65
|
+
if [ -z "${LINE}" ]; then
|
|
66
|
+
echo "claude"
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
MODEL="${LINE%%$'\t'*}"
|
|
71
|
+
REST="${LINE#*$'\t'}"
|
|
72
|
+
DIR="${REST%%$'\t'*}"
|
|
73
|
+
VERIFY="${REST#*$'\t'}"
|
|
74
|
+
|
|
75
|
+
BRANCH="$(git -C "${DIR}" branch --show-current 2>/dev/null || true)"
|
|
76
|
+
if [ -n "${BRANCH}" ]; then
|
|
77
|
+
printf '%s | %s | %s\n' "${MODEL}" "${BRANCH}" "${VERIFY}"
|
|
78
|
+
else
|
|
79
|
+
printf '%s | %s\n' "${MODEL}" "${VERIFY}"
|
|
80
|
+
fi
|
|
81
|
+
exit 0
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Enforcement-pack hook (Claude Code only; installed by `install.sh --with-hooks`).
|
|
3
|
+
# Tracks per-project whether edits have been followed by a verify run, and
|
|
4
|
+
# nudges ONCE at session stop when they have not — mechanizing doctrine rule 1
|
|
5
|
+
# ("done is proven by a command") at the exact moment it is most violated.
|
|
6
|
+
#
|
|
7
|
+
# Subcommands (wired in settings.json):
|
|
8
|
+
# edit — PostToolUse on Edit|Write|NotebookEdit : record "an edit happened"
|
|
9
|
+
# bash — PostToolUse on Bash : record verify runs + status
|
|
10
|
+
# stop — Stop : warn once if edits are unverified
|
|
11
|
+
# session — SessionStart : point at an existing HANDOFF.md
|
|
12
|
+
# doctrine— SessionStart (plugin installs only) : emit the doctrine as context
|
|
13
|
+
# (plugins cannot add a CLAUDE.md import line; this is the same
|
|
14
|
+
# word-ceiling-capped text the classic install imports. Silent when
|
|
15
|
+
# a classic install exists, so the doctrine never loads twice.)
|
|
16
|
+
#
|
|
17
|
+
# Optional strict gate: when LUCIAZERO_STRICT_VERIFY_CMD is set, `stop`
|
|
18
|
+
# actually RUNS that command and refuses the stop (exit 2) while it is red.
|
|
19
|
+
# Set it in your PERSONAL settings (settings.local.json env block, or your
|
|
20
|
+
# shell). LIMITATION: this hook cannot tell which settings scope set the
|
|
21
|
+
# variable — a committed .claude/settings.json env block reaches it too — so
|
|
22
|
+
# never commit it, and treat a repo that ships this variable as hostile.
|
|
23
|
+
# A blocked stop's continuation is never re-blocked (stop_hook_active),
|
|
24
|
+
# so this is a speed bump with evidence attached, not a wall.
|
|
25
|
+
#
|
|
26
|
+
# Requires python3 (for JSON parsing). FAILS OPEN: any internal error exits 0,
|
|
27
|
+
# so a broken hook can never block real work. Per-project state lives under
|
|
28
|
+
# $TMPDIR and never touches the repo. One exception, documented honestly: the
|
|
29
|
+
# stop hook appends one line per stop outcome (stop-clean / nudge /
|
|
30
|
+
# strict-block) to luciazero-stats.log in the harness config dir — local only,
|
|
31
|
+
# capped at ~250 lines, fail-open — so /retro can turn recurring discipline
|
|
32
|
+
# gaps into recorded lessons. Uninstall keeps it (it is learned data).
|
|
33
|
+
set -u
|
|
34
|
+
|
|
35
|
+
MODE="${1:-}"
|
|
36
|
+
|
|
37
|
+
# doctrine mode needs no python3, no state, and no stdin — handled before the
|
|
38
|
+
# shared setup so a machine without python3 still gets the doctrine (everything
|
|
39
|
+
# below fails open to doing nothing there). ${HOME:-} keeps set -u fail-open
|
|
40
|
+
# on hosts with no HOME at all.
|
|
41
|
+
if [ "${MODE}" = "doctrine" ]; then
|
|
42
|
+
# a classic install's CLAUDE.md import already loads this text — never twice
|
|
43
|
+
[ -f "${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}/luciazero.md" ] && exit 0
|
|
44
|
+
DOCTRINE="$(cd "$(dirname "$0")" 2>/dev/null && cd .. && pwd)/luciazero.md"
|
|
45
|
+
[ -f "${DOCTRINE}" ] || exit 0
|
|
46
|
+
cat "${DOCTRINE}" 2>/dev/null || true
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# Plugin-channel dedupe (the plugin's hooks.json invokes every mode with
|
|
51
|
+
# LUCIAZERO_CHANNEL=plugin): when `install.sh --with-hooks` wiring is ALSO
|
|
52
|
+
# present, the classic copy wins and the plugin copy stands down — otherwise
|
|
53
|
+
# the stop nudge double-fires and a strict verify runs twice concurrently.
|
|
54
|
+
if [ "${LUCIAZERO_CHANNEL:-}" = "plugin" ]; then
|
|
55
|
+
CFG="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}"
|
|
56
|
+
if [ -x "${CFG}/hooks/luciazero-verify.sh" ] \
|
|
57
|
+
&& grep -qF "${CFG}/hooks/luciazero-verify.sh" "${CFG}/settings.json" 2>/dev/null; then
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Hook stdin is always a pipe; when run by hand from a terminal for debugging,
|
|
63
|
+
# do not hang waiting for EOF that never comes.
|
|
64
|
+
if [ -t 0 ]; then IN=""; else IN="$(cat 2>/dev/null || true)"; fi
|
|
65
|
+
|
|
66
|
+
pyfield() { # pyfield '<python expr over dict d>' — empty string on any error
|
|
67
|
+
printf '%s' "${IN}" | python3 -c "
|
|
68
|
+
import json, sys
|
|
69
|
+
try:
|
|
70
|
+
d = json.load(sys.stdin)
|
|
71
|
+
v = ${1}
|
|
72
|
+
print('' if v is None else v)
|
|
73
|
+
except Exception:
|
|
74
|
+
print('')" 2>/dev/null || true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
CWD="$(pyfield "d.get('cwd')")"
|
|
78
|
+
[ -n "${CWD}" ] || CWD="${PWD}"
|
|
79
|
+
KEY="$(printf '%s' "${CWD}" | python3 -c 'import sys,hashlib;print(hashlib.md5(sys.stdin.buffer.read()).hexdigest()[:12])' 2>/dev/null)" || exit 0
|
|
80
|
+
[ -n "${KEY}" ] || exit 0
|
|
81
|
+
STATE="${TMPDIR:-/tmp}/luciazero-verify-state/${KEY}"
|
|
82
|
+
mkdir -p "${STATE}" 2>/dev/null || exit 0
|
|
83
|
+
|
|
84
|
+
stat_log() { # stat_log <event> — discipline stats for /retro; fail-open, capped
|
|
85
|
+
SDIR="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}"
|
|
86
|
+
SFILE="${SDIR}/luciazero-stats.log"
|
|
87
|
+
{
|
|
88
|
+
mkdir -p "${SDIR}" \
|
|
89
|
+
&& printf '%s %s %s\n' "$(date +%Y-%m-%dT%H:%M)" "$1" "$(basename "${CWD}")" >> "${SFILE}"
|
|
90
|
+
if [ "$(wc -l < "${SFILE}")" -gt 500 ]; then
|
|
91
|
+
tail -n 250 "${SFILE}" > "${SFILE}.tmp" && mv "${SFILE}.tmp" "${SFILE}"
|
|
92
|
+
fi
|
|
93
|
+
} 2>/dev/null || true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# What counts as a verify run. Deliberately broad; override per-shell with
|
|
97
|
+
# LUCIAZERO_VERIFY_REGEX (extended regex, matched against the Bash tool command).
|
|
98
|
+
# When LUCIAZERO_VERIFY_CMD is set (the repo's exact verify command, e.g.
|
|
99
|
+
# "./test.sh"), only commands that ARE it or START with it count — the broad
|
|
100
|
+
# regex also marks `cat test.sh` or `grep pytest README` as a verify run,
|
|
101
|
+
# flipping the state green without any test having run.
|
|
102
|
+
VERIFY_RE="${LUCIAZERO_VERIFY_REGEX:-verify|test\.sh|pytest|npm (run )?test|pnpm test|yarn test|cargo test|go test|vitest|jest|make (test|check)|tox|rake test|mix test|dotnet test|gradlew? (test|check)}"
|
|
103
|
+
VERIFY_CMD="${LUCIAZERO_VERIFY_CMD:-}"
|
|
104
|
+
|
|
105
|
+
case "${MODE}" in
|
|
106
|
+
edit)
|
|
107
|
+
# Documentation writes do not re-arm the nudge: the closeout skills
|
|
108
|
+
# (/done, /retro, /handoff, /experiment) all write markdown AFTER the
|
|
109
|
+
# final green verify — nudging on those would punish the prescribed flow.
|
|
110
|
+
FP="$(pyfield "d.get('tool_input', {}).get('file_path')")"
|
|
111
|
+
DOC_RE="${LUCIAZERO_DOC_REGEX:-\.(md|markdown|rst|txt)\$}"
|
|
112
|
+
if [ -n "${FP}" ] && printf '%s' "${FP}" | grep -qE "${DOC_RE}"; then
|
|
113
|
+
: # doc-only write; verify state unchanged
|
|
114
|
+
else
|
|
115
|
+
touch "${STATE}/last_edit"
|
|
116
|
+
rm -f "${STATE}/nudged" # new code edits re-arm the one-shot nudge
|
|
117
|
+
fi
|
|
118
|
+
;;
|
|
119
|
+
bash)
|
|
120
|
+
CMD="$(pyfield "d.get('tool_input', {}).get('command')")"
|
|
121
|
+
IS_VERIFY=no
|
|
122
|
+
if [ -n "${CMD}" ]; then
|
|
123
|
+
if [ -n "${VERIFY_CMD}" ]; then
|
|
124
|
+
# exact mode: the command must BE or START WITH the configured command
|
|
125
|
+
case "${CMD}" in
|
|
126
|
+
"${VERIFY_CMD}"|"${VERIFY_CMD} "*) IS_VERIFY=yes ;;
|
|
127
|
+
esac
|
|
128
|
+
elif printf '%s' "${CMD}" | grep -qE "${VERIFY_RE}"; then
|
|
129
|
+
IS_VERIFY=yes
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
if [ "${IS_VERIFY}" = yes ]; then
|
|
133
|
+
# Best-effort red/green from the tool response; unknown shape -> "ran"
|
|
134
|
+
STATUS="$(pyfield "(lambda r, c=None: (lambda c: 'ok' if c == 0 else ('fail' if isinstance(c, int) else ('fail' if r.get('is_error') is True else 'ran')))(r.get('exit_code', r.get('exitCode'))))(d.get('tool_response') or {})")"
|
|
135
|
+
printf '%s\n' "${STATUS:-ran}" > "${STATE}/last_verify"
|
|
136
|
+
# remember WHICH command produced the state — the strict gate's fast
|
|
137
|
+
# path trusts a green only when this matches its own command
|
|
138
|
+
printf '%s\n' "${CMD}" > "${STATE}/last_verify_cmd" 2>/dev/null || true
|
|
139
|
+
rm -f "${STATE}/nudged"
|
|
140
|
+
fi
|
|
141
|
+
;;
|
|
142
|
+
stop)
|
|
143
|
+
# Never re-block a continuation that a stop hook itself caused
|
|
144
|
+
ACTIVE="$(pyfield "d.get('stop_hook_active')")"
|
|
145
|
+
if [ "${ACTIVE}" = "True" ] || [ "${ACTIVE}" = "true" ]; then exit 0; fi
|
|
146
|
+
# Strict gate (opt-in, see header): actually run the user's verify command
|
|
147
|
+
# unless the tracked state is already green-after-last-edit. Any internal
|
|
148
|
+
# error — timeout, missing command, unparseable state — degrades to the
|
|
149
|
+
# ordinary fail-open nudge below, never to a block.
|
|
150
|
+
STRICT_CMD="${LUCIAZERO_STRICT_VERIFY_CMD:-}"
|
|
151
|
+
# Strict gate only on well-formed input: unparseable stdin means we know
|
|
152
|
+
# neither cwd nor stop_hook_active — running a command on guesses would
|
|
153
|
+
# break both the fail-open and the never-re-block guarantees.
|
|
154
|
+
JSON_OK="$(printf '%s' "${IN}" | python3 -c 'import json,sys; json.load(sys.stdin); print("yes")' 2>/dev/null || echo no)"
|
|
155
|
+
if [ -n "${STRICT_CMD}" ] && [ "${JSON_OK}" = yes ]; then
|
|
156
|
+
OUT="$(python3 -c '
|
|
157
|
+
import os, subprocess, sys
|
|
158
|
+
state, cwd, cmd, timeout = sys.argv[1:5]
|
|
159
|
+
def m(name):
|
|
160
|
+
try:
|
|
161
|
+
return os.path.getmtime(os.path.join(state, name))
|
|
162
|
+
except OSError:
|
|
163
|
+
return None
|
|
164
|
+
def read(name):
|
|
165
|
+
try:
|
|
166
|
+
return open(os.path.join(state, name)).read().strip()
|
|
167
|
+
except OSError:
|
|
168
|
+
return ""
|
|
169
|
+
e, v = m("last_edit"), m("last_verify")
|
|
170
|
+
# Fast path only for a green that THIS command (or a longer invocation of
|
|
171
|
+
# it) produced — a broad-regex green from a mere read of the test file must
|
|
172
|
+
# not disarm a gate whose promise is "actually runs the command".
|
|
173
|
+
vcmd = read("last_verify_cmd")
|
|
174
|
+
if (v is not None and read("last_verify") == "ok"
|
|
175
|
+
and (e is None or e <= v)
|
|
176
|
+
and (vcmd == cmd or vcmd.startswith(cmd + " "))):
|
|
177
|
+
print("green"); sys.exit(0)
|
|
178
|
+
try:
|
|
179
|
+
r = subprocess.run(cmd, shell=True, cwd=cwd or None, timeout=float(timeout),
|
|
180
|
+
capture_output=True, text=True)
|
|
181
|
+
except Exception:
|
|
182
|
+
print("error"); sys.exit(0)
|
|
183
|
+
if r.returncode in (126, 127):
|
|
184
|
+
# command not found / not executable: an internal error, not a red verify
|
|
185
|
+
print("error"); sys.exit(0)
|
|
186
|
+
if r.returncode == 0:
|
|
187
|
+
print("ok")
|
|
188
|
+
else:
|
|
189
|
+
tail = ((r.stdout or "") + "\n" + (r.stderr or "")).strip().splitlines()[-8:]
|
|
190
|
+
print("red")
|
|
191
|
+
print("\n".join(tail))
|
|
192
|
+
' "${STATE}" "${CWD}" "${STRICT_CMD}" "${LUCIAZERO_STRICT_TIMEOUT:-120}" 2>/dev/null || echo error)"
|
|
193
|
+
case "${OUT%%$'\n'*}" in
|
|
194
|
+
green) stat_log stop-clean; exit 0 ;;
|
|
195
|
+
ok)
|
|
196
|
+
printf 'ok\n' > "${STATE}/last_verify" 2>/dev/null || true
|
|
197
|
+
printf '%s\n' "${STRICT_CMD}" > "${STATE}/last_verify_cmd" 2>/dev/null || true
|
|
198
|
+
rm -f "${STATE}/nudged"
|
|
199
|
+
stat_log stop-clean
|
|
200
|
+
exit 0 ;;
|
|
201
|
+
red)
|
|
202
|
+
printf 'fail\n' > "${STATE}/last_verify" 2>/dev/null || true
|
|
203
|
+
printf '%s\n' "${STRICT_CMD}" > "${STATE}/last_verify_cmd" 2>/dev/null || true
|
|
204
|
+
stat_log strict-block
|
|
205
|
+
echo "Strict verify gate: '${STRICT_CMD}' is RED. Fix it before finishing — or say plainly that you are handing back a red state. Failing output:" >&2
|
|
206
|
+
echo "${OUT#red}" >&2
|
|
207
|
+
exit 2 ;;
|
|
208
|
+
*) : ;; # error — fall through to the ordinary fail-open nudge
|
|
209
|
+
esac
|
|
210
|
+
fi
|
|
211
|
+
# Sub-second mtime comparison via python: bash 3.2's [ -nt ] compares
|
|
212
|
+
# whole seconds only, silently missing an edit made in the same second
|
|
213
|
+
# as the last verify (the statusline already compares float mtimes).
|
|
214
|
+
NUDGE="$(python3 -c '
|
|
215
|
+
import os, sys
|
|
216
|
+
state = sys.argv[1]
|
|
217
|
+
def m(name):
|
|
218
|
+
try:
|
|
219
|
+
return os.path.getmtime(os.path.join(state, name))
|
|
220
|
+
except OSError:
|
|
221
|
+
return None
|
|
222
|
+
e, v = m("last_edit"), m("last_verify")
|
|
223
|
+
print("yes" if e is not None and (v is None or e > v) else "no")' "${STATE}" 2>/dev/null || echo no)"
|
|
224
|
+
if [ "${NUDGE}" = yes ] && [ ! -f "${STATE}/nudged" ]; then
|
|
225
|
+
touch "${STATE}/nudged"
|
|
226
|
+
stat_log nudge
|
|
227
|
+
echo "Doctrine rule 1: edits were made but no verify command has run since the last edit. Run the repo's verify command and quote its decisive line — or finish anyway and say plainly that the change is unverified. (This nudge fires once.)" >&2
|
|
228
|
+
exit 2
|
|
229
|
+
fi
|
|
230
|
+
# NUDGE=no -> genuinely clean stop; yes-but-already-nudged logs nothing
|
|
231
|
+
# (that nudge was counted when it fired)
|
|
232
|
+
[ "${NUDGE}" = no ] && stat_log stop-clean
|
|
233
|
+
;;
|
|
234
|
+
session)
|
|
235
|
+
# SessionStart: if a handoff capsule exists, emit ONE pointer line as
|
|
236
|
+
# context (stdout) — never the capsule's contents, which must be read and
|
|
237
|
+
# re-verified, not trusted. Silent (zero context cost) when none exists.
|
|
238
|
+
CAP="${CWD}/HANDOFF.md"
|
|
239
|
+
[ -f "${CAP}" ] || exit 0
|
|
240
|
+
AGE="$(python3 -c 'import os,sys,time;print(int((time.time()-os.path.getmtime(sys.argv[1]))//86400))' "${CAP}" 2>/dev/null || echo '')"
|
|
241
|
+
STALE="${LUCIAZERO_HANDOFF_STALE_DAYS:-7}"
|
|
242
|
+
if [ -n "${AGE}" ] && [ "${AGE}" -ge "${STALE}" ] 2>/dev/null; then
|
|
243
|
+
echo "HANDOFF.md exists but is ${AGE} days old — likely stale; a stale capsule is worse than none. Verify its claims with extra suspicion or delete it."
|
|
244
|
+
else
|
|
245
|
+
echo "HANDOFF.md exists (age: ${AGE:-?}d) — read it before touching code, re-verify its claims against the tree, then delete it (/handoff consume protocol)."
|
|
246
|
+
fi
|
|
247
|
+
;;
|
|
248
|
+
*)
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
251
|
+
|
|
252
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Luciazero — default operating mode
|
|
2
|
+
|
|
3
|
+
Applies to every repo, every session. The loop is plan → change → verify → fix, repeated until verify passes or the blocker is real.
|
|
4
|
+
|
|
5
|
+
## Ground truth
|
|
6
|
+
|
|
7
|
+
1. **Done is proven by a command, not by my judgment.** Before saying a change works, run something that returns an exit code — test, lint, type-check, build, or a real invocation — and quote the shortest decisive line of its output. A run that did not happen is reported as exactly that. (closeout procedure: `/done`)
|
|
8
|
+
2. **If no verification command exists, that is the first bug.** Say so and offer to create the smallest one that covers the change (procedure: `/luciazero-bootstrap`). Do not silently proceed on vibes.
|
|
9
|
+
3. **Failing test/lint means not done.** Fix the cause. Never delete, skip, weaken, or suppress a check to reach green — if a check is genuinely wrong, say why and ask.
|
|
10
|
+
|
|
11
|
+
## Loop
|
|
12
|
+
|
|
13
|
+
4. **Debugging starts with a hypothesis, not an edit.** State the suspected cause and the command whose output would confirm or refute it — run that command before touching code (procedure: `/debug`). Keep the reproduction as a regression test: red before the fix, green after.
|
|
14
|
+
5. **Orient before editing an unfamiliar repo:** find how to run, test, and lint it — CI is the most honest source of truth.
|
|
15
|
+
6. **Smallest reversible step.** Targeted diff over rewrite; extend an existing helper instead of adding a parallel one.
|
|
16
|
+
7. **Review the diff as a skeptic before saying done.** Tests prove what they cover; the last pass hunts what they do not — edge cases, error paths, changed contracts, files touched by accident. For risky or wide diffs, get an independent adversarial review — the harness's built-in review command when one exists, otherwise the `reviewer` agent — instructed to refute, not approve.
|
|
17
|
+
|
|
18
|
+
## Memory
|
|
19
|
+
|
|
20
|
+
8. **Never re-derive a dead end twice.** Non-obvious findings — null results, footguns, "tried X, failed because Y" — go into the project's notes file (`CLAUDE.md` / `AGENTS.md`, extend the one the repo uses), and those notes get read before working in an area they cover (procedure: `/retro`).
|
|
21
|
+
|
|
22
|
+
## Autonomy
|
|
23
|
+
|
|
24
|
+
9. **Stop and ask before:** deleting data, deploying or touching production, force-pushing, changing a public API or contract, spending real money, or leaving the agreed scope — make the question decidable in one reply: what, why, options, recommendation. Everything else: proceed, batching unknowns into one sharp question at the right time. Finish the whole scope; name anything left out.
|
package/install-codex.sh
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install the Luciazero doctrine + skills into OpenAI Codex CLI (~/.codex).
|
|
3
|
+
# Idempotent. Backs up AGENTS.md before editing. Writes nothing outside CODEX_HOME.
|
|
4
|
+
#
|
|
5
|
+
# Mapping (single source of truth stays in claude/):
|
|
6
|
+
# claude/luciazero.md -> marker block in ~/.codex/AGENTS.md
|
|
7
|
+
# skills/<each>/ -> ~/.codex/skills/<each>/
|
|
8
|
+
# (luciazero-bootstrap, retro, debug, done, handoff, experiment)
|
|
9
|
+
# claude/agents/reviewer.md -> ~/.codex/skills/reviewer/SKILL.md
|
|
10
|
+
# (Codex has no subagents; ships as a skill,
|
|
11
|
+
# Claude-only `tools:`/`model:` lines dropped)
|
|
12
|
+
# claude/hooks/ (enforcement pack) -> NOT installed: Codex has no hooks/statusline
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
for ARG in "$@"; do
|
|
16
|
+
echo "unknown option: ${ARG} (install-codex.sh takes no options)" >&2; exit 1
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
CODEX_DIR="${CODEX_HOME:-$HOME/.codex}"
|
|
21
|
+
AGENTS_MD="${CODEX_DIR}/AGENTS.md"
|
|
22
|
+
START='<!-- luciazero:start -->'
|
|
23
|
+
END='<!-- luciazero:end -->'
|
|
24
|
+
|
|
25
|
+
# collision-proof backup path for $1 (two runs in the same second must not overwrite)
|
|
26
|
+
bakpath() {
|
|
27
|
+
B="$1.bak.$(date +%Y%m%d%H%M%S)"
|
|
28
|
+
N=1
|
|
29
|
+
while [ -e "${B}" ]; do B="$1.bak.$(date +%Y%m%d%H%M%S).${N}"; N=$((N+1)); done
|
|
30
|
+
printf '%s' "${B}"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
echo "Installing into ${CODEX_DIR}"
|
|
34
|
+
mkdir -p "${CODEX_DIR}/skills"
|
|
35
|
+
|
|
36
|
+
# 1. doctrine as a marker block in global AGENTS.md (replaced in place on reinstall)
|
|
37
|
+
TMP="$(mktemp)"
|
|
38
|
+
if [ -f "${AGENTS_MD}" ]; then
|
|
39
|
+
cp "${AGENTS_MD}" "$(bakpath "${AGENTS_MD}")"
|
|
40
|
+
# strip the old block AND trailing blank lines, so reinstalls do not
|
|
41
|
+
# accumulate one separator blank line per run
|
|
42
|
+
awk -v s="${START}" -v e="${END}" '
|
|
43
|
+
$0==s {inblock=1; next}
|
|
44
|
+
$0==e {inblock=0; next}
|
|
45
|
+
inblock {next}
|
|
46
|
+
NF {for (i=0; i<blank; i++) print ""; blank=0; print; next}
|
|
47
|
+
{blank++}
|
|
48
|
+
' "${AGENTS_MD}" > "${TMP}"
|
|
49
|
+
fi
|
|
50
|
+
{
|
|
51
|
+
if [ -s "${TMP}" ]; then cat "${TMP}"; echo; fi
|
|
52
|
+
echo "${START}"
|
|
53
|
+
cat "${SRC}/claude/luciazero.md"
|
|
54
|
+
echo "${END}"
|
|
55
|
+
} > "${AGENTS_MD}"
|
|
56
|
+
rm -f "${TMP}"
|
|
57
|
+
echo " ok AGENTS.md doctrine block"
|
|
58
|
+
|
|
59
|
+
# 2. skills — same SKILL.md format as Claude Code, copied as-is
|
|
60
|
+
for SKILL in luciazero-bootstrap retro debug 'done' handoff experiment; do
|
|
61
|
+
rm -rf "${CODEX_DIR}/skills/${SKILL}"
|
|
62
|
+
cp -r "${SRC}/skills/${SKILL}" "${CODEX_DIR}/skills/${SKILL}"
|
|
63
|
+
echo " ok skills/${SKILL}"
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
# 3. reviewer agent as a Codex skill, minus the Claude-only tools:/model: lines
|
|
67
|
+
rm -rf "${CODEX_DIR}/skills/reviewer"
|
|
68
|
+
mkdir -p "${CODEX_DIR}/skills/reviewer"
|
|
69
|
+
awk 'NR==1 {front=($0=="---")} front && /^(tools|model): / {next} {print} front && NR>1 && $0=="---" {front=0}' \
|
|
70
|
+
"${SRC}/claude/agents/reviewer.md" > "${CODEX_DIR}/skills/reviewer/SKILL.md"
|
|
71
|
+
echo " ok skills/reviewer"
|
|
72
|
+
|
|
73
|
+
# 4. version sidecar (informational; removed by uninstall-codex.sh)
|
|
74
|
+
V_NEW="$(grep -m1 -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' "${SRC}/CHANGELOG.md" 2>/dev/null | tr -d '#[] ' || true)"
|
|
75
|
+
if [ -n "${V_NEW}" ]; then
|
|
76
|
+
printf '%s\n' "${V_NEW}" > "${CODEX_DIR}/.luciazero-version"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
echo
|
|
80
|
+
echo "Done. Verify:"
|
|
81
|
+
echo " grep -c 'luciazero:start' ${AGENTS_MD} # expect 1"
|
|
82
|
+
echo " ls ${CODEX_DIR}/skills/"
|
|
83
|
+
echo
|
|
84
|
+
echo "The doctrine applies from the next Codex session."
|