devrites 2.5.2 → 2.6.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/install.sh CHANGED
@@ -6,6 +6,7 @@
6
6
  # ./install.sh --dry-run show planned operations, change nothing
7
7
  # ./install.sh --force overwrite existing non-DevRites files
8
8
  # ./install.sh --no-agents skip the review subagents
9
+ # ./install.sh --no-codex skip Codex support files (.agents, .codex, AGENTS.md)
9
10
  # ./install.sh --no-rules skip the DevRites engineering rules
10
11
  # ./install.sh --rules-only install only the engineering rules
11
12
  # ./install.sh --short-aliases=all install /define /build /prove /seal aliases
@@ -14,7 +15,7 @@
14
15
  # curl -fsSL https://raw.githubusercontent.com/ViktorsBaikers/DevRites/main/install.sh | bash
15
16
  # curl -fsSL https://raw.githubusercontent.com/ViktorsBaikers/DevRites/main/install.sh | bash -s -- --target /path
16
17
  #
17
- # Never writes to ~/.claude. Records every installed file in
18
+ # Never writes to ~/.claude or ~/.codex. Records every installed file in
18
19
  # .claude/devrites.manifest so uninstall removes exactly what it added.
19
20
 
20
21
  set -u
@@ -120,6 +121,7 @@ FORCE=0
120
121
  WITH_SKILLS=1
121
122
  WITH_AGENTS=1
122
123
  WITH_RULES=1
124
+ WITH_CODEX=1
123
125
  ALIAS_MODE="safe" # safe | off | all
124
126
 
125
127
  # ---- parse args ----------------------------------------------------------
@@ -131,8 +133,9 @@ while [ $# -gt 0 ]; do
131
133
  --force) FORCE=1 ;;
132
134
  --no-skills) WITH_SKILLS=0 ;; # accepted so a --rules-only manifest round-trips through update.sh's flag replay
133
135
  --no-agents) WITH_AGENTS=0 ;;
136
+ --no-codex) WITH_CODEX=0 ;;
134
137
  --no-rules) WITH_RULES=0 ;;
135
- --rules-only) WITH_SKILLS=0; WITH_AGENTS=0; WITH_RULES=1; ALIAS_MODE="off" ;;
138
+ --rules-only) WITH_SKILLS=0; WITH_AGENTS=0; WITH_RULES=1; WITH_CODEX=0; ALIAS_MODE="off" ;;
136
139
  --no-short-aliases) ALIAS_MODE="off" ;; # accepted for backward compat; aliases are off by default now
137
140
  --short-aliases) dr_warn "--short-aliases with no value is a no-op (aliases default off); use --short-aliases=all to install /define /build /prove /seal."; ALIAS_MODE="off" ;; # /polish + /normalize were removed in favor of /rite-polish argument-hint modes
138
141
  --short-aliases=all) ALIAS_MODE="all" ;;
@@ -148,10 +151,11 @@ done
148
151
  [ -d "$TARGET" ] || dr_die "target is not a directory: $TARGET"
149
152
  TARGET="$(dr_abspath_dir "$TARGET")" || dr_die "cannot resolve target: $TARGET"
150
153
 
151
- # GUARD:no-global — refuse to install into the user's global ~/.claude tree.
154
+ # GUARD:no-global — refuse to install into global agent configuration trees.
152
155
  TARGET_CLAUDE="$TARGET/.claude"
153
- if dr_is_global_claude "$TARGET_CLAUDE" || [ "$TARGET" = "$HOME" ]; then
154
- dr_die "refusing to target the global ~/.claude. DevRites is project-local; choose a project directory."
156
+ TARGET_CODEX="$TARGET/.codex"
157
+ if dr_is_global_claude "$TARGET_CLAUDE" || dr_is_global_codex "$TARGET_CODEX" || [ "$TARGET" = "$HOME" ]; then
158
+ dr_die "refusing to target a global agent home. DevRites is project-local; choose a project directory."
155
159
  fi
156
160
 
157
161
  PREV_MANIFEST="$TARGET/$DR_MANIFEST_NAME"
@@ -195,6 +199,22 @@ install_file() {
195
199
  esac
196
200
  }
197
201
 
202
+ install_marker_file() {
203
+ _rel="$1"; _text="$2"; _dest="$TARGET/$_rel"
204
+ if [ "$DRYRUN" -eq 1 ]; then
205
+ dr_say " [install] $_rel"
206
+ else
207
+ mkdir -p "$(dirname "$_dest")" || dr_die "cannot create $(dirname "$_dest")"
208
+ if [ -f "$_dest" ] && printf '%s\n' "$_text" | cmp -s - "$_dest"; then
209
+ printf '%s\n' "$_rel" >> "$MANIFEST_TMP"
210
+ return 0
211
+ fi
212
+ printf '%s\n' "$_text" > "$_dest" || dr_die "cannot write $_dest"
213
+ fi
214
+ printf '%s\n' "$_rel" >> "$MANIFEST_TMP"
215
+ N_INSTALL=$((N_INSTALL+1))
216
+ }
217
+
198
218
  # install_tree SRC_DIR REL_PREFIX — install every file under SRC_DIR.
