mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:53:05 +00:00
Merge pull request #431 from fsolla/worktree-plan-command
worktree: comando plan — worktree dedicado ao /plan-issue
This commit is contained in:
commit
819a015911
9 changed files with 116 additions and 11 deletions
|
|
@ -11,6 +11,9 @@
|
|||
# Uso (terminal interativo):
|
||||
# worktree next [--go] cria/reutiliza o worktree da próxima Issue claimável;
|
||||
# com --go, cd para dentro dele
|
||||
# worktree plan [--go] cria/reutiliza o worktree de planejamento do /plan-issue
|
||||
# (branch fixo plans/plan-issue, sem vínculo com a fila);
|
||||
# com --go, cd para dentro dele
|
||||
# worktree kill [--force]
|
||||
#
|
||||
# Read-only no GitHub — claim continua sendo `pnpm agent:claim`.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Parallel agents must not fight over port 3000, the shared `teqo_test`, or a seco
|
|||
|
||||
- `slot` = numeric part of the issue code (`C15` → 15, `B164` → 164; hash fallback without digits; bumped on collision among live worktrees).
|
||||
- Dev server port `3100+slot`; databases `teqo_wt<slot>` (dev) and `teqo_wt<slot>_test` (tests), created on the **one shared container** with the full migration chain applied (one-time, ~30 s each).
|
||||
- Writes `.env.local` (DATABASE_URL, PORT, NEXT_PUBLIC_SITE_URL, PLAYWRIGHT_BASE_URL, PAYLOAD_SECRET + Blob/VAPID copied from the main repo) and `.env.test.local` (test DATABASE_URL). Both carry the marker `# teqo worktree env (generated by pnpm worktree next)`; **manual env files are never overwritten** — remove them and re-run `pnpm worktree next` to regenerate.
|
||||
- Writes `.env.local` (DATABASE_URL, PORT, NEXT_PUBLIC_SITE_URL, PLAYWRIGHT_BASE_URL, PAYLOAD_SECRET + Blob/VAPID copied from the main repo) and `.env.test.local` (test DATABASE_URL). Both carry the marker `# teqo worktree env (generated by pnpm worktree next)`; **manual env files are never overwritten** — remove them and re-run `pnpm worktree next` (or `pnpm worktree plan` for the /plan-issue worktree) to regenerate.
|
||||
- `--no-migrate` skips the migration runs; `pnpm worktree kill` drops the worktree's generated databases (best-effort, `DROP DATABASE … WITH (FORCE)`).
|
||||
- Without Docker (Cursor Cloud) it degrades to the shared `teqo`/`teqo_test` with a warning and no port override.
|
||||
- Pure derivation: `scripts/lib/worktree-env.mjs` (unit-tested in `tests/unit/worktree.unit.spec.ts`).
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ A lógica determinística (fila → código+slug → branch → worktree) vive e
|
|||
|
||||
```bash
|
||||
pnpm worktree next [--go] [--no-migrate] # cria worktree de origin/main + provisiona o ambiente
|
||||
pnpm worktree plan [--go] [--no-migrate] # worktree de PLANEJAMENTO (/plan-issue), branch fixo plans/plan-issue
|
||||
pnpm worktree kill [--force] # destrói o worktree atual + remove seus bancos gerados
|
||||
```
|
||||
|
||||
No opencode, isso é o comando **`/worktree next`** / **`/worktree kill`** (`.opencode/commands/worktree.md`), que só repassa `$ARGUMENTS` para o script.
|
||||
|
||||
`plan` é o primo do `next`: worktree de **planejamento** (branch fixo `plans/plan-issue`) para rodar a skill `/plan-issue` sem ocupar o main. Não é nomeado por Issue nenhuma — proposital, para um `next` posterior da próxima Issue nunca colidir em branch nem em slot. O fluxo desta skill (fila → próxima Issue → implementação) é só `next`.
|
||||
|
||||
No terminal interativo, para `--go` trocar de diretório de verdade, use a função `worktree()` de **`.agents/shell/worktree.sh`** (uma linha de `source` no profile): o script imprime `cd <dir>` e a função o aplica no shell que te chamou — node não consegue mudar o cwd do shell pai.
|
||||
|
||||
## Fluxo quando invocado como skill (agentes que não têm opencode)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
---
|
||||
description: Cria o worktree da próxima Issue claimável (next [--go]) ou destrói o worktree atual (kill [--force])
|
||||
description: Cria o worktree da próxima Issue claimável (next [--go]), o worktree de planejamento do /plan-issue (plan [--go]) ou destrói o worktree atual (kill [--force])
|
||||
---
|
||||
|
||||
Rode o script determinístico abaixo (não improvise a lógica) e reporte o resultado de forma concisa.
|
||||
|
||||
- `next [--go]`: cria (ou reutiliza) o worktree da próxima Issue claimável. Com `--go`, o script imprime `cd <dir>` na última linha — aplique-o na sessão de shell atual (`cd <dir>`) e reporte o novo diretório.
|
||||
- `plan [--go]`: cria (ou reutiliza) o worktree de planejamento (branch fixo `plans/plan-issue`, sem vínculo com a fila) para rodar `/plan-issue` sem ocupar o main; nunca conflita com `next`.
|
||||
- `kill [--force]`: destrói o worktree em que você está.
|
||||
|
||||
!`node scripts/worktree.mjs $ARGUMENTS`
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ These are product/architecture constraints — not a ban on editing existing cod
|
|||
- `Consent.text` was reconciled from `varchar` to `jsonb` via `20260715_163500_consent_text_to_jsonb` (matching the `richText` field). Any future field-type change is a migration, not a manual DB edit.
|
||||
- To copy production content locally, use `pnpm db:pull` (`scripts/db-pull.mjs`): it only reads prod, only writes local, and excludes supporter PII (`contact`/`signature`/`subscription` row data).
|
||||
- `pnpm db:doctor` diagnoses local database connectivity for the dev AND test targets: it names who holds the port, points at exited/crash-looping containers, and flags Postgres containers sharing one data volume (two postmasters on one PGDATA corrupt the WAL — this destroyed the local cluster's checkpoint on 2026-07-25; the canonical setup is ONE compose container on 5432 hosting `teqo` + `teqo_test`). `pnpm dev` runs the same preflight via `guard-dev-db` and refuses to start with a named remedy instead of a later ECONNREFUSED. `db:start`/`db:stop` pin the compose project with `-p teqo`, so they always target that ONE container even from a worktree directory.
|
||||
- **Per-worktree environments (`pnpm worktree next`, 2026-08-08):** parallel agents each get a deterministic isolated env — no fighting over port 3000, the shared `teqo_test`, or a second Postgres container. `worktree next [--go]` provisions: a dev-server port `3100+slot` (slot = numeric part of the issue code, hash fallback, bumped on collision among live worktrees), plus dedicated databases `teqo_wt<slot>` (dev) and `teqo_wt<slot>_test` (tests) created on the shared container with the full migration chain applied; writes `.env.local` + `.env.test.local` (gitignored, marker `# teqo worktree env (generated by pnpm worktree next)`; manual envs are never overwritten) and copies `PAYLOAD_SECRET`/Blob/VAPID from the main repo. `kill` drops the worktree's generated databases best-effort. Tests read `.env.test.local` after `.env.test` (vitest.setup.ts / playwright.config.ts / db-doctor / gate-ci mirror the layering), and `assertTestDatabase` accepts `teqo_test` or `teqo_<worktree>_test` on localhost only — still fail-closed on remote. Pure derivation lives in `scripts/lib/worktree-env.mjs` (unit-tested). Without Docker (Cursor Cloud) it degrades to the shared `teqo`/`teqo_test` with a warning.
|
||||
- **Per-worktree environments (`pnpm worktree next`, 2026-08-08):** parallel agents each get a deterministic isolated env — no fighting over port 3000, the shared `teqo_test`, or a second Postgres container. `worktree next [--go]` provisions: a dev-server port `3100+slot` (slot = numeric part of the issue code, hash fallback, bumped on collision among live worktrees), plus dedicated databases `teqo_wt<slot>` (dev) and `teqo_wt<slot>_test` (tests) created on the shared container with the full migration chain applied; writes `.env.local` + `.env.test.local` (gitignored, marker `# teqo worktree env (generated by pnpm worktree next)`; manual envs are never overwritten) and copies `PAYLOAD_SECRET`/Blob/VAPID from the main repo. `worktree plan [--go]` (2026-08-08) is the sibling for the `/plan-issue` skill: same provisioning on the fixed branch `plans/plan-issue` (`PLAN_WORKTREE_BRANCH`, `scripts/lib/worktree.mjs`), deliberately **not** named after any Issue so a later `next` never collides in branch or slot. `kill` drops the worktree's generated databases best-effort. Tests read `.env.test.local` after `.env.test` (vitest.setup.ts / playwright.config.ts / db-doctor / gate-ci mirror the layering), and `assertTestDatabase` accepts `teqo_test` or `teqo_<worktree>_test` on localhost only — still fail-closed on remote. Pure derivation lives in `scripts/lib/worktree-env.mjs` (unit-tested). Without Docker (Cursor Cloud) it degrades to the shared `teqo`/`teqo_test` with a warning.
|
||||
- To (re)populate news content, `pnpm db:seed:posts` fetches articles live from jorgesolla.com.br into the LOCAL db (same non-local `DATABASE_URL` guard as `pnpm dev`). It writes from a CLI process outside the deployed runtime, so after seeding — or after ANY direct-DB change — you must bust the production `posts` cache via `POST /api/revalidate`. See "Posts & Tags" below.
|
||||
- To (re)populate the TSE 2022 election baseline, `pnpm db:seed:tse` downloads TSE open data and imports the Bahia scope into the LOCAL db (same guard). Since the 2026-07-23 hardening, elections-tab reads are cached under the `election-tse` tag — after re-seeding a database the deployed app reads, bust it via `POST /api/revalidate?tag=election-tse`. See "Election baseline data (TSE 2022)" below.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-08):** **`pnpm worktree plan [--go] [--no-migrate]`** — worktree de **planejamento** para rodar a skill `/plan-issue` sem ocupar o main: branch fixo `plans/plan-issue` (`PLAN_WORKTREE_BRANCH`, `scripts/lib/worktree.mjs`), deliberadamente **não** nomeado por Issue nenhuma — um `next` posterior da próxima Issue nunca colide em branch nem em slot (slot do plano = hash do branch, registrado via marcador e bumpado pelos vivos). Mesmo provisionamento isolado do `next` (porta `3100+slot`, bancos `teqo_wt<slot>`/`teqo_wt<slot>_test`, migrations; sem Docker degrada ao compartilhado). `kill` cobre; comment das envs distingue `gerado por pnpm worktree plan`. Sem migration. Constant unit-testada.
|
||||
|
||||
**Recently resolved (2026-08-08):** **B168** (Issue #422) — desfeito o **encadeamento automático** das ações rápidas do wizard (B98 era o precedente que este item inverte; evidência de campo: o coordenador rejeitou a tela avançando sozinha). Ao salvar **qualquer** ação de ajuste (votos, tendência, atualização, liderança), o usuário volta à origem (`from` allowlist B110 mantido; Início sem `from`) com toast — nunca avança para outro wizard. Removidos a fila/pipeline `wizardActionChain.ts` (fundido `wizardReturnHref` + `wizardPreviousHref` só com passos internos em `campaignActionRoutes.ts`), o param `entry`, os skip resolvers/`Pular` (`WizardSkipTrailing`/`WizardTrendSkipTrailing` deletados, chrome mobile sempre “X”) e o prefill de nota sem produtor (`notePrefill`/`voteFrom`/`voteTo`). Tendência: pós-save o RSC refresha com status já aplicado e o **client** é o único dono da navegação (toast + retorno) — o redirect servidor engolia o toast (B97 corrigido no caminho). Liderança: botão flutuante pós-save passou de “Continuar” (avançava) para **“Concluir”** (retorna à origem). Sem migration. Intenção [`desfazer-encadeamento-acoes-wizard.md`](plans/desfazer-encadeamento-acoes-wizard.md); impl [`desfazer-encadeamento-acoes-wizard-impl.md`](plans/desfazer-encadeamento-acoes-wizard-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-08):** **worktree next provisiona ambiente isolado por worktree** — agentes em paralelo não disputam mais porta 3000, o `teqo_test` compartilhado nem um segundo Postgres no 5432: `pnpm worktree next [--go] [--no-migrate]` deriva deterministicamente slot do código da Issue (C15→15; hash sem dígitos; bump em colisão entre worktrees vivos) → dev server `3100+slot`, bancos `teqo_wt<slot>`/`teqo_wt<slot>_test` criados no container compartilhado com migrations aplicadas; escreve `.env.local` + `.env.test.local` (gitignored, marcador gerado; envs manuais nunca sobrescritos) e copia PAYLOAD*SECRET/Blob/VAPID do main. `kill` dropa os bancos gerados (best-effort, WITH FORCE). Suites leem `.env.test.local` depois de `.env.test` (vitest.setup/playwright/db-doctor/gate-ci espelham o layering); `assertTestDatabase` aceita `teqo_test` ou `teqo*<worktree>\_test`em localhost só (fail-closed preservado).`db:start`/`db:stop`fixam`-p teqo`(um container, qualquer diretório); max_connections 200→400. Sem Docker (Cursor Cloud): degrada ao compartilhado com aviso. Derivação pura`scripts/lib/worktree-env.mjs` (unit-tested).
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@
|
|||
|
||||
import { slugify } from '../../src/lib/slug.ts'
|
||||
|
||||
/**
|
||||
* Generic branch of the /plan-issue planning worktree (`pnpm worktree plan`).
|
||||
* Deliberately NOT tied to any Issue: it must never collide with the
|
||||
* `<code>-<slug>` branch `worktree next` derives for the next claimable Issue.
|
||||
*/
|
||||
export const PLAN_WORKTREE_BRANCH = 'plans/plan-issue'
|
||||
|
||||
/** Strip the leading `<code> — ` (or any dash variant) off a title. */
|
||||
const stripCodePrefix = (title, code) => {
|
||||
const escaped = code.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
* paralelo não disputarem porta 3000 nem o
|
||||
* `teqo_test` compartilhado. `--no-migrate` pula a
|
||||
* aplicação das migrations nos bancos novos.
|
||||
* pnpm worktree plan [--go] [--no-migrate]
|
||||
* cria/reutiliza o worktree de PLANEJAMENTO
|
||||
* (branch fixo `plans/plan-issue`) para rodar a
|
||||
* skill /plan-issue sem ocupar o main; NÃO é
|
||||
* nomeado nem derivado de Issue nenhuma, então um
|
||||
* `next` posterior da próxima Issue nunca colide
|
||||
* (nem no branch, nem no slot). Mesmo provisionamento
|
||||
* isolado do `next`.
|
||||
* pnpm worktree kill [--force] destrói o worktree em que o shell atual está
|
||||
* (recusa worktree sujo sem `--force`) e remove os
|
||||
* bancos gerados do worktree (best-effort)
|
||||
|
|
@ -41,7 +49,7 @@ import {
|
|||
isGeneratedDatabaseName,
|
||||
worktreeEnvironment,
|
||||
} from './lib/worktree-env.mjs'
|
||||
import { branchNameForIssue } from './lib/worktree.mjs'
|
||||
import { branchNameForIssue, PLAN_WORKTREE_BRANCH } from './lib/worktree.mjs'
|
||||
|
||||
const die = dieAgent('worktree')
|
||||
const WORKTREES_ROOT = join(homedir(), '.cursor', 'worktrees', 'teqo')
|
||||
|
|
@ -197,8 +205,10 @@ const writeFallbackEnv = (dir, branch) => {
|
|||
* Deterministic per-worktree environment: shared container + own databases +
|
||||
* own dev-server port + env files + migrations. Skips everything when a
|
||||
* manual `.env.local`/`.env.test.local` (no marker) exists — never clobbers.
|
||||
* `purpose` labels the env comment (`next` | `plan`); `issue` is optional and
|
||||
* fills the `issue #N` part of the comment only when present.
|
||||
*/
|
||||
const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot }) => {
|
||||
const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot, purpose = 'next' }) => {
|
||||
const manual =
|
||||
(existsSync(join(dir, '.env.local')) && !envFileIsGenerated(dir, '.env.local')) ||
|
||||
(existsSync(join(dir, '.env.test.local')) && !envFileIsGenerated(dir, '.env.test.local'))
|
||||
|
|
@ -231,9 +241,12 @@ const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot }) =>
|
|||
const payloadSecret = mainEnv.PAYLOAD_SECRET ?? randomBytes(24).toString('hex')
|
||||
const copy = (key) => (mainEnv[key] ? [`${key}=${mainEnv[key]}`] : [])
|
||||
|
||||
const issueLabel = issue ? ` · issue #${issue.number}` : ''
|
||||
const generatedBy = `gerado por pnpm worktree ${purpose}`
|
||||
|
||||
const devEnv = [
|
||||
GENERATED_ENV_MARKER,
|
||||
`# branch ${branch} · issue #${issue.number} · slot ${env.slot} — gerado por pnpm worktree next`,
|
||||
`# branch ${branch}${issueLabel} · slot ${env.slot} — ${generatedBy}`,
|
||||
`DATABASE_URL=${devUrl}`,
|
||||
`PORT=${env.devPort}`,
|
||||
`NEXT_PUBLIC_SITE_URL=http://localhost:${env.devPort}`,
|
||||
|
|
@ -254,7 +267,7 @@ const provision = async ({ dir, branch, issue, env, skipMigrate, mainRoot }) =>
|
|||
|
||||
const testEnv = [
|
||||
GENERATED_ENV_MARKER,
|
||||
`# branch ${branch} · issue #${issue.number} · slot ${env.slot} — gerado por pnpm worktree next`,
|
||||
`# branch ${branch}${issueLabel} · slot ${env.slot} — ${generatedBy}`,
|
||||
'# Lido DEPOIS de .env.test (vitest.setup.ts / playwright.config.ts) — suites isoladas.',
|
||||
`DATABASE_URL=${testUrl}`,
|
||||
`NEXT_PUBLIC_SITE_URL=http://localhost:${env.devPort}`,
|
||||
|
|
@ -321,6 +334,54 @@ const cmdNext = async (go, skipMigrate) => {
|
|||
if (go) console.log(`cd ${dir}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* `plan` — the /plan-issue planning worktree. Deliberately NOT tied to the
|
||||
* claim queue and NOT named after any Issue: `PLAN_WORKTREE_BRANCH` is a fixed
|
||||
* generic branch, so a later `pnpm worktree next` for the next claimable Issue
|
||||
* never collides with it (branch name nor slot — the plan env registers its
|
||||
* hashed slot via the marker and `next` bumps around it). Same isolated env
|
||||
* provisioning as `next`.
|
||||
*/
|
||||
const cmdPlan = async (go, skipMigrate) => {
|
||||
const branch = PLAN_WORKTREE_BRANCH
|
||||
if (git(['check-ref-format', '--allow-onelevel', branch], { okIfFails: true }) === null) {
|
||||
die(`Branch plan inválido para refname: ${branch}`)
|
||||
}
|
||||
const dir = join(WORKTREES_ROOT, branch)
|
||||
|
||||
git(['fetch', 'origin'])
|
||||
|
||||
const entries = parseWorktreeList(git(['worktree', 'list', '--porcelain']))
|
||||
if (entries.some((entry) => resolve(entry.path) === resolve(dir))) {
|
||||
console.log(`Worktree de planejamento já existe em ${dir} — reutilizando, sem duplicar.`)
|
||||
} else {
|
||||
const branchExists =
|
||||
git(['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { okIfFails: true }) !== null
|
||||
const flag = branchExists ? '-B' : '-b'
|
||||
git(['worktree', 'add', flag, branch, dir, 'origin/main'])
|
||||
|
||||
console.log('Worktree de planejamento criado:')
|
||||
console.log(` branch: ${branch}`)
|
||||
console.log(` path: ${dir}`)
|
||||
console.log(' origem: origin/main')
|
||||
}
|
||||
|
||||
const mainRoot = entries[0]?.path
|
||||
const env = worktreeEnvironment({
|
||||
branch,
|
||||
code: null,
|
||||
takenSlots: readLiveSlots(entries, dir),
|
||||
})
|
||||
await provision({ dir, branch, env, skipMigrate, mainRoot, purpose: 'plan' })
|
||||
|
||||
console.log(`\nAmbiente isolado do worktree de planejamento (slot ${env.slot}):`)
|
||||
console.log(` dev server: http://localhost:${env.devPort} (pnpm dev)`)
|
||||
console.log(` banco dev: postgresql://teqo:teqo@localhost:5432/${env.devDatabase}`)
|
||||
console.log(` banco test: postgresql://teqo:teqo@localhost:5432/${env.testDatabase}`)
|
||||
|
||||
if (go) console.log(`cd ${dir}`)
|
||||
}
|
||||
|
||||
/** Generated database names referenced by a worktree's own env files. */
|
||||
const worktreeDatabaseNamesOf = (dir) => {
|
||||
const names = []
|
||||
|
|
@ -405,12 +466,20 @@ const { flags, positional } = parseArgs(process.argv.slice(2), new Set())
|
|||
const subcommand = positional[0]
|
||||
|
||||
if (!subcommand) {
|
||||
console.log('Uso: pnpm worktree next [--go] [--no-migrate] | kill [--force]')
|
||||
console.log(
|
||||
'Uso: pnpm worktree next [--go] [--no-migrate] | plan [--go] [--no-migrate] | kill [--force]',
|
||||
)
|
||||
console.log(' next [--go] [--no-migrate]')
|
||||
console.log(' cria worktree da próxima Issue claimável (branch <code>-<slug>) e provisiona o')
|
||||
console.log(' ambiente isolado: porta de dev + bancos próprios (determinístico do branch);')
|
||||
console.log(' com --go imprime `cd <dir>` no fim (quem aplica o cd: opencode command, ou a')
|
||||
console.log(' função `worktree()` de .agents/shell/worktree.sh); --no-migrate pula migrations')
|
||||
console.log(' plan [--go] [--no-migrate]')
|
||||
console.log(
|
||||
' cria/reutiliza o worktree de planejamento (branch plans/plan-issue) para rodar a',
|
||||
)
|
||||
console.log(' skill /plan-issue sem ocupar o main nem travar na fila; provisiona o mesmo')
|
||||
console.log(' ambiente isolado de `next`; nunca colide com o branch <code>-<slug> de `next`')
|
||||
console.log(' kill [--force] destrói o worktree em que você está (recusa sujo sem --force) e')
|
||||
console.log(' remove os bancos gerados do worktree (best-effort)')
|
||||
process.exit(1)
|
||||
|
|
@ -418,10 +487,11 @@ if (!subcommand) {
|
|||
|
||||
try {
|
||||
if (subcommand === 'next') await cmdNext(Boolean(flags.go), Boolean(flags['no-migrate']))
|
||||
else if (subcommand === 'plan') await cmdPlan(Boolean(flags.go), Boolean(flags['no-migrate']))
|
||||
else if (subcommand === 'kill') {
|
||||
if (flags.go) die('`--go` só faz sentido com `next`.')
|
||||
await cmdKill(Boolean(flags.force))
|
||||
} else die(`subcomando desconhecido: ${subcommand} (esperado: next | kill)`)
|
||||
} else die(`subcomando desconhecido: ${subcommand} (esperado: next | plan | kill)`)
|
||||
} catch (error) {
|
||||
if (error?.stderr) die(error.stderr.toString().trim())
|
||||
die(error?.message ?? String(error))
|
||||
|
|
|
|||
|
|
@ -3,16 +3,20 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
GENERATED_ENV_MARKER,
|
||||
devDatabaseForSlot,
|
||||
devPortForSlot,
|
||||
GENERATED_ENV_MARKER,
|
||||
hashSlotOf,
|
||||
isGeneratedDatabaseName,
|
||||
numericSlotOfCode,
|
||||
testDatabaseForSlot,
|
||||
worktreeEnvironment,
|
||||
} from '../../scripts/lib/worktree-env.mjs'
|
||||
import { branchNameForIssue, issueCodeAndSubject } from '../../scripts/lib/worktree.mjs'
|
||||
import {
|
||||
branchNameForIssue,
|
||||
issueCodeAndSubject,
|
||||
PLAN_WORKTREE_BRANCH,
|
||||
} from '../../scripts/lib/worktree.mjs'
|
||||
|
||||
type TestIssue = {
|
||||
number: number
|
||||
|
|
@ -125,6 +129,21 @@ describe('worktreeEnvironment (per-worktree ports and databases)', () => {
|
|||
expect(testDatabaseForSlot(env.slot)).toBe(env.testDatabase)
|
||||
})
|
||||
|
||||
it('plan worktree derives a stable hashed slot and never collides with `next`', () => {
|
||||
expect(PLAN_WORKTREE_BRANCH).toBe('plans/plan-issue')
|
||||
expect(PLAN_WORKTREE_BRANCH).not.toMatch(/^[A-Z][A-Za-z0-9]*-/)
|
||||
const env = worktreeEnvironment({ branch: PLAN_WORKTREE_BRANCH, code: null })
|
||||
expect(env.slot).toBe(hashSlotOf(PLAN_WORKTREE_BRANCH))
|
||||
expect(numericSlotOfCode('')).toBeNull()
|
||||
expect(env).toEqual(worktreeEnvironment({ branch: PLAN_WORKTREE_BRANCH, code: null }))
|
||||
const bumped = worktreeEnvironment({
|
||||
branch: PLAN_WORKTREE_BRANCH,
|
||||
code: null,
|
||||
takenSlots: new Set([env.slot]),
|
||||
})
|
||||
expect(bumped.slot).toBe(env.slot + 1)
|
||||
})
|
||||
|
||||
it('caps huge codes into the hash range instead of absurd ports', () => {
|
||||
const env = worktreeEnvironment({ branch: 'C999999-foo', code: 'C999999' })
|
||||
expect(env.slot).toBeLessThanOrEqual(999)
|
||||
|
|
|
|||
Loading…
Reference in a new issue