luciazero 2.2.0 → 2.3.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 +71 -0
- package/README.md +58 -32
- package/README.th.md +56 -31
- package/claude/hooks/luciazero-statusline.sh +3 -1
- package/claude/hooks/luciazero-verify.sh +151 -12
- package/install-codex.sh +28 -0
- package/install.sh +38 -4
- package/package.json +2 -2
- package/skills/aliases.txt +1 -2
- package/skills/bisect/SKILL.md +1 -1
- package/skills/debug/SKILL.md +3 -3
- package/skills/discipline-report/SKILL.md +6 -3
- package/skills/done/SKILL.md +2 -2
- package/skills/experiment/SKILL.md +1 -1
- package/skills/imouto-mode/SKILL.md +1 -1
- package/skills/lucia-relay/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/ready/SKILL.md +2 -2
- package/skills/retro/SKILL.md +3 -3
- package/skills/show/SKILL.md +1 -1
- package/uninstall-codex.sh +14 -2
- package/uninstall.sh +14 -2
- /package/{skills/luciazero-bootstrap → migrations/luciazero-bootstrap-v2.2.0}/SKILL.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,77 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.3.0] - 2026-08-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `test.sh` clears ambient `LUCIAZERO_*` variables before running. An exported
|
|
15
|
+
`LUCIAZERO_VERIFY_CMD` flipped the hook fixtures into exact-match mode, so
|
|
16
|
+
the suite went red on exactly the machines that dogfood the pack
|
|
17
|
+
(`FAIL: stop hook nudged despite verify after edit`) while CI stayed green.
|
|
18
|
+
A new self-test re-runs the fast tier in a child poisoned with every knob the
|
|
19
|
+
hooks read, and quotes the child's own failing line.
|
|
20
|
+
- The verify hook parses under bash 3.2 again — the `/bin/bash` on stock macOS.
|
|
21
|
+
A here-document inside a command substitution (with a quoted expansion and a
|
|
22
|
+
trailing redirection on the same line) breaks that parser, and it rejects the
|
|
23
|
+
**whole file** at load time while pointing at an unrelated later line, so the
|
|
24
|
+
enforcement pack silently did nothing there. The scanner program now lives in
|
|
25
|
+
a variable. `test.sh` rejects the construct in the hooks and, with
|
|
26
|
+
`LZ_BASH32=/path/to/bash-3.2`, parses every script with the real thing.
|
|
27
|
+
- Both hooks call `hashlib.md5(..., usedforsecurity=False)` for their state
|
|
28
|
+
directory name. On a FIPS-enforcing python3 the bare call raised and the
|
|
29
|
+
tracker failed open — silently doing nothing. The digest is unchanged, so
|
|
30
|
+
existing state keys still resolve.
|
|
31
|
+
- CI now parses every repository shell script with Bash 3.2.
|
|
32
|
+
|
|
33
|
+
### Security
|
|
34
|
+
|
|
35
|
+
- A repository's **committed** `.claude/settings.json` can no longer configure
|
|
36
|
+
Luciazero at all: every `LUCIAZERO_*` key declared there is dropped and the
|
|
37
|
+
hook falls back to its own defaults. `LUCIAZERO_VERIFY_REGEX` and
|
|
38
|
+
`LUCIAZERO_VERIFY_CMD` could make any command count as a verify run,
|
|
39
|
+
`LUCIAZERO_DOC_REGEX='.*'` made every edit look like documentation so nothing
|
|
40
|
+
was ever unverified, and `LUCIAZERO_STRICT_VERIFY_CMD` was a command the stop
|
|
41
|
+
hook would run. A committed `CLAUDE_CONFIG_DIR` is refused for the same
|
|
42
|
+
reason: it could point at a repository-controlled "wired classic install" and
|
|
43
|
+
make every hook copy stand down. Only the default `~/.claude` is treated as
|
|
44
|
+
the user's config directory during the search — honouring `CLAUDE_CONFIG_DIR`
|
|
45
|
+
there let a repository point it at its own `.claude` so the scanner skipped
|
|
46
|
+
the file declaring the key. The search covers the session directory and
|
|
47
|
+
its ancestors — Claude Code merges project settings from the repository root
|
|
48
|
+
and a session's cwd is often a subdirectory — but it is **project scope
|
|
49
|
+
only**: it stops at the repository root, at `CLAUDE_PROJECT_DIR`, and at
|
|
50
|
+
`$HOME`, and never reads the user's own config directory, so a global
|
|
51
|
+
`~/.claude/settings.json` keeps configuring the hook.
|
|
52
|
+
`SessionStart` names the refused keys once. The
|
|
53
|
+
personal, gitignored `.claude/settings.local.json` is untouched, a parse error
|
|
54
|
+
leaves values alone (still fail-open), the lookup runs only in the modes that
|
|
55
|
+
consume a knob, skips a non-regular file (a planted fifo would hang the hook),
|
|
56
|
+
and refuses everything outright on an absurdly large settings file instead of
|
|
57
|
+
parsing it.
|
|
58
|
+
- Channel dedupe is decided from the running copy's own path instead of
|
|
59
|
+
`LUCIAZERO_CHANNEL`. A committed `env` block could set that variable, hand the
|
|
60
|
+
**classic** hook a plugin label, and make it stand itself down — disabling
|
|
61
|
+
enforcement with one line.
|
|
62
|
+
- `install.sh --with-hooks` refuses a python3 older than 3.9 (where hashlib
|
|
63
|
+
gained `usedforsecurity=`) instead of installing hooks that fail open, and
|
|
64
|
+
`--status` reports the version. README states the requirement.
|
|
65
|
+
- CI runs with `permissions: contents: read`, and both workflows pin every
|
|
66
|
+
action to a commit SHA.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- `shellcheck` is required, not silently skipped, when `CI` or
|
|
71
|
+
`LZ_REQUIRE_LINT` is set — a local green must not disagree with the CI that
|
|
72
|
+
gates the release.
|
|
73
|
+
- README and README.th document the committed-settings refusal and the
|
|
74
|
+
Windows/WSL requirement.
|
|
75
|
+
- Retired the `/luciazero-bootstrap` compatibility alias after the `/ready`
|
|
76
|
+
migration window. Installers remove only untouched Luciazero-owned copies and
|
|
77
|
+
preserve customized user directories.
|
|
78
|
+
- Capped skill discovery descriptions at 40 words and tightened path/offline
|
|
79
|
+
guidance for the closeout, debug, ready, retro, and discipline skills.
|
|
80
|
+
|
|
10
81
|
## [2.2.0] - 2026-08-15
|
|
11
82
|
|
|
12
83
|
### Added
|
package/README.md
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ohm41321/luciazero/main/docs/assets/lucia.png" width="220" alt="Lucia — Luciazero's mascot">
|
|
3
|
+
<h1>Luciazero</h1>
|
|
4
|
+
<p>
|
|
5
|
+
<strong>Evidence-first discipline for coding agents.</strong><br>
|
|
6
|
+
<code>plan → change → verify → fix</code>
|
|
7
|
+
</p>
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://www.npmjs.com/package/luciazero"><img src="https://img.shields.io/npm/v/luciazero" alt="npm version"></a>
|
|
10
|
+
<a href="https://github.com/ohm41321/luciazero/actions/workflows/ci.yml"><img src="https://github.com/ohm41321/luciazero/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/ohm41321/luciazero" alt="MIT license"></a>
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
**English** · [ภาษาไทย](README.th.md)
|
|
16
|
+
|
|
17
|
+
Luciazero is a lightweight discipline layer for coding agents. It works with
|
|
18
|
+
Claude Code, Codex CLI, and compatible skill runtimes through plugin, CLI, or
|
|
19
|
+
skills-only installs.
|
|
15
20
|
|
|
16
21
|
> Done is proven by a command, not by my judgment. If no verification command
|
|
17
22
|
> exists, that is the first bug.
|
|
18
23
|
|
|
19
24
|
It ships a compact [9-rule doctrine](claude/luciazero.md), 11 on-demand skills,
|
|
20
|
-
verification hooks, a risk-routed reviewer, and an eval harness. It is
|
|
21
|
-
|
|
25
|
+
verification hooks, a risk-routed reviewer, and an eval harness. It is designed
|
|
26
|
+
to make completion claims auditable—not to replace an agent runtime or become an
|
|
27
|
+
overnight orchestrator.
|
|
22
28
|
|
|
23
|
-
## See
|
|
29
|
+
## See the loop
|
|
24
30
|
|
|
25
31
|
This GIF is driven by the shipped hooks, not a mockup:
|
|
26
32
|
|
|
@@ -34,7 +40,7 @@ This GIF is driven by the shipped hooks, not a mockup:
|
|
|
34
40
|
✅ verify 3m → the latest check passed three minutes ago
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
## What it
|
|
43
|
+
## What it protects
|
|
38
44
|
|
|
39
45
|
| Failure mode | Mechanism |
|
|
40
46
|
|---|---|
|
|
@@ -49,7 +55,7 @@ This GIF is driven by the shipped hooks, not a mockup:
|
|
|
49
55
|
Mechanical guarantees run in `test.sh`; behavioral claims are measured by the
|
|
50
56
|
[eval harness](eval/README.md).
|
|
51
57
|
|
|
52
|
-
##
|
|
58
|
+
## Keep work portable
|
|
53
59
|
|
|
54
60
|
`/lucia-relay` transfers decisions and evidence instead of dumping a chat
|
|
55
61
|
transcript. Session A writes canonical `LUCIA_RELAY.json` plus a generated
|
|
@@ -74,7 +80,11 @@ checks—not model-uplift results. See the [method and limits](docs/benchmark.md
|
|
|
74
80
|
|
|
75
81
|
## Install
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
Luciazero supports Claude Code, Codex CLI, and compatible agents. Choose the
|
|
84
|
+
installation path that matches your workflow.
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary><strong>Recommended · Claude Code plugin</strong></summary>
|
|
78
88
|
|
|
79
89
|
Carries the doctrine, all skills, reviewer, and verify-tracking hooks:
|
|
80
90
|
|
|
@@ -87,20 +97,25 @@ Start a repository with `/luciazero:ready`. Plugin skills use the
|
|
|
87
97
|
`/luciazero:` prefix. The plugin has no statusline because Claude Code plugins
|
|
88
98
|
cannot set one.
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
</details>
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary><strong>Skills only · any compatible agent</strong></summary>
|
|
91
104
|
|
|
92
105
|
```bash
|
|
93
106
|
npx skills add ohm41321/luciazero
|
|
94
107
|
```
|
|
95
108
|
|
|
96
|
-
This installs the 11 skills
|
|
97
|
-
compatibility alias: no doctrine, reviewer, or hooks.
|
|
109
|
+
This installs the 11 skills: no doctrine, reviewer, or hooks.
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
</details>
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><strong>Classic install · Claude Code or Codex CLI</strong></summary>
|
|
100
115
|
|
|
101
116
|
```bash
|
|
102
117
|
npx luciazero # Claude Code
|
|
103
|
-
npx luciazero --with-hooks # Claude Code + hooks/statusline; needs Python 3
|
|
118
|
+
npx luciazero --with-hooks # Claude Code + hooks/statusline; needs Python 3.9+
|
|
104
119
|
npx luciazero codex # Codex CLI
|
|
105
120
|
|
|
106
121
|
npx luciazero uninstall
|
|
@@ -112,7 +127,9 @@ Classic installs support `--status`; Codex receives the doctrine and skills but
|
|
|
112
127
|
not Claude-only hooks/statusline. Installers back up name collisions and remove
|
|
113
128
|
only exact Luciazero-managed copies on uninstall.
|
|
114
129
|
|
|
115
|
-
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
## Update safely
|
|
116
133
|
|
|
117
134
|
Luciazero never changes classic or Codex files in the background.
|
|
118
135
|
|
|
@@ -141,7 +158,7 @@ Claude Code can auto-update the plugin at startup: open `/plugin` →
|
|
|
141
158
|
marketplaces leave this off by default. For release-only notifications, use
|
|
142
159
|
GitHub **Watch → Custom → Releases**.
|
|
143
160
|
|
|
144
|
-
##
|
|
161
|
+
## Skills at a glance
|
|
145
162
|
|
|
146
163
|
Run `/ready` first; the rest activate when their moment arrives.
|
|
147
164
|
|
|
@@ -168,7 +185,7 @@ Risky diffs also pass through one read-only `reviewer` with `security`,
|
|
|
168
185
|
`contract`, or `general` focus. Security and contract risk together receive two
|
|
169
186
|
separate passes.
|
|
170
187
|
|
|
171
|
-
## Evidence
|
|
188
|
+
## Evidence & limitations
|
|
172
189
|
|
|
173
190
|
<!-- BEGIN GENERATED: benchmark-evidence -->
|
|
174
191
|
|
|
@@ -206,10 +223,11 @@ only one run per arm per task. See the [full benchmark](docs/benchmark.md),
|
|
|
206
223
|
|
|
207
224
|
<!-- END GENERATED: benchmark-evidence -->
|
|
208
225
|
|
|
209
|
-
##
|
|
226
|
+
## Security & requirements
|
|
210
227
|
|
|
211
228
|
- Node.js 18+ for the CLI and discipline report.
|
|
212
|
-
- Bash for classic installers; Python 3 for hooks and Lucia Relay
|
|
229
|
+
- Bash for classic installers; Python 3.9+ for hooks and Lucia Relay
|
|
230
|
+
(`install.sh --with-hooks` refuses anything older).
|
|
213
231
|
- Core installers, hooks, helpers, and graders are offline. Real behavioral
|
|
214
232
|
evals invoke a model CLI and consume API credit or subscription quota.
|
|
215
233
|
- Hooks run commands on your machine. Read them before enabling them.
|
|
@@ -219,10 +237,18 @@ only one run per arm per task. See the [full benchmark](docs/benchmark.md),
|
|
|
219
237
|
- Set `LUCIAZERO_VERIFY_CMD` to the repo's exact fast verify command.
|
|
220
238
|
- Put `LUCIAZERO_STRICT_VERIFY_CMD` only in personal settings, never in a
|
|
221
239
|
committed repository config. Strict mode fails open on internal errors.
|
|
240
|
+
- A repository's committed `.claude/settings.json` cannot configure Luciazero
|
|
241
|
+
at all: every `LUCIAZERO_*` key (and `CLAUDE_CONFIG_DIR`) declared there — in
|
|
242
|
+
the session directory or any ancestor up to the repository root — is refused
|
|
243
|
+
and named once at `SessionStart`. Your own settings still configure it: the
|
|
244
|
+
search stops at the repo root and at `$HOME`, and never reads your global
|
|
245
|
+
`~/.claude/settings.json` or gitignored `.claude/settings.local.json`.
|
|
246
|
+
- Windows: the installers and hooks are Bash scripts — run them under WSL.
|
|
247
|
+
`npx luciazero discipline` works in native Node.
|
|
222
248
|
|
|
223
249
|
See [SECURITY.md](SECURITY.md) for the complete trust boundary.
|
|
224
250
|
|
|
225
|
-
##
|
|
251
|
+
## Developing Luciazero
|
|
226
252
|
|
|
227
253
|
```bash
|
|
228
254
|
./test.sh --fast # intermediate loop: core doctrine/hooks/report/Relay checks
|
|
@@ -247,7 +273,7 @@ More detail:
|
|
|
247
273
|
- [Publishing](docs/publishing.md)
|
|
248
274
|
- [Changelog](CHANGELOG.md)
|
|
249
275
|
|
|
250
|
-
##
|
|
276
|
+
## Support the project
|
|
251
277
|
|
|
252
278
|
Luciazero shares its mascot with [Lucia](https://lucia-discord-bot.vercel.app),
|
|
253
279
|
a Thai-language Discord bot. If Luciazero saves you review cycles, you can
|
package/README.th.md
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/ohm41321/luciazero/main/docs/assets/lucia.png" width="220" alt="Lucia — มาสคอตของ Luciazero">
|
|
3
|
+
<h1>Luciazero</h1>
|
|
4
|
+
<p>
|
|
5
|
+
<strong>ชั้นวินัยที่พิสูจน์ได้ด้วยหลักฐานสำหรับ coding agent</strong><br>
|
|
6
|
+
<code>วางแผน → แก้ → ตรวจ → แก้ซ้ำ</code>
|
|
7
|
+
</p>
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://www.npmjs.com/package/luciazero"><img src="https://img.shields.io/npm/v/luciazero" alt="npm version"></a>
|
|
10
|
+
<a href="https://github.com/ohm41321/luciazero/actions/workflows/ci.yml"><img src="https://github.com/ohm41321/luciazero/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/ohm41321/luciazero" alt="MIT license"></a>
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
[English](README.md) · **ภาษาไทย**
|
|
16
|
+
|
|
17
|
+
Luciazero เป็นชั้นวินัยขนาดเล็กสำหรับ coding agent รองรับ Claude Code,
|
|
18
|
+
Codex CLI และ runtime ที่ใช้ skill ได้ ผ่านการติดตั้งแบบ plugin, CLI
|
|
19
|
+
หรือเฉพาะ skill
|
|
15
20
|
|
|
16
21
|
> งานเสร็จต้องพิสูจน์ด้วยคำสั่ง ไม่ใช่คำตัดสินของ agent
|
|
17
22
|
> ถ้ายังไม่มีคำสั่งตรวจ นั่นคือบั๊กแรก
|
|
18
23
|
|
|
19
24
|
ภายในมี [doctrine 9 ข้อ](claude/luciazero.md) ที่สั้น, skill แบบเรียกเมื่อจำเป็น
|
|
20
25
|
11 ตัว, hook ติดตามการ verify, reviewer ที่ route ตามความเสี่ยง และ eval harness
|
|
21
|
-
|
|
26
|
+
นี่คือชั้นวินัยที่ทำให้คำกล่าวว่าเสร็จตรวจสอบได้ ไม่ใช่ agent runtime หรือระบบ
|
|
27
|
+
orchestration สำหรับรันงานข้ามคืน
|
|
22
28
|
|
|
23
|
-
##
|
|
29
|
+
## ดูลูปการทำงาน
|
|
24
30
|
|
|
25
31
|
GIF นี้ขับด้วย hook ที่ ship จริง ไม่ใช่ mockup:
|
|
26
32
|
|
|
@@ -34,7 +40,7 @@ GIF นี้ขับด้วย hook ที่ ship จริง ไม่ใ
|
|
|
34
40
|
✅ verify 3m → การตรวจผ่านเมื่อสามนาทีก่อน
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
##
|
|
43
|
+
## ปกป้องอะไร
|
|
38
44
|
|
|
39
45
|
| ความพัง | กลไกที่จับ |
|
|
40
46
|
|---|---|
|
|
@@ -49,7 +55,7 @@ GIF นี้ขับด้วย hook ที่ ship จริง ไม่ใ
|
|
|
49
55
|
กลไกที่ตรวจด้วยเครื่องรันใน `test.sh`; ข้ออ้างด้านพฤติกรรมวัดด้วย
|
|
50
56
|
[eval harness](eval/README.md)
|
|
51
57
|
|
|
52
|
-
##
|
|
58
|
+
## ส่งต่องานข้าม agent
|
|
53
59
|
|
|
54
60
|
`/lucia-relay` ส่งต่อการตัดสินใจและหลักฐาน แทนการเท transcript ทั้งแชต
|
|
55
61
|
Session A สร้าง `LUCIA_RELAY.json` ที่เป็น canonical พร้อม human view
|
|
@@ -72,7 +78,11 @@ fingerprint เก่า (5/6) ตัวเลขเหล่านี้เป
|
|
|
72
78
|
|
|
73
79
|
## ติดตั้ง
|
|
74
80
|
|
|
75
|
-
|
|
81
|
+
Luciazero รองรับ Claude Code, Codex CLI และ agent ที่ใช้ skill ได้ เลือกช่องทาง
|
|
82
|
+
ที่เหมาะกับ workflow ของคุณ
|
|
83
|
+
|
|
84
|
+
<details>
|
|
85
|
+
<summary><strong>แนะนำ · Claude Code plugin</strong></summary>
|
|
76
86
|
|
|
77
87
|
ได้ doctrine, skill ทั้งหมด, reviewer และ hook ติดตาม verify:
|
|
78
88
|
|
|
@@ -84,20 +94,25 @@ fingerprint เก่า (5/6) ตัวเลขเหล่านี้เป
|
|
|
84
94
|
เริ่ม repo ด้วย `/luciazero:ready` ชื่อ skill แบบ plugin มี prefix
|
|
85
95
|
`/luciazero:` และไม่มี statusline เพราะ Claude Code plugin ตั้งค่านี้ไม่ได้
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
</details>
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary><strong>เฉพาะ skill · agent ที่รองรับ</strong></summary>
|
|
88
101
|
|
|
89
102
|
```bash
|
|
90
103
|
npx skills add ohm41321/luciazero
|
|
91
104
|
```
|
|
92
105
|
|
|
93
|
-
ช่องทางนี้ติดตั้งเฉพาะ skill 11 ตัว
|
|
94
|
-
ชั่วคราว ไม่มี doctrine, reviewer หรือ hook
|
|
106
|
+
ช่องทางนี้ติดตั้งเฉพาะ skill 11 ตัว ไม่มี doctrine, reviewer หรือ hook
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
</details>
|
|
109
|
+
|
|
110
|
+
<details>
|
|
111
|
+
<summary><strong>Classic install · Claude Code หรือ Codex CLI</strong></summary>
|
|
97
112
|
|
|
98
113
|
```bash
|
|
99
114
|
npx luciazero # Claude Code
|
|
100
|
-
npx luciazero --with-hooks # Claude Code + hook/statusline; ต้องมี Python 3
|
|
115
|
+
npx luciazero --with-hooks # Claude Code + hook/statusline; ต้องมี Python 3.9+
|
|
101
116
|
npx luciazero codex # Codex CLI
|
|
102
117
|
|
|
103
118
|
npx luciazero uninstall
|
|
@@ -109,7 +124,9 @@ npx luciazero uninstall-codex
|
|
|
109
124
|
เฉพาะ Claude Installer สำรองชื่อที่ชน และตอนถอนจะลบเฉพาะสำเนาที่ Luciazero
|
|
110
125
|
ยืนยันความเป็นเจ้าของได้
|
|
111
126
|
|
|
112
|
-
|
|
127
|
+
</details>
|
|
128
|
+
|
|
129
|
+
## อัปเดตอย่างปลอดภัย
|
|
113
130
|
|
|
114
131
|
Luciazero จะไม่แก้ไฟล์ของ classic หรือ Codex อยู่เบื้องหลัง
|
|
115
132
|
|
|
@@ -137,7 +154,7 @@ Claude Code อัปเดต plugin ตอนเริ่มโปรแกร
|
|
|
137
154
|
ภายนอกจะปิดตัวเลือกนี้เป็นค่าเริ่มต้น ถ้าต้องการเพียงการแจ้งเตือน release ให้ใช้
|
|
138
155
|
GitHub **Watch → Custom → Releases**
|
|
139
156
|
|
|
140
|
-
##
|
|
157
|
+
## ภาพรวม skill ทั้ง 11 ตัว
|
|
141
158
|
|
|
142
159
|
รัน `/ready` ก่อนหนึ่งครั้ง ที่เหลือใช้เมื่อถึงจังหวะของมัน
|
|
143
160
|
|
|
@@ -163,7 +180,7 @@ GitHub **Watch → Custom → Releases**
|
|
|
163
180
|
Diff เสี่ยงจะผ่าน `reviewer` แบบอ่านอย่างเดียวใน focus `security`, `contract`
|
|
164
181
|
หรือ `general` ถ้าเสี่ยงทั้ง security และ contract จะตรวจแยกสองรอบ
|
|
165
182
|
|
|
166
|
-
##
|
|
183
|
+
## หลักฐานและข้อจำกัด
|
|
167
184
|
|
|
168
185
|
<!-- BEGIN GENERATED: benchmark-evidence -->
|
|
169
186
|
|
|
@@ -201,10 +218,10 @@ pilot มีเพียง 1 run ต่อ arm ต่อ task ดู [ผลเ
|
|
|
201
218
|
|
|
202
219
|
<!-- END GENERATED: benchmark-evidence -->
|
|
203
220
|
|
|
204
|
-
##
|
|
221
|
+
## ความปลอดภัยและ requirement
|
|
205
222
|
|
|
206
223
|
- Node.js 18+ สำหรับ CLI และ discipline report
|
|
207
|
-
- Bash สำหรับ classic installer; Python 3 สำหรับ hook และ Lucia Relay
|
|
224
|
+
- Bash สำหรับ classic installer; Python 3.9+ สำหรับ hook และ Lucia Relay (`install.sh --with-hooks` ปฏิเสธเวอร์ชันเก่ากว่านี้)
|
|
208
225
|
- Installer, hook, helper และ grader หลักรัน offline ส่วน behavioral eval จริง
|
|
209
226
|
เรียก model CLI และใช้เครดิต API หรือโควตา subscription
|
|
210
227
|
- Hook รันคำสั่งบนเครื่อง ควรอ่านก่อนเปิดใช้
|
|
@@ -214,10 +231,18 @@ pilot มีเพียง 1 run ต่อ arm ต่อ task ดู [ผลเ
|
|
|
214
231
|
- ตั้ง `LUCIAZERO_VERIFY_CMD` เป็นคำสั่ง verify ระดับเร็วที่ exact ของ repo
|
|
215
232
|
- ใส่ `LUCIAZERO_STRICT_VERIFY_CMD` ใน personal settings เท่านั้น ห้าม commit ลง
|
|
216
233
|
config ของ repository; strict mode จะ fail open เมื่อเกิด internal error
|
|
234
|
+
- `.claude/settings.json` ที่ commit ไว้ใน repository ตั้งค่า Luciazero ไม่ได้เลย:
|
|
235
|
+
คีย์ `LUCIAZERO_*` ทุกตัว (รวม `CLAUDE_CONFIG_DIR`) ที่ประกาศไว้ที่นั่น — ทั้งใน
|
|
236
|
+
ไดเรกทอรีที่เปิด session และไดเรกทอรีแม่จนถึง root ของ repo — จะถูกปฏิเสธ
|
|
237
|
+
และแจ้งชื่อคีย์หนึ่งครั้งตอน `SessionStart` ส่วน settings ของคุณเองยังใช้ได้:
|
|
238
|
+
การค้นหยุดที่ root ของ repo และที่ `$HOME` ไม่เคยอ่าน `~/.claude/settings.json`
|
|
239
|
+
หรือ `.claude/settings.local.json` ของคุณ
|
|
240
|
+
- Windows: installer และ hook เป็นสคริปต์ Bash ให้รันใน WSL;
|
|
241
|
+
`npx luciazero discipline` ใช้ได้บน Node ปกติ
|
|
217
242
|
|
|
218
243
|
อ่าน trust boundary ฉบับเต็มใน [SECURITY.md](SECURITY.md)
|
|
219
244
|
|
|
220
|
-
## พัฒนา
|
|
245
|
+
## พัฒนา Luciazero
|
|
221
246
|
|
|
222
247
|
```bash
|
|
223
248
|
./test.sh --fast # loop ระหว่างทำ: ตรวจ doctrine/hook/report/Relay ส่วนหลัก
|
|
@@ -241,7 +266,7 @@ plugin/npm, eval grader ที่พิสูจน์ตัวเองได
|
|
|
241
266
|
- [การ publish](docs/publishing.md)
|
|
242
267
|
- [Changelog](CHANGELOG.md)
|
|
243
268
|
|
|
244
|
-
##
|
|
269
|
+
## สนับสนุนโปรเจกต์
|
|
245
270
|
|
|
246
271
|
Luciazero ใช้มาสคอตร่วมกับ [Lucia](https://lucia-discord-bot.vercel.app)
|
|
247
272
|
Discord bot ภาษาไทย ถ้า Luciazero ช่วยลดรอบ review ได้
|
|
@@ -26,7 +26,9 @@ except Exception:
|
|
|
26
26
|
model = ((d.get("model") or {}).get("display_name")) or "claude"
|
|
27
27
|
cwd = ((d.get("workspace") or {}).get("current_dir")) or d.get("cwd") or os.getcwd()
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# state-directory name, not a security decision — explicit so a FIPS python3
|
|
30
|
+
# does not raise here and drop the statusline to its minimal fallback line
|
|
31
|
+
key = hashlib.md5(cwd.encode(), usedforsecurity=False).hexdigest()[:12]
|
|
30
32
|
uid = os.getuid() if hasattr(os, "getuid") else "unknown"
|
|
31
33
|
base = os.path.join(os.environ.get("TMPDIR", "/tmp"), f"luciazero-verify-state-{uid}")
|
|
32
34
|
try:
|
|
@@ -53,17 +53,11 @@ if [ "${MODE}" = "doctrine" ]; then
|
|
|
53
53
|
exit 0
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
CFG="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}"
|
|
62
|
-
if [ -x "${CFG}/hooks/luciazero-verify.sh" ] \
|
|
63
|
-
&& grep -qF "${CFG}/hooks/luciazero-verify.sh" "${CFG}/settings.json" 2>/dev/null; then
|
|
64
|
-
exit 0
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
56
|
+
hook_path() { # canonical path of $1; empty when its directory does not exist
|
|
57
|
+
HP_DIR="$(cd "$(dirname "$1")" 2>/dev/null && pwd -P)" || return 0
|
|
58
|
+
[ -n "${HP_DIR}" ] || return 0
|
|
59
|
+
printf '%s/%s' "${HP_DIR}" "$(basename "$1")"
|
|
60
|
+
}
|
|
67
61
|
|
|
68
62
|
# Hook stdin is always a pipe; when run by hand from a terminal for debugging,
|
|
69
63
|
# do not hang waiting for EOF that never comes.
|
|
@@ -82,7 +76,146 @@ except Exception:
|
|
|
82
76
|
|
|
83
77
|
CWD="$(pyfield "d.get('cwd')")"
|
|
84
78
|
[ -n "${CWD}" ] || CWD="${PWD}"
|
|
85
|
-
|
|
79
|
+
|
|
80
|
+
# A repository's COMMITTED .claude/settings.json can put anything in its `env`
|
|
81
|
+
# block, and that env reaches this hook — so NO LUCIAZERO_* knob is accepted
|
|
82
|
+
# from that scope. Each one is a way to disable enforcement while the
|
|
83
|
+
# statusline stays green: a widened LUCIAZERO_VERIFY_REGEX (or a
|
|
84
|
+
# LUCIAZERO_VERIFY_CMD pointing at `echo`) makes any command count as a verify
|
|
85
|
+
# run, LUCIAZERO_DOC_REGEX='.*' makes every edit look like documentation so
|
|
86
|
+
# nothing is ever unverified, and LUCIAZERO_STRICT_VERIFY_CMD is a command this
|
|
87
|
+
# hook would RUN at stop. CLAUDE_CONFIG_DIR is refused from that scope too: it
|
|
88
|
+
# moves the config directory the dedupe below trusts.
|
|
89
|
+
#
|
|
90
|
+
# PROJECT scope only. The walk covers the session directory and its ancestors —
|
|
91
|
+
# Claude Code merges project settings from the repository root and a session's
|
|
92
|
+
# cwd is often a subdirectory — but it stops at the repository root, at
|
|
93
|
+
# CLAUDE_PROJECT_DIR, and at $HOME, and it never reads the user's own config
|
|
94
|
+
# directory. Personal settings (global `~/.claude/settings.json`, gitignored
|
|
95
|
+
# `.claude/settings.local.json`) are the user's scope and keep working.
|
|
96
|
+
#
|
|
97
|
+
# Refusal only ever falls back to this file's own defaults, never to a block,
|
|
98
|
+
# and a parse error leaves the configured values untouched. Only the modes that
|
|
99
|
+
# consume a knob pay for the lookup.
|
|
100
|
+
# The scanner program lives in a variable, not a here-document inside
|
|
101
|
+
# $( ): bash 3.2 (still the /bin/bash on macOS) cannot parse that
|
|
102
|
+
# combination and fails the whole file at load time.
|
|
103
|
+
REFUSED_SCAN_PY='import json, os, stat, sys
|
|
104
|
+
LIMIT = 1000000 # a settings file is kilobytes; this runs on every tool call
|
|
105
|
+
MAX_DEPTH = 40 # ancestor walk is bounded, never unbounded I/O
|
|
106
|
+
|
|
107
|
+
def refused(key):
|
|
108
|
+
return isinstance(key, str) and (key.startswith("LUCIAZERO_")
|
|
109
|
+
or key == "CLAUDE_CONFIG_DIR")
|
|
110
|
+
|
|
111
|
+
def keys_in(path):
|
|
112
|
+
try:
|
|
113
|
+
info = os.stat(path)
|
|
114
|
+
except OSError:
|
|
115
|
+
return ()
|
|
116
|
+
# never read a fifo or device planted here: that would hang the hook
|
|
117
|
+
# instead of failing open
|
|
118
|
+
if not stat.S_ISREG(info.st_mode):
|
|
119
|
+
return ()
|
|
120
|
+
if info.st_size > LIMIT:
|
|
121
|
+
# absurd for a settings file: refuse everything rather than parse it
|
|
122
|
+
return ("LUCIAZERO_*",)
|
|
123
|
+
try:
|
|
124
|
+
with open(path, encoding="utf-8") as handle:
|
|
125
|
+
env = json.loads(handle.read(LIMIT)).get("env")
|
|
126
|
+
except Exception:
|
|
127
|
+
return ()
|
|
128
|
+
if not isinstance(env, dict):
|
|
129
|
+
return ()
|
|
130
|
+
return tuple(k for k in env if refused(k))
|
|
131
|
+
|
|
132
|
+
def real(path):
|
|
133
|
+
try:
|
|
134
|
+
return os.path.realpath(path)
|
|
135
|
+
except OSError:
|
|
136
|
+
return path
|
|
137
|
+
|
|
138
|
+
home = real(os.path.expanduser("~"))
|
|
139
|
+
# Only the DEFAULT config directory counts as user scope. CLAUDE_CONFIG_DIR is
|
|
140
|
+
# attacker-reachable: pointed at the project itself, it would mark the
|
|
141
|
+
# repository settings file as user scope and skip the very file that declares
|
|
142
|
+
# it, and the dedupe below would then trust a classic install inside the repo.
|
|
143
|
+
user_config = real(os.path.join(home, ".claude"))
|
|
144
|
+
project_dir = os.environ.get("CLAUDE_PROJECT_DIR")
|
|
145
|
+
project_dir = real(project_dir) if project_dir else None
|
|
146
|
+
|
|
147
|
+
found, seen = [], set()
|
|
148
|
+
directory = real(sys.argv[1] or ".")
|
|
149
|
+
for _ in range(MAX_DEPTH):
|
|
150
|
+
claude_dir = os.path.join(directory, ".claude")
|
|
151
|
+
if directory != home and real(claude_dir) != user_config:
|
|
152
|
+
for key in keys_in(os.path.join(claude_dir, "settings.json")):
|
|
153
|
+
if key not in seen:
|
|
154
|
+
seen.add(key)
|
|
155
|
+
found.append(key)
|
|
156
|
+
if directory == home:
|
|
157
|
+
break
|
|
158
|
+
if os.path.exists(os.path.join(directory, ".git")):
|
|
159
|
+
break # repository root: project scope ends here
|
|
160
|
+
if project_dir is not None and directory == project_dir:
|
|
161
|
+
break
|
|
162
|
+
parent = os.path.dirname(directory)
|
|
163
|
+
if parent == directory:
|
|
164
|
+
break
|
|
165
|
+
directory = parent
|
|
166
|
+
print("\n".join(found))
|
|
167
|
+
'
|
|
168
|
+
REFUSED_ENV_KEYS=""
|
|
169
|
+
case "${MODE}" in
|
|
170
|
+
edit|bash|bash-failure|stop|session)
|
|
171
|
+
REFUSED_ENV_KEYS="$(printf '%s' "${REFUSED_SCAN_PY}" \
|
|
172
|
+
| python3 - "${CWD}" 2>/dev/null || true)"
|
|
173
|
+
;;
|
|
174
|
+
esac
|
|
175
|
+
if [ -n "${REFUSED_ENV_KEYS}" ]; then
|
|
176
|
+
# `LUCIAZERO_*` is the oversized-file marker: drop every knob this hook reads
|
|
177
|
+
case "${REFUSED_ENV_KEYS}" in
|
|
178
|
+
*'LUCIAZERO_*'*)
|
|
179
|
+
REFUSED_ENV_KEYS='LUCIAZERO_VERIFY_CMD
|
|
180
|
+
LUCIAZERO_VERIFY_REGEX
|
|
181
|
+
LUCIAZERO_DOC_REGEX
|
|
182
|
+
LUCIAZERO_STRICT_VERIFY_CMD
|
|
183
|
+
LUCIAZERO_STRICT_TIMEOUT
|
|
184
|
+
LUCIAZERO_RELAY_STALE_DAYS
|
|
185
|
+
LUCIAZERO_HANDOFF_STALE_DAYS
|
|
186
|
+
CLAUDE_CONFIG_DIR' ;;
|
|
187
|
+
esac
|
|
188
|
+
while IFS= read -r RK; do
|
|
189
|
+
case "${RK}" in
|
|
190
|
+
LUCIAZERO_[A-Z_]*|CLAUDE_CONFIG_DIR) unset "${RK}" 2>/dev/null || true ;;
|
|
191
|
+
esac
|
|
192
|
+
done <<EOF
|
|
193
|
+
${REFUSED_ENV_KEYS}
|
|
194
|
+
EOF
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Channel dedupe: when `install.sh --with-hooks` wiring is ALSO present, the
|
|
198
|
+
# classic copy wins and every other copy (the plugin's) stands down — otherwise
|
|
199
|
+
# the stop nudge double-fires and a strict verify runs twice concurrently.
|
|
200
|
+
#
|
|
201
|
+
# Decided from this script's own path, never from LUCIAZERO_CHANNEL: an
|
|
202
|
+
# env-driven dedupe let a repository hand the CLASSIC hook a plugin label so it
|
|
203
|
+
# stood itself down. It runs after the refusal above for the same reason — a
|
|
204
|
+
# committed CLAUDE_CONFIG_DIR could otherwise point at a repository-controlled
|
|
205
|
+
# directory holding a "wired classic install", and every copy would stand down.
|
|
206
|
+
CFG="${CLAUDE_CONFIG_DIR:-${HOME:-}/.claude}"
|
|
207
|
+
CLASSIC_HOOK="$(hook_path "${CFG}/hooks/luciazero-verify.sh")"
|
|
208
|
+
SELF_HOOK="$(hook_path "$0")"
|
|
209
|
+
if [ -n "${CLASSIC_HOOK}" ] && [ "${SELF_HOOK}" != "${CLASSIC_HOOK}" ] \
|
|
210
|
+
&& [ -x "${CLASSIC_HOOK}" ] \
|
|
211
|
+
&& grep -qF "${CFG}/hooks/luciazero-verify.sh" "${CFG}/settings.json" 2>/dev/null; then
|
|
212
|
+
exit 0
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# md5 here names a state directory; it is never a security decision. Saying so
|
|
216
|
+
# explicitly keeps the hook alive on a FIPS-enforcing python3, where a bare
|
|
217
|
+
# md5() call raises and the tracker would fail open (silently doing nothing).
|
|
218
|
+
KEY="$(printf '%s' "${CWD}" | python3 -c 'import sys,hashlib;print(hashlib.md5(sys.stdin.buffer.read(), usedforsecurity=False).hexdigest()[:12])' 2>/dev/null)" || exit 0
|
|
86
219
|
[ -n "${KEY}" ] || exit 0
|
|
87
220
|
BASE="${TMPDIR:-/tmp}/luciazero-verify-state-$(id -u 2>/dev/null || echo unknown)"
|
|
88
221
|
# The base name is predictable, so validate ownership/type before touching it.
|
|
@@ -396,6 +529,12 @@ print("yes" if e is not None and (v is None or e > v) else "no")' "${STATE}" 2>/
|
|
|
396
529
|
[ "${NUDGE}" = no ] && stat_log stop-clean
|
|
397
530
|
;;
|
|
398
531
|
session)
|
|
532
|
+
# A committed settings env block that reconfigures this hook is worth one
|
|
533
|
+
# loud line: the refusal above is silent, and a repository that ships these
|
|
534
|
+
# keys is either mistaken or hostile. Names the keys, never their values.
|
|
535
|
+
if [ -n "${REFUSED_ENV_KEYS}" ]; then
|
|
536
|
+
echo "This repository's committed .claude/settings.json sets $(printf '%s' "${REFUSED_ENV_KEYS}" | tr '\n' ' ')— Luciazero refuses those keys from project scope (they can disable verify tracking or run a command at every stop). Review that env block before trusting this repo."
|
|
537
|
+
fi
|
|
399
538
|
# SessionStart emits ONE pointer, never the relay contents. A legacy
|
|
400
539
|
# HANDOFF.md gets a migration warning but is not silently rewritten.
|
|
401
540
|
CAP="${CWD}/LUCIA_RELAY.json"
|
package/install-codex.sh
CHANGED
|
@@ -64,6 +64,28 @@ install_tree() {
|
|
|
64
64
|
cp -R "${IT_SRC}" "${IT_SNAPSHOT}"
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
# Remove a retired Luciazero skill only when its managed snapshot proves
|
|
68
|
+
# ownership. A customized or colliding directory is user data and must survive
|
|
69
|
+
# the migration with an explicit warning. Symlinked skill parents are refused
|
|
70
|
+
# so the deletion cannot escape the configured directory.
|
|
71
|
+
remove_legacy_tree() {
|
|
72
|
+
LT_DST="$1"; LT_SNAPSHOT="$2"; LT_LABEL="$3"
|
|
73
|
+
if [ ! -e "${LT_DST}" ] && [ ! -L "${LT_DST}" ]; then
|
|
74
|
+
if [ ! -L "$(dirname "${LT_SNAPSHOT}")" ]; then
|
|
75
|
+
rm -rf "${LT_SNAPSHOT}"
|
|
76
|
+
fi
|
|
77
|
+
return
|
|
78
|
+
fi
|
|
79
|
+
if [ -L "$(dirname "${LT_DST}")" ] || [ -L "$(dirname "${LT_SNAPSHOT}")" ]; then
|
|
80
|
+
echo " !! ${LT_LABEL} has a symlinked parent; left untouched" >&2
|
|
81
|
+
elif same_tree "${LT_DST}" "${LT_SNAPSHOT}"; then
|
|
82
|
+
rm -rf "${LT_DST}" "${LT_SNAPSHOT}"
|
|
83
|
+
echo " ok migrated ${LT_LABEL}"
|
|
84
|
+
else
|
|
85
|
+
echo " !! ${LT_LABEL} is customized or not Luciazero-owned; left untouched" >&2
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
67
89
|
echo "Installing into ${CODEX_DIR}"
|
|
68
90
|
mkdir -p "${CODEX_DIR}/skills"
|
|
69
91
|
|
|
@@ -99,6 +121,12 @@ while IFS= read -r SKILL; do
|
|
|
99
121
|
echo " ok skills/${SKILL}"
|
|
100
122
|
done < <(skill_inventory)
|
|
101
123
|
|
|
124
|
+
# v2.3 migration: remove only the untouched /luciazero-bootstrap compatibility
|
|
125
|
+
# alias from older installs. Customized copies remain user data.
|
|
126
|
+
remove_legacy_tree "${CODEX_DIR}/skills/luciazero-bootstrap" \
|
|
127
|
+
"${MANAGED_DIR}/skills/luciazero-bootstrap" \
|
|
128
|
+
"skills/luciazero-bootstrap"
|
|
129
|
+
|
|
102
130
|
LEGACY_HANDOFF="${CODEX_DIR}/skills/handoff"
|
|
103
131
|
if [ -f "${LEGACY_HANDOFF}/SKILL.md" ]; then
|
|
104
132
|
if cmp -s "${SRC}/migrations/handoff-v1.5.0.SKILL.md" "${LEGACY_HANDOFF}/SKILL.md"; then
|
package/install.sh
CHANGED
|
@@ -100,8 +100,11 @@ if [ "${STATUS_ONLY}" = 1 ]; then
|
|
|
100
100
|
else
|
|
101
101
|
echo " MISS settings.json missing hook entries:${WIRE_MISS} (re-run ./install.sh --with-hooks)"; STATUS_RC=1
|
|
102
102
|
fi
|
|
103
|
-
if command -v python3 >/dev/null 2>&1
|
|
104
|
-
|
|
103
|
+
if command -v python3 >/dev/null 2>&1 \
|
|
104
|
+
&& python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' 2>/dev/null; then
|
|
105
|
+
echo " ok python3 >= 3.9 available (the hooks need it)"
|
|
106
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
107
|
+
echo " MISS python3 is older than 3.9 — the hooks fail open (doing nothing)"; STATUS_RC=1
|
|
105
108
|
else
|
|
106
109
|
# fail-open means a missing python3 breaks the hooks SILENTLY — surface it here
|
|
107
110
|
echo " MISS python3 not found — the installed hooks are failing open (doing nothing)"; STATUS_RC=1
|
|
@@ -158,6 +161,28 @@ install_tree() {
|
|
|
158
161
|
cp -R "${IT_SRC}" "${IT_SNAPSHOT}"
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
# Remove a retired Luciazero skill only when its managed snapshot proves
|
|
165
|
+
# ownership. A customized or colliding directory is user data and must survive
|
|
166
|
+
# the migration with an explicit warning. Symlinked skill parents are refused
|
|
167
|
+
# so the deletion cannot escape the configured directory.
|
|
168
|
+
remove_legacy_tree() {
|
|
169
|
+
LT_DST="$1"; LT_SNAPSHOT="$2"; LT_LABEL="$3"
|
|
170
|
+
if [ ! -e "${LT_DST}" ] && [ ! -L "${LT_DST}" ]; then
|
|
171
|
+
if [ ! -L "$(dirname "${LT_SNAPSHOT}")" ]; then
|
|
172
|
+
rm -rf "${LT_SNAPSHOT}"
|
|
173
|
+
fi
|
|
174
|
+
return
|
|
175
|
+
fi
|
|
176
|
+
if [ -L "$(dirname "${LT_DST}")" ] || [ -L "$(dirname "${LT_SNAPSHOT}")" ]; then
|
|
177
|
+
echo " !! ${LT_LABEL} has a symlinked parent; left untouched" >&2
|
|
178
|
+
elif same_tree "${LT_DST}" "${LT_SNAPSHOT}"; then
|
|
179
|
+
rm -rf "${LT_DST}" "${LT_SNAPSHOT}"
|
|
180
|
+
echo " ok migrated ${LT_LABEL}"
|
|
181
|
+
else
|
|
182
|
+
echo " !! ${LT_LABEL} is customized or not Luciazero-owned; left untouched" >&2
|
|
183
|
+
fi
|
|
184
|
+
}
|
|
185
|
+
|
|
161
186
|
install_file() {
|
|
162
187
|
IF_SRC="$1"; IF_DST="$2"; IF_SNAPSHOT="$3"; IF_LABEL="$4"
|
|
163
188
|
if [ -e "${IF_DST}" ] || [ -L "${IF_DST}" ]; then
|
|
@@ -191,7 +216,7 @@ install_file "${SRC}/claude/${DOCTRINE}" "${CLAUDE_DIR}/${DOCTRINE}" \
|
|
|
191
216
|
"${MANAGED_DIR}/${DOCTRINE}" "${DOCTRINE}"
|
|
192
217
|
echo " ok ${DOCTRINE}"
|
|
193
218
|
|
|
194
|
-
# 2. canonical skills
|
|
219
|
+
# 2. canonical skills
|
|
195
220
|
while IFS= read -r SKILL; do
|
|
196
221
|
install_tree "${SRC}/skills/${SKILL}" \
|
|
197
222
|
"${CLAUDE_DIR}/skills/${SKILL}" \
|
|
@@ -200,6 +225,12 @@ while IFS= read -r SKILL; do
|
|
|
200
225
|
echo " ok skills/${SKILL}"
|
|
201
226
|
done < <(skill_inventory)
|
|
202
227
|
|
|
228
|
+
# v2.3 migration: remove only the untouched /luciazero-bootstrap compatibility
|
|
229
|
+
# alias from older installs. Customized copies remain user data.
|
|
230
|
+
remove_legacy_tree "${CLAUDE_DIR}/skills/luciazero-bootstrap" \
|
|
231
|
+
"${MANAGED_DIR}/skills/luciazero-bootstrap" \
|
|
232
|
+
"skills/luciazero-bootstrap"
|
|
233
|
+
|
|
203
234
|
# v1.5 migration: remove only an untouched Luciazero /handoff. A customized
|
|
204
235
|
# skill is user data and stays in place with an explicit warning.
|
|
205
236
|
LEGACY_HANDOFF="${CLAUDE_DIR}/skills/handoff"
|
|
@@ -250,6 +281,10 @@ fi
|
|
|
250
281
|
# 6. enforcement pack (opt-in): hooks + statusline wired into settings.json
|
|
251
282
|
if [ "${WITH_HOOKS}" = 1 ]; then
|
|
252
283
|
command -v python3 >/dev/null 2>&1 || { echo "FAIL: --with-hooks requires python3" >&2; exit 1; }
|
|
284
|
+
# 3.9 is where hashlib gained usedforsecurity=, which the hooks pass so their
|
|
285
|
+
# md5 state key does not raise under FIPS and silently disable tracking
|
|
286
|
+
python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' 2>/dev/null \
|
|
287
|
+
|| { echo "FAIL: --with-hooks requires a working python3 >= 3.9" >&2; exit 1; }
|
|
253
288
|
mkdir -p "${CLAUDE_DIR}/hooks"
|
|
254
289
|
for H in luciazero-verify.sh luciazero-statusline.sh; do
|
|
255
290
|
DST="${CLAUDE_DIR}/hooks/${H}"
|
|
@@ -332,7 +367,6 @@ echo
|
|
|
332
367
|
SKILL_SUMMARY="$(catalog "${SRC}/skills/catalog.txt" | awk 'BEGIN{s=""} {s=s (s ? ", " : "") "/" $0} END{print s}')"
|
|
333
368
|
AGENT_SUMMARY="$(catalog "${SRC}/claude/agents/catalog.txt" | awk 'BEGIN{s=""} {s=s (s ? ", " : "") $0} END{print s}')"
|
|
334
369
|
echo "Skills: ${SKILL_SUMMARY}. Agents: ${AGENT_SUMMARY}."
|
|
335
|
-
echo "Compatibility alias for one release: /luciazero-bootstrap -> /ready."
|
|
336
370
|
if [ "${WITH_HOOKS}" = 1 ]; then
|
|
337
371
|
echo "Enforcement pack installed: verify-tracking hooks + statusline (see settings.json)."
|
|
338
372
|
else
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luciazero",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Verification-first discipline for coding agents (Claude Code + Codex CLI): 9-rule doctrine, 11 skills
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Verification-first discipline for coding agents (Claude Code + Codex CLI): 9-rule doctrine, 11 skills, risk-routed reviewer, fail-open enforcement hooks. npx luciazero installs it.",
|
|
5
5
|
"repository": { "type": "git", "url": "git+https://github.com/ohm41321/luciazero.git" },
|
|
6
6
|
"homepage": "https://github.com/ohm41321/luciazero#readme",
|
|
7
7
|
"bugs": { "url": "https://github.com/ohm41321/luciazero/issues" },
|
package/skills/aliases.txt
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#
|
|
2
|
-
luciazero-bootstrap
|
|
1
|
+
# Compatibility aliases are retired. Keep this file as the empty alias catalog.
|
package/skills/bisect/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bisect
|
|
3
|
-
description: Pinpoint the first bad commit for a reproducible regression
|
|
3
|
+
description: Pinpoint the first bad commit for a reproducible regression in a safe temporary worktree. Use when HEAD is bad, a known revision is good, and one unattended command distinguishes them; handles flaky endpoints and git-bisect skip exit 125.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Bisect — isolate the first bad commit safely
|
package/skills/debug/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: debug
|
|
3
|
-
description:
|
|
3
|
+
description: Debug a stubborn bug with a deterministic reproduction, hypothesis ledger, one-variable fixes, and a regression test. Use after the first obvious look fails, reproduction is unclear, or a fix attempt failed. Not for routine obvious failures; use for "ไล่บั๊ก".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Debug — hypothesis before edit
|
|
@@ -23,7 +23,7 @@ Shrink the reproduction — smaller input, fewer flags, one test instead of the
|
|
|
23
23
|
**Seed it from recorded experience first.** Before inventing hypotheses, grep the symptom's keywords (error strings, subsystem names) against two files, if they exist:
|
|
24
24
|
|
|
25
25
|
- the repo's lesson ledger `docs/lessons.md` — this project's previously debugged failures;
|
|
26
|
-
- the global heuristics file `luciazero-heuristics.md` in the harness
|
|
26
|
+
- the global heuristics file `luciazero-heuristics.md` in the configured harness directory (`${CLAUDE_CONFIG_DIR:-$HOME/.claude}` or `${CODEX_HOME:-$HOME/.codex}`) — cross-repo lessons. Use the configured path instead of assuming `~/.claude` or `~/.codex`.
|
|
27
27
|
|
|
28
28
|
A match becomes **H1** — still verify it with its `proven-by` command; a ledger match is a hypothesis with a head start, not a conclusion. No match, or no files: proceed normally.
|
|
29
29
|
|
|
@@ -45,7 +45,7 @@ H<N>: <suspected cause> — refutable by: <command / observation> → <result: r
|
|
|
45
45
|
|
|
46
46
|
## 5. Close out
|
|
47
47
|
|
|
48
|
-
- The reproduction becomes a committed regression test: **red before the fix, green after** — run it both ways and quote both results. This proves the fix touched the actual cause. The mechanical form lives in the done skill's `scripts/` dir — run
|
|
48
|
+
- The reproduction becomes a committed regression test: **red before the fix, green after** — run it both ways and quote both results. This proves the fix touched the actual cause. The mechanical form lives in the done skill's `scripts/` dir — locate that installed skill and run `<this-skill-dir>/scripts/revert-probe.sh "<verify-cmd>"`.
|
|
49
49
|
- Remove all instrumentation (prints, sleeps, debug flags) — check the diff for it explicitly.
|
|
50
50
|
- Run the full verify tier, not just the one test.
|
|
51
51
|
- If the session surfaced something reading the code cannot teach (a footgun, an environment quirk, a disproven approach), run `/retro` so the next session does not pay for this one's dead ends — for a debugged failure specifically, `/retro` records it in `docs/lessons.md` (symptom → cause → proven-by → fix), which is exactly what step 3 reads next time.
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: discipline-report
|
|
3
|
-
description: Analyze Luciazero
|
|
3
|
+
description: Analyze Luciazero stop-outcome logs for evidence-backed verification habits. Use for discipline stats, recurring nudge or strict-block patterns, local behavior reports, or machine-readable JSON.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Discipline report — turn local outcomes into evidence
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Resolve the first available local CLI:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
luciazero discipline [--days N] [--project PATH_OR_ID] [--json]
|
|
12
|
+
node <this-skill-dir>/../../bin/luciazero.js discipline [--days N] [--project PATH_OR_ID] [--json]
|
|
12
13
|
```
|
|
13
14
|
|
|
15
|
+
Use the first command only when `luciazero` is on PATH; use the second from a source checkout or npm package. If neither local form exists, report that the discipline report is unavailable offline instead of silently invoking `npx`. Use `npx` only when package resolution is explicitly allowed.
|
|
16
|
+
|
|
14
17
|
The report reads `luciazero-stats.log` from the Claude config directory by default. It accepts current schema-versioned JSON lines and legacy space-delimited records, ignores malformed lines without failing, and never sends data over the network. New enforcement-pack installs also summarize measured turn/Bash wall-clock milliseconds and Bash, verify, and model/user skill invocation counts. Parallel Bash intervals are merged before subtraction. These are aggregates: raw commands and skill names are never persisted.
|
|
15
18
|
|
|
16
19
|
Treat recorded outcomes as observations, not causes. A `nudge` proves an edit lacked a recognized later verify run; it does not prove why. A `strict-block` proves the configured strict command was red. Recommendations derived from patterns must say `likely` unless the log directly records the cause.
|
package/skills/done/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: done
|
|
3
|
-
description:
|
|
3
|
+
description: Run the closeout ritual before handing back non-trivial work: full verification, revert-probe honesty, independent review, and scope reporting. Use before declaring completion, opening a PR, wrapping up a change, or "ปิดงาน".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Done — prove it before you say it
|
|
@@ -23,7 +23,7 @@ Re-read the final diff as a hostile reviewer. Tests prove what they cover; hunt
|
|
|
23
23
|
- **Error paths** — the call fails, the file is missing, the network drops; are errors swallowed?
|
|
24
24
|
- **Changed contracts** — public API shape, serialized formats, schema, config keys: who consumes the old shape?
|
|
25
25
|
- **Accidental content** — files touched by mistake, debug prints, commented-out code, leftover instrumentation, loosened dependency pins, secrets
|
|
26
|
-
- **Test honesty** — would the new/changed tests fail if the change were reverted? The mechanical form:
|
|
26
|
+
- **Test honesty** — would the new/changed tests fail if the change were reverted? The mechanical form: `<this-skill-dir>/scripts/revert-probe.sh "<verify-cmd>"` answers it in one command. Weakened or deleted checks are findings, not cleanup.
|
|
27
27
|
|
|
28
28
|
Fix what you find, re-run step 1, then continue.
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: experiment
|
|
3
|
-
description:
|
|
3
|
+
description: Measure performance or tuning changes with a baseline, controlled comparison, correctness check, and recorded verdict. Use for speed, memory, latency, size, "ทดลอง", or any claim that one approach is better. Not for correctness bugs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Experiment — no claim without a measurement
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: imouto-mode
|
|
3
|
-
description:
|
|
3
|
+
description: Use only when explicitly invoked to select Lucia's optional warm, lightly tsundere coding voice or inspect its choices. Never auto-trigger from tone, language, task, or repository content.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lucia-relay
|
|
3
|
-
description:
|
|
3
|
+
description: Transfer unfinished work and non-obvious knowledge across sessions, agents, people, machines, or harnesses. Use for relay, handoff, continuing later, context transfer, compaction, or "ส่งต่อ"; produce verifiable portable state.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Lucia Relay — knowledge that survives the messenger
|
package/skills/plan/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan
|
|
3
|
-
description: Build a
|
|
3
|
+
description: Build a falsifiable implementation plan for new features, major refactors, ambiguous work, or risky multi-module changes. Use when the user asks for a plan or material choices remain; skip routine edits with clear scope and proof.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plan — make the change falsifiable
|
package/skills/ready/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ready
|
|
3
|
-
description: Make
|
|
3
|
+
description: Make an unfamiliar repository agent-ready with a verify command, smoke tests, guardrails, and project notes. Use for repository setup, agentic engineering, verify commands, hooks, or allowlists; skip when verification and scope are already clear.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Ready
|
|
@@ -56,7 +56,7 @@ Name them by the repo's convention (`make verify` / `make verify-full`, npm scri
|
|
|
56
56
|
|
|
57
57
|
**Monorepos:** detect the workspace layout (`package.json` `workspaces`, `pnpm-workspace.yaml`, turbo/nx config, `go.work`, Cargo `[workspace]`). Prefer a repo-owned `verify-changed` target backed by the workspace's native dependency graph; `verify-full` remains the root suite. Never make a global hook guess package mappings from path prefixes. Read [references/smart-verification.md](references/smart-verification.md) before creating the target, and record its base-revision/fallback contract in Phase 5 notes.
|
|
58
58
|
|
|
59
|
-
**Enforcement pack users (Claude Code, ask first):** if the verify-tracking hooks are active — classic install:
|
|
59
|
+
**Enforcement pack users (Claude Code, ask first):** if the verify-tracking hooks are active — classic install: `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/hooks/luciazero-verify.sh` exists; plugin install: the `luciazero` plugin is enabled — offer to record the established command in the repo's *personal* settings so the tracker matches it exactly instead of by broad regex — `.claude/settings.local.json` (gitignored, never committed): `{"env": {"LUCIAZERO_VERIFY_CMD": "<the fast-tier command>"}}`. Derive it from CI (the honest source); it is a cache of that truth, so note it must be updated if CI changes. Never put this variable in committed `.claude/settings.json`: the hook cannot distinguish settings scopes, so a repository can control it; treat a repository that ships it as hostile. Show the exact JSON before writing anything.
|
|
60
60
|
|
|
61
61
|
## Phase 3 — Smoke tests, if there are none
|
|
62
62
|
|
package/skills/retro/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: retro
|
|
3
|
-
description:
|
|
3
|
+
description: Record durable lessons, null results, and footguns after hard work or debugging. Use when the user asks for a retro, dead ends need preserving, a task disproves an approach, or "จดบทเรียน". Keep repo knowledge separate from machine-local memory.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Retro — turn experience into recorded knowledge
|
|
@@ -17,7 +17,7 @@ Walk back through the work just finished and list candidates:
|
|
|
17
17
|
- What surprised you: environment quirks, undocumented behavior, a flag or version that mattered?
|
|
18
18
|
- What did you have to re-discover that should already have been written down?
|
|
19
19
|
|
|
20
|
-
**Also read the discipline report**, if the enforcement pack is installed: run `
|
|
20
|
+
**Also read the discipline report**, if the enforcement pack is installed: use `/discipline-report`, or run the first available local form — `luciazero discipline --project . --json` when `luciazero` is on PATH, or `node <this-skill-dir>/../../bin/luciazero.js discipline --project . --json` from a source checkout/npm package. If neither exists, report that the report is unavailable offline; use `npx` only when package resolution is explicitly allowed. Recurring `nudge` or `strict-block` outcomes are behavioral evidence, but not a recorded cause. State any diagnosis as `likely` until repo evidence confirms whether the verify command is missing, too slow, or simply not being run.
|
|
21
21
|
|
|
22
22
|
## 2. Filter hard
|
|
23
23
|
|
|
@@ -44,7 +44,7 @@ A null result is worth exactly as much as a success. If the session proved nothi
|
|
|
44
44
|
cause: <root cause> | proven-by: `<command>` | fix: <what fixed it> | date: YYYY-MM-DD
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
- **True in every repository** — engineering lessons not tied to this codebase ("intermittent async test: check timezone pinning before touching the test") → append one line to `luciazero-heuristics.md` in the harness
|
|
47
|
+
- **True in every repository** — engineering lessons not tied to this codebase ("intermittent async test: check timezone pinning before touching the test") → append one line to `luciazero-heuristics.md` in the configured harness directory (`${CLAUDE_CONFIG_DIR:-$HOME/.claude}` or `${CODEX_HOME:-$HOME/.codex}`). Hard rules: one line per lesson, same update-in-place/dedup discipline, **cap the file at 100 lines** — when full, drop the weakest entry rather than growing (an unbounded heuristics file becomes context tax, the exact failure this pack exists to prevent). Never personal paths or secrets, even here.
|
|
48
48
|
- **Only this machine or this user** — local paths, installed tool versions, personal preferences, credential locations → must **never** be committed. If the harness provides a persistent memory directory (Claude Code announces its per-project `memory/` dir and `MEMORY.md` index in context when enabled), write it there and update the index, applying the same format, dedup, and prune rules. If no memory system exists (Codex CLI, or memory disabled), keep only the generalization that is true for anyone who clones the repo — never personal preferences or credential locations, even generalized; if nothing repo-true remains, state the lesson in the retro report instead of writing it anywhere — an honest gap beats a note no harness will ever load.
|
|
49
49
|
|
|
50
50
|
Format, one entry per lesson:
|
package/skills/show/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: show
|
|
3
|
-
description:
|
|
3
|
+
description: Visualize code structure, changes, and verification evidence in the smallest useful view. Use for connections, flows, diffs, file maps, Mermaid diagrams, evidence maps, or focused HTML; show facts and label unknowns.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Show — make the evidence visible
|
package/uninstall-codex.sh
CHANGED
|
@@ -32,12 +32,18 @@ same_tree() {
|
|
|
32
32
|
&& diff -qr "$1" "$2" >/dev/null 2>&1
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
tree_parents_safe() {
|
|
36
|
+
[ ! -L "$(dirname "$1")" ] && [ ! -L "$(dirname "$2")" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
remove_managed_tree() {
|
|
36
|
-
RT_DST="$1"; RT_SNAPSHOT="$2"; RT_SHIPPED="$3"; RT_LABEL="$4"
|
|
40
|
+
RT_DST="$1"; RT_SNAPSHOT="$2"; RT_SHIPPED="$3"; RT_LABEL="$4"; RT_ALLOW_SHIPPED="${5:-1}"
|
|
37
41
|
if [ ! -e "${RT_DST}" ] && [ ! -L "${RT_DST}" ]; then
|
|
38
42
|
echo " ok ${RT_LABEL} (already absent)"
|
|
43
|
+
elif ! tree_parents_safe "${RT_DST}" "${RT_SNAPSHOT}"; then
|
|
44
|
+
echo " !! ${RT_LABEL} has a symlinked parent; left untouched" >&2
|
|
39
45
|
elif same_tree "${RT_DST}" "${RT_SNAPSHOT}" \
|
|
40
|
-
|| { [ ! -e "${RT_SNAPSHOT}" ] && same_tree "${RT_DST}" "${RT_SHIPPED}"; }; then
|
|
46
|
+
|| { [ "${RT_ALLOW_SHIPPED}" = 1 ] && [ ! -e "${RT_SNAPSHOT}" ] && same_tree "${RT_DST}" "${RT_SHIPPED}"; }; then
|
|
41
47
|
rm -rf "${RT_DST}"
|
|
42
48
|
echo " ok ${RT_LABEL}"
|
|
43
49
|
else
|
|
@@ -54,6 +60,12 @@ while IFS= read -r SKILL; do
|
|
|
54
60
|
"${MANAGED_DIR}/skills/${SKILL}" "${SRC}/skills/${SKILL}" "skills/${SKILL}"
|
|
55
61
|
done < <(skill_inventory)
|
|
56
62
|
|
|
63
|
+
# v2.3 migration: also remove an untouched alias left by older installs.
|
|
64
|
+
remove_managed_tree "${CODEX_DIR}/skills/luciazero-bootstrap" \
|
|
65
|
+
"${MANAGED_DIR}/skills/luciazero-bootstrap" \
|
|
66
|
+
"${SRC}/migrations/luciazero-bootstrap-v2.2.0" \
|
|
67
|
+
"skills/luciazero-bootstrap (retired alias)" 0
|
|
68
|
+
|
|
57
69
|
AGENT_STAGE_ROOT="$(mktemp -d)"
|
|
58
70
|
trap 'rm -rf "${AGENT_STAGE_ROOT}"' EXIT
|
|
59
71
|
while IFS= read -r AGENT_NAME; do
|
package/uninstall.sh
CHANGED
|
@@ -32,12 +32,18 @@ same_tree() {
|
|
|
32
32
|
&& diff -qr "$1" "$2" >/dev/null 2>&1
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
tree_parents_safe() {
|
|
36
|
+
[ ! -L "$(dirname "$1")" ] && [ ! -L "$(dirname "$2")" ]
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
remove_managed_tree() {
|
|
36
|
-
RT_DST="$1"; RT_SNAPSHOT="$2"; RT_SHIPPED="$3"; RT_LABEL="$4"
|
|
40
|
+
RT_DST="$1"; RT_SNAPSHOT="$2"; RT_SHIPPED="$3"; RT_LABEL="$4"; RT_ALLOW_SHIPPED="${5:-1}"
|
|
37
41
|
if [ ! -e "${RT_DST}" ] && [ ! -L "${RT_DST}" ]; then
|
|
38
42
|
echo " ok ${RT_LABEL} (already absent)"
|
|
43
|
+
elif ! tree_parents_safe "${RT_DST}" "${RT_SNAPSHOT}"; then
|
|
44
|
+
echo " !! ${RT_LABEL} has a symlinked parent; left untouched" >&2
|
|
39
45
|
elif same_tree "${RT_DST}" "${RT_SNAPSHOT}" \
|
|
40
|
-
|| { [ ! -e "${RT_SNAPSHOT}" ] && same_tree "${RT_DST}" "${RT_SHIPPED}"; }; then
|
|
46
|
+
|| { [ "${RT_ALLOW_SHIPPED}" = 1 ] && [ ! -e "${RT_SNAPSHOT}" ] && same_tree "${RT_DST}" "${RT_SHIPPED}"; }; then
|
|
41
47
|
rm -rf "${RT_DST}"
|
|
42
48
|
echo " ok ${RT_LABEL}"
|
|
43
49
|
else
|
|
@@ -72,6 +78,12 @@ while IFS= read -r SKILL; do
|
|
|
72
78
|
"${MANAGED_DIR}/skills/${SKILL}" "${SRC}/skills/${SKILL}" "skills/${SKILL}"
|
|
73
79
|
done < <(skill_inventory)
|
|
74
80
|
|
|
81
|
+
# v2.3 migration: also remove an untouched alias left by older installs.
|
|
82
|
+
remove_managed_tree "${CLAUDE_DIR}/skills/luciazero-bootstrap" \
|
|
83
|
+
"${MANAGED_DIR}/skills/luciazero-bootstrap" \
|
|
84
|
+
"${SRC}/migrations/luciazero-bootstrap-v2.2.0" \
|
|
85
|
+
"skills/luciazero-bootstrap (retired alias)" 0
|
|
86
|
+
|
|
75
87
|
while IFS= read -r AGENT_NAME; do
|
|
76
88
|
remove_managed_file "${CLAUDE_DIR}/agents/${AGENT_NAME}.md" \
|
|
77
89
|
"${MANAGED_DIR}/agents/${AGENT_NAME}.md" \
|
|
File without changes
|