199
219
  install_tree() {
200
220
  _sd="$1"; _pre="$2"
@@ -210,6 +230,488 @@ install_tree() {
210
230
  # scripts/pin.sh for ad-hoc user pins).
211
231
  gen_alias_wrapper() { dr_gen_alias_wrapper "$@"; }
212
232
 
233
+ gen_codex_markdown_file() {
234
+ _src="$1"; _out="$2"
235
+ mkdir -p "$(dirname "$_out")"
236
+ sed -E \
237
+ -e 's#pack/\.claude/rules/#.agents/devrites/rules/#g' \
238
+ -e 's#pack/\.claude/rules#.agents/devrites/rules#g' \
239
+ -e 's#pack/\.claude/skills/devrites-lib/scripts/#.agents/skills/devrites-lib/scripts/#g' \
240
+ -e 's#pack/\.claude/skills/#.agents/skills/#g' \
241
+ -e 's#pack/\.claude/agents/(devrites-[A-Za-z0-9_-]+)\.md#.codex/agents/\1.toml#g' \
242
+ -e 's#pack/\.claude/agents/#.codex/agents/#g' \
243
+ -e 's#\.claude/rules/#.agents/devrites/rules/#g' \
244
+ -e 's#\.claude/rules#.agents/devrites/rules#g' \
245
+ -e 's#\.claude/skills/devrites-lib/scripts/#.agents/skills/devrites-lib/scripts/#g' \
246
+ -e 's#\.claude/skills/#.agents/skills/#g' \
247
+ -e 's#\.claude/agents/(devrites-[A-Za-z0-9_-]+)\.md#.codex/agents/\1.toml#g' \
248
+ -e 's#\.claude/agents/#.codex/agents/#g' \
249
+ -e 's#\.\./\.\./\.\./agents/(devrites-[A-Za-z0-9_-]+)\.md#.codex/agents/\1.toml#g' \
250
+ -e 's#\.\./\.\./agents/(devrites-[A-Za-z0-9_-]+)\.md#.codex/agents/\1.toml#g' \
251
+ -e 's#\.\./\.\./\.\./rules/#../../../devrites/rules/#g' \
252
+ -e 's#\.\./\.\./rules/#../../devrites/rules/#g' \
253
+ "$_src" > "$_out"
254
+ }
255
+
256
+ strip_codex_agents_md_block() {
257
+ _file="$1"
258
+ [ -f "$_file" ] || return 0
259
+ _tmp="$TMP_GEN_DIR/AGENTS.strip.md"
260
+ awk '
261
+ $0 == "<!-- BEGIN DEVRITES CODEX -->" { in_block = 1; next }
262
+ $0 == "<!-- END DEVRITES CODEX -->" { in_block = 0; next }
263
+ in_block { next }
264
+ { print }
265
+ ' "$_file" > "$_tmp" || dr_die "cannot strip DevRites block from $_file"
266
+ cp "$_tmp" "$_file" || dr_die "cannot write $_file"
267
+ grep -q '[^[:space:]]' "$_file" 2>/dev/null || rm -f "$_file"
268
+ }
269
+
270
+ strip_codex_config_block() {
271
+ _file="$1"
272
+ [ -f "$_file" ] || return 0
273
+ _tmp="$TMP_GEN_DIR/codex-config.strip.toml"
274
+ awk '
275
+ $0 == "# BEGIN DEVRITES CODEX MCP" { in_block = 1; next }
276
+ $0 == "# END DEVRITES CODEX MCP" { in_block = 0; next }
277
+ in_block { next }
278
+ { print }
279
+ ' "$_file" > "$_tmp" || dr_die "cannot strip DevRites MCP block from $_file"
280
+ cp "$_tmp" "$_file" || dr_die "cannot write $_file"
281
+ grep -q '[^[:space:]]' "$_file" 2>/dev/null || rm -f "$_file"
282
+ }
283
+
284
+ strip_codex_hooks_entries() {
285
+ _file="$1"
286
+ [ -f "$_file" ] || return 0
287
+ _tmp="$TMP_GEN_DIR/codex-hooks.strip.json"
288
+ command -v node >/dev/null 2>&1 || dr_die "node is required to merge existing .codex/hooks.json"
289
+ node "$SELF_DIR/scripts/merge-codex-hooks.mjs" strip "$_file" "$_tmp" || dr_die "cannot strip DevRites hooks from $_file"
290
+ cp "$_tmp" "$_file" || dr_die "cannot write $_file"
291
+ [ "$(tr -d '[:space:]' < "$_file" 2>/dev/null)" != "{}" ] || rm -f "$_file"
292
+ }
293
+
294
+ gen_codex_skill_file() {
295
+ _src="$1"; _out="$2"
296
+ _tmp="$TMP_GEN_DIR/codex-skill-raw-$(basename "$(dirname "$_src")").md"
297
+ mkdir -p "$(dirname "$_out")"
298
+ awk '
299
+ BEGIN { fm = 0; inserted = 0 }
300
+ NR == 1 && $0 == "---" { print; fm = 1; next }
301
+ fm && $0 == "---" {
302
+ print
303
+ print ""
304
+ print "## Codex compatibility"
305
+ print ""
306
+ print "This is the Codex mirror of a DevRites skill. In Codex:"
307
+ print ""
308
+ print "- Load DevRites engineering rules from `.agents/devrites/rules/`. Read `.agents/devrites/rules/core.md` before workflow work, then load the other `.agents/devrites/rules/*.md` files exactly when this skill asks for them."
309
+ print "- Use `.agents/skills/devrites-lib/scripts/` as the canonical runtime helper location unless this skill resolves another installed path."
310
+ print "- When this skill asks for a DevRites specialist or writer agent, spawn the matching Codex custom agent from `.codex/agents/devrites-*.toml` through Codex subagents. Wait for the subagent result and reconcile it as the skill instructs."
311
+ print "- If Codex subagents are unavailable in the current surface, use the skill documented inline fallback and label the result as an inline fallback, not an independent subagent review."
312
+ print "- Codex project hooks are installed in `.codex/hooks.json` and `.codex/hooks/`. Review and trust them with `/hooks` before relying on hook enforcement."
313
+ print ""
314
+ fm = 0
315
+ inserted = 1
316
+ next
317
+ }
318
+ { print }
319
+ END {
320
+ if (!inserted) {
321
+ print ""
322
+ print "## Codex compatibility"
323
+ print ""
324
+ print "Load DevRites rules from `.agents/devrites/rules/`, use matching `.codex/agents/devrites-*.toml` custom agents for DevRites subagent dispatch, and trust `.codex/hooks.json` with `/hooks` before relying on hooks."
325
+ }
326
+ }
327
+ ' "$_src" > "$_tmp"
328
+ gen_codex_markdown_file "$_tmp" "$_out"
329
+ }
330
+
331
+ install_codex_skill_tree() {
332
+ _sd="$1"; _pre="$2"
333
+ [ -d "$_sd" ] || return 0
334
+ while IFS= read -r f; do
335
+ _r="${f#$_sd/}"
336
+ case "$_r" in
337
+ SKILL.md)
338
+ _gen="$TMP_GEN_DIR/codex-skill-$(basename "$_sd").md"
339
+ gen_codex_skill_file "$f" "$_gen"
340
+ install_file "$_gen" "$_pre/$_r"
341
+ ;;
342
+ *.md)
343
+ _gen="$TMP_GEN_DIR/codex-md-$(basename "$_sd")-$(printf '%s' "$_r" | tr '/ ' '__').md"
344
+ gen_codex_markdown_file "$f" "$_gen"
345
+ install_file "$_gen" "$_pre/$_r"
346
+ ;;
347
+ *)
348
+ install_file "$f" "$_pre/$_r"
349
+ ;;
350
+ esac
351
+ done < <(find "$_sd" -type f)
352
+ }
353
+
354
+ toml_escape() {
355
+ printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
356
+ }
357
+
358
+ # Generate a Codex custom-agent TOML file from a Claude Code markdown agent.
359
+ # Codex does not consume .claude/agents/*.md directly; it loads project agents
360
+ # from .codex/agents/*.toml.
361
+ gen_codex_agent() {
362
+ _src="$1"; _out="$2"
363
+ _name="$(awk 'NR==1 && $0=="---"{fm=1; next} fm && $0=="---"{exit} fm && /^name:[[:space:]]*/{sub(/^name:[[:space:]]*/, ""); print; exit}' "$_src")"
364
+ _desc="$(awk 'NR==1 && $0=="---"{fm=1; next} fm && $0=="---"{exit} fm && /^description:[[:space:]]*/{sub(/^description:[[:space:]]*/, ""); print; exit}' "$_src")"
365
+ [ -n "$_name" ] || _name="$(basename "$_src" .md)"
366
+ [ -n "$_desc" ] || _desc="DevRites custom agent."
367
+ _tools="$(awk 'NR==1 && $0=="---"{fm=1; next} fm && $0=="---"{exit} fm && /^tools:[[:space:]]*/{sub(/^tools:[[:space:]]*/, ""); print; exit}' "$_src")"
368
+ mkdir -p "$(dirname "$_out")"
369
+ {
370
+ printf 'name = "%s"\n' "$(toml_escape "$_name")"
371
+ printf 'description = "%s"\n' "$(toml_escape "$_desc")"
372
+ case "$_tools" in
373
+ *Edit*|*Write*|*MultiEdit*) : ;;
374
+ *) printf 'sandbox_mode = "read-only"\n' ;;
375
+ esac
376
+ printf "%s\n" "developer_instructions = '''"
377
+ printf 'You are the Codex custom-agent version of DevRites `%s`.\n' "$_name"
378
+ printf 'Follow the source agent instructions below. Treat any Claude Code-specific hook/tool metadata as unavailable in Codex unless the current session exposes an equivalent capability.\n\n'
379
+ _body_tmp="$TMP_GEN_DIR/codex-agent-body-$(basename "$_src").md"
380
+ _body_codex="$TMP_GEN_DIR/codex-agent-body-$(basename "$_src").codex.md"
381
+ awk 'NR==1 && $0=="---"{fm=1; next} fm && $0=="---"{fm=0; body=1; next} body{print}' "$_src" > "$_body_tmp"
382
+ gen_codex_markdown_file "$_body_tmp" "$_body_codex"
383
+ cat "$_body_codex"
384
+ printf "\n%s\n" "'''"
385
+ } > "$_out"
386
+ }
387
+
388
+ gen_codex_agents_bridge() {
389
+ _out="$1"
390
+ cat > "$_out" <<'EOF'
391
+ <!-- BEGIN DEVRITES CODEX -->
392
+ ## DevRites For Codex
393
+
394
+ This project has DevRites installed for both Claude Code and Codex.
395
+
396
+ ## Codex usage
397
+
398
+ - DevRites workflow skills are available to Codex from `.agents/skills`.
399
+ - Use `$rite` or `$rite-<verb>` through Codex skills, or open `/skills` and select the matching DevRites skill.
400
+ - If the user mentions a DevRites slash command such as `/rite spec`, `/rite-build`, or `/rite-seal`, treat that as an explicit request to use the corresponding DevRites skill.
401
+ - DevRites runtime helpers are mirrored for Codex under `.agents/skills/devrites-lib/scripts/`.
402
+ - Before using any DevRites workflow skill, read `.agents/devrites/rules/core.md`. Load other `.agents/devrites/rules/*.md` files when the skill or rule index asks for them. These are DevRites engineering rules, not Codex exec-policy `.rules` files.
403
+ - Custom Codex subagents generated from the DevRites review agents live in `.codex/agents`.
404
+ - When DevRites skill prose asks for a DevRites specialist or writer agent, use the matching Codex custom agent from `.codex/agents/devrites-*.toml` through Codex subagents. If Codex subagents are unavailable in the current surface, run the skill's documented inline fallback and say that the result was not an independent subagent review.
405
+ - Claude Code agent hook metadata is not active in Codex. The generated Codex agents preserve read-only intent with Codex sandbox settings where possible; still follow DevRites' scope and no-mutation rules explicitly.
406
+
407
+ ## Workflow contract
408
+
409
+ - Keep all feature state in `.devrites/work/<slug>/` and preserve `.devrites/ACTIVE`.
410
+ - Follow the DevRites lifecycle: spec -> define -> build -> prove -> polish -> review -> seal -> ship.
411
+ - Claims of completion need recorded evidence in the feature workspace, not confidence alone.
412
+ <!-- END DEVRITES CODEX -->
413
+ EOF
414
+ }
415
+
416
+ install_codex_agents_md() {
417
+ _src="$1"
418
+ _rel="AGENTS.md"
419
+ _dest="$TARGET/$_rel"
420
+ _marker=".claude/devrites.agents-merge"
421
+
422
+ if [ -e "$_dest" ]; then
423
+ if [ "$DRYRUN" -eq 1 ]; then
424
+ if grep -q '<!-- BEGIN DEVRITES CODEX -->' "$_dest" 2>/dev/null; then
425
+ dr_say " [merge] AGENTS.md ${DR_Y}(refresh DevRites block)${DR_R}"
426
+ else
427
+ dr_say " [merge] AGENTS.md ${DR_Y}(append DevRites block)${DR_R}"
428
+ fi
429
+ else
430
+ _merged="$TMP_GEN_DIR/AGENTS.merged.md"
431
+ awk -v block_file="$_src" '
432
+ BEGIN {
433
+ while ((getline line < block_file) > 0) block = block line "\n"
434
+ close(block_file)
435
+ in_block = 0
436
+ found = 0
437
+ }
438
+ $0 == "<!-- BEGIN DEVRITES CODEX -->" {
439
+ printf "%s", block
440
+ in_block = 1
441
+ found = 1
442
+ next
443
+ }
444
+ $0 == "<!-- END DEVRITES CODEX -->" {
445
+ in_block = 0
446
+ next
447
+ }
448
+ in_block { next }
449
+ { print }
450
+ END {
451
+ if (!found) {
452
+ print ""
453
+ printf "%s", block
454
+ }
455
+ }
456
+ ' "$_dest" > "$_merged" || dr_die "cannot merge AGENTS.md"
457
+ cp "$_merged" "$_dest" || dr_die "cannot write $_dest"
458
+ fi
459
+ install_marker_file "$_marker" "AGENTS.md contains a DevRites managed block between BEGIN/END DEVRITES CODEX markers."
460
+ return 0
461
+ fi
462
+
463
+ if [ "$DRYRUN" -eq 1 ]; then
464
+ dr_say " [merge] AGENTS.md ${DR_Y}(create DevRites block)${DR_R}"
465
+ else
466
+ mkdir -p "$(dirname "$_dest")" || dr_die "cannot create $(dirname "$_dest")"
467
+ cp "$_src" "$_dest" || dr_die "cannot write $_dest"
468
+ fi
469
+ install_marker_file "$_marker" "AGENTS.md contains a DevRites managed block between BEGIN/END DEVRITES CODEX markers."
470
+ }
471
+
472
+ gen_codex_config_block() {
473
+ _out="$1"
474
+ cat > "$_out" <<'EOF'
475
+ # BEGIN DEVRITES CODEX MCP
476
+ [mcp_servers.devrites]
477
+ command = "node"
478
+ args = [".codex/mcp/devrites-mcp.mjs"]
479
+ cwd = "."
480
+ enabled = true
481
+ default_tools_approval_mode = "prompt"
482
+ startup_timeout_sec = 10
483
+ tool_timeout_sec = 60
484
+ # END DEVRITES CODEX MCP
485
+ EOF
486
+ }
487
+
488
+ install_codex_config_toml() {
489
+ _src="$1"
490
+ _rel=".codex/config.toml"
491
+ _dest="$TARGET/$_rel"
492
+ _marker=".claude/devrites.codex-config-merge"
493
+
494
+ if [ -e "$_dest" ]; then
495
+ if grep -q '^\[mcp_servers\.devrites\]' "$_dest" 2>/dev/null && ! grep -q '# BEGIN DEVRITES CODEX MCP' "$_dest" 2>/dev/null; then
496
+ [ "$DRYRUN" -eq 1 ] && dr_say " [skip] .codex/config.toml ${DR_Y}(already defines mcp_servers.devrites)${DR_R}"
497
+ [ "$DRYRUN" -eq 1 ] || dr_warn "skip .codex/config.toml DevRites MCP block (already defines mcp_servers.devrites)"
498
+ N_SKIP=$((N_SKIP+1))
499
+ return 0
500
+ fi
501
+ if [ "$DRYRUN" -eq 1 ]; then
502
+ if grep -q '# BEGIN DEVRITES CODEX MCP' "$_dest" 2>/dev/null; then
503
+ dr_say " [merge] .codex/config.toml ${DR_Y}(refresh DevRites MCP block)${DR_R}"
504
+ else
505
+ dr_say " [merge] .codex/config.toml ${DR_Y}(append DevRites MCP block)${DR_R}"
506
+ fi
507
+ else
508
+ _merged="$TMP_GEN_DIR/codex-config.merged.toml"
509
+ awk -v block_file="$_src" '
510
+ BEGIN {
511
+ while ((getline line < block_file) > 0) block = block line "\n"
512
+ close(block_file)
513
+ in_block = 0
514
+ found = 0
515
+ }
516
+ $0 == "# BEGIN DEVRITES CODEX MCP" {
517
+ printf "%s", block
518
+ in_block = 1
519
+ found = 1
520
+ next
521
+ }
522
+ $0 == "# END DEVRITES CODEX MCP" {
523
+ in_block = 0
524
+ next
525
+ }
526
+ in_block { next }
527
+ { print }
528
+ END {
529
+ if (!found) {
530
+ print ""
531
+ printf "%s", block
532
+ }
533
+ }
534
+ ' "$_dest" > "$_merged" || dr_die "cannot merge .codex/config.toml"
535
+ cp "$_merged" "$_dest" || dr_die "cannot write $_dest"
536
+ fi
537
+ install_marker_file "$_marker" ".codex/config.toml contains a DevRites managed block between BEGIN/END DEVRITES CODEX MCP markers."
538
+ return 0
539
+ fi
540
+
541
+ if [ "$DRYRUN" -eq 1 ]; then
542
+ dr_say " [merge] .codex/config.toml ${DR_Y}(create DevRites MCP block)${DR_R}"
543
+ else
544
+ mkdir -p "$(dirname "$_dest")" || dr_die "cannot create $(dirname "$_dest")"
545
+ cp "$_src" "$_dest" || dr_die "cannot write $_dest"
546
+ fi
547
+ install_marker_file "$_marker" ".codex/config.toml contains a DevRites managed block between BEGIN/END DEVRITES CODEX MCP markers."
548
+ }
549
+
550
+ gen_codex_hooks_json() {
551
+ _out="$1"
552
+ cat > "$_out" <<'EOF'
553
+ {
554
+ "$comment": "DevRites hooks for Codex. Project hooks load only after the project .codex layer is trusted; use /hooks in Codex to review and trust these non-managed hooks. Commands resolve the git root first so hooks work when Codex starts from a subdirectory.",
555
+ "hooks": {
556
+ "PreToolUse": [
557
+ {
558
+ "matcher": "Bash",
559
+ "hooks": [
560
+ {
561
+ "type": "command",
562
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && DEVRITES_REVIEWER_AGENT_REQUIRED=1 bash \"$root/.codex/hooks/devrites-reviewer-readonly.sh\"",
563
+ "statusMessage": "DevRites: checking reviewer read-only boundary"
564
+ }
565
+ ]
566
+ },
567
+ {
568
+ "matcher": "Bash",
569
+ "hooks": [
570
+ {
571
+ "type": "command",
572
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-allow.sh\"",
573
+ "statusMessage": "DevRites: checking read-only helper approval"
574
+ }
575
+ ]
576
+ },
577
+ {
578
+ "matcher": "Edit|Write|apply_patch",
579
+ "hooks": [
580
+ {
581
+ "type": "command",
582
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-a1-guard.sh\"",
583
+ "statusMessage": "DevRites: checking build write boundary"
584
+ },
585
+ {
586
+ "type": "command",
587
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && DEVRITES_WRIGHT_AGENT_REQUIRED=1 bash \"$root/.codex/hooks/devrites-wright-scope.sh\"",
588
+ "statusMessage": "DevRites: checking slice file scope"
589
+ }
590
+ ]
591
+ }
592
+ ],
593
+ "PostToolUse": [
594
+ {
595
+ "matcher": "Bash",
596
+ "hooks": [
597
+ {
598
+ "type": "command",
599
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-redwatch.sh\"",
600
+ "statusMessage": "DevRites: checking test/build result"
601
+ }
602
+ ]
603
+ }
604
+ ],
605
+ "Stop": [
606
+ {
607
+ "hooks": [
608
+ {
609
+ "type": "command",
610
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-stop-gate.sh\"",
611
+ "statusMessage": "DevRites: checking workspace stop gate"
612
+ }
613
+ ]
614
+ },
615
+ {
616
+ "hooks": [
617
+ {
618
+ "type": "command",
619
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-refresh-indexes.sh\"",
620
+ "statusMessage": "DevRites: refreshing code indexes"
621
+ }
622
+ ]
623
+ }
624
+ ],
625
+ "UserPromptSubmit": [
626
+ {
627
+ "hooks": [
628
+ {
629
+ "type": "command",
630
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-cursor.sh\""
631
+ }
632
+ ]
633
+ }
634
+ ],
635
+ "SubagentStart": [
636
+ {
637
+ "matcher": "devrites-slice-wright",
638
+ "hooks": [
639
+ {
640
+ "type": "command",
641
+ "command": "printf '%s\\n' 'DevRites: this subagent is the single write-capable slice-wright. Stay within the current slice contract and touched-files.md; write no .devrites bookkeeping.'"
642
+ }
643
+ ]
644
+ },
645
+ {
646
+ "matcher": "devrites-(code|test|frontend|security|performance|devex|doubt|simplifier|strategy|plan|spec)-reviewer|devrites-retrospector|devrites-forge-judge",
647
+ "hooks": [
648
+ {
649
+ "type": "command",
650
+ "command": "printf '%s\\n' 'DevRites: this subagent is read-only. Inspect with read/search/test commands and return findings only; do not mutate the tree.'"
651
+ }
652
+ ]
653
+ }
654
+ ],
655
+ "SubagentStop": [
656
+ {
657
+ "matcher": "devrites-slice-wright|devrites-.*reviewer|devrites-retrospector|devrites-forge-judge",
658
+ "hooks": [
659
+ {
660
+ "type": "command",
661
+ "command": "printf '%s\\n' 'DevRites: reconcile this subagent result against the active DevRites skill contract before claiming completion.'"
662
+ }
663
+ ]
664
+ }
665
+ ],
666
+ "SessionStart": [
667
+ {
668
+ "matcher": "startup|resume|clear|compact",
669
+ "hooks": [
670
+ {
671
+ "type": "command",
672
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-orient.sh\"",
673
+ "statusMessage": "DevRites: loading project orientation"
674
+ }
675
+ ]
676
+ }
677
+ ]
678
+ }
679
+ }
680
+ EOF
681
+ }
682
+
683
+ install_codex_hooks_json() {
684
+ _src="$1"
685
+ _rel=".codex/hooks.json"
686
+ _dest="$TARGET/$_rel"
687
+ _marker=".claude/devrites.codex-hooks-merge"
688
+
689
+ if [ -e "$_dest" ]; then
690
+ if [ "$DRYRUN" -eq 1 ]; then
691
+ if grep -q 'devrites-' "$_dest" 2>/dev/null; then
692
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(refresh DevRites hooks)${DR_R}"
693
+ else
694
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(append DevRites hooks)${DR_R}"
695
+ fi
696
+ else
697
+ command -v node >/dev/null 2>&1 || dr_die "node is required to merge existing .codex/hooks.json"
698
+ _merged="$TMP_GEN_DIR/codex-hooks.merged.json"
699
+ node "$SELF_DIR/scripts/merge-codex-hooks.mjs" merge "$_dest" "$_src" "$_merged" || dr_die "cannot merge .codex/hooks.json"
700
+ cp "$_merged" "$_dest" || dr_die "cannot write $_dest"
701
+ fi
702
+ install_marker_file "$_marker" ".codex/hooks.json contains DevRites managed hook entries."
703
+ return 0
704
+ fi
705
+
706
+ if [ "$DRYRUN" -eq 1 ]; then
707
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(create DevRites hooks)${DR_R}"
708
+ else
709
+ mkdir -p "$(dirname "$_dest")" || dr_die "cannot create $(dirname "$_dest")"
710
+ cp "$_src" "$_dest" || dr_die "cannot write $_dest"
711
+ fi
712
+ install_marker_file "$_marker" ".codex/hooks.json contains DevRites managed hook entries."
713
+ }
714
+
213
715
  # ---- plan + execute ------------------------------------------------------
