@orkastery/cli 0.5.0 → 0.5.3
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/adapters/claude-code/README.md +11 -2
- package/adapters/claude-code/agents/ork-master.md +2 -0
- package/adapters/claude-code/commands/master.md +8 -0
- package/adapters/claude-code/commands/onboarding.md +3 -2
- package/adapters/claude-code/commands/ork.md +9 -0
- package/adapters/codex/skills/ork/SKILL.md +18 -3
- package/adapters/hermes/README.md +1 -0
- package/adapters/hermes/bin/ork-network-status.sh +10 -0
- package/adapters/hermes/hermes.plugin.json +1 -0
- package/adapters/hermes/hitl-ingress/__init__.py +2 -2
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +6 -5
- package/adapters/openclaw/README.md +35 -4
- package/adapters/openclaw/dist/hitl-ingress.js +2 -2
- package/adapters/openclaw/dist/index.js +47 -12
- package/adapters/openclaw/openclaw.plugin.json +5 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +2 -2
- package/adapters/openclaw/src/index.ts +50 -12
- package/assets/orkmind_bridge.py +117 -9
- package/dist/adapters/claude-bg.js +59 -11
- package/dist/adapters/codex.js +4 -3
- package/dist/agents-md.js +1 -0
- package/dist/auditrun.js +6 -2
- package/dist/board.js +16 -14
- package/dist/branch-de-estado.js +66 -10
- package/dist/busca-semantica.js +52 -12
- package/dist/canarios-rm057.js +149 -0
- package/dist/canarios.js +2 -0
- package/dist/ci.js +8 -1
- package/dist/claim-lint.js +3 -1
- package/dist/claims.js +39 -1
- package/dist/company-brain-context.js +158 -22
- package/dist/docs.js +110 -3
- package/dist/doctor-pulse-cron.js +194 -0
- package/dist/doctor-rede.js +149 -0
- package/dist/doctor.js +294 -37
- package/dist/entrega-pr.js +27 -8
- package/dist/esgotamentos.js +181 -0
- package/dist/experiencia.js +12 -2
- package/dist/fabrica-estado.js +35 -9
- package/dist/fabrica-publicar.js +6 -0
- package/dist/fechamento.js +20 -66
- package/dist/fix.js +3 -3
- package/dist/forja.js +31 -7
- package/dist/gates.js +5 -1
- package/dist/hitl-contract.js +59 -4
- package/dist/hitl-curto.js +11 -6
- package/dist/hitl-gates.js +5 -0
- package/dist/hitl-lote.js +5 -2
- package/dist/hitl-resumo.js +33 -1
- package/dist/hitl-tempo-parado.js +201 -0
- package/dist/hitl-texto-livre.js +5 -5
- package/dist/horario.js +27 -0
- package/dist/hosts.js +59 -13
- package/dist/impedimento.js +205 -0
- package/dist/index.js +433 -88
- package/dist/indice-vetorial.js +87 -10
- package/dist/indice.js +3 -1
- package/dist/init.js +28 -5
- package/dist/integracoes-locais.js +4 -0
- package/dist/intelligence-graph-cli.js +267 -30
- package/dist/intelligence-graph-contexto.js +215 -0
- package/dist/intelligence-graph-contract.js +71 -12
- package/dist/intelligence-graph-extract-md.js +41 -23
- package/dist/intelligence-graph-extract-ts.js +181 -11
- package/dist/intelligence-graph-extract.js +186 -9
- package/dist/intelligence-graph-index.js +205 -24
- package/dist/intelligence-graph-parsers.js +6 -4
- package/dist/intelligence-graph-repo.js +10 -0
- package/dist/leases.js +680 -87
- package/dist/ledger-stats.js +15 -1
- package/dist/ledger.js +12 -0
- package/dist/licoes.js +6 -4
- package/dist/liveness.js +2 -1
- package/dist/maestro-runtime.js +7 -1
- package/dist/manifest.js +44 -1
- package/dist/master-nota.js +14 -5
- package/dist/master.js +129 -12
- package/dist/mcp-artifacts.js +36 -3
- package/dist/mcp-git.js +80 -15
- package/dist/mcp-grafo-worker.js +93 -0
- package/dist/mcp-grafo.js +279 -0
- package/dist/mcp-install.js +2 -1
- package/dist/mcp-server.js +23 -1
- package/dist/mcp-ship.js +2 -0
- package/dist/mcp-verify.js +3 -1
- package/dist/memoria.js +48 -6
- package/dist/network-roadmap.js +195 -20
- package/dist/ocupacao.js +17 -5
- package/dist/onboarding.js +9 -4
- package/dist/orkmind.js +90 -5
- package/dist/orquestracao.js +14 -11
- package/dist/parado-no-condutor.js +1089 -0
- package/dist/phase.js +68 -15
- package/dist/policies.js +79 -3
- package/dist/portfolio.js +4 -1
- package/dist/projeto-alvo.js +5 -1
- package/dist/prompts.js +36 -1
- package/dist/prova-ativacao.js +414 -0
- package/dist/pulse-delivery.js +60 -7
- package/dist/pulse.js +99 -3
- package/dist/ratelimit.js +20 -4
- package/dist/recall.js +3 -1
- package/dist/rede-adesao.js +399 -0
- package/dist/rede-forja.js +289 -0
- package/dist/rede-projetos.js +239 -0
- package/dist/rede-status.js +336 -0
- package/dist/rede.js +980 -0
- package/dist/retry.js +69 -2
- package/dist/roadmap-status.js +162 -7
- package/dist/runtime-context.js +5 -1
- package/dist/runtime-profiles.js +22 -1
- package/dist/saida-segura.js +58 -0
- package/dist/session-watcher-claude.js +113 -23
- package/dist/sessoes-contas.js +187 -0
- package/dist/sessoes-inventario.js +6 -12
- package/dist/sessoes.js +87 -3
- package/dist/ship.js +110 -20
- package/dist/thread.js +212 -14
- package/dist/util.js +71 -4
- package/dist/verify.js +2 -2
- package/dist/worktree.js +34 -8
- package/eval/casos/onboarding.json +1 -1
- package/eval/fixtures/fx-pedido-colado/caso.json +20 -0
- package/package.json +7 -3
- package/references/definition-of-done.md +1 -1
- package/skills/README.md +1 -1
- package/skills/core/onboarding/SKILL.md +3 -2
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/master-metrics/SKILL.md +8 -3
- package/skills/phases/ship-release/SKILL.md +6 -3
package/dist/ship.js
CHANGED
|
@@ -50,6 +50,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
50
50
|
};
|
|
51
51
|
})();
|
|
52
52
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
|
+
exports.commitQueIncorporou = commitQueIncorporou;
|
|
53
54
|
exports.autorizacaoDePush = autorizacaoDePush;
|
|
54
55
|
exports.arvoreComBranch = arvoreComBranch;
|
|
55
56
|
exports.prepararArvore = prepararArvore;
|
|
@@ -62,6 +63,7 @@ exports.ship = ship;
|
|
|
62
63
|
exports.textoDoShip = textoDoShip;
|
|
63
64
|
const fs = __importStar(require("node:fs"));
|
|
64
65
|
const path = __importStar(require("node:path"));
|
|
66
|
+
const node_child_process_1 = require("node:child_process");
|
|
65
67
|
const gates_1 = require("./gates");
|
|
66
68
|
const leases_1 = require("./leases");
|
|
67
69
|
const ledger_1 = require("./ledger");
|
|
@@ -76,6 +78,45 @@ const contrato_publico_1 = require("./contrato-publico");
|
|
|
76
78
|
const prova_minima_1 = require("./prova-minima");
|
|
77
79
|
const horario_1 = require("./horario");
|
|
78
80
|
const fabrica_publicar_1 = require("./fabrica-publicar");
|
|
81
|
+
const branch_de_estado_1 = require("./branch-de-estado");
|
|
82
|
+
/**
|
|
83
|
+
* RM-037 (fatia 5, A1): o commit de primeiro pai da base que incorporou o `shaDe`: o merge que trouxe a branch, ou
|
|
84
|
+
* o proprio `shaDe` quando a base avancou por fast-forward ate ele. Com a branch ja incorporada, a ponta da base e
|
|
85
|
+
* outra coisa: no `ship_done` da ork-docsusuarios (27/09/2026), o unico do `ork ship` com `jaIncorporado` nos ledgers
|
|
86
|
+
* deste projeto, ela era o merge de outra thread. O que trouxe a branch e o commit mais velho do primeiro pai da base
|
|
87
|
+
* que descende do `shaDe`. GO-FIX (rodada 1 do CHECK): `--first-parent` junto com `--ancestry-path` so propaga a
|
|
88
|
+
* descendencia pelos commits de primeiro pai, e o merge do PR que recebeu "Update branch" no GitHub (o pai dele e um
|
|
89
|
+
* merge que descende do `shaDe`, nao o `shaDe`) sumia da lista; agora sao duas listas e a intersecao. `null` quando o
|
|
90
|
+
* git nao responde.
|
|
91
|
+
*/
|
|
92
|
+
function commitQueIncorporou(raiz, shaDe, ponta, passos = []) {
|
|
93
|
+
if (shaDe === ponta)
|
|
94
|
+
return shaDe;
|
|
95
|
+
// `passos` recebe os comandos que de fato rodaram, na ordem: e a evidencia do ship (sugestao da conferencia da rodada 2).
|
|
96
|
+
const curto = (sha) => sha.slice(0, 8);
|
|
97
|
+
// As listas crescem com o historico entre a branch e a ponta: o buffer vai alem do 1 MiB padrao do spawnSync.
|
|
98
|
+
const listar = (args) => {
|
|
99
|
+
passos.push(`git ${args.map((a) => a.replace(/^([0-9a-f]{40,64})\.\.([0-9a-f]{40,64})$/, (_, x, y) => `${curto(x)}..${curto(y)}`)).join(' ')}`);
|
|
100
|
+
const r = (0, node_child_process_1.spawnSync)('git', args, { cwd: raiz, encoding: 'utf8', timeout: 120000, maxBuffer: 256 * 1024 * 1024 });
|
|
101
|
+
return r.status === 0 && !r.error ? r.stdout.split('\n').filter((l) => /^[0-9a-f]{40,64}$/.test(l)) : null;
|
|
102
|
+
};
|
|
103
|
+
const primeiroPai = listar(['rev-list', '--first-parent', `${shaDe}..${ponta}`]);
|
|
104
|
+
const descendentes = listar(['rev-list', '--ancestry-path', `${shaDe}..${ponta}`]);
|
|
105
|
+
if (!primeiroPai || !descendentes)
|
|
106
|
+
return null;
|
|
107
|
+
const doShaDe = new Set(descendentes);
|
|
108
|
+
// Do mais velho para o mais novo: o primeiro de primeiro pai que descende do `shaDe`.
|
|
109
|
+
const maisVelho = [...primeiroPai].reverse().find((c) => doShaDe.has(c));
|
|
110
|
+
if (!maisVelho)
|
|
111
|
+
return null;
|
|
112
|
+
// O primeiro pai dele e o proprio `shaDe`: a branch ja estava no primeiro pai da base (fast-forward). Sem a resposta
|
|
113
|
+
// do git, nada e gravado: devolver o mais velho poria no `mergeSha` o filho do `shaDe` (sugestao da rodada 2).
|
|
114
|
+
passos.push(`git rev-parse ${curto(maisVelho)}^1`);
|
|
115
|
+
const pai = (0, util_1.exec)('git', ['rev-parse', `${maisVelho}^1`], raiz);
|
|
116
|
+
if (!pai.ok)
|
|
117
|
+
return null;
|
|
118
|
+
return pai.stdout.trim() === shaDe ? shaDe : maisVelho;
|
|
119
|
+
}
|
|
79
120
|
/** Os caminhos de contrato publico que a entrega traz sobre a base (diff desde o merge-base). */
|
|
80
121
|
function contratosDaEntrega(raiz, shaPara, shaDe) {
|
|
81
122
|
const base = (0, util_1.exec)('git', ['merge-base', shaPara, shaDe], raiz);
|
|
@@ -185,7 +226,7 @@ function prepararArvore(raiz, para) {
|
|
|
185
226
|
}
|
|
186
227
|
const dir = dirTemporarioDeShip(raiz, para);
|
|
187
228
|
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
188
|
-
const r = (0, util_1.exec)('git', ['worktree', 'add', dir, para], raiz);
|
|
229
|
+
const r = (0, util_1.exec)('git', ['worktree', 'add', '--', dir, para], raiz);
|
|
189
230
|
if (!r.ok) {
|
|
190
231
|
throw new Error(`nao foi possivel montar a arvore de merge de "${para}": ${(r.stderr || r.stdout).trim()}`);
|
|
191
232
|
}
|
|
@@ -211,7 +252,8 @@ function shaDaRef(dir, ref) {
|
|
|
211
252
|
* codigo de saida do `git push` nao prova que o remoto ficou com o commit certo.
|
|
212
253
|
*/
|
|
213
254
|
function shaNoRemoto(dir, remoto, branch) {
|
|
214
|
-
|
|
255
|
+
// RM-047: so nome de remoto chega ao git, e depois do `--` (nunca vira opcao nem transporte).
|
|
256
|
+
const r = (0, util_1.exec)('git', ['ls-remote', '--', (0, branch_de_estado_1.exigirRemoto)(remoto, 'ship'), `refs/heads/${branch}`], dir, 120000);
|
|
215
257
|
if (!r.ok)
|
|
216
258
|
return null;
|
|
217
259
|
const linha = r.stdout.split('\n').find((l) => l.trim() !== '');
|
|
@@ -222,7 +264,7 @@ function shaNoRemoto(dir, remoto, branch) {
|
|
|
222
264
|
}
|
|
223
265
|
/** O remoto esta configurado neste repositorio? */
|
|
224
266
|
function remotoConfigurado(dir, remoto) {
|
|
225
|
-
return (0, util_1.exec)('git', ['remote', 'get-url', remoto], dir).ok;
|
|
267
|
+
return (0, util_1.exec)('git', ['remote', 'get-url', '--', (0, branch_de_estado_1.exigirRemoto)(remoto, 'ship')], dir).ok;
|
|
226
268
|
}
|
|
227
269
|
function baseDoResultado(thread, de, opcoes) {
|
|
228
270
|
return {
|
|
@@ -237,6 +279,7 @@ function baseDoResultado(thread, de, opcoes) {
|
|
|
237
279
|
shaDe: '',
|
|
238
280
|
shaParaAntes: '',
|
|
239
281
|
mergeSha: null,
|
|
282
|
+
pontaDaBase: null,
|
|
240
283
|
jaIncorporado: false,
|
|
241
284
|
remoto: opcoes.remoto ?? 'origin',
|
|
242
285
|
shaRemoto: null,
|
|
@@ -329,7 +372,10 @@ function ship(carregado, threadId, opcoes) {
|
|
|
329
372
|
const dir = (0, thread_1.dirThread)(raiz, threadId);
|
|
330
373
|
const de = opcoes.de ?? thread.base.branch;
|
|
331
374
|
const para = opcoes.para;
|
|
332
|
-
|
|
375
|
+
// RM-047: o `--remoto` vem da linha de comando. Antes de qualquer git (inclusive a leitura da reversao),
|
|
376
|
+
// so nome de remoto passa; o resto recusa com `ship.remoto-invalido`, e o ship nao entrega sem push provado
|
|
377
|
+
// por um remoto que nunca existiu.
|
|
378
|
+
const remoto = (0, branch_de_estado_1.exigirRemoto)(opcoes.remoto ?? 'origin', 'ship');
|
|
333
379
|
const r = baseDoResultado(thread, de, opcoes);
|
|
334
380
|
// I-43 (T7): antes de qualquer coisa, se a entrega ANTERIOR desta thread foi
|
|
335
381
|
// revertida na base, isso vira `rollback_done`. E o insumo de maior peso do indice
|
|
@@ -348,6 +394,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
348
394
|
correcao,
|
|
349
395
|
de,
|
|
350
396
|
para,
|
|
397
|
+
// Fatia 2 do ensaio da 0.5.0 (P2): o gate do ensaio e marcado, e os leitores de estado o ignoram.
|
|
398
|
+
...(r.dryRun ? { dryRun: true } : {}),
|
|
351
399
|
});
|
|
352
400
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipBloqueado, {
|
|
353
401
|
de,
|
|
@@ -373,6 +421,26 @@ function ship(carregado, threadId, opcoes) {
|
|
|
373
421
|
const branchAtrasDaBase = pontaDe && pontaPara && de !== para
|
|
374
422
|
? !(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, pontaDe], raiz).ok
|
|
375
423
|
: undefined;
|
|
424
|
+
// Fatia 2 do ensaio da 0.5.0 (P9): sem delta (a branch da thread ja esta contida na base) e com a base
|
|
425
|
+
// local a frente da ref de rastreio do remoto, o push publicaria a base sem merge de thread. A medida e
|
|
426
|
+
// local, sem rede; sem remoto, sem ref de rastreio ou com --sem-push, o fato nao vem e nada muda.
|
|
427
|
+
// CHECK, rodada 1 (B1): o merge que o proprio ship ja fez e nao chegou ao remoto (push recusado,
|
|
428
|
+
// --sem-push, retomada do MCP) tambem deixa a base a frente sem delta, mas leva a ponta da thread como
|
|
429
|
+
// pai que nao e o primeiro: e entrega de thread, e o retry empurra como antes. Rodada 2 (S-R2-1): so
|
|
430
|
+
// quando a linha principal da base, alem do remoto, e toda de merge; commit direto nela segue barrado.
|
|
431
|
+
const rastreio = pontaDe && pontaPara && de !== para && !opcoes.semPush && remotoConfigurado(raiz, remoto)
|
|
432
|
+
? shaDaRef(raiz, `refs/remotes/${remoto}/${para}`) : null;
|
|
433
|
+
const mergeDaThreadNaBase = (desde) => {
|
|
434
|
+
const diretos = (0, util_1.exec)('git', ['rev-list', '--first-parent', '--no-merges', `${desde}..${pontaPara}`], raiz);
|
|
435
|
+
return diretos.ok && diretos.stdout.trim() === '' &&
|
|
436
|
+
(0, util_1.exec)('git', ['rev-list', '--merges', '--parents', `${desde}..${pontaPara}`], raiz).stdout.split('\n')
|
|
437
|
+
.some((linha) => linha.trim().split(' ').slice(2).includes(pontaDe));
|
|
438
|
+
};
|
|
439
|
+
const semDeltaComBaseAFrente = rastreio
|
|
440
|
+
? (0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaDe, pontaPara], raiz).ok &&
|
|
441
|
+
!(0, util_1.exec)('git', ['merge-base', '--is-ancestor', pontaPara, rastreio], raiz).ok &&
|
|
442
|
+
!mergeDaThreadNaBase(rastreio)
|
|
443
|
+
: undefined;
|
|
376
444
|
const violacoes = (0, policies_1.avaliarPolicies)(manifesto, {
|
|
377
445
|
gate: 'ship',
|
|
378
446
|
de,
|
|
@@ -380,6 +448,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
380
448
|
baseBranch: manifesto.worktree.base_branch,
|
|
381
449
|
threadId,
|
|
382
450
|
branchAtrasDaBase,
|
|
451
|
+
semDeltaComBaseAFrente,
|
|
452
|
+
remoto,
|
|
383
453
|
});
|
|
384
454
|
r.violacoes = violacoes;
|
|
385
455
|
if (!r.dryRun) {
|
|
@@ -425,7 +495,7 @@ function ship(carregado, threadId, opcoes) {
|
|
|
425
495
|
}
|
|
426
496
|
// 4. Lease `main-tree`: so uma thread mergeia por vez.
|
|
427
497
|
if (r.dryRun) {
|
|
428
|
-
const ocupado = (0, leases_1.
|
|
498
|
+
const ocupado = (0, leases_1.leasesColidentes)(raiz, leases_1.LEASE_MAIN_TREE)[0] ?? null;
|
|
429
499
|
r.leaseOcupadoPor = ocupado;
|
|
430
500
|
// O ensaio tambem responde ONDE o merge aconteceria e se aquela arvore esta limpa:
|
|
431
501
|
// descobrir isso so na hora do merge real seria descobrir tarde demais.
|
|
@@ -434,11 +504,17 @@ function ship(carregado, threadId, opcoes) {
|
|
|
434
504
|
? `${dona} (arvore que tem ${para} em check-out; ${temAlteracaoNaoCommitada(dona) ? 'SUJA, o ship reprovaria com tree.blocked' : 'limpa'})`
|
|
435
505
|
: `worktree temporaria em ${path.join((0, manifest_1.dirEstado)(raiz), 'tmp')} (ninguem tem ${para} em check-out)`;
|
|
436
506
|
r.detalhe = `arvore de merge: ${arvoreDoMerge}`;
|
|
507
|
+
// Ensaio de 03/10 (R5): sem remoto (ou com --sem-push), o ensaio diz o mesmo que o ship real, e nao
|
|
508
|
+
// promete um push e uma prova que nao vao acontecer.
|
|
509
|
+
const semPushNoReal = opcoes.semPush ? 'push nao executado por --sem-push (merge local concluido)'
|
|
510
|
+
: !remotoConfigurado(raiz, remoto) ? `remoto "${remoto}" nao configurado: merge local concluido, sem push a provar` : null;
|
|
437
511
|
r.passos = [
|
|
438
512
|
`arvore de merge: ${arvoreDoMerge}`,
|
|
439
513
|
`git merge --no-ff ${de} -m "<mensagem de ship>"`,
|
|
440
|
-
`
|
|
441
|
-
|
|
514
|
+
...(semPushNoReal ? [`sem push: ${semPushNoReal}`] : [
|
|
515
|
+
`git push ${remoto} ${para}`,
|
|
516
|
+
`git ls-remote ${remoto} refs/heads/${para} (prova do push)`,
|
|
517
|
+
]),
|
|
442
518
|
];
|
|
443
519
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.shipIniciado, {
|
|
444
520
|
de,
|
|
@@ -469,11 +545,11 @@ function ship(carregado, threadId, opcoes) {
|
|
|
469
545
|
posicao: aquisicao.posicaoNaFila,
|
|
470
546
|
naFrente: (0, leases_1.esperandoPor)(raiz, leases_1.LEASE_MAIN_TREE).length,
|
|
471
547
|
bloqueadaPor: dono?.thread ?? '(desconhecida)',
|
|
472
|
-
motivo: 'lease.busy',
|
|
548
|
+
motivo: aquisicao.motivo ?? 'lease.busy',
|
|
473
549
|
});
|
|
474
|
-
return bloquear('lease.busy', `lease ${leases_1.LEASE_MAIN_TREE} esta com a thread ${dono?.thread ?? '(desconhecida)'} ` +
|
|
550
|
+
return bloquear(aquisicao.motivo ?? 'lease.busy', aquisicao.falhaRetomada ? aquisicao.detalhe : `lease ${leases_1.LEASE_MAIN_TREE} esta com a thread ${dono?.thread ?? '(desconhecida)'} ` +
|
|
475
551
|
`desde ${dono?.adquiridoEm ?? '?'} (${dono?.motivo ?? 'sem motivo'}); ` +
|
|
476
|
-
`esta thread entrou na merge queue na posicao ${aquisicao.posicaoNaFila}`,
|
|
552
|
+
`esta thread entrou na merge queue na posicao ${aquisicao.posicaoNaFila}`, aquisicao.correcao);
|
|
477
553
|
}
|
|
478
554
|
r.lease = aquisicao.lease;
|
|
479
555
|
(0, ledger_1.registrar)(dir, threadId, ledger_1.TIPOS_DE_EVENTO.leaseAdquirido, {
|
|
@@ -508,6 +584,9 @@ function ship(carregado, threadId, opcoes) {
|
|
|
508
584
|
const jaIncorporado = (0, util_1.exec)('git', ['merge-base', '--is-ancestor', shaDe, shaPara], raiz).ok;
|
|
509
585
|
r.jaIncorporado = jaIncorporado;
|
|
510
586
|
let mergeSha = shaPara;
|
|
587
|
+
// RM-037 (fatia 5, A1): a ponta que o push prova. Com o merge feito aqui, e ele; com a branch ja incorporada, a
|
|
588
|
+
// ponta de antes, que pode trazer outros merges depois do que entregou esta thread.
|
|
589
|
+
let pontaDaBase = shaPara;
|
|
511
590
|
if (!jaIncorporado) {
|
|
512
591
|
const mensagem = opcoes.mensagem ??
|
|
513
592
|
`ship(${threadId}): merge de ${de} em ${para}\n\n` +
|
|
@@ -515,7 +594,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
515
594
|
`modo: ${(0, modos_1.tagDoModo)(thread.modo)}\n` +
|
|
516
595
|
`autorizado por: ${autorizacao.por} (${autorizacao.tipo})\n` +
|
|
517
596
|
`verificado em: HEAD ${verificacao.commit}\n`;
|
|
518
|
-
|
|
597
|
+
// RM-047: o merge e do sha verificado, e nao do nome que veio de `--de` (nome nunca vira opcao do git).
|
|
598
|
+
const merge = (0, util_1.exec)('git', ['merge', '--no-ff', '-m', mensagem, shaDe], arvore.dir, 300000);
|
|
519
599
|
passos.push(`git merge --no-ff ${de}`);
|
|
520
600
|
if (!merge.ok) {
|
|
521
601
|
(0, util_1.exec)('git', ['merge', '--abort'], arvore.dir);
|
|
@@ -527,6 +607,7 @@ function ship(carregado, threadId, opcoes) {
|
|
|
527
607
|
return bloquear('artifact.missing', 'nao foi possivel ler o HEAD apos o merge', 'confira o repositorio');
|
|
528
608
|
}
|
|
529
609
|
mergeSha = novo;
|
|
610
|
+
pontaDaBase = novo;
|
|
530
611
|
// Verificacao independente do merge: a origem precisa ser ancestral do resultado.
|
|
531
612
|
const incorporou = (0, util_1.exec)('git', ['merge-base', '--is-ancestor', shaDe, mergeSha], arvore.dir).ok;
|
|
532
613
|
passos.push(`git merge-base --is-ancestor ${shaDe.slice(0, 8)} ${mergeSha.slice(0, 8)}`);
|
|
@@ -535,9 +616,16 @@ function ship(carregado, threadId, opcoes) {
|
|
|
535
616
|
}
|
|
536
617
|
}
|
|
537
618
|
else {
|
|
538
|
-
|
|
619
|
+
// RM-037 (fatia 5, A1): o merge que trouxe a branch, e nao a ponta da base.
|
|
620
|
+
const incorporadoEm = commitQueIncorporou(raiz, shaDe, shaPara, passos);
|
|
621
|
+
if (!incorporadoEm) {
|
|
622
|
+
return bloquear('artifact.missing', `${de} ja e ancestral de ${para}, mas o git nao achou o commit de primeiro pai que o incorporou`, 'confira o repositorio: o ship_done precisa do merge que entregou a thread');
|
|
623
|
+
}
|
|
624
|
+
mergeSha = incorporadoEm;
|
|
625
|
+
passos.push(`(nada a mergear: ${de} ja e ancestral de ${para}, incorporado em ${mergeSha.slice(0, 8)})`);
|
|
539
626
|
}
|
|
540
627
|
r.mergeSha = mergeSha;
|
|
628
|
+
r.pontaDaBase = pontaDaBase;
|
|
541
629
|
opcoes.revalidarGit?.('push', arvore.dir);
|
|
542
630
|
// 6. Push PROVADO por ls-remote.
|
|
543
631
|
const temRemoto = remotoConfigurado(arvore.dir, remoto);
|
|
@@ -551,8 +639,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
551
639
|
else {
|
|
552
640
|
const existeNoRemoto = shaNoRemoto(arvore.dir, remoto, para) !== null;
|
|
553
641
|
const argsPush = existeNoRemoto
|
|
554
|
-
? ['push', remoto,
|
|
555
|
-
: ['push', '-u', remoto,
|
|
642
|
+
? ['push', '--', remoto, `refs/heads/${para}:refs/heads/${para}`]
|
|
643
|
+
: ['push', '-u', '--', remoto, `refs/heads/${para}:refs/heads/${para}`];
|
|
556
644
|
const push = (0, util_1.exec)('git', argsPush, arvore.dir, 300000);
|
|
557
645
|
passos.push(`git ${argsPush.join(' ')}`);
|
|
558
646
|
if (!push.ok) {
|
|
@@ -563,10 +651,10 @@ function ship(carregado, threadId, opcoes) {
|
|
|
563
651
|
const shaRemoto = shaNoRemoto(arvore.dir, remoto, para);
|
|
564
652
|
passos.push(`git ls-remote ${remoto} refs/heads/${para}`);
|
|
565
653
|
r.shaRemoto = shaRemoto;
|
|
566
|
-
r.pushVerificado = shaRemoto !== null && shaRemoto ===
|
|
654
|
+
r.pushVerificado = shaRemoto !== null && shaRemoto === pontaDaBase;
|
|
567
655
|
if (!r.pushVerificado) {
|
|
568
656
|
r.passos = passos;
|
|
569
|
-
return bloquear('runtime.unavailable', `ls-remote devolveu ${shaRemoto ?? '(nada)'} e o destino local esta em ${
|
|
657
|
+
return bloquear('runtime.unavailable', `ls-remote devolveu ${shaRemoto ?? '(nada)'} e o destino local esta em ${pontaDaBase}: ` +
|
|
570
658
|
'o push nao ficou provado', 'rode o ship de novo e confira o remoto');
|
|
571
659
|
}
|
|
572
660
|
}
|
|
@@ -585,6 +673,8 @@ function ship(carregado, threadId, opcoes) {
|
|
|
585
673
|
shaDe,
|
|
586
674
|
shaParaAntes: shaPara,
|
|
587
675
|
mergeSha,
|
|
676
|
+
// RM-037 (fatia 5, A1): a ponta da base vai em campo proprio; o `mergeSha` e o merge que entregou a thread.
|
|
677
|
+
pontaDaBase,
|
|
588
678
|
jaIncorporado,
|
|
589
679
|
remoto: r.remoto,
|
|
590
680
|
shaRemoto: r.shaRemoto,
|
|
@@ -647,12 +737,12 @@ function textoDoShip(r) {
|
|
|
647
737
|
linhas.push(` ${local(r.autorizacao.razao)}`);
|
|
648
738
|
if (r.verificacao) {
|
|
649
739
|
const v = r.verificacao;
|
|
650
|
-
linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${
|
|
740
|
+
linhas.push(` verificacao ${v.ok ? 'passou' : 'REPROVOU'} no HEAD ${(0, util_1.shaCurto)(v.commit)} ` +
|
|
651
741
|
`(${v.claims.filter((c) => c.verificado).length}/${v.claims.length} claims, ` +
|
|
652
742
|
`${v.comandos.filter((c) => c.ok).length}/${v.comandos.length} comandos)`);
|
|
653
743
|
}
|
|
654
744
|
if (r.ci) {
|
|
655
|
-
linhas.push(` ci
|
|
745
|
+
linhas.push(` ci ${r.ci.required ? `${r.ci.state.toUpperCase()} ${r.ci.context} em ${r.ci.sha.slice(0, 8)}` : 'não exigido'}`);
|
|
656
746
|
}
|
|
657
747
|
linhas.push(...(0, policies_1.linhasDeAviso)(r.violacoes ?? []));
|
|
658
748
|
if (r.leaseOcupadoPor) {
|
|
@@ -681,11 +771,11 @@ function textoDoShip(r) {
|
|
|
681
771
|
}
|
|
682
772
|
linhas.push('');
|
|
683
773
|
linhas.push(r.jaIncorporado
|
|
684
|
-
? `Nada a mergear: ${r.de} ja estava em ${r.para}.`
|
|
774
|
+
? `Nada a mergear: ${r.de} ja estava em ${r.para}, incorporado em ${r.mergeSha}.`
|
|
685
775
|
: `Merge --no-ff concluido: ${r.mergeSha}`);
|
|
686
776
|
if (r.pushVerificado) {
|
|
687
777
|
linhas.push(`Push PROVADO: ls-remote de ${r.remoto}/${r.para} devolveu ${r.shaRemoto}`);
|
|
688
|
-
linhas.push(` e o destino local esta no mesmo sha: ${r.mergeSha}`);
|
|
778
|
+
linhas.push(` e o destino local esta no mesmo sha: ${r.pontaDaBase ?? r.mergeSha}`);
|
|
689
779
|
}
|
|
690
780
|
else {
|
|
691
781
|
linhas.push(`Push nao provado: ${local(r.detalhe) || 'sem remoto configurado'}`);
|
package/dist/thread.js
CHANGED
|
@@ -45,8 +45,18 @@ exports.caminhoThread = caminhoThread;
|
|
|
45
45
|
exports.listarIds = listarIds;
|
|
46
46
|
exports.lerThread = lerThread;
|
|
47
47
|
exports.gravarThread = gravarThread;
|
|
48
|
+
exports.avisoDeThreadSemBase = avisoDeThreadSemBase;
|
|
48
49
|
exports.idDisponivel = idDisponivel;
|
|
50
|
+
exports.pastaDaWorktree = pastaDaWorktree;
|
|
51
|
+
exports.alvoDaWorktree = alvoDaWorktree;
|
|
52
|
+
exports.worktreePrevista = worktreePrevista;
|
|
49
53
|
exports.criarWorktree = criarWorktree;
|
|
54
|
+
exports.pedidoDeWorktree = pedidoDeWorktree;
|
|
55
|
+
exports.linhaDaWorktree = linhaDaWorktree;
|
|
56
|
+
exports.avisoDeThreadSemWorktree = avisoDeThreadSemWorktree;
|
|
57
|
+
exports.avisoDeWorktreeQueFalharia = avisoDeWorktreeQueFalharia;
|
|
58
|
+
exports.avisoDeChaveSemCommit = avisoDeChaveSemCommit;
|
|
59
|
+
exports.avisoDaWorktree = avisoDaWorktree;
|
|
50
60
|
exports.novaThread = novaThread;
|
|
51
61
|
exports.exigirVarianteCompativel = exigirVarianteCompativel;
|
|
52
62
|
exports.blocoDaThread = blocoDaThread;
|
|
@@ -113,9 +123,22 @@ function carimbarBase(raiz) {
|
|
|
113
123
|
const commit = (0, util_1.exec)('git', ['rev-parse', 'HEAD'], raiz);
|
|
114
124
|
return {
|
|
115
125
|
branch: branch.ok ? branch.stdout.trim() : 'desconhecida',
|
|
116
|
-
commit: commit.ok ? commit.stdout.trim() :
|
|
126
|
+
commit: commit.ok ? commit.stdout.trim() : util_1.COMMIT_DESCONHECIDO,
|
|
117
127
|
};
|
|
118
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Ensaio da 0.5.0: a thread aberta antes do primeiro commit nascia sem base, e nada avisava; o
|
|
131
|
+
* ship depois nao tem de onde partir. O aviso vai ao stderr e nao muda a saida do comando.
|
|
132
|
+
*/
|
|
133
|
+
function avisoDeThreadSemBase(thread, gravada) {
|
|
134
|
+
if (thread.base.commit !== util_1.COMMIT_DESCONHECIDO)
|
|
135
|
+
return null;
|
|
136
|
+
return gravada
|
|
137
|
+
? `Aviso: o repositório ainda não tem commit, e a thread ${thread.id} nasceu sem base: o ship não tem de onde partir. ` +
|
|
138
|
+
`Faça o primeiro commit, feche esta com ork thread close ${thread.id} --motivo engano --por <quem> ` +
|
|
139
|
+
'--justificativa "aberta antes do primeiro commit" e abra outra.'
|
|
140
|
+
: 'Aviso: o repositório ainda não tem commit, e a thread nasceria sem base: faça o primeiro commit antes de abrir a primeira thread.';
|
|
141
|
+
}
|
|
119
142
|
/** Resolve um id livre a partir do assunto normalizado (`<abbrev>-<assunto>`). */
|
|
120
143
|
function idDisponivel(raiz, abbrev, assunto) {
|
|
121
144
|
let candidato = assunto;
|
|
@@ -127,6 +150,43 @@ function idDisponivel(raiz, abbrev, assunto) {
|
|
|
127
150
|
}
|
|
128
151
|
return { id: `${abbrev}-${candidato}`, assunto: candidato };
|
|
129
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Pasta da worktree de uma thread: `worktree.dir` resolvido na arvore principal, nunca na worktree de onde o comando
|
|
155
|
+
* roda. P4 do ensaio da 0.5.0 (CHECK): com a chave valendo, o `ork thread new` rodado de dentro da worktree de outra
|
|
156
|
+
* thread aninhava a nova na pasta da mae, e o release da mae levava a da filha junto.
|
|
157
|
+
*/
|
|
158
|
+
function pastaDaWorktree(carregado, id) {
|
|
159
|
+
return path.resolve((0, estado_thread_1.raizDoEstado)(carregado.raiz), carregado.manifesto.worktree.dir, id);
|
|
160
|
+
}
|
|
161
|
+
/** Pasta e branch da worktree de uma thread: o que `criarWorktree` cria e o `--dry-run` preve. */
|
|
162
|
+
function alvoDaWorktree(carregado, id, slug, opcoes = {}) {
|
|
163
|
+
return { dir: pastaDaWorktree(carregado, id), branch: opcoes.branchExistente ?? `ork/${slug}` };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* P4 do ensaio da 0.5.0: a worktree que `criarWorktree` criaria para este id, sem criar nada. Mesma pasta, mesma
|
|
167
|
+
* branch e o commit de onde ela partiria: a branch existente (ciclo `merge-branch`), senao a base do manifesto,
|
|
168
|
+
* senao o HEAD. Diz tambem o que faria a criacao recusar: a pasta que ja existe, o repositorio sem commit ou a
|
|
169
|
+
* branch nova que ja existe.
|
|
170
|
+
*/
|
|
171
|
+
function worktreePrevista(carregado, id, slug, opcoes = {}) {
|
|
172
|
+
const alvo = alvoDaWorktree(carregado, id, slug, opcoes);
|
|
173
|
+
const refs = opcoes.branchExistente
|
|
174
|
+
? [opcoes.branchExistente]
|
|
175
|
+
: [opcoes.base ?? carregado.manifesto.worktree.base_branch, 'HEAD'];
|
|
176
|
+
let commit = null;
|
|
177
|
+
for (const ref of refs) {
|
|
178
|
+
const r = (0, util_1.exec)('git', ['rev-parse', '--verify', ref], carregado.raiz);
|
|
179
|
+
if (r.ok) {
|
|
180
|
+
commit = r.stdout.trim();
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const branchJaExiste = !opcoes.branchExistente &&
|
|
185
|
+
(0, util_1.exec)('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${alvo.branch}`], carregado.raiz).ok;
|
|
186
|
+
const falha = fs.existsSync(alvo.dir) ? 'pasta-existe'
|
|
187
|
+
: commit === null ? 'sem-commit' : branchJaExiste ? 'branch-existe' : null;
|
|
188
|
+
return { ...alvo, commit, falha };
|
|
189
|
+
}
|
|
130
190
|
/**
|
|
131
191
|
* Cria a worktree isolada da thread (paralelismo sem colisao entre threads).
|
|
132
192
|
*
|
|
@@ -136,14 +196,16 @@ function idDisponivel(raiz, abbrev, assunto) {
|
|
|
136
196
|
*/
|
|
137
197
|
function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
138
198
|
const { raiz, manifesto } = carregado;
|
|
139
|
-
const dir =
|
|
140
|
-
const branch = opcoes.branchExistente ?? `ork/${slug}`;
|
|
199
|
+
const { dir, branch } = alvoDaWorktree(carregado, id, slug, opcoes);
|
|
141
200
|
if (fs.existsSync(dir)) {
|
|
142
201
|
throw new Error(`worktree ja existe em ${dir}`);
|
|
143
202
|
}
|
|
144
203
|
const base = opcoes.base ?? manifesto.worktree.base_branch;
|
|
145
204
|
const commitBase = (0, util_1.exec)('git', ['rev-parse', '--verify', base], raiz);
|
|
146
205
|
const ref = commitBase.ok ? commitBase.stdout.trim() : 'HEAD';
|
|
206
|
+
// Fatia 2 do ensaio da 0.5.0 (P3): a pasta das worktrees que o ork cria agora nasce fora do git.
|
|
207
|
+
const pastaDasWorktrees = path.dirname(dir);
|
|
208
|
+
const pastaNova = !fs.existsSync(pastaDasWorktrees);
|
|
147
209
|
// Branch nova (`-b`) no caso comum; branch que ja existe no ciclo `merge-branch`.
|
|
148
210
|
const argumentos = opcoes.branchExistente
|
|
149
211
|
? ['worktree', 'add', dir, opcoes.branchExistente]
|
|
@@ -152,6 +214,8 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
|
152
214
|
if (!r.ok) {
|
|
153
215
|
throw new Error(`git worktree add falhou: ${(r.stderr || r.stdout).trim()}`);
|
|
154
216
|
}
|
|
217
|
+
if (pastaNova)
|
|
218
|
+
(0, util_1.ignorarPastaNoGit)(pastaDasWorktrees, (0, estado_thread_1.raizDoEstado)(raiz));
|
|
155
219
|
const lista = (0, util_1.exec)('git', ['worktree', 'list', '--porcelain'], raiz);
|
|
156
220
|
const criada = lista.ok && lista.stdout.split('\n').some((l) => l.trim() === `worktree ${dir}`);
|
|
157
221
|
if (!criada) {
|
|
@@ -160,6 +224,105 @@ function criarWorktree(carregado, id, slug, opcoes = {}) {
|
|
|
160
224
|
(0, estado_thread_1.vincularEstado)(raiz, id, dir);
|
|
161
225
|
return { dir, branch };
|
|
162
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolve `--worktree`, `--sem-worktree` e `worktree.por_thread`. As flags vencem a chave; as duas juntas, ou
|
|
229
|
+
* `--sem-worktree` com valor (o parser leva o argumento seguinte), sao erro de uso. `--worktree` sozinho ou `auto`
|
|
230
|
+
* cria a worktree; outro texto e um diretorio que ja existe, como antes.
|
|
231
|
+
*/
|
|
232
|
+
function pedidoDeWorktree(flags, porThread) {
|
|
233
|
+
const worktree = flags.worktree === false ? undefined : flags.worktree;
|
|
234
|
+
if (typeof worktree === 'string' && !worktree.trim())
|
|
235
|
+
throw new Error('uso: --worktree pede auto ou um diretório que já existe');
|
|
236
|
+
if (typeof flags.semWorktree === 'string') {
|
|
237
|
+
throw new Error(`uso: --sem-worktree não leva valor (recebeu "${flags.semWorktree}")`);
|
|
238
|
+
}
|
|
239
|
+
const semWorktree = flags.semWorktree === true;
|
|
240
|
+
if (semWorktree && worktree !== undefined)
|
|
241
|
+
throw new Error('uso: --worktree e --sem-worktree se excluem; use um dos dois');
|
|
242
|
+
if (semWorktree)
|
|
243
|
+
return { criar: false, dir: null, origem: 'sem-worktree' };
|
|
244
|
+
if (worktree === true || worktree === 'auto')
|
|
245
|
+
return { criar: true, dir: null, origem: 'flag' };
|
|
246
|
+
if (typeof worktree === 'string')
|
|
247
|
+
return { criar: false, dir: worktree, origem: 'diretorio' };
|
|
248
|
+
return porThread ? { criar: true, dir: null, origem: 'chave' } : { criar: false, dir: null, origem: 'nenhuma' };
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* P4 do ensaio da 0.5.0: a linha que diz de onde veio a worktree da thread nova. Na criacao, so quando ela veio da
|
|
252
|
+
* chave (quem passou `--worktree auto` sabe de onde ela veio); no `--dry-run`, para qualquer origem.
|
|
253
|
+
*/
|
|
254
|
+
function linhaDaWorktree(por, gravada, variante) {
|
|
255
|
+
if (!por || (gravada && por !== 'chave'))
|
|
256
|
+
return null;
|
|
257
|
+
const verbo = gravada ? 'criada' : 'seria criada';
|
|
258
|
+
if (por === 'chave') {
|
|
259
|
+
return ` worktree: ${verbo} pela chave worktree.por_thread do orkastery.yaml; para criar sem ela, use --sem-worktree`;
|
|
260
|
+
}
|
|
261
|
+
if (por === 'flag')
|
|
262
|
+
return ` worktree: ${verbo} pelo --worktree auto`;
|
|
263
|
+
return ` worktree: ${verbo} pelo ciclo ${variante ?? 'da thread'}, que exige worktree isolada`;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* P4 do ensaio da 0.5.0: o que acontece no SHIP com a thread criada por `--sem-worktree`. O `ork ship` entrega a
|
|
267
|
+
* branch em que a raiz estava na criacao (`thread.base.branch`), e a policy `push_direto_na_base` do manifesto barra
|
|
268
|
+
* (`block`), avisa (outro valor) ou deixa passar (sem ela, ou `off`) a entrega de uma branch para ela mesma ou da
|
|
269
|
+
* base. Raiz fora de branch (HEAD destacado ou repositorio sem commit) nao tem branch de origem.
|
|
270
|
+
*/
|
|
271
|
+
function avisoDeThreadSemWorktree(thread, manifesto, gravada = true) {
|
|
272
|
+
const base = manifesto.worktree.base_branch;
|
|
273
|
+
const branch = thread.base.branch;
|
|
274
|
+
const inicio = gravada
|
|
275
|
+
? `Aviso: thread ${thread.id} sem worktree (--sem-worktree): ela trabalha na raiz do projeto`
|
|
276
|
+
: `Aviso: sem worktree (--sem-worktree), a thread ${thread.id} trabalharia na raiz do projeto`;
|
|
277
|
+
const ensure = `Antes do GO, ork worktree ensure ${thread.id} cria a worktree e a branch da thread`;
|
|
278
|
+
if (thread.base.commit === util_1.COMMIT_DESCONHECIDO) {
|
|
279
|
+
// Sem commit, nem o `ork worktree ensure` tem de onde partir: o caminho e o do aviso de thread sem base.
|
|
280
|
+
return `${inicio}, num repositório ainda sem commit, fora de qualquer branch, e o ork ship não tem branch de origem ` +
|
|
281
|
+
'para entregar. Faça o primeiro commit e siga o aviso de thread sem base.';
|
|
282
|
+
}
|
|
283
|
+
if (branch === 'HEAD') {
|
|
284
|
+
return `${inicio}, com o HEAD destacado, fora de qualquer branch, e o ork ship não tem branch de origem para entregar. ${ensure}.`;
|
|
285
|
+
}
|
|
286
|
+
if (branch !== base) {
|
|
287
|
+
return `${inicio}, na branch ${branch}, e o ork ship entrega essa branch como estiver, com o que mais entrar nela; ` +
|
|
288
|
+
`com --para ${branch}, a entrega vira push direto, que a policy push_direto_na_base confere. ${ensure}.`;
|
|
289
|
+
}
|
|
290
|
+
const policy = manifesto.policies?.push_direto_na_base;
|
|
291
|
+
const efeito = policy === 'block'
|
|
292
|
+
? 'o ork ship barra a entrega por push_direto_na_base (block): não há branch de thread para mergear'
|
|
293
|
+
: policy === undefined || policy === 'off'
|
|
294
|
+
? 'o ork ship empurra a base direto, sem branch de thread (push_direto_na_base desligada)'
|
|
295
|
+
: `o ork ship avisa push_direto_na_base (${policy}) e empurra a base direto, sem branch de thread`;
|
|
296
|
+
return `${inicio}, na branch base ${base}, e ${efeito}. ${ensure}; depois do GO, os commits já estão na base e o ship não os separa.`;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* P4 do ensaio da 0.5.0 (CHECK): no `--dry-run`, por que a criacao de verdade recusaria a worktree, em vez de preve-la.
|
|
300
|
+
* O ciclo que exige worktree nao aceita `--sem-worktree`, entao a saida so e sugerida para a chave e a flag.
|
|
301
|
+
*/
|
|
302
|
+
function avisoDeWorktreeQueFalharia(f) {
|
|
303
|
+
const semWorktree = f.por === 'ciclo' ? '' : ' ou crie com --sem-worktree';
|
|
304
|
+
if (f.motivo === 'pasta-existe') {
|
|
305
|
+
return `Aviso: a pasta ${f.dir} já existe, e a criação de verdade recusaria a worktree da thread: tire a pasta${semWorktree}.`;
|
|
306
|
+
}
|
|
307
|
+
if (f.motivo === 'branch-existe') {
|
|
308
|
+
return `Aviso: a branch ${f.branch} já existe, e a criação de verdade recusaria a worktree da thread: tire a branch${semWorktree}.`;
|
|
309
|
+
}
|
|
310
|
+
return 'Aviso: o repositório ainda não tem commit, e a criação de verdade recusaria a worktree da thread no git worktree add: ' +
|
|
311
|
+
'faça o primeiro commit antes.';
|
|
312
|
+
}
|
|
313
|
+
/** P4 do ensaio da 0.5.0: a worktree que a chave pede nao tem de onde partir num repositorio sem commit. */
|
|
314
|
+
function avisoDeChaveSemCommit(gravada) {
|
|
315
|
+
return 'Aviso: worktree.por_thread pede a worktree da thread, mas o repositório ainda não tem commit: ' +
|
|
316
|
+
`a thread ${gravada ? 'nasceu' : 'nasceria'} na raiz do projeto, sem worktree.`;
|
|
317
|
+
}
|
|
318
|
+
/** P4: o aviso de worktree do `ork thread new`, para o stderr, ou `null` quando nao ha o que avisar. */
|
|
319
|
+
function avisoDaWorktree(origem, resultado, manifesto) {
|
|
320
|
+
if (origem === 'sem-worktree')
|
|
321
|
+
return avisoDeThreadSemWorktree(resultado.thread, manifesto, resultado.gravada);
|
|
322
|
+
if (origem === 'chave' && !resultado.worktreePor)
|
|
323
|
+
return avisoDeChaveSemCommit(resultado.gravada);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
163
326
|
/** Cria a thread: valida modo contra o manifesto, gera o slug e carimba a base. */
|
|
164
327
|
function novaThread(carregado, opcoes) {
|
|
165
328
|
if (opcoes.dryRun)
|
|
@@ -217,6 +380,13 @@ function criarThreadSerializada(carregado, opcoes) {
|
|
|
217
380
|
else if (opcoes.branch) {
|
|
218
381
|
throw new Error('--branch so vale com --ciclo merge-branch');
|
|
219
382
|
}
|
|
383
|
+
// `greenfield`, `merge-branch` e `feature-xl-faseada` exigem worktree isolada: sao os
|
|
384
|
+
// ciclos que escrevem codigo em paralelo com outras threads.
|
|
385
|
+
const exigeWorktree = variante ? (0, ciclos_1.definicaoDaVariante)(variante).exigeWorktree : false;
|
|
386
|
+
// P4 do ensaio da 0.5.0 (D4): o ciclo que exige worktree nao nasce sem ela, e a recusa sai antes de gravar.
|
|
387
|
+
if (exigeWorktree && opcoes.origemDaWorktree === 'sem-worktree') {
|
|
388
|
+
throw new Error(`o ciclo ${variante} exige worktree isolada: crie a thread sem --sem-worktree ou escolha outro ciclo`);
|
|
389
|
+
}
|
|
220
390
|
const reservation = opcoes.reservation;
|
|
221
391
|
const operation = reservation ? (0, creation_operation_store_1.readCreationOperation)(raiz, reservation.operationId, reservation.principal) : null;
|
|
222
392
|
if (operation && (!operation.reserved.threadIds.includes(reservation.threadId)
|
|
@@ -241,11 +411,11 @@ function criarThreadSerializada(carregado, opcoes) {
|
|
|
241
411
|
&& fs.readdirSync(dirThread(raiz, reservation.threadId)).some((name) => !/^\.thread\.json\..+\.tmp$/.test(name))) {
|
|
242
412
|
throw new Error('creation.conflict: diretório reservado sem origem confirmada');
|
|
243
413
|
}
|
|
414
|
+
// P4 do ensaio da 0.5.0 (CHECK): o ensaio usa o mesmo id livre da criacao (o `idDisponivel` so le o disco), para a
|
|
415
|
+
// worktree que ele mostra ser a que a criacao usaria.
|
|
244
416
|
const { id, assunto } = reservation
|
|
245
417
|
? { id: reservation.threadId, assunto: reservation.threadId.slice(abbrev.length + 1) }
|
|
246
|
-
:
|
|
247
|
-
? { id: `${abbrev}-${assuntoBruto}`, assunto: assuntoBruto }
|
|
248
|
-
: idDisponivel(raiz, abbrev, assuntoBruto);
|
|
418
|
+
: idDisponivel(raiz, abbrev, assuntoBruto);
|
|
249
419
|
const slug = opcoes.slug ?? (0, slug_1.montarSlug)(abbrev, assunto, blocos[0].slugFases);
|
|
250
420
|
// O override `--slug` do builder tambem passa pela regex canonica.
|
|
251
421
|
if (!(0, slug_1.slugValido)(slug)) {
|
|
@@ -281,13 +451,38 @@ function criarThreadSerializada(carregado, opcoes) {
|
|
|
281
451
|
};
|
|
282
452
|
if (variante === 'feature-xl-faseada')
|
|
283
453
|
thread.fatias = opcoes.fatias ?? 3;
|
|
284
|
-
|
|
285
|
-
|
|
454
|
+
// P4 do ensaio da 0.5.0: por que a thread ganha a worktree. O ensaio e a chave preveem a worktree antes; a pedida
|
|
455
|
+
// por flag ou por ciclo segue direto ao git, como era. A pedida so pela chave espera o primeiro commit (D3): sem
|
|
456
|
+
// commit, a thread fica na raiz, como antes. O ciclo que exige worktree vence a chave e a flag na origem: a worktree
|
|
457
|
+
// e dele, e o `--sem-worktree` que a linha da chave sugeriria ele recusa (CHECK, rodada 2).
|
|
458
|
+
const pedeWorktree = opcoes.criarWorktree === true || exigeWorktree;
|
|
459
|
+
const pelaChave = opcoes.criarWorktree === true && opcoes.origemDaWorktree === 'chave';
|
|
460
|
+
const prevista = pedeWorktree && (opcoes.dryRun || pelaChave)
|
|
461
|
+
? worktreePrevista(carregado, id, slug, { branchExistente: opcoes.branch }) : null;
|
|
462
|
+
const worktreePor = !pedeWorktree || (pelaChave && !exigeWorktree && prevista?.falha === 'sem-commit') ? null
|
|
463
|
+
: exigeWorktree ? 'ciclo' : pelaChave ? 'chave' : 'flag';
|
|
464
|
+
if (opcoes.dryRun) {
|
|
465
|
+
// P4 (D8): o ensaio mostra a worktree que a criacao usaria; quando ela recusaria, diz por que, em vez de preve-la.
|
|
466
|
+
if (worktreePor && prevista?.falha) {
|
|
467
|
+
return { thread, gravada: false, worktreePor: null,
|
|
468
|
+
worktreeFalharia: { motivo: prevista.falha, dir: prevista.dir, branch: prevista.branch, por: worktreePor } };
|
|
469
|
+
}
|
|
470
|
+
if (worktreePor && prevista?.commit) {
|
|
471
|
+
thread.worktree = prevista.dir;
|
|
472
|
+
thread.base = { branch: prevista.branch, commit: prevista.commit };
|
|
473
|
+
}
|
|
474
|
+
return { thread, gravada: false, worktreePor };
|
|
475
|
+
}
|
|
476
|
+
// P4 (CHECK): a worktree pedida so pela chave, com a pasta ou a branch ja ocupadas, recusa dizendo de onde veio o
|
|
477
|
+
// pedido e qual e a saida.
|
|
478
|
+
const ocupadaPelaChave = pelaChave && !exigeWorktree && prevista &&
|
|
479
|
+
(prevista.falha === 'pasta-existe' || prevista.falha === 'branch-existe') ? prevista : null;
|
|
480
|
+
if (ocupadaPelaChave) {
|
|
481
|
+
const qual = ocupadaPelaChave.falha === 'pasta-existe' ? `a pasta ${ocupadaPelaChave.dir}` : `a branch ${ocupadaPelaChave.branch}`;
|
|
482
|
+
throw new Error(`${qual} já existe, e a chave worktree.por_thread pede a worktree da thread: tire-a ou crie com --sem-worktree`);
|
|
483
|
+
}
|
|
286
484
|
let worktreeCriada = null;
|
|
287
|
-
|
|
288
|
-
// ciclos que escrevem codigo em paralelo com outras threads.
|
|
289
|
-
const exigeWorktree = variante ? (0, ciclos_1.definicaoDaVariante)(variante).exigeWorktree : false;
|
|
290
|
-
if (opcoes.criarWorktree || exigeWorktree) {
|
|
485
|
+
if (worktreePor) {
|
|
291
486
|
worktreeCriada = criarWorktree(carregado, id, slug, {
|
|
292
487
|
branchExistente: opcoes.branch,
|
|
293
488
|
});
|
|
@@ -309,6 +504,8 @@ function criarThreadSerializada(carregado, opcoes) {
|
|
|
309
504
|
base: thread.base,
|
|
310
505
|
projeto: manifesto.project.name,
|
|
311
506
|
worktree: thread.worktree,
|
|
507
|
+
// P4 do ensaio da 0.5.0 (D9): quem abriu a thread sem worktree de proposito fica no rastro.
|
|
508
|
+
...(opcoes.origemDaWorktree === 'sem-worktree' ? { semWorktree: true } : {}),
|
|
312
509
|
variante,
|
|
313
510
|
branchDeOrigem: opcoes.branch ?? null,
|
|
314
511
|
maquina: thread.maquina,
|
|
@@ -319,10 +516,11 @@ function criarThreadSerializada(carregado, opcoes) {
|
|
|
319
516
|
dir: worktreeCriada.dir,
|
|
320
517
|
branch: worktreeCriada.branch,
|
|
321
518
|
base: manifesto.worktree.base_branch,
|
|
519
|
+
origem: worktreePor,
|
|
322
520
|
fonte: 'git worktree list --porcelain',
|
|
323
521
|
});
|
|
324
522
|
}
|
|
325
|
-
return { thread, gravada: true };
|
|
523
|
+
return { thread, gravada: true, worktreePor };
|
|
326
524
|
}
|
|
327
525
|
/**
|
|
328
526
|
* O bloco de loop que conduz a fase DESTA thread.
|
|
@@ -362,7 +560,7 @@ function resumoDaThread(thread, conducao) {
|
|
|
362
560
|
linhas.push(` ciclo ${thread.variante ?? 'padrao do modo'}`);
|
|
363
561
|
linhas.push(` fase ${thread.faseAtual}`);
|
|
364
562
|
linhas.push(` status ${thread.status}`);
|
|
365
|
-
linhas.push(` base ${thread.base.branch} @ ${thread.base.commit
|
|
563
|
+
linhas.push(` base ${thread.base.branch} @ ${(0, util_1.shaCurto)(thread.base.commit)}`);
|
|
366
564
|
linhas.push(` worktree ${thread.worktree ?? '(raiz do projeto)'}`);
|
|
367
565
|
// I-36 (T17): quem passa a leitura da conducao ve a linha unica; sem ela o resumo fica como era.
|
|
368
566
|
if (conducao !== undefined)
|