timsquad 3.4.0 → 3.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/README.ko.md +4 -0
- package/README.md +4 -0
- package/dist/commands/audit.d.ts +22 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +233 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +84 -3
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +50 -3
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/full.js +1 -0
- package/dist/commands/full.js.map +1 -1
- package/dist/commands/git/pr.js +6 -5
- package/dist/commands/git/pr.js.map +1 -1
- package/dist/commands/git/release.js +2 -7
- package/dist/commands/git/release.js.map +1 -1
- package/dist/commands/improve.js +2 -2
- package/dist/commands/improve.js.map +1 -1
- package/dist/commands/init.js +42 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +34 -2
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/meta-index.d.ts.map +1 -1
- package/dist/commands/meta-index.js +30 -0
- package/dist/commands/meta-index.js.map +1 -1
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +6 -2
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +71 -14
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/session.js +3 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/skills.js +4 -7
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +18 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +180 -6
- package/dist/commands/workflow.js.map +1 -1
- package/dist/daemon/context-writer.d.ts +14 -0
- package/dist/daemon/context-writer.d.ts.map +1 -1
- package/dist/daemon/context-writer.js +29 -0
- package/dist/daemon/context-writer.js.map +1 -1
- package/dist/daemon/event-queue.d.ts +4 -0
- package/dist/daemon/event-queue.d.ts.map +1 -1
- package/dist/daemon/event-queue.js +107 -6
- package/dist/daemon/event-queue.js.map +1 -1
- package/dist/daemon/file-watcher.d.ts +14 -8
- package/dist/daemon/file-watcher.d.ts.map +1 -1
- package/dist/daemon/file-watcher.js +78 -41
- package/dist/daemon/file-watcher.js.map +1 -1
- package/dist/daemon/index.d.ts +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +129 -53
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/jsonl-watcher.d.ts +1 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -1
- package/dist/daemon/jsonl-watcher.js.map +1 -1
- package/dist/daemon/session-notes.d.ts +33 -0
- package/dist/daemon/session-notes.d.ts.map +1 -0
- package/dist/daemon/session-notes.js +74 -0
- package/dist/daemon/session-notes.js.map +1 -0
- package/dist/daemon/session-state.d.ts +8 -0
- package/dist/daemon/session-state.d.ts.map +1 -1
- package/dist/daemon/session-state.js +33 -0
- package/dist/daemon/session-state.js.map +1 -1
- package/dist/daemon/shutdown.d.ts.map +1 -1
- package/dist/daemon/shutdown.js +3 -1
- package/dist/daemon/shutdown.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts +4 -0
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +63 -3
- package/dist/lib/agent-generator.js.map +1 -1
- package/dist/lib/compile-rules.d.ts +2 -0
- package/dist/lib/compile-rules.d.ts.map +1 -1
- package/dist/lib/compile-rules.js +2 -0
- package/dist/lib/compile-rules.js.map +1 -1
- package/dist/lib/compiler.d.ts +21 -1
- package/dist/lib/compiler.d.ts.map +1 -1
- package/dist/lib/compiler.js +113 -3
- package/dist/lib/compiler.js.map +1 -1
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +17 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/project.d.ts.map +1 -1
- package/dist/lib/project.js +8 -3
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/skill-generator.d.ts +1 -1
- package/dist/lib/skill-generator.d.ts.map +1 -1
- package/dist/lib/skill-generator.js +39 -3
- package/dist/lib/skill-generator.js.map +1 -1
- package/dist/lib/ssot-map.d.ts +31 -0
- package/dist/lib/ssot-map.d.ts.map +1 -0
- package/dist/lib/ssot-map.js +76 -0
- package/dist/lib/ssot-map.js.map +1 -0
- package/dist/lib/template.js +1 -0
- package/dist/lib/template.js.map +1 -1
- package/dist/lib/workflow-state.d.ts +1 -1
- package/dist/lib/workflow-state.d.ts.map +1 -1
- package/dist/lib/workflow-state.js +1 -1
- package/dist/lib/workflow-state.js.map +1 -1
- package/dist/types/config.d.ts +10 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +22 -17
- package/dist/types/config.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/meta-index.d.ts +8 -0
- package/dist/types/meta-index.d.ts.map +1 -1
- package/dist/types/project.d.ts +1 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/ssot-map.d.ts +28 -0
- package/dist/types/ssot-map.d.ts.map +1 -0
- package/dist/types/ssot-map.js +6 -0
- package/dist/types/ssot-map.js.map +1 -0
- package/package.json +4 -4
- package/templates/base/agents/base/tsq-architect.md +2 -2
- package/templates/base/agents/base/tsq-librarian.md +45 -0
- package/templates/base/knowledge/checklists/plan-quality.md +31 -0
- package/templates/base/knowledge/checklists/stability-verification.md +14 -0
- package/templates/base/skills/_shared/naming-conventions.md +49 -0
- package/templates/base/skills/_template/SKILL.md +33 -17
- package/templates/base/skills/audit/SKILL.md +66 -0
- package/templates/base/skills/coding/SKILL.md +49 -29
- package/templates/base/skills/coding/rules/async-patterns.md +81 -0
- package/templates/base/skills/coding/rules/code-organization.md +80 -0
- package/templates/base/skills/coding/rules/error-handling.md +76 -0
- package/templates/base/skills/coding/rules/type-safety.md +85 -0
- package/templates/base/skills/controller/SKILL.md +50 -84
- package/templates/base/skills/controller/delegation/developer.md +25 -0
- package/templates/base/skills/controller/delegation/librarian.md +33 -0
- package/templates/base/skills/controller/delegation/reviewer.md +19 -0
- package/templates/base/skills/controller/memory/.gitkeep +0 -0
- package/templates/base/skills/controller/triggers/phase-complete.md +25 -0
- package/templates/base/skills/controller/triggers/sequence-complete.md +15 -0
- package/templates/base/skills/controller/triggers/ssot-changed.md +14 -0
- package/templates/base/skills/controller/triggers/task-complete.md +14 -0
- package/templates/base/skills/database/SKILL.md +8 -25
- package/templates/base/skills/database/rules/query-optimization.md +32 -0
- package/templates/base/skills/database/rules/supabase-patterns.md +94 -0
- package/templates/base/skills/librarian/SKILL.md +53 -0
- package/templates/base/skills/main-session-constraints/SKILL.md +62 -0
- package/templates/base/skills/methodology/tdd/SKILL.md +6 -0
- package/templates/base/skills/product-audit/SKILL.md +115 -0
- package/templates/base/skills/product-audit/checklists/01-security.md +86 -0
- package/templates/base/skills/product-audit/checklists/02-performance.md +67 -0
- package/templates/base/skills/product-audit/checklists/03-seo.md +46 -0
- package/templates/base/skills/product-audit/checklists/04-accessibility.md +66 -0
- package/templates/base/skills/product-audit/checklists/05-ui-ux.md +50 -0
- package/templates/base/skills/product-audit/checklists/06-architecture.md +53 -0
- package/templates/base/skills/product-audit/checklists/07-functional-requirements.md +55 -0
- package/templates/base/skills/product-audit/rules/audit-protocol.md +136 -0
- package/templates/base/skills/product-audit/rules/false-positive-guard.md +81 -0
- package/templates/base/skills/product-audit/rules/scoring-criteria.md +113 -0
- package/templates/base/skills/product-audit/templates/improvement-plan-template.md +60 -0
- package/templates/base/skills/product-audit/templates/report-template.md +88 -0
- package/templates/base/skills/prompt-engineering/SKILL.md +54 -73
- package/templates/base/skills/retrospective/SKILL.md +70 -95
- package/templates/base/skills/retrospective/references/improvement-template.md +26 -0
- package/templates/base/skills/review/SKILL.md +72 -0
- package/templates/base/skills/security/SKILL.md +50 -37
- package/templates/base/skills/security/rules/auth-patterns.md +62 -0
- package/templates/base/skills/security/rules/dependency-security.md +69 -0
- package/templates/base/skills/security/rules/input-validation.md +68 -0
- package/templates/base/skills/security/rules/secrets-management.md +65 -0
- package/templates/base/skills/spec/SKILL.md +60 -0
- package/templates/base/skills/stability-verification/SKILL.md +64 -0
- package/templates/base/skills/stability-verification/references/release-checklist.md +34 -0
- package/templates/base/skills/stability-verification/references/security-fix-patterns.md +112 -0
- package/templates/base/skills/stability-verification/rules/verification-layers.md +67 -0
- package/templates/base/skills/stability-verification/rules/verification-workflow.md +69 -0
- package/templates/base/skills/stability-verification/scripts/verify.sh +294 -0
- package/templates/base/skills/testing/SKILL.md +33 -25
- package/templates/base/skills/testing/references/e2e-stability.md +33 -0
- package/templates/base/skills/tsq-cli/SKILL.md +73 -0
- package/templates/base/skills/tsq-cli/references/cli-reference.md +92 -0
- package/templates/base/skills/tsq-protocol/SKILL.md +41 -25
- package/templates/base/skills/typescript/SKILL.md +3 -9
- package/templates/base/timsquad/ssot/test-spec.template.md +11 -1
- package/templates/base/timsquad/ssot-map.template.yaml +41 -0
- package/templates/platforms/claude-code/rules/api-conventions.md +12 -0
- package/templates/platforms/claude-code/rules/build-gate.md +28 -0
- package/templates/platforms/claude-code/rules/completion-verification.md +30 -0
- package/templates/platforms/claude-code/rules/context-monitor.md +23 -0
- package/templates/platforms/claude-code/rules/librarian-constraints.md +11 -0
- package/templates/platforms/claude-code/rules/plan-review.md +45 -0
- package/templates/platforms/claude-code/rules/quality-guards.md +43 -0
- package/templates/platforms/claude-code/rules/session-notes.md +18 -0
- package/templates/platforms/claude-code/rules/skill-suggest.md +27 -0
- package/templates/platforms/claude-code/rules/test-conventions.md +13 -0
- package/templates/platforms/claude-code/scripts/build-gate.sh +73 -0
- package/templates/platforms/claude-code/scripts/change-scope-guard.sh +113 -0
- package/templates/platforms/claude-code/scripts/completion-guard.sh +122 -24
- package/templates/platforms/claude-code/scripts/context-restore.sh +68 -0
- package/templates/platforms/claude-code/scripts/e2e-commit-gate.sh +70 -0
- package/templates/platforms/claude-code/scripts/e2e-marker.sh +51 -0
- package/templates/platforms/claude-code/scripts/phase-guard.sh +5 -5
- package/templates/platforms/claude-code/scripts/pre-compact.sh +70 -0
- package/templates/platforms/claude-code/scripts/safe-guard.sh +83 -0
- package/templates/platforms/claude-code/scripts/skill-inject.sh +216 -0
- package/templates/platforms/claude-code/scripts/skill-rules.json +95 -0
- package/templates/platforms/claude-code/scripts/skill-suggest.sh +105 -0
- package/templates/platforms/claude-code/scripts/subagent-inject.sh +53 -0
- package/templates/platforms/claude-code/settings.json +71 -9
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Completion Guard — Stop Hook
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# 1. Implementation phase에서 테스트 미실행 시 블로킹 (decision: block)
|
|
4
|
+
# 2. 세션 노트 컨텍스트를 systemMessage로 주입 (컨텍스트 생존 메모)
|
|
5
|
+
# 3. 컨텍스트 윈도우 85% 경고
|
|
5
6
|
#
|
|
6
7
|
# Input: JSON via stdin (Claude Code hook protocol)
|
|
7
|
-
# Output: JSON with
|
|
8
|
+
# Output: JSON with decision (block) or systemMessage
|
|
8
9
|
|
|
9
10
|
set -e
|
|
10
11
|
|
|
12
|
+
# ── 0. Stop hook loop prevention ──
|
|
13
|
+
INPUT=$(cat 2>/dev/null || echo "")
|
|
14
|
+
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
|
|
15
|
+
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
|
|
16
|
+
echo '{}'
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
|
|
11
20
|
# Find project root
|
|
12
21
|
PROJECT_ROOT="$(pwd)"
|
|
13
22
|
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
@@ -23,35 +32,124 @@ if [ ! -d "$PROJECT_ROOT/.timsquad" ]; then
|
|
|
23
32
|
exit 0
|
|
24
33
|
fi
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
BLOCK_REASON=""
|
|
36
|
+
|
|
37
|
+
# ── 1. Test execution gate ──
|
|
38
|
+
# 변경된 소스 파일에 대한 테스트 실행 여부 확인
|
|
39
|
+
# implementation phase에서만 block, 그 외에는 warning
|
|
27
40
|
PHASE_FILE="$PROJECT_ROOT/.timsquad/state/current-phase.json"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
PHASE="unknown"
|
|
42
|
+
if [ -f "$PHASE_FILE" ]; then
|
|
43
|
+
PHASE=$(jq -r '.current // .current_phase // .phase // "unknown"' "$PHASE_FILE" 2>/dev/null || echo "unknown")
|
|
31
44
|
fi
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
TEST_WARNING=""
|
|
47
|
+
SESSION_STATE="$PROJECT_ROOT/.timsquad/.daemon/session-state.json"
|
|
48
|
+
if [ -f "$SESSION_STATE" ]; then
|
|
49
|
+
# Check if any bash commands were executed (tests run via bash)
|
|
50
|
+
BASH_COMMANDS=$(jq -r '.metrics.bashCommands // 0' "$SESSION_STATE" 2>/dev/null || echo "0")
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
if [ "$BASH_COMMANDS" -eq 0 ] 2>/dev/null; then
|
|
53
|
+
# No tests or bash at all — check for changed source files
|
|
54
|
+
CHANGED_SRC=$(cd "$PROJECT_ROOT" && git diff --name-only --diff-filter=ACMR HEAD 2>/dev/null | grep -E '\.(ts|tsx|js|jsx)$' | grep -vE '\.(test|spec)\.' | head -5)
|
|
55
|
+
if [ -n "$CHANGED_SRC" ]; then
|
|
56
|
+
# List related test files that should have been run
|
|
57
|
+
RELATED_TESTS=""
|
|
58
|
+
while IFS= read -r SRC_FILE; do
|
|
59
|
+
[ -z "$SRC_FILE" ] && continue
|
|
60
|
+
BASE_NAME=$(basename "$SRC_FILE" | sed 's/\.[^.]*$//')
|
|
61
|
+
POTENTIAL=$(cd "$PROJECT_ROOT" && find . -name "${BASE_NAME}.test.*" -o -name "${BASE_NAME}.spec.*" 2>/dev/null | head -1)
|
|
62
|
+
if [ -n "$POTENTIAL" ]; then
|
|
63
|
+
RELATED_TESTS="$RELATED_TESTS $POTENTIAL"
|
|
64
|
+
fi
|
|
65
|
+
done <<< "$CHANGED_SRC"
|
|
66
|
+
|
|
67
|
+
if [ -n "$RELATED_TESTS" ]; then
|
|
68
|
+
TEST_WARNING="[Test Gate] 변경된 파일에 대한 테스트가 실행되지 않았습니다. 관련 테스트:$RELATED_TESTS"
|
|
69
|
+
else
|
|
70
|
+
TEST_WARNING="[Test Gate] 변경된 소스 파일이 있으나 테스트가 실행되지 않았습니다."
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [ "$PHASE" = "implementation" ]; then
|
|
74
|
+
BLOCK_REASON="$TEST_WARNING 테스트를 실행한 후 완료하세요."
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
39
78
|
fi
|
|
40
79
|
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
80
|
+
# ── 2. Session notes context injection ──
|
|
81
|
+
SESSION_CTX=""
|
|
82
|
+
NOTES_FILE="$PROJECT_ROOT/.timsquad/.daemon/session-notes.jsonl"
|
|
83
|
+
if [ -f "$NOTES_FILE" ]; then
|
|
84
|
+
LAST_LINE=$(tail -1 "$NOTES_FILE" 2>/dev/null || echo "")
|
|
85
|
+
if [ -n "$LAST_LINE" ]; then
|
|
86
|
+
TURN=$(echo "$LAST_LINE" | jq -r '.turn // 0' 2>/dev/null)
|
|
87
|
+
TOOLS=$(echo "$LAST_LINE" | jq -r '.metrics.tools // 0' 2>/dev/null)
|
|
88
|
+
FAILS=$(echo "$LAST_LINE" | jq -r '.metrics.fails // 0' 2>/dev/null)
|
|
89
|
+
AGENTS=$(echo "$LAST_LINE" | jq -r '(.activeAgents // []) | join(", ")' 2>/dev/null)
|
|
90
|
+
FILES=$(echo "$LAST_LINE" | jq -r '(.recentFiles // [])[:5] | map(split("/") | last) | join(", ")' 2>/dev/null)
|
|
91
|
+
|
|
92
|
+
CTX_PCT=$(echo "$LAST_LINE" | jq -r '.contextPct // 0' 2>/dev/null)
|
|
93
|
+
|
|
94
|
+
SESSION_CTX="[Session] Turn $TURN | Tools: $TOOLS"
|
|
95
|
+
[ "$FAILS" != "0" ] && [ "$FAILS" != "null" ] && SESSION_CTX="$SESSION_CTX (Fail: $FAILS)"
|
|
96
|
+
[ -n "$AGENTS" ] && [ "$AGENTS" != "null" ] && SESSION_CTX="$SESSION_CTX | Active: $AGENTS"
|
|
97
|
+
[ -n "$FILES" ] && [ "$FILES" != "null" ] && SESSION_CTX="$SESSION_CTX | Files: $FILES"
|
|
98
|
+
|
|
99
|
+
# ── 3. Context window monitor (85% threshold) ──
|
|
100
|
+
if [ "$CTX_PCT" -ge 85 ] 2>/dev/null; then
|
|
101
|
+
SESSION_CTX="$SESSION_CTX\\n⚠ [Context ${CTX_PCT}%] 컨텍스트 윈도우 85% 이상 사용. 중요 정보를 session-notes에 기록하고, 현재 태스크를 정리한 뒤 완료하세요."
|
|
102
|
+
elif [ "$CTX_PCT" -ge 70 ] 2>/dev/null; then
|
|
103
|
+
SESSION_CTX="$SESSION_CTX | Ctx: ${CTX_PCT}%"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
46
106
|
fi
|
|
47
107
|
|
|
48
|
-
#
|
|
49
|
-
|
|
108
|
+
# ── 4. Skill Verification reminder (max 3 skills) ──
|
|
109
|
+
VERIFICATION=""
|
|
110
|
+
SKILLS_DIR="$PROJECT_ROOT/.claude/skills"
|
|
111
|
+
if [ -d "$SKILLS_DIR" ]; then
|
|
112
|
+
SKILL_COUNT=0
|
|
113
|
+
for SKILL_DIR in "$SKILLS_DIR"/*/; do
|
|
114
|
+
[ "$SKILL_COUNT" -ge 3 ] && break
|
|
115
|
+
SKILL_FILE="$SKILL_DIR/SKILL.md"
|
|
116
|
+
[ ! -f "$SKILL_FILE" ] && continue
|
|
50
117
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
118
|
+
SKILL_NAME=$(basename "$SKILL_DIR")
|
|
119
|
+
# Skip non-active/template skills
|
|
120
|
+
case "$SKILL_NAME" in _template|tsq-cli|main-session-constraints) continue ;; esac
|
|
121
|
+
|
|
122
|
+
# Extract Verification section (2 lines max)
|
|
123
|
+
CHECKS=$(awk '/^## Verification/{capture=1; next} /^## /{capture=0} capture && /\|.*\|.*\|/ && !/Check.*Command/' "$SKILL_FILE" 2>/dev/null | head -2)
|
|
124
|
+
if [ -n "$CHECKS" ]; then
|
|
125
|
+
VERIFICATION="$VERIFICATION
|
|
126
|
+
[Skill Verification] $SKILL_NAME: $CHECKS"
|
|
127
|
+
SKILL_COUNT=$((SKILL_COUNT + 1))
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
54
130
|
fi
|
|
55
131
|
|
|
56
|
-
#
|
|
57
|
-
|
|
132
|
+
# ── 5. Combine and output ──
|
|
133
|
+
# BLOCK_REASON이 있으면 decision:block으로 강제 속행 (세션 컨텍스트 포함)
|
|
134
|
+
if [ -n "$BLOCK_REASON" ]; then
|
|
135
|
+
FULL_REASON="$BLOCK_REASON"
|
|
136
|
+
[ -n "$SESSION_CTX" ] && FULL_REASON="$FULL_REASON
|
|
137
|
+
$SESSION_CTX"
|
|
138
|
+
[ -n "$VERIFICATION" ] && FULL_REASON="$FULL_REASON
|
|
139
|
+
$VERIFICATION"
|
|
140
|
+
jq -n --arg reason "$FULL_REASON" '{"decision": "block", "reason": $reason}'
|
|
141
|
+
elif [ -n "$SESSION_CTX" ] || [ -n "$VERIFICATION" ] || [ -n "$TEST_WARNING" ]; then
|
|
142
|
+
FULL_MSG=""
|
|
143
|
+
[ -n "$SESSION_CTX" ] && FULL_MSG="$SESSION_CTX"
|
|
144
|
+
if [ -n "$TEST_WARNING" ]; then
|
|
145
|
+
[ -n "$FULL_MSG" ] && FULL_MSG="$FULL_MSG
|
|
146
|
+
$TEST_WARNING" || FULL_MSG="$TEST_WARNING"
|
|
147
|
+
fi
|
|
148
|
+
if [ -n "$VERIFICATION" ]; then
|
|
149
|
+
[ -n "$FULL_MSG" ] && FULL_MSG="$FULL_MSG
|
|
150
|
+
$VERIFICATION" || FULL_MSG="$VERIFICATION"
|
|
151
|
+
fi
|
|
152
|
+
jq -n --arg msg "$FULL_MSG" '{"systemMessage": $msg}'
|
|
153
|
+
else
|
|
154
|
+
echo '{}'
|
|
155
|
+
fi
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Context Restore — SessionStart (compact) Hook
|
|
3
|
+
# 컴팩션 후 핵심 컨텍스트를 재주입하여 에이전트 맥락 유실을 방지한다.
|
|
4
|
+
#
|
|
5
|
+
# Input: JSON via stdin (Claude Code hook protocol)
|
|
6
|
+
# Output: JSON with systemMessage (컨텍스트 재주입)
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
# Find project root
|
|
11
|
+
PROJECT_ROOT="$(pwd)"
|
|
12
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
13
|
+
if [ -d "$PROJECT_ROOT/.timsquad" ]; then
|
|
14
|
+
break
|
|
15
|
+
fi
|
|
16
|
+
PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
|
|
17
|
+
done
|
|
18
|
+
|
|
19
|
+
if [ ! -d "$PROJECT_ROOT/.timsquad" ]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Build context summary
|
|
24
|
+
CONTEXT=""
|
|
25
|
+
|
|
26
|
+
# 1. Project name from config
|
|
27
|
+
PROJECT_NAME=""
|
|
28
|
+
CONFIG_FILE="$PROJECT_ROOT/.timsquad/config.yaml"
|
|
29
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
30
|
+
PROJECT_NAME=$(grep -m1 'name:' "$CONFIG_FILE" 2>/dev/null | awk '{print $2}' || echo "")
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [ -n "$PROJECT_NAME" ]; then
|
|
34
|
+
CONTEXT="[Context Restore] Project: $PROJECT_NAME"
|
|
35
|
+
else
|
|
36
|
+
CONTEXT="[Context Restore] TimSquad project"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# 2. Current phase from workflow
|
|
40
|
+
WORKFLOW_FILE="$PROJECT_ROOT/.timsquad/workflow.json"
|
|
41
|
+
if [ -f "$WORKFLOW_FILE" ]; then
|
|
42
|
+
CURRENT_PHASE=$(jq -r '.currentPhase // "unknown"' "$WORKFLOW_FILE" 2>/dev/null || echo "unknown")
|
|
43
|
+
CONTEXT="$CONTEXT | Phase: $CURRENT_PHASE"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# 3. Read compact summary if exists (saved by pre-compact.sh)
|
|
47
|
+
COMPACT_SUMMARY="$PROJECT_ROOT/.timsquad/.daemon/compact-summary.md"
|
|
48
|
+
if [ -f "$COMPACT_SUMMARY" ]; then
|
|
49
|
+
SUMMARY_CONTENT=$(head -10 "$COMPACT_SUMMARY" 2>/dev/null || echo "")
|
|
50
|
+
if [ -n "$SUMMARY_CONTENT" ]; then
|
|
51
|
+
CONTEXT="$CONTEXT
|
|
52
|
+
$SUMMARY_CONTENT"
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# 4. Key constraints (3 lines max)
|
|
57
|
+
CONTEXT="$CONTEXT
|
|
58
|
+
[Key Constraints] TSQ CLI 사용 필수 (tsq log/feedback/commit). 직접 파일 조작 금지. 구현 전 검증 기준 명시."
|
|
59
|
+
|
|
60
|
+
# Output as systemMessage
|
|
61
|
+
jq -n --arg msg "$CONTEXT" '{
|
|
62
|
+
hookSpecificOutput: {
|
|
63
|
+
hookEventName: "SessionStart",
|
|
64
|
+
systemMessage: $msg
|
|
65
|
+
}
|
|
66
|
+
}'
|
|
67
|
+
|
|
68
|
+
exit 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# e2e-commit-gate.sh — Pre-commit hook: validates E2E marker before allowing commit
|
|
3
|
+
# Checks: JSON marker exists, valid format, source_hash matches, not expired
|
|
4
|
+
#
|
|
5
|
+
# Exit 0 = allow commit, Exit 1 = block commit
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
MARKER_FILE=".e2e-passed"
|
|
10
|
+
|
|
11
|
+
# No marker = no E2E tests configured, allow
|
|
12
|
+
if [ ! -f "$MARKER_FILE" ]; then
|
|
13
|
+
exit 0
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Try to parse as JSON; if not JSON, treat as bare touch (legacy fallback)
|
|
17
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
18
|
+
echo "Warning: jq not installed, skipping E2E marker validation"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Attempt JSON parse
|
|
23
|
+
if ! jq empty "$MARKER_FILE" 2>/dev/null; then
|
|
24
|
+
# Bare touch file (legacy) — allow with warning
|
|
25
|
+
echo "Warning: .e2e-passed is not JSON format. Consider running E2E tests with e2e-marker.sh"
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Validate required fields
|
|
30
|
+
EXIT_CODE=$(jq -r '.exit_code // empty' "$MARKER_FILE")
|
|
31
|
+
SOURCE_HASH=$(jq -r '.source_hash // empty' "$MARKER_FILE")
|
|
32
|
+
EXPIRES_AT=$(jq -r '.expires_at // empty' "$MARKER_FILE")
|
|
33
|
+
|
|
34
|
+
# exit_code must be 0
|
|
35
|
+
if [ -n "$EXIT_CODE" ] && [ "$EXIT_CODE" -ne 0 ]; then
|
|
36
|
+
echo "E2E commit gate: BLOCKED — marker shows exit_code=$EXIT_CODE (tests failed)"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# source_hash check
|
|
41
|
+
if [ -n "$SOURCE_HASH" ] && [ "$SOURCE_HASH" != "unknown" ]; then
|
|
42
|
+
CURRENT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
43
|
+
if [ "$SOURCE_HASH" != "$CURRENT_HASH" ]; then
|
|
44
|
+
echo "E2E commit gate: WARNING — marker source_hash ($SOURCE_HASH) != current HEAD ($CURRENT_HASH)"
|
|
45
|
+
echo " E2E tests may be stale. Consider re-running."
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Expiry check
|
|
50
|
+
if [ -n "$EXPIRES_AT" ]; then
|
|
51
|
+
EXPIRES_EPOCH=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$EXPIRES_AT" +%s 2>/dev/null || \
|
|
52
|
+
date -u -d "$EXPIRES_AT" +%s 2>/dev/null || echo "0")
|
|
53
|
+
NOW_EPOCH=$(date -u +%s)
|
|
54
|
+
if [ "$EXPIRES_EPOCH" -gt 0 ] && [ "$NOW_EPOCH" -gt "$EXPIRES_EPOCH" ]; then
|
|
55
|
+
echo "E2E commit gate: BLOCKED — marker expired at $EXPIRES_AT"
|
|
56
|
+
echo " Re-run E2E tests to refresh the marker."
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# All checks passed
|
|
62
|
+
PASSED=$(jq -r '.passed // 0' "$MARKER_FILE")
|
|
63
|
+
FLAKY=$(jq -r '.flaky // 0' "$MARKER_FILE")
|
|
64
|
+
if [ "$FLAKY" -gt 0 ]; then
|
|
65
|
+
echo "E2E commit gate: PASSED ($PASSED tests, $FLAKY flaky — investigate flaky tests)"
|
|
66
|
+
else
|
|
67
|
+
echo "E2E commit gate: PASSED ($PASSED tests)"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
exit 0
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# e2e-marker.sh — E2E 테스트 결과를 JSON 마커로 기록
|
|
3
|
+
# 용도: PostToolUse 훅에서 E2E 테스트 완료 후 호출
|
|
4
|
+
# 출력: .e2e-passed (JSON)
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
MARKER_FILE=".e2e-passed"
|
|
9
|
+
EXPIRY_HOURS="${E2E_MARKER_EXPIRY_HOURS:-24}"
|
|
10
|
+
|
|
11
|
+
# 인자: exit_code passed failed flaky
|
|
12
|
+
EXIT_CODE="${1:-0}"
|
|
13
|
+
PASSED="${2:-0}"
|
|
14
|
+
FAILED="${3:-0}"
|
|
15
|
+
FLAKY="${4:-0}"
|
|
16
|
+
|
|
17
|
+
# exit_code != 0 이면 마커 미생성
|
|
18
|
+
if [ "$EXIT_CODE" -ne 0 ]; then
|
|
19
|
+
rm -f "$MARKER_FILE"
|
|
20
|
+
echo "E2E failed (exit_code=$EXIT_CODE) — marker removed"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# source_hash: 현재 소스의 git short hash
|
|
25
|
+
SOURCE_HASH=""
|
|
26
|
+
if command -v git >/dev/null 2>&1; then
|
|
27
|
+
SOURCE_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# timestamp & expiry
|
|
31
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
32
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
33
|
+
EXPIRES_AT=$(date -u -v+"${EXPIRY_HOURS}"H +"%Y-%m-%dT%H:%M:%SZ")
|
|
34
|
+
else
|
|
35
|
+
EXPIRES_AT=$(date -u -d "+${EXPIRY_HOURS} hours" +"%Y-%m-%dT%H:%M:%SZ")
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# JSON 마커 생성
|
|
39
|
+
cat > "$MARKER_FILE" <<MARKER
|
|
40
|
+
{
|
|
41
|
+
"timestamp": "$TIMESTAMP",
|
|
42
|
+
"exit_code": $EXIT_CODE,
|
|
43
|
+
"passed": $PASSED,
|
|
44
|
+
"failed": $FAILED,
|
|
45
|
+
"flaky": $FLAKY,
|
|
46
|
+
"source_hash": "$SOURCE_HASH",
|
|
47
|
+
"expires_at": "$EXPIRES_AT"
|
|
48
|
+
}
|
|
49
|
+
MARKER
|
|
50
|
+
|
|
51
|
+
echo "E2E marker created: $PASSED passed, $FAILED failed, $FLAKY flaky"
|
|
@@ -22,7 +22,7 @@ if [ -z "$INPUT" ]; then
|
|
|
22
22
|
fi
|
|
23
23
|
|
|
24
24
|
# Extract file path from tool input
|
|
25
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // ""' 2>/dev/null)
|
|
25
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.filePath // ""' 2>/dev/null || echo "")
|
|
26
26
|
|
|
27
27
|
# If no file path, allow (not a file operation)
|
|
28
28
|
if [ -z "$FILE_PATH" ] || [ "$FILE_PATH" = "null" ]; then
|
|
@@ -52,24 +52,24 @@ if [ ! -f "$PHASE_FILE" ]; then
|
|
|
52
52
|
exit 0
|
|
53
53
|
fi
|
|
54
54
|
|
|
55
|
-
PHASE=$(jq -r '.phase // "unknown"' "$PHASE_FILE" 2>/dev/null)
|
|
55
|
+
PHASE=$(jq -r '.current // .current_phase // .phase // "unknown"' "$PHASE_FILE" 2>/dev/null)
|
|
56
56
|
|
|
57
57
|
# Normalize file path (make relative to project root)
|
|
58
|
-
REL_PATH="${FILE_PATH
|
|
58
|
+
REL_PATH="${FILE_PATH#"$PROJECT_ROOT"/}"
|
|
59
59
|
|
|
60
60
|
# Phase enforcement rules
|
|
61
61
|
case "$PHASE" in
|
|
62
62
|
planning|design)
|
|
63
63
|
# Block source code modifications during planning/design
|
|
64
64
|
if echo "$REL_PATH" | grep -qE '^src/|^lib/|^app/|^pages/|^components/'; then
|
|
65
|
-
|
|
65
|
+
jq -n --arg phase "$PHASE" '{hookSpecificOutput:{permissionDecision:"deny"},systemMessage:("[Phase Guard] " + $phase + " phase에서 코드 수정이 금지됩니다. SSOT 문서 작성을 먼저 완료하세요.")}'
|
|
66
66
|
exit 0
|
|
67
67
|
fi
|
|
68
68
|
;;
|
|
69
69
|
implementation)
|
|
70
70
|
# Block SSOT modifications during implementation (prevent drift)
|
|
71
71
|
if echo "$REL_PATH" | grep -qE '^\.timsquad/ssot/'; then
|
|
72
|
-
echo
|
|
72
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Phase Guard] implementation phase에서 SSOT 직접 수정이 금지됩니다. 변경이 필요하면 PM에게 L2 피드백을 보고하세요."}'
|
|
73
73
|
exit 0
|
|
74
74
|
fi
|
|
75
75
|
;;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Pre-Compact — PreCompact Hook
|
|
3
|
+
# 컴팩션 직전 현재 상태 요약을 compact-summary.md에 저장한다.
|
|
4
|
+
# context-restore.sh가 이 파일을 읽어 compact 후 재주입.
|
|
5
|
+
#
|
|
6
|
+
# Input: JSON via stdin (Claude Code hook protocol)
|
|
7
|
+
# Output: none (파일 생성만)
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Find project root
|
|
12
|
+
PROJECT_ROOT="$(pwd)"
|
|
13
|
+
while [ "$PROJECT_ROOT" != "/" ]; do
|
|
14
|
+
if [ -d "$PROJECT_ROOT/.timsquad" ]; then
|
|
15
|
+
break
|
|
16
|
+
fi
|
|
17
|
+
PROJECT_ROOT="$(dirname "$PROJECT_ROOT")"
|
|
18
|
+
done
|
|
19
|
+
|
|
20
|
+
if [ ! -d "$PROJECT_ROOT/.timsquad" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
DAEMON_DIR="$PROJECT_ROOT/.timsquad/.daemon"
|
|
25
|
+
mkdir -p "$DAEMON_DIR"
|
|
26
|
+
|
|
27
|
+
SUMMARY_FILE="$DAEMON_DIR/compact-summary.md"
|
|
28
|
+
|
|
29
|
+
# Collect summary data
|
|
30
|
+
SUMMARY="# Compact Summary (auto-generated)"
|
|
31
|
+
SUMMARY="$SUMMARY
|
|
32
|
+
Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
33
|
+
|
|
34
|
+
# Current phase
|
|
35
|
+
WORKFLOW_FILE="$PROJECT_ROOT/.timsquad/workflow.json"
|
|
36
|
+
if [ -f "$WORKFLOW_FILE" ]; then
|
|
37
|
+
PHASE=$(jq -r '.currentPhase // "unknown"' "$WORKFLOW_FILE" 2>/dev/null || echo "unknown")
|
|
38
|
+
SUMMARY="$SUMMARY
|
|
39
|
+
Phase: $PHASE"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Recent changed files (top 5)
|
|
43
|
+
RECENT_FILES=$(git -C "$PROJECT_ROOT" diff --name-only HEAD 2>/dev/null | head -5 || echo "")
|
|
44
|
+
if [ -n "$RECENT_FILES" ]; then
|
|
45
|
+
SUMMARY="$SUMMARY
|
|
46
|
+
Recent changes: $RECENT_FILES"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Active tasks from session state
|
|
50
|
+
SESSION_STATE="$DAEMON_DIR/session-state.json"
|
|
51
|
+
if [ -f "$SESSION_STATE" ]; then
|
|
52
|
+
TASK_INFO=$(jq -r '.currentTask // "none"' "$SESSION_STATE" 2>/dev/null || echo "none")
|
|
53
|
+
SUMMARY="$SUMMARY
|
|
54
|
+
Active task: $TASK_INFO"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Session notes (last entry)
|
|
58
|
+
NOTES_FILE="$DAEMON_DIR/session-notes.jsonl"
|
|
59
|
+
if [ -f "$NOTES_FILE" ]; then
|
|
60
|
+
LAST_NOTE=$(tail -1 "$NOTES_FILE" 2>/dev/null | jq -r '.summary // ""' 2>/dev/null || echo "")
|
|
61
|
+
if [ -n "$LAST_NOTE" ] && [ "$LAST_NOTE" != "null" ]; then
|
|
62
|
+
SUMMARY="$SUMMARY
|
|
63
|
+
Last note: $LAST_NOTE"
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Write summary (fail-open: if write fails, don't block compact)
|
|
68
|
+
echo "$SUMMARY" > "$SUMMARY_FILE" 2>/dev/null || true
|
|
69
|
+
|
|
70
|
+
exit 0
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Safe Guard — PreToolUse Hook (Bash)
|
|
3
|
+
# Blocks or escalates destructive shell commands.
|
|
4
|
+
#
|
|
5
|
+
# Input: JSON via stdin (Claude Code hook protocol)
|
|
6
|
+
# Output: JSON with permissionDecision (allow/deny/ask)
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
# Read hook input from stdin (non-blocking)
|
|
11
|
+
INPUT=""
|
|
12
|
+
if read -t 1 -r line; then
|
|
13
|
+
INPUT="$line"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Fail-open: no input → allow
|
|
17
|
+
if [ -z "$INPUT" ]; then
|
|
18
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Extract command from tool input
|
|
23
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
24
|
+
|
|
25
|
+
if [ -z "$COMMAND" ] || [ "$COMMAND" = "null" ]; then
|
|
26
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# ── DENY: Hard block (exit 0 + permissionDecision deny) ──
|
|
31
|
+
|
|
32
|
+
# Root deletion
|
|
33
|
+
if echo "$COMMAND" | grep -qE 'rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+|.*--force\s+)/*$|rm\s+-rf\s+/\s'; then
|
|
34
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Safe Guard] 루트 디렉토리 삭제 명령이 차단되었습니다."}'
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# git push --force to main/master
|
|
39
|
+
if echo "$COMMAND" | grep -qiE 'git\s+push\s+.*--force.*\s+(main|master)|git\s+push\s+-f\s+.*\s+(main|master)'; then
|
|
40
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Safe Guard] main/master 브랜치에 force push가 차단되었습니다. 일반 push를 사용하세요."}'
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# git reset --hard
|
|
45
|
+
if echo "$COMMAND" | grep -qE 'git\s+reset\s+--hard'; then
|
|
46
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Safe Guard] git reset --hard가 차단되었습니다. 커밋되지 않은 변경사항이 유실될 수 있습니다."}'
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# SQL destructive operations
|
|
51
|
+
if echo "$COMMAND" | grep -qiE 'DROP\s+(TABLE|DATABASE|SCHEMA)|TRUNCATE\s+TABLE'; then
|
|
52
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Safe Guard] 파괴적 SQL 명령이 차단되었습니다. 데이터 유실 위험이 있습니다."}'
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# chmod 777
|
|
57
|
+
if echo "$COMMAND" | grep -qE 'chmod\s+777'; then
|
|
58
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"deny"},"systemMessage":"[Safe Guard] chmod 777이 차단되었습니다. 최소 권한 원칙을 따르세요."}'
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# ── ASK: Escalate to user confirmation ──
|
|
63
|
+
|
|
64
|
+
# npm publish
|
|
65
|
+
if echo "$COMMAND" | grep -qE 'npm\s+publish'; then
|
|
66
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"npm publish는 패키지를 공개 배포합니다. 계속하시겠습니까?"}}'
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# git push to main/master (non-force)
|
|
71
|
+
if echo "$COMMAND" | grep -qiE 'git\s+push\s+.*\s+(main|master)|git\s+push\s+(main|master)'; then
|
|
72
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"main/master 브랜치에 push합니다. 계속하시겠습니까?"}}'
|
|
73
|
+
exit 0
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# rm -rf (non-root, general)
|
|
77
|
+
if echo "$COMMAND" | grep -qE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|.*-rf)\s'; then
|
|
78
|
+
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"재귀적 삭제 명령입니다. 대상을 확인해주세요."}}'
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# ── Default: allow ──
|
|
83
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|