214
716
  dr_info "DevRites installer"
215
717
  dr_say " source : $SELF_DIR"
@@ -217,6 +719,7 @@ dr_say " target : $TARGET"
217
719
  dr_say " skills : $([ "$WITH_SKILLS" -eq 1 ] && echo yes || echo no)"
218
720
  dr_say " rules : $([ "$WITH_RULES" -eq 1 ] && echo 'DevRites engineering rules' || echo 'skipped')"
219
721
  dr_say " agents : $([ "$WITH_AGENTS" -eq 1 ] && echo yes || echo no)"
722
+ dr_say " codex : $([ "$WITH_CODEX" -eq 1 ] && echo yes || echo no)"
220
723
  dr_say " aliases: $ALIAS_MODE"
221
724
  [ "$DRYRUN" -eq 1 ] && dr_info " (dry run — no changes will be made)"
222
725
  dr_say ""
@@ -228,6 +731,7 @@ if [ "$WITH_SKILLS" -eq 1 ]; then
228
731
  while IFS= read -r d; do
229
732
  name="$(basename "$d")"
230
733
  install_tree "$d" ".claude/skills/$name"
734
+ [ "$WITH_CODEX" -eq 1 ] && install_codex_skill_tree "$d" ".agents/skills/$name"
231
735
  done < <(find "$PACK_SRC/skills" -mindepth 1 -maxdepth 1 -type d)
