Skip to content
37 changes: 28 additions & 9 deletions CONTRIBUTING.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,16 @@ entry file and follows it to the full instructions.

**3. Tell it what you want to do**

Start with something simple, like:
Start with something simple. Use **the number of the issue assigned to you**:

> I want to pick up issue #123. Where do I start?
> I want to pick up issue #NUMBER. Where do I start?

> ⚠️ `#NUMBER` is a placeholder, not a command. Replace it with the real number
> — for example, `#487`. If you paste the text as-is, the AI will go looking for
> an issue that doesn't exist.
>
> No issue assigned yet? Just say so: *"I haven't picked up a task yet, can you
> help me choose one?"*

The AI will ask you two things: **which language you'd like to talk in** and
**how much experience** you have contributing to open source. From there it
Expand Down Expand Up @@ -498,14 +505,26 @@ git commit --no-verify
Automatically: **build**, **lint**, **tests**, **coverage**, **dependency
audit**, and then the **SonarCloud** analysis.

Two things that commonly cause confusion:
### 🟥 Seeing red on your PR? It's probably not your fault

This is the part that scares most people opening their first pull request. Some
jobs can fail for reasons **unrelated to your code**:

- **Your PR comes from a fork.** For security, GitHub **does not hand repository
secrets to pull requests from forks** — that's what stops someone from opening
a malicious PR just to capture keys. Jobs that depend on those values may fail
or be skipped, and that's expected.
- **The dependency audit job** may be red because of vulnerabilities in
development tooling that have no published fix yet. That predates your PR.
- **SonarCloud runs in a separate workflow**, triggered after CI — precisely to
work around the fork/secrets limitation. If it takes a while to show up, wait.

**What to do:** don't try to "fix" those failures. Check that the **build**,
**lint** and **test** jobs passed — those do depend on your code. If one of them
fails, that's worth investigating.

- **SonarCloud runs in a separate workflow**, triggered after CI. This is
necessary because PRs from forks don't receive secrets — without the split, it
would fail every time.
- The **dependency audit** job may show red because of vulnerabilities in
development tooling that have no published fix yet. If your PR didn't touch
dependencies, that's **not** your fault.
When in doubt, **ask in the PR**. Nobody will mind, and the answer is very likely
"you can ignore that one, it's on our side".

Full details in
[`docs/04 - processo/ci-e-validacao.md`](docs/04%20-%20processo/ci-e-validacao.md)
Expand Down
38 changes: 30 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,16 @@ de porta e é direcionada para as instruções completas.

**3. Diga o que você quer fazer**

Comece por algo simples, como:
Comece por algo simples. Use **o número da issue que foi atribuída a você**:

> Quero pegar a issue #123. Por onde começo?
> Quero pegar a issue #NÚMERO. Por onde começo?

> ⚠️ `#NÚMERO` é um espaço a preencher, não um comando. Troque pelo número real
> — por exemplo, `#487`. Se você colar o texto como está, a IA vai procurar uma
> issue que não existe.
>
> Ainda não tem issue atribuída? Diga isso mesmo: *"ainda não peguei nenhuma
> tarefa, pode me ajudar a escolher?"*

A IA vai perguntar duas coisas: **em qual idioma você prefere conversar** e
**qual é a sua experiência** com contribuição em open source. A partir daí ela
Expand Down Expand Up @@ -476,13 +483,28 @@ git commit --no-verify
Automaticamente: **build**, **lint**, **testes**, **cobertura**, **auditoria de
dependências** e, em seguida, a análise do **SonarCloud**.

Pontos que costumam gerar dúvida:
### 🟥 Viu vermelho no seu PR? Provavelmente não é culpa sua

Esta é a parte que mais assusta quem abre o primeiro pull request. Alguns jobs
podem falhar por motivos **que não têm relação com o seu código**:

- **Seu PR vem de um fork.** Por segurança, o GitHub **não entrega os secrets do
repositório** para pull requests vindos de forks — é o que impede que alguém
abra um PR malicioso só para capturar chaves. Jobs que dependem desses valores
podem falhar ou ser pulados, e isso é esperado.
- **O job de auditoria de dependências** pode estar vermelho por vulnerabilidades
em ferramentas de desenvolvimento que ainda não têm correção publicada. É
anterior ao seu PR.
- **O SonarCloud roda num workflow separado**, disparado depois da CI —
justamente para contornar a limitação de secrets em forks. Se ele demorar a
aparecer, aguarde.

