@orkastery/cli 0.4.3 → 0.5.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/adapters/README.md +11 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +3 -1
- package/adapters/claude-code/agents/ork-check.md +1 -1
- package/adapters/claude-code/agents/ork-go.md +2 -2
- package/adapters/claude-code/agents/ork-goal.md +1 -1
- package/adapters/claude-code/agents/ork-master.md +1 -1
- package/adapters/claude-code/agents/ork-plan.md +1 -1
- package/adapters/claude-code/agents/ork-ship.md +1 -1
- package/adapters/claude-code/commands/master.md +11 -8
- package/adapters/claude-code/commands/ork.md +8 -0
- package/adapters/claude-code/commands/ship.md +2 -2
- package/adapters/codex/skills/ork/SKILL.md +12 -1
- package/adapters/hermes/README.md +3 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +17 -2
- package/adapters/hermes/bin/ork-brain.sh +3 -0
- package/adapters/hermes/bin/ork-maestro.sh +4 -1
- package/adapters/hermes/bin/ork-network-roadmap.sh +11 -0
- package/adapters/hermes/bin/ork-roadmap-status.sh +9 -0
- package/adapters/hermes/hermes.plugin.json +2 -0
- package/adapters/hermes/hitl-ingress/__init__.py +36 -3
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +19 -20
- package/adapters/openclaw/README.md +34 -2
- package/adapters/openclaw/dist/hitl-ingress.js +37 -3
- package/adapters/openclaw/dist/index.js +92 -20
- package/adapters/openclaw/openclaw.plugin.json +10 -1
- package/adapters/openclaw/package.json +2 -2
- package/adapters/openclaw/src/hitl-ingress.ts +32 -3
- package/adapters/openclaw/src/index.ts +108 -20
- package/assets/docs/padroes/documentacao-de-produto.md +2 -2
- package/assets/docs/padroes/roadmap-de-produto.md +1 -1
- package/assets/docs/produto/_modelo-feature.md +2 -0
- package/assets/orkmind_bridge.py +214 -1
- package/dist/adapters/claude-bg.js +6 -3
- package/dist/board.js +74 -0
- package/dist/branch-de-estado.js +5 -4
- package/dist/busca-semantica.js +161 -0
- package/dist/ci.js +141 -10
- package/dist/company-brain-cli.js +27 -2
- package/dist/company-brain-context.js +132 -0
- package/dist/company-brain-dossie.js +376 -0
- package/dist/company-brain-mcp.js +4 -1
- package/dist/conducao.js +71 -18
- package/dist/decisao-autonoma.js +42 -0
- package/dist/docs.js +64 -22
- package/dist/doctor.js +33 -0
- package/dist/entrega-pr.js +109 -5
- package/dist/experiencia-instalacao.js +256 -0
- package/dist/experiencia.js +72 -0
- package/dist/fabrica-estado.js +43 -10
- package/dist/fechamento.js +105 -0
- package/dist/forja.js +435 -0
- package/dist/gates.js +9 -1
- package/dist/hitl-classificacao.js +11 -1
- package/dist/hitl-contract.js +50 -7
- package/dist/hitl-curto.js +158 -0
- package/dist/hitl-gates.js +67 -1
- package/dist/hitl-lote.js +30 -15
- package/dist/hitl-presentation.js +10 -7
- package/dist/hitl-resumo.js +33 -12
- package/dist/hitl-texto-livre.js +150 -0
- package/dist/hosts.js +86 -1
- package/dist/index.js +602 -38
- package/dist/indice-vetorial.js +317 -0
- package/dist/init.js +13 -0
- package/dist/integracoes-locais.js +1 -1
- package/dist/intelligence-benchmark-contract.js +570 -0
- package/dist/intelligence-graph-cli.js +430 -0
- package/dist/intelligence-graph-contract.js +577 -0
- package/dist/intelligence-graph-extract-md.js +469 -0
- package/dist/intelligence-graph-extract-ts.js +810 -0
- package/dist/intelligence-graph-extract.js +369 -0
- package/dist/intelligence-graph-index.js +512 -0
- package/dist/intelligence-graph-parsers.js +327 -0
- package/dist/intelligence-graph-query.js +344 -0
- package/dist/intelligence-graph-repo.js +187 -0
- package/dist/leases.js +84 -0
- package/dist/ledger.js +14 -0
- package/dist/maestro-cli.js +10 -1
- package/dist/maestro-contract.js +5 -1
- package/dist/maestro-discovery.js +16 -2
- package/dist/maestro-snapshot.js +6 -2
- package/dist/manifest.js +142 -5
- package/dist/master-batch.js +3 -2
- package/dist/master-digest.js +2 -2
- package/dist/master-nota.js +203 -0
- package/dist/master.js +4 -0
- package/dist/mcp-experiencia.js +164 -0
- package/dist/mcp-git.js +4 -0
- package/dist/mcp-install.js +3 -1
- package/dist/mcp-maestro.js +3 -2
- package/dist/mcp-server.js +84 -5
- package/dist/memoria.js +147 -1
- package/dist/network-roadmap.js +907 -0
- package/dist/ocupacao.js +38 -2
- package/dist/onboarding.js +113 -7
- package/dist/orkmind.js +165 -9
- package/dist/phase.js +166 -13
- package/dist/policies.js +2 -0
- package/dist/projeto-alvo.js +551 -0
- package/dist/prompts.js +1 -1
- package/dist/pulse-consentimento.js +4 -2
- package/dist/pulse-delivery.js +5 -0
- package/dist/pulse-resposta.js +674 -49
- package/dist/pulse.js +21 -0
- package/dist/retry.js +51 -8
- package/dist/roadmap-reservas.js +262 -20
- package/dist/roadmap-status.js +213 -0
- package/dist/thread-close.js +3 -0
- package/dist/verify.js +4 -3
- package/eval/casos/orchestration-experience-pt-br.json +48 -0
- package/eval/casos/orchestration-experience.json +48 -0
- package/package.json +1 -1
- package/schemas/code-artifact-graph.v1.schema.json +581 -0
- package/schemas/graph-benchmark.v1.schema.json +2240 -0
- package/schemas/maestro-snapshot.schema.json +23 -0
- package/skills/README.md +7 -5
- package/skills/core/onboarding/SKILL.md +7 -0
- package/skills/core/orchestration-experience/SKILL.md +113 -0
- package/skills/core/orchestration-experience-pt-br/SKILL.md +112 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +2 -2
- package/skills/phases/go-implementation/SKILL.md +2 -0
|
@@ -76,6 +76,9 @@ async function rodarOrk(args, signal, entrada) {
|
|
|
76
76
|
env.ORK_RECEIPT_VERIFIERS = verifiers;
|
|
77
77
|
// I-36 (D6): o adaptador declara o proprio canal de conducao. Descreve a porta, nao concede autoridade.
|
|
78
78
|
env.ORK_CANAL = 'openclaw';
|
|
79
|
+
// RM-052 (D3): o cwd do gateway nao e projeto de ninguem. Sem `projeto` pedido, o nucleo usa o unico
|
|
80
|
+
// projeto conhecido da maquina ou devolve a escolha; nunca o manifesto que estiver no cwd do gateway.
|
|
81
|
+
env.ORK_PROJETO_EXPLICITO = '1';
|
|
79
82
|
return new Promise((resolve) => {
|
|
80
83
|
const filho = execFile(bin, args, { maxBuffer: MAX_BUFFER, signal, env }, (erro, stdout, stderr) => {
|
|
81
84
|
const partes = [stdout, stderr].map((t) => (t ?? '').trim()).filter((t) => t !== '');
|
|
@@ -156,10 +159,36 @@ function argvDaResposta(p, alvo) {
|
|
|
156
159
|
return [alvo, 'answer', thread, pedido, '--resposta-stdin', '--por', `telegram:${usuario}`,
|
|
157
160
|
'--mensagem', `telegram:${chat}:${referencia}`, '--origem', 'telegram'];
|
|
158
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* RM-052 (D4): o projeto pedido chega como NOME de projeto registrado (`ork projetos`), nunca como
|
|
164
|
+
* caminho. O modelo escolhe entre os projetos da maquina; nao aponta o nucleo para um diretorio.
|
|
165
|
+
*/
|
|
166
|
+
const PADRAO_DO_PROJETO = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
|
|
167
|
+
const PARAMETRO_PROJETO = {
|
|
168
|
+
type: 'string',
|
|
169
|
+
pattern: PADRAO_DO_PROJETO.source,
|
|
170
|
+
description: 'Nome do projeto que o dono pediu (ex.: orkastery), como em `ork projetos`. Sem ele e com mais de um projeto na máquina, a resposta é a escolha; nunca use o diretório do gateway.',
|
|
171
|
+
};
|
|
172
|
+
const PROJETO_DA_RM052 = {
|
|
173
|
+
padrao: PADRAO_DO_PROJETO,
|
|
174
|
+
descricao: PARAMETRO_PROJETO.description,
|
|
175
|
+
recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery), nunca um caminho',
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* RM-054 (fatia 2, D-G4): no roadmap da rede, `projeto` tambem aceita a forja (`github:dono/repo`,
|
|
179
|
+
* `gitlab:grupo/repo`): numa maquina sem clone, e o unico caminho ate o projeto enquanto a rede por
|
|
180
|
+
* pessoa (RM-053) nao chega. Caminho e URL continuam fora (a URL pode levar credencial), e o nucleo
|
|
181
|
+
* confere de novo, porque o adaptador declara ORK_PROJETO_EXPLICITO=1.
|
|
182
|
+
*/
|
|
183
|
+
const PROJETO_DA_REDE = {
|
|
184
|
+
padrao: /^(?:[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}|(?:github|gitlab):[A-Za-z0-9][A-Za-z0-9._-]{0,99}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){1,8})$/,
|
|
185
|
+
descricao: 'Projeto que o dono pediu: o nome, como em `ork projetos` (ex.: orkastery), ou a forja (github:dono/repo, gitlab:grupo/repo) quando não há clone nesta máquina. Sem ele, vêm todos os projetos conhecidos. Nunca caminho nem URL.',
|
|
186
|
+
recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery) ou github:dono/repo, nunca caminho nem URL',
|
|
187
|
+
};
|
|
159
188
|
const FERRAMENTAS = [
|
|
160
189
|
{
|
|
161
190
|
name: 'ork_maestro',
|
|
162
|
-
description: 'Ao receber a frase exata orkastery maestro,
|
|
191
|
+
description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback.',
|
|
163
192
|
parameters: { type: 'object', additionalProperties: false, properties: {
|
|
164
193
|
thread: { type: 'string' }, section: { type: 'string', enum: ['portfolio', 'demands', 'threads', 'sessions', 'blockers', 'leases', 'retries', 'hitl', 'ship', 'master', 'nextActions'] },
|
|
165
194
|
offset: { type: 'integer', minimum: 0, maximum: 100000 },
|
|
@@ -281,6 +310,27 @@ const FERRAMENTAS = [
|
|
|
281
310
|
parameters: schema({ thread: { type: 'string' }, id: { type: 'string' } }),
|
|
282
311
|
argv: (p) => ['brain', 'get', texto(p, 'id'), '--thread', texto(p, 'thread')],
|
|
283
312
|
},
|
|
313
|
+
{
|
|
314
|
+
name: 'ork_brain_context',
|
|
315
|
+
description: 'Pacote de contexto citável do Company Brain: entidades pedidas e seus pais, cada uma com a citação da fonte, o frescor contra o portfólio e as lacunas. Só leitura.',
|
|
316
|
+
parameters: schema({
|
|
317
|
+
thread: { type: 'string' },
|
|
318
|
+
ids: { type: 'string', description: 'ids do portfólio separados por vírgula (prod-, proj-, init-)' },
|
|
319
|
+
}),
|
|
320
|
+
argv: (p) => ['brain', 'context', '--thread', texto(p, 'thread'), '--ids', texto(p, 'ids')],
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'ork_brain_dossie',
|
|
324
|
+
description: 'Dossiê de decisão da thread: vínculo com objetivo e projeto, contexto citável, alternativas, decisão, quem decidiu e evidência, com os ids do Company Brain e as lacunas. Resposta do dono só com recibo conferido. Só leitura.',
|
|
325
|
+
parameters: {
|
|
326
|
+
type: 'object', additionalProperties: false, required: ['thread'],
|
|
327
|
+
properties: {
|
|
328
|
+
thread: { type: 'string' },
|
|
329
|
+
decisao: { type: 'string', description: 'id do pedido ou fact- da decisão; omita para a thread inteira' },
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
argv: (p) => ['brain', 'dossie', '--thread', texto(p, 'thread'), ...(p.decisao === undefined ? [] : ['--decisao', texto(p, 'decisao')])],
|
|
333
|
+
},
|
|
284
334
|
{
|
|
285
335
|
name: 'ork_claims_add',
|
|
286
336
|
description: 'Registra uma alegacao verificavel com o comando que a comprova. Toda citacao de arquivo ou teste vira claim.',
|
|
@@ -358,30 +408,32 @@ const FERRAMENTAS = [
|
|
|
358
408
|
},
|
|
359
409
|
{
|
|
360
410
|
name: 'ork_master',
|
|
361
|
-
description: '
|
|
411
|
+
description: 'Pede ao dono a nota do MASTER (0 a 5, com o porque) de uma entrega: devolve a linha com o codigo curto para mostrar a ele. A nota volta pelo Telegram, com a prova do ingresso. Esta tool nunca grava nota e nao recebe nome de pessoa (RM-048).',
|
|
362
412
|
parameters: schema({
|
|
363
413
|
thread: { type: 'string' },
|
|
364
|
-
score: { type: 'string', description: 'inteiro de 0 a 5, dado pelo humano' },
|
|
365
|
-
justificativa: { type: 'string' },
|
|
366
|
-
quem: { type: 'string' },
|
|
367
414
|
}),
|
|
368
|
-
argv: (p) => [
|
|
369
|
-
'master',
|
|
370
|
-
texto(p, 'thread'),
|
|
371
|
-
'--score',
|
|
372
|
-
texto(p, 'score'),
|
|
373
|
-
'--justificativa',
|
|
374
|
-
texto(p, 'justificativa'),
|
|
375
|
-
'--por',
|
|
376
|
-
texto(p, 'quem'),
|
|
377
|
-
],
|
|
415
|
+
argv: (p) => ['master', 'pedir', texto(p, 'thread'), '--formato', 'telegram'],
|
|
378
416
|
},
|
|
379
417
|
{
|
|
380
418
|
name: 'ork_board',
|
|
381
|
-
description: '
|
|
419
|
+
description: 'As threads DESTE projeto nesta maquina e o escalonador dizendo quem avanca agora e quem espera. NAO le o roadmap: nunca conclua sobre o roadmap a partir do board (zero threads nao e roadmap vazio); para o roadmap use ork_network_roadmap. O cabecalho diz o projeto consultado e o que nao foi lido.',
|
|
382
420
|
parameters: schema({}),
|
|
383
421
|
argv: () => ['board', 'plan'],
|
|
384
422
|
},
|
|
423
|
+
{
|
|
424
|
+
name: 'ork_roadmap_status',
|
|
425
|
+
description: 'Status report do roadmap SO desta maquina (o checkout local), no formato aprovado pelo dono (grupos com icones, #HITL e o fecho): nao le as reservas nem as outras maquinas, e o cabecalho diz isso. Para o status do roadmap com as threads de todas as maquinas, as reservas, as fontes e as lacunas, use ork_network_roadmap. Somente leitura: transporte o texto como vem, sem reescrever. Passe projeto com o nome que o dono pediu (ex.: orkastery); sem ele e com mais de um projeto na maquina, a resposta e a escolha. Nunca deduza o roadmap de ork_board ou ork_maestro.',
|
|
426
|
+
parameters: schema({}),
|
|
427
|
+
argv: () => ['roadmap', 'status'],
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: 'ork_network_roadmap',
|
|
431
|
+
description: 'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas.',
|
|
432
|
+
parameters: schema({}),
|
|
433
|
+
projeto: PROJETO_DA_REDE,
|
|
434
|
+
// RM-054 (fatia 2): uma chamada de CLI; o `--projeto` vai no inicio e o nucleo o devolve ao `network`.
|
|
435
|
+
argv: () => ['network', 'roadmap'],
|
|
436
|
+
},
|
|
385
437
|
{
|
|
386
438
|
name: 'ork_master_batch',
|
|
387
439
|
description: 'Todas as entregas em JSON, com o indice derivado do ledger, as ja pontuadas e as aceitas por omissao. Roda ork master --todas; o nome e da antiga fila de score (ork master --batch), aposentada na I-43.',
|
|
@@ -389,21 +441,41 @@ const FERRAMENTAS = [
|
|
|
389
441
|
argv: () => ['master', '--todas', '--json'],
|
|
390
442
|
},
|
|
391
443
|
];
|
|
444
|
+
/** RM-052: toda tool aceita `projeto` opcional, sem mudar o que ela ja exigia. */
|
|
445
|
+
function comProjeto(f) {
|
|
446
|
+
const propriedades = (f.parameters.properties ?? {});
|
|
447
|
+
const p = f.projeto;
|
|
448
|
+
const projeto = p ? { type: 'string', pattern: p.padrao.source, description: p.descricao } : PARAMETRO_PROJETO;
|
|
449
|
+
return { ...f.parameters, properties: { ...propriedades, projeto } };
|
|
450
|
+
}
|
|
451
|
+
/** `--projeto <nome>` vai no inicio do argv; o resto dos parametros segue para a tool como antes. */
|
|
452
|
+
function argvComProjeto(f, params) {
|
|
453
|
+
const { projeto, ...resto } = params;
|
|
454
|
+
if (projeto !== undefined && (typeof projeto !== 'string' || !(f.projeto ?? PROJETO_DA_RM052).padrao.test(projeto))) {
|
|
455
|
+
throw new Error('projeto.invalido');
|
|
456
|
+
}
|
|
457
|
+
return { argv: [...(projeto === undefined ? [] : ['--projeto', projeto]), ...f.argv(resto)], resto };
|
|
458
|
+
}
|
|
392
459
|
const plugin = defineToolPlugin({
|
|
393
460
|
id: 'orkastery',
|
|
394
461
|
name: 'Orkastery',
|
|
395
|
-
description: 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.',
|
|
462
|
+
description: 'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap.',
|
|
396
463
|
tools: (tool) => FERRAMENTAS.map((f) => tool({
|
|
397
464
|
name: f.name,
|
|
398
465
|
description: f.description,
|
|
399
|
-
parameters: f
|
|
466
|
+
parameters: comProjeto(f),
|
|
400
467
|
execute: (params, _config, contexto) => {
|
|
468
|
+
let chamada;
|
|
401
469
|
try {
|
|
402
|
-
|
|
470
|
+
chamada = argvComProjeto(f, params);
|
|
403
471
|
}
|
|
404
|
-
catch {
|
|
472
|
+
catch (e) {
|
|
473
|
+
if (e.message === 'projeto.invalido') {
|
|
474
|
+
return `[ork recusou] projeto.invalido: ${(f.projeto ?? PROJETO_DA_RM052).recusa}`;
|
|
475
|
+
}
|
|
405
476
|
return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido';
|
|
406
477
|
}
|
|
478
|
+
return rodarOrk(chamada.argv, contexto.signal, f.entrada?.(chamada.resto));
|
|
407
479
|
},
|
|
408
480
|
})),
|
|
409
481
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "orkastery",
|
|
3
3
|
"name": "Orkastery",
|
|
4
4
|
"description": "Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.5.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {},
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"ork_brain_status",
|
|
26
26
|
"ork_brain_query",
|
|
27
27
|
"ork_brain_get",
|
|
28
|
+
"ork_brain_context",
|
|
29
|
+
"ork_brain_dossie",
|
|
28
30
|
"ork_claims_add",
|
|
29
31
|
"ork_verify",
|
|
30
32
|
"ork_verify_baseline",
|
|
@@ -35,7 +37,14 @@
|
|
|
35
37
|
"ork_ship",
|
|
36
38
|
"ork_master",
|
|
37
39
|
"ork_board",
|
|
40
|
+
"ork_roadmap_status",
|
|
41
|
+
"ork_network_roadmap",
|
|
38
42
|
"ork_master_batch"
|
|
39
43
|
]
|
|
44
|
+
},
|
|
45
|
+
"toolMetadata": {
|
|
46
|
+
"ork_network_roadmap": {
|
|
47
|
+
"profiles": ["coding", "messaging"]
|
|
48
|
+
}
|
|
40
49
|
}
|
|
41
50
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkastery/openclaw-plugin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Plugin Orkastery para OpenClaw:
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Plugin Orkastery para OpenClaw: 27 tools ork_* que traduzem intencao em chamada de CLI do nucleo `ork`. Zero regra de negocio no host.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": true,
|
|
7
7
|
"license": "MIT",
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/** D12: this host is the `openclaw` channel, and the channel is signed, not labelled. */
|
|
3
3
|
import { createHash, createHmac } from 'node:crypto';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { isAbsolute, join } from 'node:path';
|
|
5
7
|
|
|
6
8
|
interface NativeEvent {
|
|
7
9
|
content: string; channel: string; commandAuthorized?: boolean; accountId?: string;
|
|
@@ -18,14 +20,41 @@ interface NativeApi { on: (name: 'inbound_claim', handler: (event: NativeEvent,
|
|
|
18
20
|
* stay equal. The code starts with a letter and has a digit: ordinary chat never has this shape,
|
|
19
21
|
* and it never reads as an answer to the batch, which starts with the question number.
|
|
20
22
|
*/
|
|
21
|
-
const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,
|
|
23
|
+
const PULSE_CONSENT = new RegExp("^[ \\t]*(?=[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{0,2}[2-9])[A-HJKMNP-TV-Z][2-9A-HJKMNP-TV-Z]{3}[ \\t]+[^\\r\\n]{1,200}$", 'i');
|
|
22
24
|
const PULSE_LOTE = new RegExp("^[ \\t]*[0-9]{1,2}[ \\t]*[a-zA-Z](?:[ \\t,;]*[0-9]{1,2}[ \\t]*[a-zA-Z])*[ \\t]*[.!]?[ \\t]*$");
|
|
23
25
|
/** I-50 (RM-039): the pulse cadence tag, alone in the message. It starts with '#', so it never reads as the other two. */
|
|
24
26
|
const PULSE_CADENCIA = new RegExp("^[ \\t]*#OrkPulse(?:On(?:-(?:15|30|60)m)?|Off)[ \\t]*[.!]?[ \\t]*$", 'i');
|
|
25
27
|
/** The pulse address inside the signed body: neither a thread nor a request. The core translates. */
|
|
26
28
|
const PULSE_ALVO = 'pulse', PULSE_ENDERECO = 'resposta';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* RM-048 (D2 and D3): the free-text shapes, generated in the core from the closed vocabulary. `lista`
|
|
31
|
+
* ("1. B, 2. aprovo") is always claimed; `livre` ("sim", "aprovo", "a") only while the core's
|
|
32
|
+
* listening window is open: outside it, the word goes to the assistant, as it always did. The
|
|
33
|
+
* window is a routing hint, not proof: the message still goes through the signature and the core rule.
|
|
34
|
+
*/
|
|
35
|
+
const PULSE_LISTA = new RegExp("^[ \\t]*[0-9]{1,2}(?:[ \\t]*[.):-])?[ \\t]*(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?)(?:[ \\t\\r\\n,;]*[0-9]{1,2}(?:[ \\t]*[.):-])?[ \\t]*(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?))*[ \\t]*(?:[.!][ \\t]*)?$", 'i');
|
|
36
|
+
const PULSE_LIVRE = new RegExp("^[ \\t]*(?:[1-4]|(?:[a-d]|pode seguir|pode ir|pode|sim|s|ok|aprovo|aprovado|aprovada|aprovar|aprova|segue|siga|seguir|manda|confirmo|confirmado|de acordo|agora n[aã]o|n[aã]o|n|recuso|recusado|recusar|reprovo|reprovado|revisar|revis[aã]o|refazer|volta|voltar|esperar|espera|aguardar|aguarda|aguarde|depois|mais tarde|detalhes?|evid[eê]ncias?))[ \\t]*(?:[.!]+[ \\t]*)?$", 'i');
|
|
37
|
+
/** RM-048 (item 8): the digest keyboard line goes to the core at the same signed address. */
|
|
38
|
+
const PULSE_RATIFICACAO = new RegExp("^[ \\t]*ratificar(?:-lote)?(?:[ \\t]+[A-Za-z0-9._-]{1,80}){2,3}[ \\t]*$", 'i');
|
|
39
|
+
/** Text above this is never tested against the shapes: they run before the allowlist. */
|
|
40
|
+
const PULSE_TETO = 300, ESCUTA_TETO = 4096;
|
|
41
|
+
|
|
42
|
+
/** The bare-word window (ork.pulse-escuta/v1), read with a byte cap; any error closes it. */
|
|
43
|
+
export function listeningOpen(root: string | undefined, now = Date.now()): boolean {
|
|
44
|
+
try {
|
|
45
|
+
if (!root || !isAbsolute(root)) return false;
|
|
46
|
+
const raw = readFileSync(join(root, '.orkastery', 'monitor', 'pulse-escuta.json'));
|
|
47
|
+
if (raw.length > ESCUTA_TETO) return false;
|
|
48
|
+
const value = JSON.parse(raw.toString('utf8'));
|
|
49
|
+
return value?.contrato === 'ork.pulse-escuta/v1' && typeof value.livreAte === 'string' && Date.parse(value.livreAte) > now;
|
|
50
|
+
} catch { return false; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isPulseAnswer(text: string, root: string | undefined = process.env.ORK_HITL_ROOT): boolean {
|
|
54
|
+
if (text.includes('\0') || text.length > PULSE_TETO) return false;
|
|
55
|
+
if (PULSE_CONSENT.test(text) || PULSE_LOTE.test(text) || PULSE_CADENCIA.test(text) || PULSE_LISTA.test(text) ||
|
|
56
|
+
PULSE_RATIFICACAO.test(text)) return true;
|
|
57
|
+
return PULSE_LIVRE.test(text) && listeningOpen(root);
|
|
29
58
|
}
|
|
30
59
|
|
|
31
60
|
export function registerHitlIngress(api: NativeApi, bin: string): void {
|
|
@@ -71,6 +71,9 @@ async function rodarOrk(args: string[], signal?: AbortSignal, entrada?: string):
|
|
|
71
71
|
if (verifiers) env.ORK_RECEIPT_VERIFIERS = verifiers;
|
|
72
72
|
// I-36 (D6): o adaptador declara o proprio canal de conducao. Descreve a porta, nao concede autoridade.
|
|
73
73
|
env.ORK_CANAL = 'openclaw';
|
|
74
|
+
// RM-052 (D3): o cwd do gateway nao e projeto de ninguem. Sem `projeto` pedido, o nucleo usa o unico
|
|
75
|
+
// projeto conhecido da maquina ou devolve a escolha; nunca o manifesto que estiver no cwd do gateway.
|
|
76
|
+
env.ORK_PROJETO_EXPLICITO = '1';
|
|
74
77
|
return new Promise((resolve) => {
|
|
75
78
|
const filho = execFile(bin, args, { maxBuffer: MAX_BUFFER, signal, env }, (erro, stdout, stderr) => {
|
|
76
79
|
const partes = [stdout, stderr].map((t) => (t ?? '').trim()).filter((t) => t !== '');
|
|
@@ -154,6 +157,38 @@ function argvDaResposta(p: Record<string, unknown>, alvo: 'gate' | 'sessions'):
|
|
|
154
157
|
'--mensagem', `telegram:${chat}:${referencia}`, '--origem', 'telegram'];
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
/**
|
|
161
|
+
* RM-052 (D4): o projeto pedido chega como NOME de projeto registrado (`ork projetos`), nunca como
|
|
162
|
+
* caminho. O modelo escolhe entre os projetos da maquina; nao aponta o nucleo para um diretorio.
|
|
163
|
+
*/
|
|
164
|
+
const PADRAO_DO_PROJETO = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/;
|
|
165
|
+
const PARAMETRO_PROJETO = {
|
|
166
|
+
type: 'string',
|
|
167
|
+
pattern: PADRAO_DO_PROJETO.source,
|
|
168
|
+
description: 'Nome do projeto que o dono pediu (ex.: orkastery), como em `ork projetos`. Sem ele e com mais de um projeto na máquina, a resposta é a escolha; nunca use o diretório do gateway.',
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** O `projeto` de uma tool: o padrao que o host confere, a descricao para o modelo e a recusa. */
|
|
172
|
+
interface ProjetoDaTool { padrao: RegExp; descricao: string; recusa: string }
|
|
173
|
+
|
|
174
|
+
const PROJETO_DA_RM052: ProjetoDaTool = {
|
|
175
|
+
padrao: PADRAO_DO_PROJETO,
|
|
176
|
+
descricao: PARAMETRO_PROJETO.description,
|
|
177
|
+
recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery), nunca um caminho',
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* RM-054 (fatia 2, D-G4): no roadmap da rede, `projeto` tambem aceita a forja (`github:dono/repo`,
|
|
182
|
+
* `gitlab:grupo/repo`): numa maquina sem clone, e o unico caminho ate o projeto enquanto a rede por
|
|
183
|
+
* pessoa (RM-053) nao chega. Caminho e URL continuam fora (a URL pode levar credencial), e o nucleo
|
|
184
|
+
* confere de novo, porque o adaptador declara ORK_PROJETO_EXPLICITO=1.
|
|
185
|
+
*/
|
|
186
|
+
const PROJETO_DA_REDE: ProjetoDaTool = {
|
|
187
|
+
padrao: /^(?:[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}|(?:github|gitlab):[A-Za-z0-9][A-Za-z0-9._-]{0,99}(?:\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){1,8})$/,
|
|
188
|
+
descricao: 'Projeto que o dono pediu: o nome, como em `ork projetos` (ex.: orkastery), ou a forja (github:dono/repo, gitlab:grupo/repo) quando não há clone nesta máquina. Sem ele, vêm todos os projetos conhecidos. Nunca caminho nem URL.',
|
|
189
|
+
recusa: 'informe o NOME de um projeto de `ork projetos` (ex.: orkastery) ou github:dono/repo, nunca caminho nem URL',
|
|
190
|
+
};
|
|
191
|
+
|
|
157
192
|
/** Catálogo em paridade nome a nome com o manifesto; aprovação genérica legada aposentada. */
|
|
158
193
|
interface FerramentaOrk {
|
|
159
194
|
name: string;
|
|
@@ -161,12 +196,14 @@ interface FerramentaOrk {
|
|
|
161
196
|
parameters: Record<string, unknown>;
|
|
162
197
|
argv: (params: Record<string, unknown>) => string[];
|
|
163
198
|
entrada?: (params: Record<string, unknown>) => string;
|
|
199
|
+
/** O `projeto` desta tool, quando nao e o da RM-052 (so nome). */
|
|
200
|
+
projeto?: ProjetoDaTool;
|
|
164
201
|
}
|
|
165
202
|
|
|
166
203
|
const FERRAMENTAS: FerramentaOrk[] = [
|
|
167
204
|
{
|
|
168
205
|
name: 'ork_maestro',
|
|
169
|
-
description: 'Ao receber a frase exata orkastery maestro,
|
|
206
|
+
description: 'Ao receber a frase exata orkastery maestro: sem projeto nomeado, chame ork_network_roadmap sem projeto (o panorama da rede, com fontes, frescor e lacunas) e apresente-o como vem; com projeto nomeado, consulte este panorama somente leitura com projeto. Não cria thread. Apresente fontes, lacunas e HITL com recomendação e opções claras; horários para o dono vêm dos campos *Local (fuso do dono), nunca do ISO. O panorama NÃO lê o roadmap, as reservas nem as outras máquinas (veja notConsulted): zero threads nunca é roadmap vazio; para o roadmap use ork_network_roadmap. Ação posterior exige operação autorizada e readback.',
|
|
170
207
|
parameters: { type: 'object', additionalProperties: false, properties: {
|
|
171
208
|
thread: { type: 'string' }, section: { type: 'string', enum: ['portfolio','demands','threads','sessions','blockers','leases','retries','hitl','ship','master','nextActions'] },
|
|
172
209
|
offset: { type: 'integer', minimum: 0, maximum: 100000 },
|
|
@@ -292,6 +329,29 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
292
329
|
parameters: schema({ thread: { type: 'string' }, id: { type: 'string' } }),
|
|
293
330
|
argv: (p) => ['brain', 'get', texto(p, 'id'), '--thread', texto(p, 'thread')],
|
|
294
331
|
},
|
|
332
|
+
{
|
|
333
|
+
name: 'ork_brain_context',
|
|
334
|
+
description:
|
|
335
|
+
'Pacote de contexto citável do Company Brain: entidades pedidas e seus pais, cada uma com a citação da fonte, o frescor contra o portfólio e as lacunas. Só leitura.',
|
|
336
|
+
parameters: schema({
|
|
337
|
+
thread: { type: 'string' },
|
|
338
|
+
ids: { type: 'string', description: 'ids do portfólio separados por vírgula (prod-, proj-, init-)' },
|
|
339
|
+
}),
|
|
340
|
+
argv: (p) => ['brain', 'context', '--thread', texto(p, 'thread'), '--ids', texto(p, 'ids')],
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: 'ork_brain_dossie',
|
|
344
|
+
description:
|
|
345
|
+
'Dossiê de decisão da thread: vínculo com objetivo e projeto, contexto citável, alternativas, decisão, quem decidiu e evidência, com os ids do Company Brain e as lacunas. Resposta do dono só com recibo conferido. Só leitura.',
|
|
346
|
+
parameters: {
|
|
347
|
+
type: 'object', additionalProperties: false, required: ['thread'],
|
|
348
|
+
properties: {
|
|
349
|
+
thread: { type: 'string' },
|
|
350
|
+
decisao: { type: 'string', description: 'id do pedido ou fact- da decisão; omita para a thread inteira' },
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
argv: (p) => ['brain', 'dossie', '--thread', texto(p, 'thread'), ...(p.decisao === undefined ? [] : ['--decisao', texto(p, 'decisao')])],
|
|
354
|
+
},
|
|
295
355
|
{
|
|
296
356
|
name: 'ork_claims_add',
|
|
297
357
|
description:
|
|
@@ -375,31 +435,35 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
375
435
|
{
|
|
376
436
|
name: 'ork_master',
|
|
377
437
|
description:
|
|
378
|
-
'
|
|
438
|
+
'Pede ao dono a nota do MASTER (0 a 5, com o porque) de uma entrega: devolve a linha com o codigo curto para mostrar a ele. A nota volta pelo Telegram, com a prova do ingresso. Esta tool nunca grava nota e nao recebe nome de pessoa (RM-048).',
|
|
379
439
|
parameters: schema({
|
|
380
440
|
thread: { type: 'string' },
|
|
381
|
-
score: { type: 'string', description: 'inteiro de 0 a 5, dado pelo humano' },
|
|
382
|
-
justificativa: { type: 'string' },
|
|
383
|
-
quem: { type: 'string' },
|
|
384
441
|
}),
|
|
385
|
-
argv: (p) => [
|
|
386
|
-
'master',
|
|
387
|
-
texto(p, 'thread'),
|
|
388
|
-
'--score',
|
|
389
|
-
texto(p, 'score'),
|
|
390
|
-
'--justificativa',
|
|
391
|
-
texto(p, 'justificativa'),
|
|
392
|
-
'--por',
|
|
393
|
-
texto(p, 'quem'),
|
|
394
|
-
],
|
|
442
|
+
argv: (p) => ['master', 'pedir', texto(p, 'thread'), '--formato', 'telegram'],
|
|
395
443
|
},
|
|
396
444
|
{
|
|
397
445
|
name: 'ork_board',
|
|
398
446
|
description:
|
|
399
|
-
'
|
|
447
|
+
'As threads DESTE projeto nesta maquina e o escalonador dizendo quem avanca agora e quem espera. NAO le o roadmap: nunca conclua sobre o roadmap a partir do board (zero threads nao e roadmap vazio); para o roadmap use ork_network_roadmap. O cabecalho diz o projeto consultado e o que nao foi lido.',
|
|
400
448
|
parameters: schema({}),
|
|
401
449
|
argv: () => ['board', 'plan'],
|
|
402
450
|
},
|
|
451
|
+
{
|
|
452
|
+
name: 'ork_roadmap_status',
|
|
453
|
+
description:
|
|
454
|
+
'Status report do roadmap SO desta maquina (o checkout local), no formato aprovado pelo dono (grupos com icones, #HITL e o fecho): nao le as reservas nem as outras maquinas, e o cabecalho diz isso. Para o status do roadmap com as threads de todas as maquinas, as reservas, as fontes e as lacunas, use ork_network_roadmap. Somente leitura: transporte o texto como vem, sem reescrever. Passe projeto com o nome que o dono pediu (ex.: orkastery); sem ele e com mais de um projeto na maquina, a resposta e a escolha. Nunca deduza o roadmap de ork_board ou ork_maestro.',
|
|
455
|
+
parameters: schema({}),
|
|
456
|
+
argv: () => ['roadmap', 'status'],
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: 'ork_network_roadmap',
|
|
460
|
+
description:
|
|
461
|
+
'Roadmap da rede, somente leitura: para cada projeto, o status report do roadmap (RM-048) com as threads de TODAS as máquinas, as reservas, as threads por máquina com a idade da batida, a fonte e a hora de cada parte e as lacunas. É a fonte para qualquer pergunta sobre o roadmap ou o status report: transporte o texto como vem, sem reescrever nem resumir. Passe projeto com o nome que o dono pediu (ex.: orkastery) ou github:dono/repo; sem projeto, vêm todos os projetos conhecidos, e esse é o panorama da frase orkastery maestro sem projeto. Lacuna, "não lido" e "Não consultado" são fontes que ficaram sem leitura: nunca conclua "roadmap vazio" nem "nenhuma máquina publicou" a partir delas.',
|
|
462
|
+
parameters: schema({}),
|
|
463
|
+
projeto: PROJETO_DA_REDE,
|
|
464
|
+
// RM-054 (fatia 2): uma chamada de CLI; o `--projeto` vai no inicio e o nucleo o devolve ao `network`.
|
|
465
|
+
argv: () => ['network', 'roadmap'],
|
|
466
|
+
},
|
|
403
467
|
{
|
|
404
468
|
name: 'ork_master_batch',
|
|
405
469
|
description:
|
|
@@ -409,20 +473,44 @@ const FERRAMENTAS: FerramentaOrk[] = [
|
|
|
409
473
|
},
|
|
410
474
|
];
|
|
411
475
|
|
|
476
|
+
/** RM-052: toda tool aceita `projeto` opcional, sem mudar o que ela ja exigia. */
|
|
477
|
+
function comProjeto(f: FerramentaOrk): Record<string, unknown> {
|
|
478
|
+
const propriedades = (f.parameters.properties ?? {}) as Record<string, unknown>;
|
|
479
|
+
const p = f.projeto;
|
|
480
|
+
const projeto = p ? { type: 'string', pattern: p.padrao.source, description: p.descricao } : PARAMETRO_PROJETO;
|
|
481
|
+
return { ...f.parameters, properties: { ...propriedades, projeto } };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** `--projeto <nome>` vai no inicio do argv; o resto dos parametros segue para a tool como antes. */
|
|
485
|
+
function argvComProjeto(f: FerramentaOrk, params: Record<string, unknown>): { argv: string[]; resto: Record<string, unknown> } {
|
|
486
|
+
const { projeto, ...resto } = params;
|
|
487
|
+
if (projeto !== undefined && (typeof projeto !== 'string' || !(f.projeto ?? PROJETO_DA_RM052).padrao.test(projeto))) {
|
|
488
|
+
throw new Error('projeto.invalido');
|
|
489
|
+
}
|
|
490
|
+
return { argv: [...(projeto === undefined ? [] : ['--projeto', projeto]), ...f.argv(resto)], resto };
|
|
491
|
+
}
|
|
492
|
+
|
|
412
493
|
const plugin = defineToolPlugin({
|
|
413
494
|
id: 'orkastery',
|
|
414
495
|
name: 'Orkastery',
|
|
415
496
|
description:
|
|
416
|
-
'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI.',
|
|
497
|
+
'Conducao de looping threads em 6 fases pelo nucleo `ork`, exposta ao OpenClaw como tools `ork_*`. Zero regra de negocio no host: cada tool e uma chamada de CLI. Toda tool aceita projeto (nome do projeto pedido); o cwd do gateway nunca escolhe o projeto. O status do roadmap vem de ork_network_roadmap.',
|
|
417
498
|
tools: (tool) =>
|
|
418
499
|
FERRAMENTAS.map((f) =>
|
|
419
500
|
tool({
|
|
420
501
|
name: f.name,
|
|
421
502
|
description: f.description,
|
|
422
|
-
parameters: f
|
|
503
|
+
parameters: comProjeto(f),
|
|
423
504
|
execute: (params, _config, contexto) => {
|
|
424
|
-
|
|
425
|
-
|
|
505
|
+
let chamada: ReturnType<typeof argvComProjeto>;
|
|
506
|
+
try { chamada = argvComProjeto(f, params); }
|
|
507
|
+
catch (e) {
|
|
508
|
+
if ((e as Error).message === 'projeto.invalido') {
|
|
509
|
+
return `[ork recusou] projeto.invalido: ${(f.projeto ?? PROJETO_DA_RM052).recusa}`;
|
|
510
|
+
}
|
|
511
|
+
return '[ork recusou] resposta humana não confirmada; confira origem e correlação do pedido';
|
|
512
|
+
}
|
|
513
|
+
return rodarOrk(chamada.argv, contexto.signal, f.entrada?.(chamada.resto));
|
|
426
514
|
},
|
|
427
515
|
})
|
|
428
516
|
),
|
|
@@ -16,7 +16,7 @@ O **roadmap** registra problemas, hipóteses, escolhas, prioridades e andamento
|
|
|
16
16
|
|
|
17
17
|
| Regra | Aplicação |
|
|
18
18
|
| --- | --- |
|
|
19
|
-
| Identidade | Atribua IDs estáveis, por exemplo `PLAT-01`, `SYS-01`, `MOD-01`, `FEAT-042`, `UC-042-01`, `BR-042-01`, `API-01`, `EVT-01`, `JOB-01` e `RM-042`. Renomear não muda o ID. |
|
|
19
|
+
| Identidade | Atribua IDs estáveis, por exemplo `PLAT-01`, `SYS-01`, `MOD-01`, `FEAT-042`, `UC-042-01`, `BR-042-01`, `API-01`, `EVT-01`, `JOB-01` e `RM-042`. Renomear não muda o ID. No Orkastery, o número da FEAT nova sai de `ork roadmap feat`, reservado entre máquinas; nunca do maior número da sua branch. |
|
|
20
20
|
| Escopo e estado | Informe se a página descreve `vigente`, `em desenvolvimento`, `proposto` ou `descontinuado`, com ambiente, versão e data de verificação. |
|
|
21
21
|
| Rastreabilidade | Ligue feature ↔ item de roadmap ↔ casos de uso/regras ↔ contratos/esquemas ↔ código/PR ↔ testes/evidências ↔ release. Use `Não aplicável — motivo` quando uma seção não fizer sentido. |
|
|
22
22
|
| Fonte | Diferencie `confirmado` (código, contrato, teste ou decisão aprovada), `planejado` (item de roadmap) e `a validar`. Nunca complete uma lacuna por inferência silenciosa. |
|
|
@@ -75,7 +75,7 @@ Registre risco, perfis e matriz de permissões (RBAC/ABAC), autenticação/sess
|
|
|
75
75
|
|
|
76
76
|
## 4. Modelo de página de feature
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Reserve o número com `ork roadmap feat --thread <thread>` e copie [o modelo](../produto/_modelo-feature.md) para cada feature; substitua colchetes por valores ou por `Não aplicável — motivo`. O frontmatter é a camada que agentes e o verificador leem; o corpo é a camada que pessoas leem. Acrescente tabelas de campos e contratos quando houver vários elementos.
|
|
79
79
|
|
|
80
80
|
Chaves do frontmatter de uma feature:
|
|
81
81
|
|
|
@@ -80,7 +80,7 @@ Copie [o modelo](../roadmap/_modelo-item.md) para cada item. O frontmatter guard
|
|
|
80
80
|
|
|
81
81
|
Revisite itens ativos na cadência do time e sempre após decisão, PR relevante, teste, deploy, alteração de flag ou medição de resultado. A cada revisão, compare roadmap, documentação da feature, código, contratos e evidências de produção; anote divergências com owner e prazo. Agentes podem sugerir atualização de status a partir de eventos do repositório, mas um PR mesclado atualiza somente a dimensão **código** até que deploy e exposição sejam comprovados. Registre a data de atualização e mantenha histórico das decisões e mudanças de escopo.
|
|
82
82
|
|
|
83
|
-
No Orkastery, `ork docs sincronizar` é esse agente: ele atualiza `estado.codigo` e `evidencias.codigo.commit` a partir do ledger (`ship_done`) e do git, e a seção `sdlc` a partir da thread. **Ele nunca mexe em ciclo, documentação, deploy, exposição ou habilitação** — essas dimensões são decisão de pessoa, e o verificador só cobra que estejam coerentes com o código.
|
|
83
|
+
No Orkastery, `ork docs sincronizar` é esse agente: ele atualiza `estado.codigo` e `evidencias.codigo.commit` a partir do ledger (`ship_done`) e do git, e a seção `sdlc` a partir da thread. **Ele nunca mexe em ciclo, documentação, deploy, exposição ou habilitação** — essas dimensões são decisão de pessoa, e o verificador só cobra que estejam coerentes com o código. Na worktree de uma thread, ele só toca o item dela e os índices, para que o PR de uma thread não mude o item de outra; `--so RM-NNN` escolhe os itens, e `--todos` (o padrão na raiz do projeto, onde o condutor sincroniza depois do merge) volta a todos.
|
|
84
84
|
|
|
85
85
|
## 6. Três leitores e fatos de SDLC (adaptação Orkastery)
|
|
86
86
|
|
|
@@ -17,6 +17,8 @@ fontes:
|
|
|
17
17
|
comandos: []
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
<!-- O número da feature sai de `ork roadmap feat --thread <thread>`, reservado entre máquinas; nunca do maior número da branch. -->
|
|
21
|
+
|
|
20
22
|
# FEAT-000 — Nome da feature
|
|
21
23
|
|
|
22
24
|
> **Em uma frase:** [o que esta feature faz, para quem, em até 240 caracteres]
|