232
736
  fi
233
737
 
@@ -237,17 +741,41 @@ if [ "$WITH_SKILLS" -eq 1 ] && [ "$ALIAS_MODE" = "all" ]; then
237
741
  a="${pair%%:*}"; to="${pair#*:}"
238
742
  gen_alias_wrapper "$a" "$to" "$TMP_GEN_DIR/$a.md"
239
743
  install_file "$TMP_GEN_DIR/$a.md" ".claude/skills/$a/SKILL.md"
744
+ [ "$WITH_CODEX" -eq 1 ] && install_file "$TMP_GEN_DIR/$a.md" ".agents/skills/$a/SKILL.md"
240
745
  done
241
746
  fi
242
747
 
243
748
  # 3) agents
244
749
  if [ "$WITH_AGENTS" -eq 1 ]; then
245
750
  install_tree "$PACK_SRC/agents" ".claude/agents"
751
+ if [ "$WITH_CODEX" -eq 1 ]; then
752
+ mkdir -p "$TMP_GEN_DIR/codex-agents"
753
+ while IFS= read -r f; do
754
+ name="$(basename "$f" .md)"
755
+ gen_codex_agent "$f" "$TMP_GEN_DIR/codex-agents/$name.toml"
756
+ install_file "$TMP_GEN_DIR/codex-agents/$name.toml" ".codex/agents/$name.toml"
757
+ done < <(find "$PACK_SRC/agents" -maxdepth 1 -type f -name '*.md')
758
+ fi
759
+ fi
760
+
761
+ # 3b) Codex project instructions. Codex discovers repository guidance from
762
+ # AGENTS.md, so install or merge a small bridge when skills are installed.
763
+ if [ "$WITH_CODEX" -eq 1 ] && [ "$WITH_SKILLS" -eq 1 ]; then
764
+ CODEX_AGENTS_MD="$TMP_GEN_DIR/AGENTS.md"
765
+ gen_codex_agents_bridge "$CODEX_AGENTS_MD"
766
+ install_codex_agents_md "$CODEX_AGENTS_MD"
767
+ if [ -f "$SELF_DIR/mcp/devrites-mcp.mjs" ]; then
768
+ install_file "$SELF_DIR/mcp/devrites-mcp.mjs" ".codex/mcp/devrites-mcp.mjs"
769
+ CODEX_CONFIG_TOML="$TMP_GEN_DIR/codex-config.toml"
770
+ gen_codex_config_block "$CODEX_CONFIG_TOML"
771
+ install_codex_config_toml "$CODEX_CONFIG_TOML"
772
+ fi
246
773
  fi