**O que fazer:** não tente "consertar" esses erros. Confira se os jobs de
**build**, **lint** e **testes** passaram — esses sim dependem do seu código. Se
algum deles falhar, aí vale investigar.

- O **SonarCloud roda num workflow separado**, disparado após a CI. É necessário
porque PRs vindos de forks não recebem secrets — sem isso, ele falharia sempre.
- O job de **auditoria de dependências** pode aparecer vermelho por
vulnerabilidades em ferramentas de desenvolvimento que ainda não têm correção
publicada. Se o seu PR não mexeu em dependências, isso **não** é culpa dele.
Na dúvida, **comente no PR perguntando**. Ninguém vai achar ruim, e é bem
provável que a resposta seja "pode ignorar, é do nosso lado".

O detalhamento está em
[`docs/04 - processo/ci-e-validacao.md`](docs/04%20-%20processo/ci-e-validacao.md).
Expand Down
20 changes: 20 additions & 0 deletions docs/05 - contribuicao/IA-GUIA.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ Depois de identificar a issue, **leia o conteúdo dela**. Se a ferramenta tiver
acesso à internet ou ao `gh`, busque direto. Se não tiver, peça para a pessoa
colar a descrição.

### ⚠️ Quando o número da issue não resolve

Se a busca não encontrar a issue, ou se o número parecer um exemplo copiado da
documentação — `#123`, `#NÚMERO`, `#NUMBER`, `#000` —, **não insista na busca e
não peça a descrição como se fosse problema de acesso.** Diga com clareza o que
aconteceu:

> Não encontrei a issue #123 no repositório. Esse número aparece como exemplo no
> guia de contribuição — se você copiou de lá, me diga o número real da issue que
> te atribuíram.
>
> Se ainda não tem uma tarefa atribuída, posso te ajudar a escolher.

Isso poupa a pessoa de achar que ela errou alguma configuração. O caso mais comum
é justamente esse: alguém colando o exemplo da documentação literalmente.

> 💡 O mesmo vale para qualquer valor de exemplo que apareça na documentação —
> `SEU-USUARIO`, `feat/nome-da-sua-feature`, `<pacote>`. São espaços a preencher.
> Ao encontrar um deles literalmente, pergunte o valor real em vez de tentar usar.

> ⚠️ Várias issues deste projeto descrevem um estado que já mudou. Antes de
> agir, **confira contra o código atual**. Se a issue disser que algo não existe
> e você encontrar que existe, avise a pessoa.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ProjectForm } from '@/components/Dashboard/TeamProject';

