devrites 2.5.2 → 2.6.1

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,487 @@ 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
+ "hooks": {
555
+ "PreToolUse": [
556
+ {
557
+ "matcher": "Bash",
558
+ "hooks": [
559
+ {
560
+ "type": "command",
561
+ "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\"",
562
+ "statusMessage": "DevRites: checking reviewer read-only boundary"
563
+ }
564
+ ]
565
+ },
566
+ {
567
+ "matcher": "Bash",
568
+ "hooks": [
569
+ {
570
+ "type": "command",
571
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-allow.sh\"",
572
+ "statusMessage": "DevRites: checking read-only helper approval"
573
+ }
574
+ ]
575
+ },
576
+ {
577
+ "matcher": "Edit|Write|apply_patch",
578
+ "hooks": [
579
+ {
580
+ "type": "command",
581
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-a1-guard.sh\"",
582
+ "statusMessage": "DevRites: checking build write boundary"
583
+ },
584
+ {
585
+ "type": "command",
586
+ "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\"",
587
+ "statusMessage": "DevRites: checking slice file scope"
588
+ }
589
+ ]
590
+ }
591
+ ],
592
+ "PostToolUse": [
593
+ {
594
+ "matcher": "Bash",
595
+ "hooks": [
596
+ {
597
+ "type": "command",
598
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-redwatch.sh\"",
599
+ "statusMessage": "DevRites: checking test/build result"
600
+ }
601
+ ]
602
+ }
603
+ ],
604
+ "Stop": [
605
+ {
606
+ "hooks": [
607
+ {
608
+ "type": "command",
609
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-stop-gate.sh\"",
610
+ "statusMessage": "DevRites: checking workspace stop gate"
611
+ }
612
+ ]
613
+ },
614
+ {
615
+ "hooks": [
616
+ {
617
+ "type": "command",
618
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-refresh-indexes.sh\"",
619
+ "statusMessage": "DevRites: refreshing code indexes"
620
+ }
621
+ ]
622
+ }
623
+ ],
624
+ "UserPromptSubmit": [
625
+ {
626
+ "hooks": [
627
+ {
628
+ "type": "command",
629
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-cursor.sh\""
630
+ }
631
+ ]
632
+ }
633
+ ],
634
+ "SubagentStart": [
635
+ {
636
+ "matcher": "devrites-slice-wright",
637
+ "hooks": [
638
+ {
639
+ "type": "command",
640
+ "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.'"
641
+ }
642
+ ]
643
+ },
644
+ {
645
+ "matcher": "devrites-(code|test|frontend|security|performance|devex|doubt|simplifier|strategy|plan|spec)-reviewer|devrites-retrospector|devrites-forge-judge",
646
+ "hooks": [
647
+ {
648
+ "type": "command",
649
+ "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.'"
650
+ }
651
+ ]
652
+ }
653
+ ],
654
+ "SubagentStop": [
655
+ {
656
+ "matcher": "devrites-slice-wright|devrites-.*reviewer|devrites-retrospector|devrites-forge-judge",
657
+ "hooks": [
658
+ {
659
+ "type": "command",
660
+ "command": "printf '%s\\n' 'DevRites: reconcile this subagent result against the active DevRites skill contract before claiming completion.'"
661
+ }
662
+ ]
663
+ }
664
+ ],
665
+ "SessionStart": [
666
+ {
667
+ "matcher": "startup|resume|clear|compact",
668
+ "hooks": [
669
+ {
670
+ "type": "command",
671
+ "command": "root=\"$(git rev-parse --show-toplevel 2>/dev/null || pwd)\"; cd \"$root\" && bash \"$root/.codex/hooks/devrites-orient.sh\"",
672
+ "statusMessage": "DevRites: loading project orientation"
673
+ }
674
+ ]
675
+ }
676
+ ]
677
+ }
678
+ }
679
+ EOF
680
+ }
681
+
682
+ install_codex_hooks_json() {
683
+ _src="$1"
684
+ _rel=".codex/hooks.json"
685
+ _dest="$TARGET/$_rel"
686
+ _marker=".claude/devrites.codex-hooks-merge"
687
+
688
+ if [ -e "$_dest" ]; then
689
+ if [ "$DRYRUN" -eq 1 ]; then
690
+ if grep -q 'devrites-' "$_dest" 2>/dev/null; then
691
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(refresh DevRites hooks)${DR_R}"
692
+ else
693
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(append DevRites hooks)${DR_R}"
694
+ fi
695
+ else
696
+ command -v node >/dev/null 2>&1 || dr_die "node is required to merge existing .codex/hooks.json"
697
+ _merged="$TMP_GEN_DIR/codex-hooks.merged.json"
698
+ node "$SELF_DIR/scripts/merge-codex-hooks.mjs" merge "$_dest" "$_src" "$_merged" || dr_die "cannot merge .codex/hooks.json"
699
+ cp "$_merged" "$_dest" || dr_die "cannot write $_dest"
700
+ fi
701
+ install_marker_file "$_marker" ".codex/hooks.json contains DevRites managed hook entries."
702
+ return 0
703
+ fi
704
+
705
+ if [ "$DRYRUN" -eq 1 ]; then
706
+ dr_say " [merge] .codex/hooks.json ${DR_Y}(create DevRites hooks)${DR_R}"
707
+ else
708
+ mkdir -p "$(dirname "$_dest")" || dr_die "cannot create $(dirname "$_dest")"
709
+ cp "$_src" "$_dest" || dr_die "cannot write $_dest"
710
+ fi
711
+ install_marker_file "$_marker" ".codex/hooks.json contains DevRites managed hook entries."
712
+ }
713
+
213
714
  # ---- plan + execute ------------------------------------------------------