247
774
 
248
775
  # 4) DevRites engineering rules
249
776
  if [ "$WITH_RULES" -eq 1 ]; then
250
777
  install_tree "$PACK_SRC/rules" ".claude/rules"
778
+ [ "$WITH_CODEX" -eq 1 ] && install_tree "$PACK_SRC/rules" ".agents/devrites/rules"
251
779
  fi
252
780
 
253
781
  # 4b) hooks — auto-approve the read-only orientation/gate scripts (no per-run permission
@@ -256,6 +784,12 @@ fi
256
784
  # manifest, so it is preserved on uninstall/update and the user's own settings stay safe.
257
785
  if [ "$WITH_SKILLS" -eq 1 ] && [ -d "$PACK_SRC/hooks" ]; then
258
786
  install_tree "$PACK_SRC/hooks" ".claude/hooks"
787
+ if [ "$WITH_CODEX" -eq 1 ]; then
788
+ install_tree "$PACK_SRC/hooks" ".codex/hooks"
789
+ CODEX_HOOKS_JSON="$TMP_GEN_DIR/codex-hooks.json"
790
+ gen_codex_hooks_json "$CODEX_HOOKS_JSON"
791
+ install_codex_hooks_json "$CODEX_HOOKS_JSON"
792
+ fi
259
793
  if [ ! -e "$TARGET/.claude/settings.json" ]; then
