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.
- package/CHANGELOG.md +16 -0
- package/agents/visualnovel/vn-producer.md +2 -2
- package/dist/cli.js +250 -275
- package/docs/USER-GUIDE.md +3 -3
- package/docs/collaborative-design.md +1 -1
- package/docs/context-management.md +5 -22
- package/docs/coordination-rules.md +1 -1
- package/docs/directory-structure.md +9 -4
- package/docs/gates/README.md +2 -2
- package/hooks/detect-division.sh +1 -1
- package/hooks/guard.py +445 -0
- package/hooks/guard.sh +18 -0
- package/hooks/log-agent-stop.sh +1 -1
- package/hooks/log-agent.sh +1 -1
- package/hooks/notify.sh +1 -1
- package/hooks/post-compact.sh +1 -1
- package/hooks/pre-compact.sh +1 -1
- package/hooks/session-start.sh +41 -5
- package/hooks/session-stop.sh +5 -1
- package/hooks/validate-commit.sh +11 -3
- package/hooks/worktree-stop.sh +48 -0
- package/package.json +1 -1
- package/profiles/audio.yaml +0 -1
- package/profiles/core.yaml +9 -4
- package/profiles/visual-novel.yaml +0 -1
- package/rules/implementation-workflow.md +24 -0
- package/skills/help/SKILL.md +2 -2
- package/skills/start/SKILL.md +2 -2
- package/skills/story-done/SKILL.md +2 -2
- package/skills/ux-design/SKILL.md +1 -1
- package/hooks/validate-push.sh +0 -21
- package/hooks/validate-secrets.sh +0 -23
package/hooks/pre-compact.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set +e
|
|
3
3
|
main() {
|
|
4
|
-
[[ -d
|
|
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
|
package/hooks/session-start.sh
CHANGED
|
@@ -3,13 +3,49 @@
|
|
|
3
3
|
# Graceful: wraps in main(), never blocks session
|
|
4
4
|
set +e
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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="
|
|
94
|
+
STATE_FILE="$STATE_DIR/active.md"
|
|
59
95
|
if [ -f "$STATE_FILE" ]; then
|
|
60
96
|
echo ""
|
|
61
97
|
echo "=== ACTIVE SESSION STATE DETECTED ==="
|
package/hooks/session-stop.sh
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set +e
|
|
3
3
|
main() {
|
|
4
|
-
[[ -d
|
|
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
|
package/hooks/validate-commit.sh
CHANGED
|
@@ -5,12 +5,20 @@ main() {
|
|
|
5
5
|
local INPUT
|
|
6
6
|
INPUT=$(cat 2>/dev/null) || true
|
|
7
7
|
|
|
8
|
-
local COMMAND
|
|
9
|
-
|
|
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
|
-
|
|
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
package/profiles/audio.yaml
CHANGED
package/profiles/core.yaml
CHANGED
|
@@ -50,14 +50,14 @@ hooks:
|
|
|
50
50
|
- detect-division.sh
|
|
51
51
|
- detect-gaps.sh
|
|
52
52
|
- validate-commit.sh
|
|
53
|
-
-
|
|
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*)"
|
|
@@ -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:
|
package/skills/help/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/skills/start/SKILL.md
CHANGED
|
@@ -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 (
|
|
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
|
|
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 →
|
|
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
|
|
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
|
|
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
|
|
package/hooks/validate-push.sh
DELETED
|
@@ -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
|