export default function ProjectEditPage({
export default async function ProjectEditPage({
params,
}: {
params: { id: string };
params: Promise<{ id: string }>;
}) {
return <ProjectForm editIdProp={params.id} />;
const { id } = await params;

return <ProjectForm editIdProp={id} />;
}
8 changes: 5 additions & 3 deletions src/app/(private)/dashboard/team-projects/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { ProjectDetails } from '@/components/Dashboard/TeamProject';

export default function ProjectDetailsPage({
export default async function ProjectDetailsPage({
params,
}: {
params: { id: string };
params: Promise<{ id: string }>;
}) {
return <ProjectDetails projectId={params.id} />;
const { id } = await params;

return <ProjectDetails projectId={id} />;
}
3 changes: 2 additions & 1 deletion src/app/api/feedback/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { logger } from '@/lib/logger';

export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
Expand Down
8 changes: 5 additions & 3 deletions src/app/api/project-skill/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ const idSchema = z.string().min(25, 'ID inválido').max(36, 'ID inválido');

export async function GET(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const auth = await checkAuth();
if (!auth.authorized) return auth.response;

const idParse = idSchema.safeParse(context.params.id);
const idParse = idSchema.safeParse(params.id);
if (!idParse.success) {
return buildResponse({
success: false,
Expand Down Expand Up @@ -54,8 +55,9 @@ export async function GET(

export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const auth = await checkAuth();
if (!auth.authorized) return auth.response;

Expand Down
9 changes: 6 additions & 3 deletions src/app/api/project-stack/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ const updatePercentageSchema = z.object({

export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const auth = await checkAuth();
if (!auth.authorized) return auth.response;

Expand Down Expand Up @@ -52,8 +53,9 @@ export async function GET(

export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const auth = await checkAuth();
if (!auth.authorized) return auth.response;

Expand Down Expand Up @@ -121,8 +123,9 @@ export async function DELETE(

export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const auth = await checkAuth();
if (!auth.authorized) return auth.response;

Expand Down
9 changes: 6 additions & 3 deletions src/app/api/stack-taken/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const idSchema = z.string().min(1, 'ID inválido.');

export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
Expand Down Expand Up @@ -69,8 +70,9 @@ export async function GET(

export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
Expand Down Expand Up @@ -133,8 +135,9 @@ export async function PUT(

export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
Expand Down
36 changes: 30 additions & 6 deletions src/app/api/team-project/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,15 @@ async function applyProjectStackChanges(

export async function GET(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
if (!authorized || !session) return response;

const { id } = context.params;
const { id } = params;

const idParse = idSchema.safeParse(id);

Expand Down Expand Up @@ -228,14 +229,15 @@ export async function GET(

export async function PUT(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
if (!authorized || !session) return response;

const idParse = idSchema.safeParse(context.params.id);
const idParse = idSchema.safeParse(params.id);

if (!idParse.success) {
return buildResponse({
Expand Down Expand Up @@ -296,7 +298,28 @@ export async function PUT(
github,
} = parse.data;

// ------------------------------------------------------------------
// Bloqueio de edição estrutural — TEMPORARIAMENTE DESATIVADO
//
// A regra impedia alterar o projeto depois que alguém assumisse uma stack.
// Ela está desligada até a plataforma entrar em operação de fato, por dois
// motivos:
//
// 1. O escopo estava largo demais: `hasStructuralProjectChanges` considera
// nome, prazo e valor total como "estrutura", travando edições que não
// têm relação com a composição da equipe.
//
// 2. A comparação de prazo gera falso positivo. O formulário carrega a data
// como `deadline.split('T')[0]`, perdendo a hora; o banco guarda o
// timestamp completo. Os dois nunca coincidem, então abrir a tela e
// salvar sem mudar nada já disparava o bloqueio.
//
// Ao reativar, corrija os dois pontos antes.
// ------------------------------------------------------------------
const BLOQUEAR_EDICAO_APOS_FORMACAO_DE_EQUIPE = false;

if (
BLOQUEAR_EDICAO_APOS_FORMACAO_DE_EQUIPE &&
existing.stacksTaken.length > 0 &&
hasStructuralProjectChanges(existing, parse.data)
) {
Expand Down Expand Up @@ -350,14 +373,15 @@ export async function PUT(

export async function DELETE(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response, session } = await checkAuth({
allowedRoles: ROLE_GROUPS.ALL,
});
if (!authorized || !session) return response;

const idParse = idSchema.safeParse(context.params.id);
const idParse = idSchema.safeParse(params.id);

if (!idParse.success) {
return buildResponse({
Expand Down
15 changes: 6 additions & 9 deletions src/app/api/user-admin/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ const updateUserSchema = z.object({

export async function GET(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response } = await checkAuth({ requireAdmin: true });
if (!authorized) return response;

const params = await context.params;

const idParse = idSchema.safeParse(params.id);
if (!idParse.success) {
return buildResponse({
Expand Down Expand Up @@ -72,13 +71,12 @@ export async function GET(

export async function PUT(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response } = await checkAuth({ requireAdmin: true });
if (!authorized) return response;

const params = await context.params;

const idParse = idSchema.safeParse(params.id);
if (!idParse.success) {
return buildResponse({
Expand Down Expand Up @@ -182,13 +180,12 @@ export async function PUT(

export async function DELETE(
request: NextRequest,
context: { params: { id: string } }
context: { params: Promise<{ id: string }> }
) {
const params = await context.params;
const { authorized, response } = await checkAuth({ requireAdmin: true });
if (!authorized) return response;

const params = await context.params;

const idParse = idSchema.safeParse(params.id);
if (!idParse.success) {
return buildResponse({
Expand Down
Loading
Loading