260
794
  if [ "$DRYRUN" -eq 1 ]; then
261
795
  dr_say " [seed] .claude/settings.json ${DR_Y}(DevRites hooks — preserved on uninstall/update)${DR_R}"
@@ -318,6 +852,7 @@ fi
318
852
  DR_INSTALL_FLAGS=""
319
853
  [ "$WITH_SKILLS" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-skills"
320
854
  [ "$WITH_AGENTS" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-agents"
855
+ [ "$WITH_CODEX" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-codex"
321
856
  [ "$WITH_RULES" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-rules"
322
857
  case "$ALIAS_MODE" in
323
858
  off) DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-short-aliases" ;;
@@ -335,14 +870,26 @@ if [ -f "$PREV_MANIFEST" ]; then
335
870
  while IFS= read -r _rel; do
336
871
  case "$_rel" in ''|\#*) continue ;; esac # skip comments + blank lines
337
872
  case "$_rel" in .devrites/*) continue ;; esac # never touch runtime state
873
+ case "$_rel" in AGENTS.md|.codex/config.toml|.codex/hooks.json) continue ;; esac
338
874
  grep -Fxq -- "$_rel" "$MANIFEST_TMP" && continue # still shipped → keep
339
875
  _dead="$TARGET/$_rel"
340
876
  [ -e "$_dead" ] || continue # already gone
341
877
  if [ "$DRYRUN" -eq 1 ]; then
342
878
  dr_say " [prune] $_rel ${DR_Y}(dropped from pack)${DR_R}"
343
879
  else
880
+ case "$_rel" in
881
+ .claude/devrites.agents-merge)
882
+ strip_codex_agents_md_block "$TARGET/AGENTS.md"
883
+ ;;
884
+ .claude/devrites.codex-config-merge)
885
+ strip_codex_config_block "$TARGET/.codex/config.toml"
886
+ ;;
887
+ .claude/devrites.codex-hooks-merge)
888
+ strip_codex_hooks_entries "$TARGET/.codex/hooks.json"
889
+ ;;
890
+ esac
344
891
  rm -f "$_dead"
345
- # tidy now-empty parent dirs, bounded strictly below .claude/
892
+ # tidy now-empty parent dirs, bounded strictly below the target
346
893
  _d="$(dirname "$_dead")"
347
894
  while [ "$_d" != "$TARGET/.claude" ] && [ "$_d" != "$TARGET" ] && [ "$_d" != "/" ]; do
348
895
  rmdir "$_d" 2>/dev/null || break
@@ -376,11 +923,13 @@ dr_say "${DR_B}Utilities:${DR_R} /rite-pressure-test /rite-zoom-out /rite-prot
376
923
  if [ "$ALIAS_MODE" = "all" ]; then
377
924
  dr_say "${DR_B}Aliases:${DR_R} /define /build /prove /seal"
378
925
  fi
379
- [ "$WITH_RULES" -eq 1 ] && dr_say "${DR_B}Rules:${DR_R} engineering rules installed to .claude/rules/"
926
+ [ "$WITH_RULES" -eq 1 ] && dr_say "${DR_B}Rules:${DR_R} engineering rules installed to .claude/rules/$([ "$WITH_CODEX" -eq 1 ] && echo ' and mirrored to .agents/devrites/rules/')"
927
+ [ "$WITH_CODEX" -eq 1 ] && [ "$WITH_SKILLS" -eq 1 ] && dr_say "${DR_B}Codex:${DR_R} skills mirrored to .agents/skills/; project guidance in AGENTS.md"
380
928
  dr_say ""
381
929
  dr_say "${DR_B}Next:${DR_R}"
382
- dr_say " 1. Open the project in Claude Code and accept the workspace trust prompt."
383
- dr_say " 2. Run ${DR_C}/rite${DR_R} for the menu, or ${DR_C}/rite-spec <feature>${DR_R} to start."
930
+ dr_say " 1. Open the project in Claude Code or Codex and accept the workspace trust prompt."
931
+ dr_say " 2. Claude Code: run ${DR_C}/rite${DR_R} for the menu, or ${DR_C}/rite-spec <feature>${DR_R} to start."
932
+ [ "$WITH_CODEX" -eq 1 ] && [ "$WITH_SKILLS" -eq 1 ] && dr_say " Codex: use ${DR_C}\$rite${DR_R} / ${DR_C}\$rite-spec${DR_R} through skills, or pick DevRites from ${DR_C}/skills${DR_R}."
384
933
  dr_say " 3. ${DR_B}HITL by default${DR_R} — HITL slices pause at typed gates; resume with ${DR_C}/rite-resolve <qid> \"<answer>\"${DR_R}."
385
934
  dr_say " For unattended runs: ${DR_C}touch .devrites/AFK${DR_R} (see .claude/rules/afk-hitl.md for the contract)."
386
935
  [ "$N_SKIP" -gt 0 ] && dr_say " • $N_SKIP file(s) were skipped (already present). Re-run with --force to overwrite."