timsquad 3.5.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 +48 -2
- package/dist/commands/daemon.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 +32 -0
- 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/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +63 -6
- package/dist/commands/retro.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.map +1 -1
- package/dist/daemon/index.js +42 -36
- package/dist/daemon/index.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +11 -0
- 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/skill-generator.d.ts +1 -1
- package/dist/lib/skill-generator.d.ts.map +1 -1
- package/dist/lib/skill-generator.js +17 -2
- 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 +1 -1
- package/templates/base/agents/base/tsq-librarian.md +45 -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/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/librarian-constraints.md +11 -0
- package/templates/platforms/claude-code/rules/test-conventions.md +13 -0
- package/templates/platforms/claude-code/scripts/change-scope-guard.sh +113 -0
- package/templates/platforms/claude-code/scripts/completion-guard.sh +75 -13
- 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 +1 -1
- package/templates/platforms/claude-code/scripts/pre-compact.sh +70 -0
- package/templates/platforms/claude-code/scripts/skill-inject.sh +216 -0
- package/templates/platforms/claude-code/scripts/skill-rules.json +11 -1
- package/templates/platforms/claude-code/scripts/subagent-inject.sh +53 -0
- package/templates/platforms/claude-code/settings.json +27 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SSOT Map — 티어별 문서 주입 기준
|
|
2
|
+
# Tier 0: 모든 작업에 항상 주입 (Hook → systemMessage)
|
|
3
|
+
# Tier 1: Phase 시작 시 Controller가 주입
|
|
4
|
+
# Tier 2: Sequence 범위 문서 (Controller가 선택 주입)
|
|
5
|
+
# Tier 3: 개별 Task에 필요한 구체 spec (Controller가 선택 주입)
|
|
6
|
+
|
|
7
|
+
tier-0-always:
|
|
8
|
+
description: "모든 작업에 항상 주입되는 제약 문서"
|
|
9
|
+
inject_via: hook
|
|
10
|
+
documents:
|
|
11
|
+
- compiled: rules/security-constraints.md
|
|
12
|
+
source: security-spec.md
|
|
13
|
+
- compiled: rules/completion-criteria.md
|
|
14
|
+
source: requirements.md
|
|
15
|
+
|
|
16
|
+
tier-1-phase:
|
|
17
|
+
description: "Phase 시작 시 controller가 주입하는 방향성 문서"
|
|
18
|
+
inject_via: controller
|
|
19
|
+
documents:
|
|
20
|
+
- compiled: references/prd-summary.spec.md
|
|
21
|
+
source: prd.md
|
|
22
|
+
- compiled: references/architecture.spec.md
|
|
23
|
+
source: planning.md
|
|
24
|
+
|
|
25
|
+
tier-2-sequence:
|
|
26
|
+
description: "Sequence 범위의 기능별 문서"
|
|
27
|
+
inject_via: controller
|
|
28
|
+
documents:
|
|
29
|
+
- compiled: references/service.spec.md
|
|
30
|
+
source: service-spec.md
|
|
31
|
+
- compiled: references/data-design.spec.md
|
|
32
|
+
source: data-design.md
|
|
33
|
+
|
|
34
|
+
tier-3-task:
|
|
35
|
+
description: "개별 Task에 필요한 구체 spec"
|
|
36
|
+
inject_via: controller
|
|
37
|
+
documents:
|
|
38
|
+
- compiled: references/error-codes.spec.md
|
|
39
|
+
source: error-codes.md
|
|
40
|
+
- compiled: references/functional.spec.md
|
|
41
|
+
source: functional-spec.md
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "src/api/**/*.ts"
|
|
4
|
+
- "src/routes/**/*.ts"
|
|
5
|
+
- "src/controllers/**/*.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
- RESTful 네이밍 사용 (복수형 리소스, kebab-case)
|
|
9
|
+
- 에러 응답 표준 형식: `{ code: string, message: string, details?: unknown }`
|
|
10
|
+
- HTTP 상태 코드 정확히 매핑 (200/201/400/401/403/404/500)
|
|
11
|
+
- 요청 validation은 엔드포인트 진입 시점에서 수행
|
|
12
|
+
- 비즈니스 로직은 서비스 레이어에 위치 (컨트롤러에 직접 작성 금지)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "tests/**/*.test.ts"
|
|
4
|
+
- "tests/**/*.spec.ts"
|
|
5
|
+
- "**/*.test.ts"
|
|
6
|
+
- "**/*.spec.ts"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
- describe/it 중첩 3단계 이하 유지
|
|
10
|
+
- 테스트 데이터는 인라인 또는 fixtures/ 디렉토리 사용
|
|
11
|
+
- 각 테스트는 독립적 (공유 상태 금지, beforeEach에서 초기화)
|
|
12
|
+
- 테스트 이름은 "should + 기대 동작" 형식
|
|
13
|
+
- mock은 최소 범위로 사용 (외부 의존성만 mock)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# change-scope-guard.sh — PreToolUse Hook (Edit/Write)
|
|
3
|
+
# Tracks cumulative file changes and enforces SCR (Single Change Rule).
|
|
4
|
+
# 3 files: warning, 6 files: block, >100 lines total: warning
|
|
5
|
+
#
|
|
6
|
+
# Input: JSON via stdin (Claude Code hook protocol)
|
|
7
|
+
# Output: JSON with permissionDecision (allow/deny/ask)
|
|
8
|
+
#
|
|
9
|
+
# State file: /tmp/tsq-scope-guard-${SESSION_ID:-default}.json
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
WARN_FILES=3
|
|
14
|
+
BLOCK_FILES=6
|
|
15
|
+
WARN_LINES=100
|
|
16
|
+
|
|
17
|
+
# Session-scoped state file
|
|
18
|
+
SESSION_ID="${CLAUDE_SESSION_ID:-default}"
|
|
19
|
+
STATE_FILE="/tmp/tsq-scope-guard-${SESSION_ID}.json"
|
|
20
|
+
|
|
21
|
+
# Read hook input from stdin
|
|
22
|
+
INPUT=""
|
|
23
|
+
if read -t 1 -r line; then
|
|
24
|
+
INPUT="$line"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Fail-open: no input → allow
|
|
28
|
+
if [ -z "$INPUT" ]; then
|
|
29
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Extract tool name
|
|
34
|
+
TOOL_NAME=""
|
|
35
|
+
if command -v jq >/dev/null 2>&1; then
|
|
36
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.toolName // empty' 2>/dev/null || true)
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Only track Edit and Write tools
|
|
40
|
+
if [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Write" ]; then
|
|
41
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Extract file path from tool input
|
|
46
|
+
FILE_PATH=""
|
|
47
|
+
if command -v jq >/dev/null 2>&1; then
|
|
48
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.toolInput.file_path // empty' 2>/dev/null || true)
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [ -z "$FILE_PATH" ]; then
|
|
52
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Initialize state file if needed
|
|
57
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
58
|
+
echo '{"files":[],"total_lines":0}' > "$STATE_FILE"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Read current state
|
|
62
|
+
if command -v jq >/dev/null 2>&1; then
|
|
63
|
+
CURRENT_FILES=$(jq -r '.files[]' "$STATE_FILE" 2>/dev/null || true)
|
|
64
|
+
TOTAL_LINES=$(jq -r '.total_lines // 0' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
65
|
+
else
|
|
66
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# Check if file is already tracked
|
|
71
|
+
ALREADY_TRACKED=false
|
|
72
|
+
while IFS= read -r f; do
|
|
73
|
+
if [ "$f" = "$FILE_PATH" ]; then
|
|
74
|
+
ALREADY_TRACKED=true
|
|
75
|
+
break
|
|
76
|
+
fi
|
|
77
|
+
done <<< "$CURRENT_FILES"
|
|
78
|
+
|
|
79
|
+
# Add new file if not tracked
|
|
80
|
+
if [ "$ALREADY_TRACKED" = false ]; then
|
|
81
|
+
jq --arg fp "$FILE_PATH" '.files += [$fp]' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Estimate line changes (for Edit: old_string length approximation)
|
|
85
|
+
if [ "$TOOL_NAME" = "Edit" ]; then
|
|
86
|
+
NEW_LINES=$(echo "$INPUT" | jq -r '.toolInput.new_string // ""' 2>/dev/null | wc -l || echo "0")
|
|
87
|
+
TOTAL_LINES=$((TOTAL_LINES + NEW_LINES))
|
|
88
|
+
jq --argjson tl "$TOTAL_LINES" '.total_lines = $tl' "$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Count unique files
|
|
92
|
+
FILE_COUNT=$(jq '.files | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
93
|
+
|
|
94
|
+
# Decision logic
|
|
95
|
+
if [ "$FILE_COUNT" -ge "$BLOCK_FILES" ]; then
|
|
96
|
+
MSG="SCR violation: ${FILE_COUNT} files modified (limit: ${BLOCK_FILES}). Split into smaller tasks."
|
|
97
|
+
echo "{\"hookSpecificOutput\":{\"permissionDecision\":\"deny\",\"message\":\"${MSG}\"}}"
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ "$FILE_COUNT" -ge "$WARN_FILES" ]; then
|
|
102
|
+
MSG="SCR warning: ${FILE_COUNT} files modified. Consider splitting this task."
|
|
103
|
+
echo "{\"hookSpecificOutput\":{\"permissionDecision\":\"allow\",\"message\":\"${MSG}\"}}"
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if [ "$TOTAL_LINES" -ge "$WARN_LINES" ]; then
|
|
108
|
+
MSG="Large change: ~${TOTAL_LINES} lines modified across ${FILE_COUNT} files."
|
|
109
|
+
echo "{\"hookSpecificOutput\":{\"permissionDecision\":\"allow\",\"message\":\"${MSG}\"}}"
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
echo '{"hookSpecificOutput":{"permissionDecision":"allow"}}'
|
|
@@ -34,18 +34,44 @@ fi
|
|
|
34
34
|
|
|
35
35
|
BLOCK_REASON=""
|
|
36
36
|
|
|
37
|
-
# ── 1. Test execution gate
|
|
38
|
-
#
|
|
39
|
-
#
|
|
37
|
+
# ── 1. Test execution gate ──
|
|
38
|
+
# 변경된 소스 파일에 대한 테스트 실행 여부 확인
|
|
39
|
+
# implementation phase에서만 block, 그 외에는 warning
|
|
40
40
|
PHASE_FILE="$PROJECT_ROOT/.timsquad/state/current-phase.json"
|
|
41
|
+
PHASE="unknown"
|
|
41
42
|
if [ -f "$PHASE_FILE" ]; then
|
|
42
43
|
PHASE=$(jq -r '.current // .current_phase // .phase // "unknown"' "$PHASE_FILE" 2>/dev/null || echo "unknown")
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
fi
|
|
45
|
+
|
|
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")
|
|
51
|
+
|
|
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 테스트를 실행한 후 완료하세요."
|
|
49
75
|
fi
|
|
50
76
|
fi
|
|
51
77
|
fi
|
|
@@ -79,15 +105,51 @@ if [ -f "$NOTES_FILE" ]; then
|
|
|
79
105
|
fi
|
|
80
106
|
fi
|
|
81
107
|
|
|
82
|
-
# ── 4.
|
|
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
|
|
117
|
+
|
|
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
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# ── 5. Combine and output ──
|
|
83
133
|
# BLOCK_REASON이 있으면 decision:block으로 강제 속행 (세션 컨텍스트 포함)
|
|
84
134
|
if [ -n "$BLOCK_REASON" ]; then
|
|
85
135
|
FULL_REASON="$BLOCK_REASON"
|
|
86
|
-
[ -n "$SESSION_CTX" ] && FULL_REASON="$
|
|
136
|
+
[ -n "$SESSION_CTX" ] && FULL_REASON="$FULL_REASON
|
|
87
137
|
$SESSION_CTX"
|
|
138
|
+
[ -n "$VERIFICATION" ] && FULL_REASON="$FULL_REASON
|
|
139
|
+
$VERIFICATION"
|
|
88
140
|
jq -n --arg reason "$FULL_REASON" '{"decision": "block", "reason": $reason}'
|
|
89
|
-
elif [ -n "$SESSION_CTX" ]; then
|
|
90
|
-
|
|
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}'
|
|
91
153
|
else
|
|
92
154
|
echo '{}'
|
|
93
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"
|
|
@@ -55,7 +55,7 @@ fi
|
|
|
55
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
|
|
@@ -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
|