aios-core 4.0.2 → 4.1.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/.aios-core/cli/commands/migrate/analyze.js +6 -6
- package/.aios-core/cli/commands/migrate/backup.js +2 -2
- package/.aios-core/cli/commands/migrate/execute.js +4 -4
- package/.aios-core/cli/commands/migrate/index.js +5 -5
- package/.aios-core/cli/commands/migrate/rollback.js +6 -6
- package/.aios-core/cli/commands/migrate/update-imports.js +2 -2
- package/.aios-core/cli/commands/migrate/validate.js +2 -2
- package/.aios-core/cli/commands/pro/index.js +52 -0
- package/.aios-core/cli/index.js +1 -1
- package/.aios-core/core/ids/registry-updater.js +29 -3
- package/.aios-core/core/migration/migration-config.yaml +2 -2
- package/.aios-core/core/migration/module-mapping.yaml +2 -2
- package/.aios-core/core/registry/README.md +2 -2
- package/.aios-core/core/synapse/context/context-builder.js +34 -0
- package/.aios-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
- package/.aios-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
- package/.aios-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
- package/.aios-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
- package/.aios-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
- package/.aios-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
- package/.aios-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
- package/.aios-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
- package/.aios-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
- package/.aios-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
- package/.aios-core/core/synapse/diagnostics/report-formatter.js +484 -0
- package/.aios-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
- package/.aios-core/core/synapse/engine.js +73 -20
- package/.aios-core/core/synapse/runtime/hook-runtime.js +60 -0
- package/.aios-core/core-config.yaml +6 -0
- package/.aios-core/data/agent-config-requirements.yaml +2 -2
- package/.aios-core/data/aios-kb.md +4 -4
- package/.aios-core/data/entity-registry.yaml +5 -5
- package/.aios-core/development/agents/architect.md +10 -10
- package/.aios-core/development/agents/devops.md +93 -50
- package/.aios-core/development/agents/qa.md +94 -40
- package/.aios-core/development/agents/ux-design-expert.md +25 -25
- package/.aios-core/development/scripts/activation-runtime.js +63 -0
- package/.aios-core/development/scripts/generate-greeting.js +9 -8
- package/.aios-core/development/scripts/unified-activation-pipeline.js +102 -2
- package/.aios-core/development/tasks/{db-expansion-pack-integration.md → db-squad-integration.md} +5 -5
- package/.aios-core/development/tasks/{integrate-expansion-pack.md → integrate-squad.md} +2 -2
- package/.aios-core/development/tasks/next.md +3 -3
- package/.aios-core/development/tasks/pr-automation.md +2 -2
- package/.aios-core/development/tasks/publish-npm.md +257 -0
- package/.aios-core/development/tasks/release-management.md +4 -4
- package/.aios-core/development/tasks/setup-github.md +1 -1
- package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
- package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +14 -14
- package/.aios-core/development/tasks/update-aios.md +1 -1
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +1 -1
- package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +5 -5
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +21 -21
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +25 -25
- package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +4 -4
- package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +3 -3
- package/.aios-core/docs/standards/STANDARDS-INDEX.md +13 -13
- package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +1 -1
- package/.aios-core/framework-config.yaml +4 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/index.js +182 -0
- package/.aios-core/infrastructure/scripts/codex-skills-sync/validate.js +172 -0
- package/.aios-core/infrastructure/scripts/ide-sync/README.md +14 -0
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +6 -0
- package/.aios-core/infrastructure/scripts/tool-resolver.js +4 -4
- package/.aios-core/infrastructure/scripts/validate-paths.js +142 -0
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +11 -11
- package/.aios-core/infrastructure/templates/github-workflows/README.md +1 -1
- package/.aios-core/install-manifest.yaml +190 -106
- package/.aios-core/local-config.yaml.template +2 -0
- package/.aios-core/product/README.md +2 -2
- package/.aios-core/product/data/integration-patterns.md +1 -1
- package/.aios-core/product/templates/ide-rules/cline-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/codex-rules.md +65 -0
- package/.aios-core/product/templates/ide-rules/copilot-rules.md +1 -1
- package/.aios-core/product/templates/ide-rules/roo-rules.md +1 -1
- package/.aios-core/user-guide.md +15 -14
- package/.aios-core/workflow-intelligence/engine/output-formatter.js +1 -1
- package/.claude/hooks/enforce-architecture-first.py +196 -0
- package/.claude/hooks/install-hooks.sh +41 -0
- package/.claude/hooks/mind-clone-governance.py +192 -0
- package/.claude/hooks/pre-commit-mmos-guard.sh +99 -0
- package/.claude/hooks/pre-commit-version-check.sh +156 -0
- package/.claude/hooks/read-protection.py +151 -0
- package/.claude/hooks/slug-validation.py +176 -0
- package/.claude/hooks/sql-governance.py +182 -0
- package/.claude/hooks/synapse-engine.js +9 -20
- package/.claude/hooks/write-path-validation.py +194 -0
- package/README.md +44 -14
- package/bin/aios-init.js +255 -184
- package/bin/aios-minimal.js +2 -2
- package/bin/aios.js +19 -19
- package/package.json +7 -4
- package/packages/aios-pro-cli/bin/aios-pro.js +75 -2
- package/packages/aios-pro-cli/package.json +5 -1
- package/packages/aios-pro-cli/src/recover.js +100 -0
- package/packages/installer/src/__tests__/performance-benchmark.js +382 -0
- package/packages/installer/src/config/ide-configs.js +12 -1
- package/packages/installer/src/config/templates/core-config-template.js +2 -2
- package/packages/installer/src/installer/aios-core-installer.js +2 -2
- package/packages/installer/src/installer/file-hasher.js +97 -0
- package/packages/installer/src/installer/post-install-validator.js +41 -1
- package/packages/installer/src/pro/pro-scaffolder.js +335 -0
- package/packages/installer/src/utils/aios-colors.js +2 -2
- package/packages/installer/src/wizard/feedback.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +2 -2
- package/packages/installer/src/wizard/index.js +58 -19
- package/packages/installer/src/wizard/pro-setup.js +547 -0
- package/packages/installer/src/wizard/questions.js +20 -14
- package/packages/installer/src/wizard/validators.js +1 -1
- package/scripts/package-synapse.js +323 -0
- package/scripts/validate-package-completeness.js +317 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# MMOS Architecture Guard - Pre-commit Hook
|
|
3
|
+
# Version: 1.0
|
|
4
|
+
# Purpose: Prevent architectural violations in file placement
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "🛡️ Running MMOS Architecture Guard..."
|
|
9
|
+
|
|
10
|
+
# Colors
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
YELLOW='\033[1;33m'
|
|
14
|
+
NC='\033[0m' # No Color
|
|
15
|
+
|
|
16
|
+
violations=0
|
|
17
|
+
|
|
18
|
+
# Get staged files
|
|
19
|
+
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
|
|
20
|
+
|
|
21
|
+
# Rule 1: No mind-specific folders in docs/mmos/
|
|
22
|
+
echo "Checking Rule 1: No mind-specific folders in docs/mmos/..."
|
|
23
|
+
|
|
24
|
+
if echo "$staged_files" | grep -qE "docs/mmos/(validations|migrations)/[a-z_-]+"; then
|
|
25
|
+
echo -e "${RED}❌ VIOLATION: Mind-specific folders detected in docs/mmos/${NC}"
|
|
26
|
+
echo ""
|
|
27
|
+
echo "Found:"
|
|
28
|
+
echo "$staged_files" | grep -E "docs/mmos/(validations|migrations)/[a-z_-]+" | sed 's/^/ - /'
|
|
29
|
+
echo ""
|
|
30
|
+
echo -e "${YELLOW}Fix: Move to outputs/minds/{slug}/docs/ instead${NC}"
|
|
31
|
+
echo ""
|
|
32
|
+
echo "Examples:"
|
|
33
|
+
echo " ❌ docs/mmos/validations/pedro-valerio-checklist.md"
|
|
34
|
+
echo " ✅ outputs/minds/pedro_valerio/docs/validation-checklist.md"
|
|
35
|
+
echo ""
|
|
36
|
+
violations=$((violations + 1))
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Rule 2: No output files in expansion pack
|
|
40
|
+
echo "Checking Rule 2: No output files in expansion pack..."
|
|
41
|
+
|
|
42
|
+
if echo "$staged_files" | grep -qE "expansion-packs/mmos/(benchmarks|outputs|results)/"; then
|
|
43
|
+
echo -e "${RED}❌ VIOLATION: Output files in expansion pack${NC}"
|
|
44
|
+
echo ""
|
|
45
|
+
echo "Found:"
|
|
46
|
+
echo "$staged_files" | grep -E "expansion-packs/mmos/(benchmarks|outputs|results)/" | sed 's/^/ - /'
|
|
47
|
+
echo ""
|
|
48
|
+
echo -e "${YELLOW}Fix: Move to docs/mmos/qa/benchmarks/ or appropriate output location${NC}"
|
|
49
|
+
echo ""
|
|
50
|
+
violations=$((violations + 1))
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Rule 3: Check for common naming violations
|
|
54
|
+
echo "Checking Rule 3: Mind-specific files should be in outputs/minds/..."
|
|
55
|
+
|
|
56
|
+
# Check for files with mind names in docs/mmos/ (excluding allowed folders)
|
|
57
|
+
for mind_name in $(ls outputs/minds/ 2>/dev/null | grep -v "^README" || true); do
|
|
58
|
+
if echo "$staged_files" | grep -qE "docs/mmos/.*${mind_name}"; then
|
|
59
|
+
# Exclude allowed locations (reports can mention minds)
|
|
60
|
+
if ! echo "$staged_files" | grep -qE "docs/mmos/(reports|architecture)/"; then
|
|
61
|
+
echo -e "${YELLOW}⚠️ WARNING: File containing mind name '$mind_name' in docs/mmos/${NC}"
|
|
62
|
+
echo "$staged_files" | grep -E "docs/mmos/.*${mind_name}" | sed 's/^/ - /'
|
|
63
|
+
echo ""
|
|
64
|
+
echo "Verify this is system-level documentation, not mind-specific"
|
|
65
|
+
echo ""
|
|
66
|
+
fi
|
|
67
|
+
fi
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
# Rule 4: Verify outputs/minds/{slug}/ structure
|
|
71
|
+
echo "Checking Rule 4: outputs/minds/{slug}/ structure..."
|
|
72
|
+
|
|
73
|
+
if echo "$staged_files" | grep -qE "outputs/minds/[^/]+/[^/]+\.(md|yaml|json)$"; then
|
|
74
|
+
echo -e "${YELLOW}⚠️ WARNING: Files in outputs/minds/{slug}/ root detected${NC}"
|
|
75
|
+
echo ""
|
|
76
|
+
echo "Found:"
|
|
77
|
+
echo "$staged_files" | grep -E "outputs/minds/[^/]+/[^/]+\.(md|yaml|json)$" | sed 's/^/ - /'
|
|
78
|
+
echo ""
|
|
79
|
+
echo "Pipeline outputs should be in subfolders:"
|
|
80
|
+
echo " - analysis/, synthesis/, implementation/, system_prompts/, kb/"
|
|
81
|
+
echo " - Process docs should be in docs/"
|
|
82
|
+
echo " - Logs should be in logs/"
|
|
83
|
+
echo ""
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Final verdict
|
|
87
|
+
echo ""
|
|
88
|
+
if [ $violations -gt 0 ]; then
|
|
89
|
+
echo -e "${RED}❌ COMMIT REJECTED: $violations architectural violation(s) found${NC}"
|
|
90
|
+
echo ""
|
|
91
|
+
echo "Review:"
|
|
92
|
+
echo " - docs/mmos/ARCHITECTURE_RULES.md"
|
|
93
|
+
echo " - .aios-core/checklists/mmos-architecture-guard.md"
|
|
94
|
+
echo ""
|
|
95
|
+
exit 1
|
|
96
|
+
else
|
|
97
|
+
echo -e "${GREEN}✅ Architecture guard passed${NC}"
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Pre-Commit Hook: Version Metadata Validation
|
|
4
|
+
#
|
|
5
|
+
# Ensures all tasks and agents have version metadata in frontmatter.
|
|
6
|
+
# This prevents silent version mismatches (e.g., v1.0 agent + v2.0 task).
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# Run manually: .aios-core/hooks/pre-commit-version-check.sh
|
|
10
|
+
# Install: .aios-core/hooks/install-hooks.sh
|
|
11
|
+
#
|
|
12
|
+
# Story: STORY-3.10 (Version Alignment & Compatibility Checks)
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
echo "🔍 Validating task and agent version metadata..."
|
|
17
|
+
|
|
18
|
+
# Colors for output
|
|
19
|
+
RED='\033[0;31m'
|
|
20
|
+
GREEN='\033[0;32m'
|
|
21
|
+
YELLOW='\033[1;33m'
|
|
22
|
+
NC='\033[0m' # No Color
|
|
23
|
+
|
|
24
|
+
# Track validation status
|
|
25
|
+
ERRORS=0
|
|
26
|
+
|
|
27
|
+
# Function to check if file has required version fields
|
|
28
|
+
check_task_version() {
|
|
29
|
+
local file="$1"
|
|
30
|
+
local missing_fields=()
|
|
31
|
+
|
|
32
|
+
if ! grep -q "^task_name:" "$file"; then
|
|
33
|
+
missing_fields+=("task_name")
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if ! grep -q "^task_version:" "$file"; then
|
|
37
|
+
missing_fields+=("task_version")
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if ! grep -q "^required_agent_version:" "$file"; then
|
|
41
|
+
missing_fields+=("required_agent_version")
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
if ! grep -q "^description:" "$file"; then
|
|
45
|
+
missing_fields+=("description")
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if ! grep -q "^last_updated:" "$file"; then
|
|
49
|
+
missing_fields+=("last_updated")
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ ${#missing_fields[@]} -gt 0 ]; then
|
|
53
|
+
echo -e "${RED}❌ ERROR: $file missing version metadata${NC}"
|
|
54
|
+
for field in "${missing_fields[@]}"; do
|
|
55
|
+
echo -e "${RED} - Missing field: '$field'${NC}"
|
|
56
|
+
done
|
|
57
|
+
echo ""
|
|
58
|
+
ERRORS=$((ERRORS + 1))
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
check_agent_version() {
|
|
66
|
+
local file="$1"
|
|
67
|
+
local missing_fields=()
|
|
68
|
+
|
|
69
|
+
if ! grep -q "^agent_name:" "$file"; then
|
|
70
|
+
missing_fields+=("agent_name")
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if ! grep -q "^agent_version:" "$file"; then
|
|
74
|
+
missing_fields+=("agent_version")
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if ! grep -q "^compatible_task_versions:" "$file"; then
|
|
78
|
+
missing_fields+=("compatible_task_versions")
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if ! grep -q "^description:" "$file"; then
|
|
82
|
+
missing_fields+=("description")
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if ! grep -q "^last_updated:" "$file"; then
|
|
86
|
+
missing_fields+=("last_updated")
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ ${#missing_fields[@]} -gt 0 ]; then
|
|
90
|
+
echo -e "${RED}❌ ERROR: $file missing version metadata${NC}"
|
|
91
|
+
for field in "${missing_fields[@]}"; do
|
|
92
|
+
echo -e "${RED} - Missing field: '$field'${NC}"
|
|
93
|
+
done
|
|
94
|
+
echo ""
|
|
95
|
+
ERRORS=$((ERRORS + 1))
|
|
96
|
+
return 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
return 0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Check all tasks in expansion-packs/creator-os/tasks/
|
|
103
|
+
echo "Checking CreatorOS tasks..."
|
|
104
|
+
TASK_COUNT=0
|
|
105
|
+
for task in expansion-packs/creator-os/tasks/*.md; do
|
|
106
|
+
# Skip backup files
|
|
107
|
+
if [[ "$task" == *"-backup.md" ]]; then
|
|
108
|
+
echo -e "${YELLOW}⏭️ Skipping backup: $task${NC}"
|
|
109
|
+
continue
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ -f "$task" ]; then
|
|
113
|
+
if check_task_version "$task"; then
|
|
114
|
+
echo -e "${GREEN}✅ $task${NC}"
|
|
115
|
+
fi
|
|
116
|
+
TASK_COUNT=$((TASK_COUNT + 1))
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
# Check all agents in expansion-packs/creator-os/agents/
|
|
121
|
+
echo ""
|
|
122
|
+
echo "Checking CreatorOS agents..."
|
|
123
|
+
AGENT_COUNT=0
|
|
124
|
+
for agent in expansion-packs/creator-os/agents/*.md; do
|
|
125
|
+
if [ -f "$agent" ]; then
|
|
126
|
+
if check_agent_version "$agent"; then
|
|
127
|
+
echo -e "${GREEN}✅ $agent${NC}"
|
|
128
|
+
fi
|
|
129
|
+
AGENT_COUNT=$((AGENT_COUNT + 1))
|
|
130
|
+
fi
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
echo ""
|
|
134
|
+
echo "────────────────────────────────────────────────────────────"
|
|
135
|
+
echo "Summary:"
|
|
136
|
+
echo " - Tasks checked: $TASK_COUNT"
|
|
137
|
+
echo " - Agents checked: $AGENT_COUNT"
|
|
138
|
+
echo " - Errors found: $ERRORS"
|
|
139
|
+
echo "────────────────────────────────────────────────────────────"
|
|
140
|
+
|
|
141
|
+
if [ $ERRORS -gt 0 ]; then
|
|
142
|
+
echo ""
|
|
143
|
+
echo -e "${RED}❌ Version validation FAILED${NC}"
|
|
144
|
+
echo ""
|
|
145
|
+
echo "How to fix:"
|
|
146
|
+
echo "1. Add YAML frontmatter to each flagged file"
|
|
147
|
+
echo "2. Include all required fields (task_name, task_version, etc.)"
|
|
148
|
+
echo "3. See Story 3.10 for examples"
|
|
149
|
+
echo ""
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo ""
|
|
154
|
+
echo -e "${GREEN}✅ Version validation PASSED${NC}"
|
|
155
|
+
echo ""
|
|
156
|
+
exit 0
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: Read Protection
|
|
4
|
+
|
|
5
|
+
REGRA: Arquivos protegidos DEVEM ser lidos completamente (sem limit/offset).
|
|
6
|
+
|
|
7
|
+
Este hook intercepta chamadas Read e bloqueia leitura parcial em arquivos
|
|
8
|
+
que requerem contexto completo para edição segura.
|
|
9
|
+
|
|
10
|
+
Exit Codes:
|
|
11
|
+
- 0: Permitido
|
|
12
|
+
- 2: Bloqueado (arquivo protegido com limit/offset)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
import os
|
|
18
|
+
import fnmatch
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
# =============================================================================
|
|
22
|
+
# CONFIGURAÇÃO: Arquivos que DEVEM ser lidos completamente
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
PROTECTED_PATTERNS = [
|
|
26
|
+
# Configuração do Claude
|
|
27
|
+
".claude/CLAUDE.md",
|
|
28
|
+
".claude/rules/*.md",
|
|
29
|
+
".claude/settings*.json",
|
|
30
|
+
|
|
31
|
+
# Definições de agentes
|
|
32
|
+
".aios-core/development/agents/*.md",
|
|
33
|
+
".aios-upstream/.aios-core/development/agents/*.md",
|
|
34
|
+
|
|
35
|
+
# Documentação crítica
|
|
36
|
+
"docs/mmos/ARCHITECTURE_RULES.md",
|
|
37
|
+
"supabase/docs/SCHEMA.md",
|
|
38
|
+
|
|
39
|
+
# Configuração do projeto
|
|
40
|
+
"package.json",
|
|
41
|
+
"tsconfig.json",
|
|
42
|
+
"tsconfig.*.json",
|
|
43
|
+
|
|
44
|
+
# Arquivos de código críticos
|
|
45
|
+
"app/components/ui/icons/icon-map.ts",
|
|
46
|
+
|
|
47
|
+
# Migrations (sempre ler completo antes de editar)
|
|
48
|
+
"supabase/migrations/*.sql",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
# =============================================================================
|
|
52
|
+
# LÓGICA DO HOOK
|
|
53
|
+
# =============================================================================
|
|
54
|
+
|
|
55
|
+
def get_project_root():
|
|
56
|
+
"""Obtém o root do projeto via variável de ambiente ou cwd."""
|
|
57
|
+
return os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
|
|
58
|
+
|
|
59
|
+
def normalize_path(file_path: str, project_root: str) -> str:
|
|
60
|
+
"""Normaliza path para relativo ao projeto."""
|
|
61
|
+
if file_path.startswith(project_root):
|
|
62
|
+
return file_path[len(project_root):].lstrip("/")
|
|
63
|
+
if file_path.startswith("/"):
|
|
64
|
+
# Path absoluto fora do projeto
|
|
65
|
+
return file_path
|
|
66
|
+
return file_path
|
|
67
|
+
|
|
68
|
+
def matches_protected_pattern(relative_path: str) -> bool:
|
|
69
|
+
"""Verifica se o path corresponde a algum padrão protegido."""
|
|
70
|
+
for pattern in PROTECTED_PATTERNS:
|
|
71
|
+
if fnmatch.fnmatch(relative_path, pattern):
|
|
72
|
+
return True
|
|
73
|
+
# Também verificar se é substring (para paths com wildcards)
|
|
74
|
+
if "*" not in pattern and pattern in relative_path:
|
|
75
|
+
return True
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def has_partial_read_params(tool_input: dict) -> tuple[bool, str]:
|
|
79
|
+
"""Verifica se a chamada Read tem parâmetros de leitura parcial."""
|
|
80
|
+
limit = tool_input.get("limit")
|
|
81
|
+
offset = tool_input.get("offset")
|
|
82
|
+
|
|
83
|
+
reasons = []
|
|
84
|
+
if limit is not None:
|
|
85
|
+
reasons.append(f"limit={limit}")
|
|
86
|
+
if offset is not None and offset != 0:
|
|
87
|
+
reasons.append(f"offset={offset}")
|
|
88
|
+
|
|
89
|
+
return len(reasons) > 0, ", ".join(reasons)
|
|
90
|
+
|
|
91
|
+
def main():
|
|
92
|
+
# Ler input do stdin
|
|
93
|
+
try:
|
|
94
|
+
input_data = json.load(sys.stdin)
|
|
95
|
+
except json.JSONDecodeError:
|
|
96
|
+
# Se não conseguir parsear, permitir (fail-open)
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
|
|
99
|
+
tool_name = input_data.get("tool_name", "")
|
|
100
|
+
tool_input = input_data.get("tool_input", {})
|
|
101
|
+
|
|
102
|
+
# Só processar Read
|
|
103
|
+
if tool_name != "Read":
|
|
104
|
+
sys.exit(0)
|
|
105
|
+
|
|
106
|
+
file_path = tool_input.get("file_path", "")
|
|
107
|
+
if not file_path:
|
|
108
|
+
sys.exit(0)
|
|
109
|
+
|
|
110
|
+
# Normalizar path
|
|
111
|
+
project_root = get_project_root()
|
|
112
|
+
relative_path = normalize_path(file_path, project_root)
|
|
113
|
+
|
|
114
|
+
# Verificar se é arquivo protegido
|
|
115
|
+
if not matches_protected_pattern(relative_path):
|
|
116
|
+
sys.exit(0)
|
|
117
|
+
|
|
118
|
+
# Verificar se tem parâmetros de leitura parcial
|
|
119
|
+
is_partial, partial_reason = has_partial_read_params(tool_input)
|
|
120
|
+
|
|
121
|
+
if not is_partial:
|
|
122
|
+
# Leitura completa, permitir
|
|
123
|
+
sys.exit(0)
|
|
124
|
+
|
|
125
|
+
# BLOQUEAR: Tentando ler arquivo protegido parcialmente
|
|
126
|
+
error_message = f"""
|
|
127
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
128
|
+
║ 🛑 READ PROTECTION: Arquivo protegido deve ser lido completamente ║
|
|
129
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
130
|
+
║ ║
|
|
131
|
+
║ Arquivo: {relative_path[:58]:<58} ║
|
|
132
|
+
║ Problema: {partial_reason:<57} ║
|
|
133
|
+
║ ║
|
|
134
|
+
║ REGRA: Este arquivo está na lista de arquivos protegidos. ║
|
|
135
|
+
║ Arquivos protegidos DEVEM ser lidos completamente para evitar: ║
|
|
136
|
+
║ - Edições com contexto incompleto ║
|
|
137
|
+
║ - Duplicações acidentais ║
|
|
138
|
+
║ - Breaking changes não intencionais ║
|
|
139
|
+
║ ║
|
|
140
|
+
║ SOLUÇÃO: Remova os parâmetros 'limit' e 'offset' da chamada Read. ║
|
|
141
|
+
║ ║
|
|
142
|
+
║ ✅ Read(file_path="{relative_path[:40]}")
|
|
143
|
+
║ ❌ Read(file_path="...", limit=100) ║
|
|
144
|
+
║ ║
|
|
145
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
146
|
+
"""
|
|
147
|
+
print(error_message, file=sys.stderr)
|
|
148
|
+
sys.exit(2)
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Hook: Slug Validation
|
|
4
|
+
|
|
5
|
+
REGRA: Todos os slugs DEVEM usar snake_case (underscores, não hyphens).
|
|
6
|
+
|
|
7
|
+
Este hook intercepta comandos SQL que inserem/atualizam slugs e valida
|
|
8
|
+
o formato antes de permitir a operação.
|
|
9
|
+
|
|
10
|
+
Pattern válido: ^[a-z0-9]+(_[a-z0-9]+)*$
|
|
11
|
+
Exemplos válidos: jose_carlos_amorim, alan_nicolas, elon_musk
|
|
12
|
+
Exemplos inválidos: jose-carlos-amorim, JoseAmorim, ELON_MUSK
|
|
13
|
+
|
|
14
|
+
Exit Codes:
|
|
15
|
+
- 0: Permitido
|
|
16
|
+
- 2: Bloqueado (slug com formato inválido)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
import re
|
|
22
|
+
|
|
23
|
+
# =============================================================================
|
|
24
|
+
# CONFIGURAÇÃO
|
|
25
|
+
# =============================================================================
|
|
26
|
+
|
|
27
|
+
# Pattern válido para slugs
|
|
28
|
+
SLUG_PATTERN = re.compile(r"^[a-z0-9]+(_[a-z0-9]+)*$")
|
|
29
|
+
|
|
30
|
+
# Tabelas que têm coluna slug
|
|
31
|
+
TABLES_WITH_SLUG = [
|
|
32
|
+
"minds",
|
|
33
|
+
"contents",
|
|
34
|
+
"content_projects",
|
|
35
|
+
"tools",
|
|
36
|
+
"drivers",
|
|
37
|
+
"mapping_systems",
|
|
38
|
+
"frameworks",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# LÓGICA DO HOOK
|
|
43
|
+
# =============================================================================
|
|
44
|
+
|
|
45
|
+
def extract_slug_values(command: str) -> list[tuple[str, str]]:
|
|
46
|
+
"""
|
|
47
|
+
Extrai valores de slug de comandos SQL INSERT/UPDATE.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Lista de tuplas (table, slug_value)
|
|
51
|
+
"""
|
|
52
|
+
found = []
|
|
53
|
+
command_upper = command.upper()
|
|
54
|
+
|
|
55
|
+
for table in TABLES_WITH_SLUG:
|
|
56
|
+
table_upper = table.upper()
|
|
57
|
+
|
|
58
|
+
# Detectar INSERT INTO table (..., slug, ...) VALUES (..., 'value', ...)
|
|
59
|
+
insert_pattern = rf"INSERT\s+INTO\s+{table}\s*\([^)]*\bslug\b[^)]*\)\s*VALUES\s*\(([^)]+)\)"
|
|
60
|
+
insert_match = re.search(insert_pattern, command, re.IGNORECASE)
|
|
61
|
+
if insert_match:
|
|
62
|
+
values_str = insert_match.group(1)
|
|
63
|
+
# Extrair valores entre aspas
|
|
64
|
+
slug_values = re.findall(r"'([^']+)'", values_str)
|
|
65
|
+
for sv in slug_values:
|
|
66
|
+
# Verificar se parece um slug (não é UUID, não é número)
|
|
67
|
+
if not re.match(r"^[0-9a-f-]{36}$", sv) and not sv.isdigit():
|
|
68
|
+
found.append((table, sv))
|
|
69
|
+
|
|
70
|
+
# Detectar UPDATE table SET slug = 'value'
|
|
71
|
+
update_pattern = rf"UPDATE\s+{table}\s+.*SET\s+.*\bslug\s*=\s*'([^']+)'"
|
|
72
|
+
update_match = re.search(update_pattern, command, re.IGNORECASE)
|
|
73
|
+
if update_match:
|
|
74
|
+
found.append((table, update_match.group(1)))
|
|
75
|
+
|
|
76
|
+
return found
|
|
77
|
+
|
|
78
|
+
def validate_slug(slug: str) -> tuple[bool, str]:
|
|
79
|
+
"""
|
|
80
|
+
Valida se o slug está no formato correto.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
(is_valid, error_message)
|
|
84
|
+
"""
|
|
85
|
+
if SLUG_PATTERN.match(slug):
|
|
86
|
+
return True, ""
|
|
87
|
+
|
|
88
|
+
errors = []
|
|
89
|
+
|
|
90
|
+
if "-" in slug:
|
|
91
|
+
errors.append("contém hyphens (use underscores)")
|
|
92
|
+
if any(c.isupper() for c in slug):
|
|
93
|
+
errors.append("contém maiúsculas (use lowercase)")
|
|
94
|
+
if slug.startswith("_") or slug.endswith("_"):
|
|
95
|
+
errors.append("começa ou termina com underscore")
|
|
96
|
+
if "__" in slug:
|
|
97
|
+
errors.append("contém underscores duplos")
|
|
98
|
+
if not errors:
|
|
99
|
+
errors.append("formato inválido")
|
|
100
|
+
|
|
101
|
+
return False, ", ".join(errors)
|
|
102
|
+
|
|
103
|
+
def main():
|
|
104
|
+
# Ler input do stdin
|
|
105
|
+
try:
|
|
106
|
+
input_data = json.load(sys.stdin)
|
|
107
|
+
except json.JSONDecodeError:
|
|
108
|
+
sys.exit(0)
|
|
109
|
+
|
|
110
|
+
tool_name = input_data.get("tool_name", "")
|
|
111
|
+
tool_input = input_data.get("tool_input", {})
|
|
112
|
+
|
|
113
|
+
# Só processar Bash
|
|
114
|
+
if tool_name != "Bash":
|
|
115
|
+
sys.exit(0)
|
|
116
|
+
|
|
117
|
+
command = tool_input.get("command", "")
|
|
118
|
+
if not command:
|
|
119
|
+
sys.exit(0)
|
|
120
|
+
|
|
121
|
+
# Extrair slugs do comando
|
|
122
|
+
slug_values = extract_slug_values(command)
|
|
123
|
+
|
|
124
|
+
if not slug_values:
|
|
125
|
+
sys.exit(0)
|
|
126
|
+
|
|
127
|
+
# Validar cada slug
|
|
128
|
+
invalid_slugs = []
|
|
129
|
+
for table, slug in slug_values:
|
|
130
|
+
is_valid, error = validate_slug(slug)
|
|
131
|
+
if not is_valid:
|
|
132
|
+
invalid_slugs.append((table, slug, error))
|
|
133
|
+
|
|
134
|
+
if not invalid_slugs:
|
|
135
|
+
sys.exit(0)
|
|
136
|
+
|
|
137
|
+
# BLOQUEAR: Slug inválido detectado
|
|
138
|
+
slug_errors = "\n".join([
|
|
139
|
+
f"║ • {table}.slug = '{slug[:30]}' → {error[:30]:<30} ║"
|
|
140
|
+
for table, slug, error in invalid_slugs[:5]
|
|
141
|
+
])
|
|
142
|
+
|
|
143
|
+
# Sugerir correção
|
|
144
|
+
suggestions = "\n".join([
|
|
145
|
+
f"║ • '{slug}' → '{slug.lower().replace('-', '_')}'{'':>30} ║"
|
|
146
|
+
for _, slug, _ in invalid_slugs[:5]
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
error_message = f"""
|
|
150
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
151
|
+
║ 🛑 SLUG VALIDATION: Formato de slug inválido ║
|
|
152
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
153
|
+
║ ║
|
|
154
|
+
║ Slugs inválidos detectados: ║
|
|
155
|
+
{slug_errors}
|
|
156
|
+
║ ║
|
|
157
|
+
║ REGRA: Todos os slugs DEVEM usar snake_case ║
|
|
158
|
+
║ ║
|
|
159
|
+
║ Pattern válido: ^[a-z0-9]+(_[a-z0-9]+)*$ ║
|
|
160
|
+
║ ✅ jose_carlos_amorim ║
|
|
161
|
+
║ ✅ alan_nicolas ║
|
|
162
|
+
║ ❌ jose-carlos-amorim (hyphen) ║
|
|
163
|
+
║ ❌ JoseAmorim (camelCase) ║
|
|
164
|
+
║ ║
|
|
165
|
+
║ Sugestões de correção: ║
|
|
166
|
+
{suggestions}
|
|
167
|
+
║ ║
|
|
168
|
+
║ POR QUÊ: Slugs inconsistentes causam falhas silenciosas no frontend. ║
|
|
169
|
+
║ ║
|
|
170
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
171
|
+
"""
|
|
172
|
+
print(error_message, file=sys.stderr)
|
|
173
|
+
sys.exit(2)
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|