214
715
  dr_info "DevRites installer"
215
716
  dr_say " source : $SELF_DIR"
@@ -217,6 +718,7 @@ dr_say " target : $TARGET"
217
718
  dr_say " skills : $([ "$WITH_SKILLS" -eq 1 ] && echo yes || echo no)"
218
719
  dr_say " rules : $([ "$WITH_RULES" -eq 1 ] && echo 'DevRites engineering rules' || echo 'skipped')"
219
720
  dr_say " agents : $([ "$WITH_AGENTS" -eq 1 ] && echo yes || echo no)"
721
+ dr_say " codex : $([ "$WITH_CODEX" -eq 1 ] && echo yes || echo no)"
220
722
  dr_say " aliases: $ALIAS_MODE"
221
723
  [ "$DRYRUN" -eq 1 ] && dr_info " (dry run — no changes will be made)"
222
724
  dr_say ""
@@ -228,6 +730,7 @@ if [ "$WITH_SKILLS" -eq 1 ]; then
228
730
  while IFS= read -r d; do
229
731
  name="$(basename "$d")"
230
732
  install_tree "$d" ".claude/skills/$name"
733
+ [ "$WITH_CODEX" -eq 1 ] && install_codex_skill_tree "$d" ".agents/skills/$name"
231
734
  done < <(find "$PACK_SRC/skills" -mindepth 1 -maxdepth 1 -type d)
232
735
  fi
233
736
 
@@ -237,17 +740,41 @@ if [ "$WITH_SKILLS" -eq 1 ] && [ "$ALIAS_MODE" = "all" ]; then
237
740
  a="${pair%%:*}"; to="${pair#*:}"
238
741
  gen_alias_wrapper "$a" "$to" "$TMP_GEN_DIR/$a.md"
239
742
  install_file "$TMP_GEN_DIR/$a.md" ".claude/skills/$a/SKILL.md"
743
+ [ "$WITH_CODEX" -eq 1 ] && install_file "$TMP_GEN_DIR/$a.md" ".agents/skills/$a/SKILL.md"
240
744
  done
241
745
  fi
242
746
 
243
747
  # 3) agents
244
748
  if [ "$WITH_AGENTS" -eq 1 ]; then
245
749
  install_tree "$PACK_SRC/agents" ".claude/agents"
750
+ if [ "$WITH_CODEX" -eq 1 ]; then
751
+ mkdir -p "$TMP_GEN_DIR/codex-agents"
752
+ while IFS= read -r f; do
753
+ name="$(basename "$f" .md)"
754
+ gen_codex_agent "$f" "$TMP_GEN_DIR/codex-agents/$name.toml"
755
+ install_file "$TMP_GEN_DIR/codex-agents/$name.toml" ".codex/agents/$name.toml"
756
+ done < <(find "$PACK_SRC/agents" -maxdepth 1 -type f -name '*.md')
757
+ fi
758
+ fi
759
+
760
+ # 3b) Codex project instructions. Codex discovers repository guidance from
761
+ # AGENTS.md, so install or merge a small bridge when skills are installed.
762
+ if [ "$WITH_CODEX" -eq 1 ] && [ "$WITH_SKILLS" -eq 1 ]; then
763
+ CODEX_AGENTS_MD="$TMP_GEN_DIR/AGENTS.md"
764
+ gen_codex_agents_bridge "$CODEX_AGENTS_MD"
765
+ install_codex_agents_md "$CODEX_AGENTS_MD"
766
+ if [ -f "$SELF_DIR/mcp/devrites-mcp.mjs" ]; then
767
+ install_file "$SELF_DIR/mcp/devrites-mcp.mjs" ".codex/mcp/devrites-mcp.mjs"
768
+ CODEX_CONFIG_TOML="$TMP_GEN_DIR/codex-config.toml"
769
+ gen_codex_config_block "$CODEX_CONFIG_TOML"
770
+ install_codex_config_toml "$CODEX_CONFIG_TOML"
771
+ fi
246
772
  fi
247
773
 
248
774
  # 4) DevRites engineering rules
249
775
  if [ "$WITH_RULES" -eq 1 ]; then
250
776
  install_tree "$PACK_SRC/rules" ".claude/rules"
