@luanpdd/kit-mcp 1.34.0 → 1.36.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.md +1 -1
- package/bin/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -0
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -0
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +5 -2
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -0
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: dependency-check
|
|
3
|
-
stage: pre-execute
|
|
4
|
-
blocking: true
|
|
5
|
-
description: Verify that key-link references between plans of prior waves resolve before launching the next wave.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Dependency check gate
|
|
9
|
-
|
|
10
|
-
**When to run:** before launching wave N+1 in `execute-phase`, for each plan in wave N+1
|
|
11
|
-
that declares `key_links` referencing artifacts of wave N.
|
|
12
|
-
|
|
13
|
-
## Check
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
node "./.claude/framework/bin/tools.cjs" verify key-links {phase_dir}/{plan}-PLAN.md
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
For each link:
|
|
20
|
-
|
|
21
|
-
- if pattern resolves to an existing file → ok
|
|
22
|
-
- if not found → fail with `{plan, via, from, pattern}`
|
|
23
|
-
|
|
24
|
-
## Verdict
|
|
25
|
-
|
|
26
|
-
- **passed** — proceed to wave N+1
|
|
27
|
-
- **block** — present cross-plan connection gap table; ask `investigate | continue (risky) | abort`
|
|
28
|
-
|
|
29
|
-
## Notes
|
|
30
|
-
|
|
31
|
-
Key-links are the explicit contract between waves. A missing key-link means wave N
|
|
32
|
-
shipped something with a different shape than wave N+1 expected. Catching this
|
|
33
|
-
*before* spawning N+1 executors saves a full wave of failed work.
|
|
1
|
+
---
|
|
2
|
+
id: dependency-check
|
|
3
|
+
stage: pre-execute
|
|
4
|
+
blocking: true
|
|
5
|
+
description: Verify that key-link references between plans of prior waves resolve before launching the next wave.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Dependency check gate
|
|
9
|
+
|
|
10
|
+
**When to run:** before launching wave N+1 in `execute-phase`, for each plan in wave N+1
|
|
11
|
+
that declares `key_links` referencing artifacts of wave N.
|
|
12
|
+
|
|
13
|
+
## Check
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
node "./.claude/framework/bin/tools.cjs" verify key-links {phase_dir}/{plan}-PLAN.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For each link:
|
|
20
|
+
|
|
21
|
+
- if pattern resolves to an existing file → ok
|
|
22
|
+
- if not found → fail with `{plan, via, from, pattern}`
|
|
23
|
+
|
|
24
|
+
## Verdict
|
|
25
|
+
|
|
26
|
+
- **passed** — proceed to wave N+1
|
|
27
|
+
- **block** — present cross-plan connection gap table; ask `investigate | continue (risky) | abort`
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
Key-links are the explicit contract between waves. A missing key-link means wave N
|
|
32
|
+
shipped something with a different shape than wave N+1 expected. Catching this
|
|
33
|
+
*before* spawning N+1 executors saves a full wave of failed work.
|
|
@@ -1,179 +1,179 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: dept-cycle-prevention
|
|
3
|
-
stage: pre-verify
|
|
4
|
-
blocking: true
|
|
5
|
-
description: Detecta tabela departments (ou similar) com parent_id FK self-referencial mas sem trigger anti-cycle. Loop circular em dept hierarchy esgota connection pool via WITH RECURSIVE infinito. Skip se projeto não tem supabase/migrations/.
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Department Cycle Prevention gate
|
|
9
|
-
|
|
10
|
-
**When to run:** pre-verify (blocking — anti-pitfall P0 multi-tenant hierarchy).
|
|
11
|
-
|
|
12
|
-
## Check
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
#!/usr/bin/env bash
|
|
16
|
-
# PT-BR: detecta departments com parent_id self-referencial sem trigger anti-cycle.
|
|
17
|
-
# Anti-pitfall #3 multi-tenant: dept hierarchy com loop circular (A.parent=B, B.parent=A) → WITH RECURSIVE infinito → connection pool exhaustion.
|
|
18
|
-
# Bash 3.2-portable (macOS default).
|
|
19
|
-
set -e
|
|
20
|
-
|
|
21
|
-
MIGRATIONS_DIR="supabase/migrations"
|
|
22
|
-
|
|
23
|
-
if [ ! -d "$MIGRATIONS_DIR" ]; then
|
|
24
|
-
echo "INFO: $MIGRATIONS_DIR não existe — projeto não usa Supabase migrations. Gate skipped."
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
# PT-BR: nomes comuns de tabelas hierárquicas (departments + variantes)
|
|
29
|
-
HIERARCHY_TABLE_PATTERNS="departments|teams|groups|categories|nodes|tree"
|
|
30
|
-
|
|
31
|
-
VIOLATIONS=0
|
|
32
|
-
VIOLATIONS_DETAIL=""
|
|
33
|
-
|
|
34
|
-
# PT-BR: iterar migrations em ordem cronológica
|
|
35
|
-
MIGRATION_FILES=$(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null | sort)
|
|
36
|
-
|
|
37
|
-
if [ -z "$MIGRATION_FILES" ]; then
|
|
38
|
-
echo "INFO: nenhum arquivo .sql em $MIGRATIONS_DIR — gate skipped."
|
|
39
|
-
exit 0
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# PT-BR: encontrar tabelas com parent_id auto-referencial (heurística: parent_id + REFERENCES <self>)
|
|
43
|
-
SELF_REF_TABLES=""
|
|
44
|
-
|
|
45
|
-
for f in $MIGRATION_FILES; do
|
|
46
|
-
# PT-BR: detecta padrão "parent_id ... references public.<table>(id)" onde <table> bate com nome da tabela sendo criada
|
|
47
|
-
# (case-insensitive, multi-line awk porque DDL pode ter quebras de linha)
|
|
48
|
-
TABLES_IN_FILE=$(awk '
|
|
49
|
-
BEGIN { in_create = 0; current_table = "" }
|
|
50
|
-
/create[ \t]+table[ \t]+(if[ \t]+not[ \t]+exists[ \t]+)?[a-z_]+\.[a-z_]+/ {
|
|
51
|
-
in_create = 1
|
|
52
|
-
match($0, /create[ \t]+table[ \t]+(if[ \t]+not[ \t]+exists[ \t]+)?([a-z_]+\.[a-z_]+)/)
|
|
53
|
-
if (RSTART > 0) {
|
|
54
|
-
# extrair só o último match group (table name)
|
|
55
|
-
s = substr($0, RSTART, RLENGTH)
|
|
56
|
-
n = split(s, parts, /[ \t]+/)
|
|
57
|
-
current_table = parts[n]
|
|
58
|
-
}
|
|
59
|
-
next
|
|
60
|
-
}
|
|
61
|
-
in_create && /parent_id[ \t]+uuid[ \t]+references[ \t]+([a-z_]+\.[a-z_]+)/ {
|
|
62
|
-
match($0, /references[ \t]+([a-z_]+\.[a-z_]+)/)
|
|
63
|
-
if (RSTART > 0) {
|
|
64
|
-
ref_table = substr($0, RSTART + 11, RLENGTH - 11)
|
|
65
|
-
gsub(/[ \t]/, "", ref_table)
|
|
66
|
-
if (ref_table == current_table) {
|
|
67
|
-
print current_table
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/;[ \t]*$/ && in_create { in_create = 0; current_table = "" }
|
|
72
|
-
' "$f" 2>/dev/null)
|
|
73
|
-
|
|
74
|
-
if [ -n "$TABLES_IN_FILE" ]; then
|
|
75
|
-
SELF_REF_TABLES="$SELF_REF_TABLES
|
|
76
|
-
$TABLES_IN_FILE"
|
|
77
|
-
fi
|
|
78
|
-
done
|
|
79
|
-
|
|
80
|
-
# PT-BR: filtrar entries vazias
|
|
81
|
-
SELF_REF_TABLES=$(echo "$SELF_REF_TABLES" | grep -v "^$" | sort -u)
|
|
82
|
-
|
|
83
|
-
if [ -z "$SELF_REF_TABLES" ]; then
|
|
84
|
-
echo "INFO: nenhuma tabela com parent_id self-referencial detectada — gate skipped."
|
|
85
|
-
exit 0
|
|
86
|
-
fi
|
|
87
|
-
|
|
88
|
-
# PT-BR: para cada tabela self-ref, verificar se há trigger ou function anti-cycle
|
|
89
|
-
for table in $SELF_REF_TABLES; do
|
|
90
|
-
# PT-BR: extrair só nome da tabela (sem schema)
|
|
91
|
-
table_name=$(echo "$table" | awk -F. '{print $2}')
|
|
92
|
-
|
|
93
|
-
# PT-BR: heurística: procurar trigger com nome contendo cycle/loop/recursion + tabela
|
|
94
|
-
CYCLE_GUARD_FOUND=0
|
|
95
|
-
for f in $MIGRATION_FILES; do
|
|
96
|
-
if grep -iE "(create[ \t]+(or[ \t]+replace[ \t]+)?(function|trigger))[ \t]+[a-z_]*(cycle|loop|recurs|hierarchy_check|anti_cycle)" "$f" 2>/dev/null \
|
|
97
|
-
| grep -iqE "$table_name|$(echo "$table" | tr '.' '_')"; then
|
|
98
|
-
CYCLE_GUARD_FOUND=1
|
|
99
|
-
break
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# PT-BR: pattern alternativo: trigger genérico que menciona a tabela e WITH RECURSIVE em corpo
|
|
103
|
-
if grep -iqE "create[ \t]+trigger.*on[ \t]+$table" "$f" 2>/dev/null \
|
|
104
|
-
&& grep -iqE "with[ \t]+recursive.*parent_id" "$f" 2>/dev/null; then
|
|
105
|
-
CYCLE_GUARD_FOUND=1
|
|
106
|
-
break
|
|
107
|
-
fi
|
|
108
|
-
done
|
|
109
|
-
|
|
110
|
-
if [ "$CYCLE_GUARD_FOUND" -eq 0 ]; then
|
|
111
|
-
VIOLATIONS=$((VIOLATIONS + 1))
|
|
112
|
-
VIOLATIONS_DETAIL="${VIOLATIONS_DETAIL}
|
|
113
|
-
Tabela '$table' tem parent_id self-referencial mas sem trigger anti-cycle detectado"
|
|
114
|
-
fi
|
|
115
|
-
done
|
|
116
|
-
|
|
117
|
-
if [ "$VIOLATIONS" -eq 0 ]; then
|
|
118
|
-
echo "PASS: todas as tabelas hierárquicas têm proteção anti-cycle."
|
|
119
|
-
exit 0
|
|
120
|
-
else
|
|
121
|
-
echo "FAIL: $VIOLATIONS tabela(s) hierárquica(s) sem trigger anti-cycle:$VIOLATIONS_DETAIL"
|
|
122
|
-
echo ""
|
|
123
|
-
echo "Fix: adicionar trigger BEFORE INSERT/UPDATE que detecta cycle via WITH RECURSIVE:"
|
|
124
|
-
cat << 'EOF'
|
|
125
|
-
|
|
126
|
-
create or replace function private.check_no_dept_cycle()
|
|
127
|
-
returns trigger
|
|
128
|
-
language plpgsql
|
|
129
|
-
security invoker
|
|
130
|
-
set search_path = ''
|
|
131
|
-
as $$
|
|
132
|
-
declare
|
|
133
|
-
cycle_detected boolean;
|
|
134
|
-
begin
|
|
135
|
-
if new.parent_id is null then return new; end if;
|
|
136
|
-
|
|
137
|
-
with recursive ancestors as (
|
|
138
|
-
select id, parent_id, 1 as depth from public.departments where id = new.parent_id
|
|
139
|
-
union all
|
|
140
|
-
select d.id, d.parent_id, a.depth + 1
|
|
141
|
-
from public.departments d
|
|
142
|
-
join ancestors a on d.id = a.parent_id
|
|
143
|
-
where a.depth < 10 -- max 10 níveis
|
|
144
|
-
)
|
|
145
|
-
select exists (select 1 from ancestors where id = new.id) into cycle_detected;
|
|
146
|
-
|
|
147
|
-
if cycle_detected then
|
|
148
|
-
raise exception 'department hierarchy cycle detected: % cannot be parent of %',
|
|
149
|
-
new.parent_id, new.id;
|
|
150
|
-
end if;
|
|
151
|
-
|
|
152
|
-
return new;
|
|
153
|
-
end;
|
|
154
|
-
$$;
|
|
155
|
-
|
|
156
|
-
create trigger check_no_dept_cycle_trigger
|
|
157
|
-
before insert or update of parent_id on public.departments
|
|
158
|
-
for each row execute function private.check_no_dept_cycle();
|
|
159
|
-
|
|
160
|
-
EOF
|
|
161
|
-
echo "Ref: kit/skills/_shared-multi-tenant/glossary.md (Department Hierarchy)"
|
|
162
|
-
exit 1
|
|
163
|
-
fi
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Verdict
|
|
167
|
-
|
|
168
|
-
- **passed** — todas tabelas hierárquicas têm proteção anti-cycle → continuar
|
|
169
|
-
- **block** — apresentar tabelas violadoras + DDL pronto do trigger anti-cycle
|
|
170
|
-
|
|
171
|
-
## Notes
|
|
172
|
-
|
|
173
|
-
Detecção é heurística baseada em naming (`parent_id` + `references <self_table>`). Pode produzir:
|
|
174
|
-
- **Falso-negativo** se a coluna se chamar `parent` em vez de `parent_id`, ou referenciar tabela diferente (não self-ref)
|
|
175
|
-
- **Falso-positivo** se o nome do trigger não contém palavras-chave (`cycle`, `loop`, `recurs`, `anti_cycle`) — neste caso, renomear o trigger ou estender a allowlist do gate
|
|
176
|
-
|
|
177
|
-
Tabelas hierárquicas tipicamente afetadas: `departments`, `teams`, `groups`, `categories`, `nodes`, `tree`.
|
|
178
|
-
|
|
179
|
-
Limit recursivo padrão sugerido: 10 níveis. Hierarquia mais profunda que isso indica modelagem questionável (Linear/Notion suportam até 5 níveis).
|
|
1
|
+
---
|
|
2
|
+
id: dept-cycle-prevention
|
|
3
|
+
stage: pre-verify
|
|
4
|
+
blocking: true
|
|
5
|
+
description: Detecta tabela departments (ou similar) com parent_id FK self-referencial mas sem trigger anti-cycle. Loop circular em dept hierarchy esgota connection pool via WITH RECURSIVE infinito. Skip se projeto não tem supabase/migrations/.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Department Cycle Prevention gate
|
|
9
|
+
|
|
10
|
+
**When to run:** pre-verify (blocking — anti-pitfall P0 multi-tenant hierarchy).
|
|
11
|
+
|
|
12
|
+
## Check
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
#!/usr/bin/env bash
|
|
16
|
+
# PT-BR: detecta departments com parent_id self-referencial sem trigger anti-cycle.
|
|
17
|
+
# Anti-pitfall #3 multi-tenant: dept hierarchy com loop circular (A.parent=B, B.parent=A) → WITH RECURSIVE infinito → connection pool exhaustion.
|
|
18
|
+
# Bash 3.2-portable (macOS default).
|
|
19
|
+
set -e
|
|
20
|
+
|
|
21
|
+
MIGRATIONS_DIR="supabase/migrations"
|
|
22
|
+
|
|
23
|
+
if [ ! -d "$MIGRATIONS_DIR" ]; then
|
|
24
|
+
echo "INFO: $MIGRATIONS_DIR não existe — projeto não usa Supabase migrations. Gate skipped."
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# PT-BR: nomes comuns de tabelas hierárquicas (departments + variantes)
|
|
29
|
+
HIERARCHY_TABLE_PATTERNS="departments|teams|groups|categories|nodes|tree"
|
|
30
|
+
|
|
31
|
+
VIOLATIONS=0
|
|
32
|
+
VIOLATIONS_DETAIL=""
|
|
33
|
+
|
|
34
|
+
# PT-BR: iterar migrations em ordem cronológica
|
|
35
|
+
MIGRATION_FILES=$(ls "$MIGRATIONS_DIR"/*.sql 2>/dev/null | sort)
|
|
36
|
+
|
|
37
|
+
if [ -z "$MIGRATION_FILES" ]; then
|
|
38
|
+
echo "INFO: nenhum arquivo .sql em $MIGRATIONS_DIR — gate skipped."
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# PT-BR: encontrar tabelas com parent_id auto-referencial (heurística: parent_id + REFERENCES <self>)
|
|
43
|
+
SELF_REF_TABLES=""
|
|
44
|
+
|
|
45
|
+
for f in $MIGRATION_FILES; do
|
|
46
|
+
# PT-BR: detecta padrão "parent_id ... references public.<table>(id)" onde <table> bate com nome da tabela sendo criada
|
|
47
|
+
# (case-insensitive, multi-line awk porque DDL pode ter quebras de linha)
|
|
48
|
+
TABLES_IN_FILE=$(awk '
|
|
49
|
+
BEGIN { in_create = 0; current_table = "" }
|
|
50
|
+
/create[ \t]+table[ \t]+(if[ \t]+not[ \t]+exists[ \t]+)?[a-z_]+\.[a-z_]+/ {
|
|
51
|
+
in_create = 1
|
|
52
|
+
match($0, /create[ \t]+table[ \t]+(if[ \t]+not[ \t]+exists[ \t]+)?([a-z_]+\.[a-z_]+)/)
|
|
53
|
+
if (RSTART > 0) {
|
|
54
|
+
# extrair só o último match group (table name)
|
|
55
|
+
s = substr($0, RSTART, RLENGTH)
|
|
56
|
+
n = split(s, parts, /[ \t]+/)
|
|
57
|
+
current_table = parts[n]
|
|
58
|
+
}
|
|
59
|
+
next
|
|
60
|
+
}
|
|
61
|
+
in_create && /parent_id[ \t]+uuid[ \t]+references[ \t]+([a-z_]+\.[a-z_]+)/ {
|
|
62
|
+
match($0, /references[ \t]+([a-z_]+\.[a-z_]+)/)
|
|
63
|
+
if (RSTART > 0) {
|
|
64
|
+
ref_table = substr($0, RSTART + 11, RLENGTH - 11)
|
|
65
|
+
gsub(/[ \t]/, "", ref_table)
|
|
66
|
+
if (ref_table == current_table) {
|
|
67
|
+
print current_table
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/;[ \t]*$/ && in_create { in_create = 0; current_table = "" }
|
|
72
|
+
' "$f" 2>/dev/null)
|
|
73
|
+
|
|
74
|
+
if [ -n "$TABLES_IN_FILE" ]; then
|
|
75
|
+
SELF_REF_TABLES="$SELF_REF_TABLES
|
|
76
|
+
$TABLES_IN_FILE"
|
|
77
|
+
fi
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
# PT-BR: filtrar entries vazias
|
|
81
|
+
SELF_REF_TABLES=$(echo "$SELF_REF_TABLES" | grep -v "^$" | sort -u)
|
|
82
|
+
|
|
83
|
+
if [ -z "$SELF_REF_TABLES" ]; then
|
|
84
|
+
echo "INFO: nenhuma tabela com parent_id self-referencial detectada — gate skipped."
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# PT-BR: para cada tabela self-ref, verificar se há trigger ou function anti-cycle
|
|
89
|
+
for table in $SELF_REF_TABLES; do
|
|
90
|
+
# PT-BR: extrair só nome da tabela (sem schema)
|
|
91
|
+
table_name=$(echo "$table" | awk -F. '{print $2}')
|
|
92
|
+
|
|
93
|
+
# PT-BR: heurística: procurar trigger com nome contendo cycle/loop/recursion + tabela
|
|
94
|
+
CYCLE_GUARD_FOUND=0
|
|
95
|
+
for f in $MIGRATION_FILES; do
|
|
96
|
+
if grep -iE "(create[ \t]+(or[ \t]+replace[ \t]+)?(function|trigger))[ \t]+[a-z_]*(cycle|loop|recurs|hierarchy_check|anti_cycle)" "$f" 2>/dev/null \
|
|
97
|
+
| grep -iqE "$table_name|$(echo "$table" | tr '.' '_')"; then
|
|
98
|
+
CYCLE_GUARD_FOUND=1
|
|
99
|
+
break
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# PT-BR: pattern alternativo: trigger genérico que menciona a tabela e WITH RECURSIVE em corpo
|
|
103
|
+
if grep -iqE "create[ \t]+trigger.*on[ \t]+$table" "$f" 2>/dev/null \
|
|
104
|
+
&& grep -iqE "with[ \t]+recursive.*parent_id" "$f" 2>/dev/null; then
|
|
105
|
+
CYCLE_GUARD_FOUND=1
|
|
106
|
+
break
|
|
107
|
+
fi
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
if [ "$CYCLE_GUARD_FOUND" -eq 0 ]; then
|
|
111
|
+
VIOLATIONS=$((VIOLATIONS + 1))
|
|
112
|
+
VIOLATIONS_DETAIL="${VIOLATIONS_DETAIL}
|
|
113
|
+
Tabela '$table' tem parent_id self-referencial mas sem trigger anti-cycle detectado"
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
if [ "$VIOLATIONS" -eq 0 ]; then
|
|
118
|
+
echo "PASS: todas as tabelas hierárquicas têm proteção anti-cycle."
|
|
119
|
+
exit 0
|
|
120
|
+
else
|
|
121
|
+
echo "FAIL: $VIOLATIONS tabela(s) hierárquica(s) sem trigger anti-cycle:$VIOLATIONS_DETAIL"
|
|
122
|
+
echo ""
|
|
123
|
+
echo "Fix: adicionar trigger BEFORE INSERT/UPDATE que detecta cycle via WITH RECURSIVE:"
|
|
124
|
+
cat << 'EOF'
|
|
125
|
+
|
|
126
|
+
create or replace function private.check_no_dept_cycle()
|
|
127
|
+
returns trigger
|
|
128
|
+
language plpgsql
|
|
129
|
+
security invoker
|
|
130
|
+
set search_path = ''
|
|
131
|
+
as $$
|
|
132
|
+
declare
|
|
133
|
+
cycle_detected boolean;
|
|
134
|
+
begin
|
|
135
|
+
if new.parent_id is null then return new; end if;
|
|
136
|
+
|
|
137
|
+
with recursive ancestors as (
|
|
138
|
+
select id, parent_id, 1 as depth from public.departments where id = new.parent_id
|
|
139
|
+
union all
|
|
140
|
+
select d.id, d.parent_id, a.depth + 1
|
|
141
|
+
from public.departments d
|
|
142
|
+
join ancestors a on d.id = a.parent_id
|
|
143
|
+
where a.depth < 10 -- max 10 níveis
|
|
144
|
+
)
|
|
145
|
+
select exists (select 1 from ancestors where id = new.id) into cycle_detected;
|
|
146
|
+
|
|
147
|
+
if cycle_detected then
|
|
148
|
+
raise exception 'department hierarchy cycle detected: % cannot be parent of %',
|
|
149
|
+
new.parent_id, new.id;
|
|
150
|
+
end if;
|
|
151
|
+
|
|
152
|
+
return new;
|
|
153
|
+
end;
|
|
154
|
+
$$;
|
|
155
|
+
|
|
156
|
+
create trigger check_no_dept_cycle_trigger
|
|
157
|
+
before insert or update of parent_id on public.departments
|
|
158
|
+
for each row execute function private.check_no_dept_cycle();
|
|
159
|
+
|
|
160
|
+
EOF
|
|
161
|
+
echo "Ref: kit/skills/_shared-multi-tenant/glossary.md (Department Hierarchy)"
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Verdict
|
|
167
|
+
|
|
168
|
+
- **passed** — todas tabelas hierárquicas têm proteção anti-cycle → continuar
|
|
169
|
+
- **block** — apresentar tabelas violadoras + DDL pronto do trigger anti-cycle
|
|
170
|
+
|
|
171
|
+
## Notes
|
|
172
|
+
|
|
173
|
+
Detecção é heurística baseada em naming (`parent_id` + `references <self_table>`). Pode produzir:
|
|
174
|
+
- **Falso-negativo** se a coluna se chamar `parent` em vez de `parent_id`, ou referenciar tabela diferente (não self-ref)
|
|
175
|
+
- **Falso-positivo** se o nome do trigger não contém palavras-chave (`cycle`, `loop`, `recurs`, `anti_cycle`) — neste caso, renomear o trigger ou estender a allowlist do gate
|
|
176
|
+
|
|
177
|
+
Tabelas hierárquicas tipicamente afetadas: `departments`, `teams`, `groups`, `categories`, `nodes`, `tree`.
|
|
178
|
+
|
|
179
|
+
Limit recursivo padrão sugerido: 10 níveis. Hierarquia mais profunda que isso indica modelagem questionável (Linear/Notion suportam até 5 níveis).
|