claude-code-arcane 2.12.0 → 2.14.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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  set +e
3
3
  main() {
4
- [[ -d production/session-state ]] && echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] pre-compact" >> production/session-state/sessions.log
4
+ [[ -d .claude/session-state ]] && echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] pre-compact" >> .claude/session-state/sessions.log
5
5
  }
6
6
  main 2>/dev/null
7
7
  exit 0
@@ -3,13 +3,49 @@
3
3
  # Graceful: wraps in main(), never blocks session
4
4
  set +e
5
5
 
6
- main() {
7
- mkdir -p production/session-state 2>/dev/null
6
+ STATE_DIR=".claude/session-state"
7
+ LEGACY_STATE_DIR="production/session-state"
8
+
9
+ # The folder holds session notes, never project files: a .gitignore with `*` keeps it
10
+ # out of every commit without touching the project's own .gitignore.
11
+ ensure_state_dir() {
12
+ mkdir -p "$STATE_DIR" 2>/dev/null
13
+ [ -f "$STATE_DIR/.gitignore" ] || echo '*' > "$STATE_DIR/.gitignore" 2>/dev/null
14
+ }
15
+
16
+ # active.md used to live in production/session-state/. Move it once, so a task left
17
+ # half done before the update still shows up here.
18
+ migrate_legacy_state() {
19
+ [ -f "$LEGACY_STATE_DIR/active.md" ] || return 0
20
+ [ -f "$STATE_DIR/active.md" ] && return 0
21
+ ensure_state_dir
22
+ mv "$LEGACY_STATE_DIR/active.md" "$STATE_DIR/active.md" 2>/dev/null &&
23
+ echo "Moved $LEGACY_STATE_DIR/active.md to $STATE_DIR/active.md"
24
+ }
8
25
 
9
- SESSION_LOG="production/session-state/sessions.log"
10
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Session started" >> "$SESSION_LOG" 2>/dev/null
26
+ # A session started inside a worktree should know it, and where the main checkout is.
27
+ worktree_notice() {
28
+ local DIRS GIT_DIR COMMON_DIR TOP BRANCH MAIN MAIN_BRANCH
29
+ DIRS=$(git rev-parse --path-format=absolute --git-dir --git-common-dir 2>/dev/null) || return 0
30
+ GIT_DIR=$(echo "$DIRS" | sed -n 1p)
31
+ COMMON_DIR=$(echo "$DIRS" | sed -n 2p)
32
+ [ -z "$COMMON_DIR" ] || [ "$GIT_DIR" = "$COMMON_DIR" ] && return 0
33
+ TOP=$(git rev-parse --show-toplevel 2>/dev/null)
34
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
35
+ MAIN=$(dirname "$COMMON_DIR")
36
+ MAIN_BRANCH=$(git -C "$MAIN" rev-parse --abbrev-ref HEAD 2>/dev/null)
37
+ echo "Worktree: you are in $TOP on branch $BRANCH."
38
+ echo "Main checkout: $MAIN on branch $MAIN_BRANCH. See the git-worktrees rule before pulling or switching branches."
39
+ echo ""
40
+ }
41
+
42
+ main() {
43
+ migrate_legacy_state
44
+ [ -d "$STATE_DIR" ] && ensure_state_dir
45
+ [ -d "$STATE_DIR" ] && echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Session started" >> "$STATE_DIR/sessions.log" 2>/dev/null
11
46
 
12
47
  echo "=== Claude Code Arcane — Session Context ==="
48
+ worktree_notice
13
49
 
14
50
  BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
15
51
  if [ -n "$BRANCH" ]; then
@@ -55,7 +91,7 @@ main() {
55
91
  fi
56
92
  fi
57
93
 
58
- STATE_FILE="production/session-state/active.md"
94
+ STATE_FILE="$STATE_DIR/active.md"
59
95
  if [ -f "$STATE_FILE" ]; then
60
96
  echo ""
61
97
  echo "=== ACTIVE SESSION STATE DETECTED ==="
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  set +e
3
3
  main() {
4
- [[ -d production/session-state ]] && echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Session stopped" >> production/session-state/sessions.log
4
+ [[ -d .claude/session-state ]] || return 0
5
+ # Claude creates the folder the first time it writes active.md; keep it out of
6
+ # commits from that same turn on.
7
+ [[ -f .claude/session-state/.gitignore ]] || echo '*' > .claude/session-state/.gitignore
8
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] Session stopped" >> .claude/session-state/sessions.log
5
9
  }
6
10
  main 2>/dev/null
7
11
  exit 0
@@ -5,12 +5,20 @@ main() {
5
5
  local INPUT
6
6
  INPUT=$(cat 2>/dev/null) || true
7
7
 
8
- local COMMAND
9
- COMMAND=$(echo "$INPUT" | grep -oE '"command"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"command"\s*:\s*"//;s/"$//') || true
8
+ local COMMAND GUARDED=1
9
+ # guard.sh parses the JSON properly; the regex is only a fallback without Python
10
+ # and stops at the first escaped quote.
11
+ COMMAND=$(printf '%s' "$INPUT" | bash "$(dirname "${BASH_SOURCE[0]}")/guard.sh" --command 2>/dev/null) || true
12
+ if [[ -z "$COMMAND" ]]; then
13
+ GUARDED=0
14
+ COMMAND=$(echo "$INPUT" | grep -oE '"command"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"command"\s*:\s*"//;s/"$//') || true
15
+ fi
10
16
 
11
17
  [[ -z "$COMMAND" || "$COMMAND" != *"git commit"* ]] && return 0
12
18
 
13
- if [[ "$COMMAND" == *"--no-verify"* ]]; then
19
+ # With Python the guard denies --no-verify on the parsed command; this substring
20
+ # check would also fire on a commit message that only mentions the flag.
21
+ if [[ "$GUARDED" == 0 && "$COMMAND" == *"--no-verify"* ]]; then
14
22
  echo "BLOCK: --no-verify is not allowed. Fix the underlying issue instead of bypassing hooks." >&2
15
23
  exit 2
16
24
  fi
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # Hook: worktree-stop.sh — Stop
3
+ #
4
+ # A session that enters a worktree with EnterWorktree tends to stay there after the
5
+ # task is done, and the next pull or branch switch fails inside it. When the turn
6
+ # ends inside such a worktree with everything committed, block the stop once and ask
7
+ # Claude to leave with ExitWorktree (keep), or to say the task continues.
8
+ #
9
+ # Fires once per commit: a new commit means new progress, so it may ask again.
10
+ # Never fires when the session itself started inside the worktree (ExitWorktree
11
+ # could not leave it) or while there are uncommitted changes (work in progress).
12
+ set +e
13
+
14
+ main() {
15
+ local INPUT PROJECT TOP BRANCH HEAD MARKER_DIR MARKER REASON
16
+ INPUT=$(cat 2>/dev/null)
17
+ # Second stop of the same turn: Claude already answered the block.
18
+ echo "$INPUT" | grep -qE '"stop_hook_active"[[:space:]]*:[[:space:]]*true' && return 0
19
+
20
+ PROJECT="${CLAUDE_PROJECT_DIR:-}"
21
+ [ -n "$PROJECT" ] || return 0
22
+ TOP=$(git rev-parse --show-toplevel 2>/dev/null) || return 0
23
+
24
+ # Only worktrees EnterWorktree creates: under the project the session started in.
25
+ case "$TOP" in
26
+ "$PROJECT"/.claude/worktrees/*) ;;
27
+ *) return 0 ;;
28
+ esac
29
+
30
+ [ -z "$(git status --porcelain 2>/dev/null)" ] || return 0
31
+
32
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
33
+ HEAD=$(git rev-parse HEAD 2>/dev/null)
34
+ MARKER_DIR="$PROJECT/.claude/session-state"
35
+ MARKER="$MARKER_DIR/.worktree-stop"
36
+ [ "$(cat "$MARKER" 2>/dev/null)" = "$TOP $HEAD" ] && return 0
37
+ mkdir -p "$MARKER_DIR" 2>/dev/null
38
+ [ -f "$MARKER_DIR/.gitignore" ] || echo '*' > "$MARKER_DIR/.gitignore"
39
+ echo "$TOP $HEAD" > "$MARKER"
40
+
41
+ REASON="You are still inside the worktree $TOP (branch $BRANCH) and its changes are committed. If the task is finished, push the branch if the task included pushing, then leave with ExitWorktree (action: keep) and tell the user the worktree path and branch. If the task continues, say so in one line and stop."
42
+ REASON=${REASON//\\/\/}
43
+ REASON=${REASON//\"/\'}
44
+ printf '{"decision": "block", "reason": "%s"}\n' "$REASON"
45
+ }
46
+
47
+ main
48
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-arcane",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Skills, agents, hooks and rules for Claude Code — installable via npx claude-code-arcane",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,5 +41,4 @@ permissions:
41
41
  - "Glob(*)"
42
42
  - "Grep(*)"
43
43
  deny:
44
- - "Bash(rm -rf *)"
45
44
  - "Bash(git push --force*)"
@@ -50,14 +50,14 @@ hooks:
50
50
  - detect-division.sh
51
51
  - detect-gaps.sh
52
52
  - validate-commit.sh
53
- - validate-push.sh
54
- - validate-secrets.sh
53
+ - guard.sh
55
54
  - validate-assets.sh
56
55
  - validate-skill-change.sh
57
56
  - notify.sh
58
57
  - pre-compact.sh
59
58
  - post-compact.sh
60
59
  - session-stop.sh
60
+ - worktree-stop.sh
61
61
  - log-agent.sh
62
62
  - log-agent-stop.sh
63
63
 
@@ -70,10 +70,15 @@ permissions:
70
70
  - "Bash(git rev-parse*)"
71
71
  - "Bash(ls *)"
72
72
  - "Bash(dir *)"
73
- - "Bash(gh *)"
73
+ - "Bash(gh pr *)"
74
+ - "Bash(gh issue *)"
75
+ - "Bash(gh run *)"
76
+ - "Bash(gh repo view*)"
77
+ - "Bash(gh release view*)"
78
+ - "Bash(gh search *)"
79
+ - "Bash(gh auth status*)"
74
80
  - "Bash(curl -s*)"
75
81
  deny:
76
- - "Bash(rm -rf *)"
77
82
  - "Bash(git push --force*)"
78
83
  - "Bash(git push -f *)"
79
84
  - "Bash(git reset --hard*)"
@@ -51,5 +51,4 @@ permissions:
51
51
  - "Glob(*)"
52
52
  - "Grep(*)"
53
53
  deny:
54
- - "Bash(rm -rf *)"
55
54
  - "Bash(git push --force*)"
@@ -39,6 +39,30 @@ Use AskUserQuestion exactly like this:
39
39
  - **Goal-driven:** Help the user define a verifiable goal statement and 2-4 checkpoints, create tasks, implement toward each checkpoint. No formal plan.
40
40
  - **Directo:** Proceed immediately without ceremony.
41
41
 
42
+ ## Progress file
43
+
44
+ In **Plan + Goal** and **Goal-driven** mode, keep `.claude/session-state/active.md` up to date. A compaction or a new session loses the conversation, not this file: `session-start.sh` shows its first lines when a session starts and after every compaction.
45
+
46
+ - **Create it** when the goal is set. If `.claude/session-state/` does not exist yet, create it with a `.gitignore` containing `*`, so the file never lands in a commit.
47
+ - **Update it** at every checkpoint, and always before a compaction.
48
+ - **Close it** when the task ends: set `Status: closed` and record the final verification result.
49
+ - Keep it under 30 lines:
50
+
51
+ ```markdown
52
+ # <task>
53
+ Status: in progress | closed
54
+ Goal: <verifiable outcome>
55
+ Criteria: <how we know it is done>
56
+ Plan:
57
+ - [x] <step done>
58
+ - [ ] <step pending>
59
+ Decisions: <decision> — <why>
60
+ Next: <the next concrete step>
61
+ Not verified: <what has not been checked yet>
62
+ ```
63
+
64
+ Directo and Action Plan tasks do not need it.
65
+
42
66
  ## Before writing new code
43
67
 
44
68
  In every mode, search in this order and take the first option that fully works:
@@ -14,7 +14,7 @@ model: haiku
14
14
 
15
15
  - Stage: !`cat production/stage.txt 2>/dev/null | tr -d '[:space:]' || echo 'not set'`
16
16
  - Latest sprint: !`ls -t production/sprints/*.md 2>/dev/null | head -1 || echo 'none'`
17
- - Session state: !`head -5 production/session-state/active.md 2>/dev/null || echo 'none'`
17
+ - Session state: !`head -5 .claude/session-state/active.md 2>/dev/null || echo 'none'`
18
18
 
19
19
  Read-only skill — reports findings, writes no files. Lightweight orientation; for a formal readiness check use `/gate-check`.
20
20
 
@@ -29,7 +29,7 @@ Read `.claude/docs/workflow-catalog.yaml` (authoritative phases/steps). Glob `.c
29
29
 
30
30
  ## Step 3: Read Session Context
31
31
 
32
- Read `production/session-state/active.md` — what was most recently worked on, in-progress tasks, open questions.
32
+ Read `.claude/session-state/active.md` — what was most recently worked on, in-progress tasks, open questions.
33
33
 
34
34
  ## Step 4: Check Step Completion
35
35
 
@@ -12,7 +12,7 @@ Entry point para sesiones nuevas. Detecta contexto, hace preguntas clave, rutea
12
12
 
13
13
  ## Paso 1: Detectar contexto
14
14
 
15
- Escanear en paralelo: Unity (`ProjectSettings/ProjectVersion.txt`), Unreal (`*.uproject`), Web/Node (`package.json`), Go (`go.mod`), Python (`pyproject.toml`/`requirements.txt`), Flutter (`pubspec.yaml`), Rust (`Cargo.toml`), docs (`README.md`, `CLAUDE.md`, `design/`, `docs/`), session state (`production/session-state/active.md`).
15
+ Escanear en paralelo: Unity (`ProjectSettings/ProjectVersion.txt`), Unreal (`*.uproject`), Web/Node (`package.json`), Go (`go.mod`), Python (`pyproject.toml`/`requirements.txt`), Flutter (`pubspec.yaml`), Rust (`Cargo.toml`), docs (`README.md`, `CLAUDE.md`, `design/`, `docs/`), session state (`.claude/session-state/active.md`).
16
16
 
17
17
  ## Paso 2: Determinar etapa
18
18
 
@@ -45,7 +45,7 @@ AskUserQuestion multi-tab:
45
45
 
46
46
  ## Paso 5: Set session state
47
47
 
48
- Crear `production/session-state/active.md`: project name, division, stage, user role, next action, stack detected, existing artifacts, recommended flow (3 commands).
48
+ Crear `.claude/session-state/active.md`: project name, division, stage, user role, next action, stack detected, existing artifacts, recommended flow (3 commands).
49
49
 
50
50
  ## Paso 6: First action
51
51
 
@@ -12,7 +12,7 @@ Closes loop between design and implementation. Verifies acceptance criteria, che
12
12
 
13
13
  ## Phase 1: Find Story
14
14
 
15
- Resolve review mode: `--review` arg → `production/review-mode.txt` → default `lean`. Story source: arg path → `production/session-state/active.md` → most recent sprint file (IN PROGRESS stories) → AskUserQuestion.
15
+ Resolve review mode: `--review` arg → `production/review-mode.txt` → default `lean`. Story source: arg path → `.claude/session-state/active.md` → most recent sprint file (IN PROGRESS stories) → AskUserQuestion.
16
16
 
17
17
  ## Phase 2: Read Story
18
18
 
@@ -55,7 +55,7 @@ BLOCKED → do not proceed, list fixes, offer help.
55
55
 
56
56
  ## Phase 7: Update Story
57
57
 
58
- Ask approval → edit story: `Status: Complete` + `## Completion Notes` (date, criteria, deviations, test evidence, code review status). Advisory deviations → offer to log in `docs/tech-debt-register.md`. Update `production/sprint-status.yaml` if exists. Append to `production/session-state/active.md`.
58
+ Ask approval → edit story: `Status: Complete` + `## Completion Notes` (date, criteria, deviations, test evidence, code review status). Advisory deviations → offer to log in `docs/tech-debt-register.md`. Update `production/sprint-status.yaml` if exists. Append to `.claude/session-state/active.md`.
59
59
 
60
60
  ## Phase 8: Surface Next Story
61
61
 
@@ -38,7 +38,7 @@ If output file exists → read it, show section status table (Complete/Empty/Pla
38
38
 
39
39
  ## Skeleton Creation
40
40
 
41
- Create output file with `[To be designed]` placeholders for all sections. Update `production/session-state/active.md`.
41
+ Create output file with `[To be designed]` placeholders for all sections. Update `.claude/session-state/active.md`.
42
42
 
43
43
  > → Read references/spec-sections.md for [section inventories for UX spec, HUD, pattern library, and key section guidance]
44
44
 
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env bash
2
- set +e
3
-
4
- main() {
5
- local INPUT
6
- INPUT=$(cat 2>/dev/null) || true
7
-
8
- local COMMAND
9
- COMMAND=$(echo "$INPUT" | grep -oE '"command"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"command"\s*:\s*"//;s/"$//') || true
10
-
11
- [[ -z "$COMMAND" || "$COMMAND" != *"git push"* ]] && return 0
12
-
13
- if [[ "$COMMAND" == *"--force"* || "$COMMAND" == *"-f "* ]]; then
14
- if [[ "$COMMAND" == *"main"* || "$COMMAND" == *"master"* ]]; then
15
- echo "BLOCK: force push to main/master is prohibited." >&2
16
- exit 2
17
- fi
18
- fi
19
- }
20
- main
21
- exit 0
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env bash
2
- set +e
3
-
4
- main() {
5
- local INPUT
6
- INPUT=$(cat 2>/dev/null) || true
7
-
8
- local COMMAND
9
- COMMAND=$(echo "$INPUT" | grep -oE '"command"\s*:\s*"[^"]*"' | head -1 | sed 's/.*"command"\s*:\s*"//;s/"$//') || true
10
-
11
- [[ -z "$COMMAND" ]] && return 0
12
-
13
- if echo "$COMMAND" | grep -qE '(cat|type|less|more|head|tail)\s+.*\.env' 2>/dev/null; then
14
- echo "BLOCK: reading .env files is prohibited for security." >&2
15
- exit 2
16
- fi
17
-
18
- if echo "$COMMAND" | grep -qE 'curl.*\-d.*(api_key|password|secret|token)=' 2>/dev/null; then
19
- echo "WARN: command appears to send credentials over network. Review carefully." >&2
20
- fi
21
- }
22
- main
23
- exit 0