777
+ [ "$WITH_CODEX" -eq 1 ] && install_tree "$PACK_SRC/rules" ".agents/devrites/rules"
251
778
  fi
252
779
 
253
780
  # 4b) hooks — auto-approve the read-only orientation/gate scripts (no per-run permission
@@ -256,6 +783,12 @@ fi
256
783
  # manifest, so it is preserved on uninstall/update and the user's own settings stay safe.
257
784
  if [ "$WITH_SKILLS" -eq 1 ] && [ -d "$PACK_SRC/hooks" ]; then
258
785
  install_tree "$PACK_SRC/hooks" ".claude/hooks"
786
+ if [ "$WITH_CODEX" -eq 1 ]; then
787
+ install_tree "$PACK_SRC/hooks" ".codex/hooks"
788
+ CODEX_HOOKS_JSON="$TMP_GEN_DIR/codex-hooks.json"
789
+ gen_codex_hooks_json "$CODEX_HOOKS_JSON"
790
+ install_codex_hooks_json "$CODEX_HOOKS_JSON"
791
+ fi
259
792
  if [ ! -e "$TARGET/.claude/settings.json" ]; then
260
793
  if [ "$DRYRUN" -eq 1 ]; then
261
794
  dr_say " [seed] .claude/settings.json ${DR_Y}(DevRites hooks — preserved on uninstall/update)${DR_R}"
@@ -318,6 +851,7 @@ fi
318
851
  DR_INSTALL_FLAGS=""
319
852
  [ "$WITH_SKILLS" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-skills"
320
853
  [ "$WITH_AGENTS" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-agents"
854
+ [ "$WITH_CODEX" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-codex"
321
855
  [ "$WITH_RULES" -eq 0 ] && DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-rules"
322
856
  case "$ALIAS_MODE" in
323
857
  off) DR_INSTALL_FLAGS="$DR_INSTALL_FLAGS --no-short-aliases" ;;
@@ -335,14 +869,26 @@ if [ -f "$PREV_MANIFEST" ]; then
335
869
  while IFS= read -r _rel; do
336
870
  case "$_rel" in ''|\#*) continue ;; esac # skip comments + blank lines
337
871
  case "$_rel" in .devrites/*) continue ;; esac # never touch runtime state
872
+ case "$_rel" in AGENTS.md|.codex/config.toml|.codex/hooks.json) continue ;; esac
338
873
  grep -Fxq -- "$_rel" "$MANIFEST_TMP" && continue # still shipped → keep
339
874
  _dead="$TARGET/$_rel"
340
875
  [ -e "$_dead" ] || continue # already gone
341
876
  if [ "$DRYRUN" -eq 1 ]; then
342
877
  dr_say " [prune] $_rel ${DR_Y}(dropped from pack)${DR_R}"
343
878
  else
879
+ case "$_rel" in
880
+ .claude/devrites.agents-merge)
881
+ strip_codex_agents_md_block "$TARGET/AGENTS.md"
882
+ ;;
883
+ .claude/devrites.codex-config-merge)
884
+ strip_codex_config_block "$TARGET/.codex/config.toml"
885
+ ;;
886
+ .claude/devrites.codex-hooks-merge)
887
+ strip_codex_hooks_entries "$TARGET/.codex/hooks.json"
888
+ ;;
889
+ esac
344
890
  rm -f "$_dead"
345
- # tidy now-empty parent dirs, bounded strictly below .claude/
891
+ # tidy now-empty parent dirs, bounded strictly below the target
346
892
  _d="$(dirname "$_dead")"
347
893
  while [ "$_d" != "$TARGET/.claude" ] && [ "$_d" != "$TARGET" ] && [ "$_d" != "/" ]; do
348
894
  rmdir "$_d" 2>/dev/null || break
@@ -376,11 +922,13 @@ dr_say "${DR_B}Utilities:${DR_R} /rite-pressure-test /rite-zoom-out /rite-prot
376
922
  if [ "$ALIAS_MODE" = "all" ]; then
377
923
  dr_say "${DR_B}Aliases:${DR_R} /define /build /prove /seal"
378
924
  fi
379
- [ "$WITH_RULES" -eq 1 ] && dr_say "${DR_B}Rules:${DR_R} engineering rules installed to .claude/rules/"
925
+ [ "$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/')"
926
+ [ "$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
927
  dr_say ""
381
928
  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."
929
+ dr_say " 1. Open the project in Claude Code or Codex and accept the workspace trust prompt."
930
+ dr_say " 2. Claude Code: run ${DR_C}/rite${DR_R} for the menu, or ${DR_C}/rite-spec <feature>${DR_R} to start."
931
+ [ "$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
932
  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
933
  dr_say " For unattended runs: ${DR_C}touch .devrites/AFK${DR_R} (see .claude/rules/afk-hitl.md for the contract)."
386
934
  [ "$N_SKIP" -gt 0 ] && dr_say " • $N_SKIP file(s) were skipped (already present). Re-run with --force to overwrite."