@orkastery/cli 0.2.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/LICENSE +21 -0
- package/README.md +87 -0
- package/adapters/README.md +22 -0
- package/adapters/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/claude-code/.claude-plugin/plugin.json +31 -0
- package/adapters/claude-code/README.md +102 -0
- package/adapters/claude-code/agents/ork-check.md +32 -0
- package/adapters/claude-code/agents/ork-go.md +32 -0
- package/adapters/claude-code/agents/ork-goal.md +33 -0
- package/adapters/claude-code/agents/ork-master.md +32 -0
- package/adapters/claude-code/agents/ork-plan.md +32 -0
- package/adapters/claude-code/agents/ork-ship.md +32 -0
- package/adapters/claude-code/commands/check.md +45 -0
- package/adapters/claude-code/commands/go.md +46 -0
- package/adapters/claude-code/commands/goal.md +54 -0
- package/adapters/claude-code/commands/master.md +42 -0
- package/adapters/claude-code/commands/ork.md +37 -0
- package/adapters/claude-code/commands/plan.md +47 -0
- package/adapters/claude-code/commands/ship.md +45 -0
- package/adapters/claude-code/hooks/hooks.json +17 -0
- package/adapters/claude-code/hooks/ork-guard.js +130 -0
- package/adapters/hermes/README.md +45 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +25 -0
- package/adapters/hermes/hermes.plugin.json +21 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +116 -0
- package/adapters/openclaw/README.md +63 -0
- package/adapters/openclaw/bin/ork-abrir-thread.sh +24 -0
- package/adapters/openclaw/openclaw.plugin.json +138 -0
- package/dist/adapters/claude-bg.js +308 -0
- package/dist/auditoria.js +848 -0
- package/dist/auditrun.js +976 -0
- package/dist/board.js +314 -0
- package/dist/canarios.js +271 -0
- package/dist/catalogo.js +124 -0
- package/dist/ciclos.js +156 -0
- package/dist/claims.js +226 -0
- package/dist/divida.js +374 -0
- package/dist/doctor.js +246 -0
- package/dist/evalrunner.js +458 -0
- package/dist/fix.js +450 -0
- package/dist/gates.js +92 -0
- package/dist/handoff.js +521 -0
- package/dist/hosts.js +331 -0
- package/dist/index.js +1910 -0
- package/dist/init.js +231 -0
- package/dist/leases.js +508 -0
- package/dist/ledger.js +138 -0
- package/dist/manifest.js +255 -0
- package/dist/master.js +453 -0
- package/dist/memoria.js +773 -0
- package/dist/modos.js +220 -0
- package/dist/orkmind.js +487 -0
- package/dist/orquestracao.js +559 -0
- package/dist/phase.js +572 -0
- package/dist/policies.js +176 -0
- package/dist/prompts.js +406 -0
- package/dist/ratelimit.js +179 -0
- package/dist/recall.js +252 -0
- package/dist/retry.js +917 -0
- package/dist/sandbox.js +94 -0
- package/dist/sessoes.js +104 -0
- package/dist/ship.js +551 -0
- package/dist/slug.js +100 -0
- package/dist/superficie.js +1347 -0
- package/dist/thread.js +333 -0
- package/dist/tokens.js +228 -0
- package/dist/types.js +20 -0
- package/dist/util.js +156 -0
- package/dist/verify.js +262 -0
- package/dist/worktree.js +576 -0
- package/dist/yaml.js +112 -0
- package/eval/README.md +85 -0
- package/eval/casos/check-quality.json +103 -0
- package/eval/casos/code-reviewer.json +102 -0
- package/eval/casos/decision-triage.json +103 -0
- package/eval/casos/go-implementation.json +103 -0
- package/eval/casos/goal-definition.json +103 -0
- package/eval/casos/master-metrics.json +103 -0
- package/eval/casos/narrative-guardian.json +163 -0
- package/eval/casos/orkastery-bootstrap.json +102 -0
- package/eval/casos/plan-specification.json +102 -0
- package/eval/casos/roadmap-keeper.json +103 -0
- package/eval/casos/scope-check-capability-map.json +83 -0
- package/eval/casos/security-auditor.json +102 -0
- package/eval/casos/ship-release.json +122 -0
- package/eval/casos/test-engineer.json +122 -0
- package/eval/casos/thread-state.json +82 -0
- package/eval/casos/thread-tracing.json +83 -0
- package/eval/casos/web-performance-auditor.json +102 -0
- package/eval/fixtures/b0-slug-e-modos/caso.json +56 -0
- package/eval/fixtures/b2-master-log/caso.json +91 -0
- package/eval/fixtures/fx-concurrency/caso.json +15 -0
- package/eval/fixtures/fx-hallucination/caso.json +12 -0
- package/eval/fixtures/fx-happy/caso.json +16 -0
- package/eval/fixtures/fx-schema-drift/caso.json +15 -0
- package/eval/fixtures/fx-stale-base/caso.json +12 -0
- package/eval/fixtures/fx-wiki-destroy/caso.json +16 -0
- package/eval/fixtures/superficie-de-rede/README.md +22 -0
- package/eval/fixtures/superficie-de-rede/api-express.js +30 -0
- package/eval/fixtures/superficie-de-rede/api_fastapi.py +17 -0
- package/eval/fixtures/superficie-de-rede/api_gin.go +18 -0
- package/package.json +56 -0
- package/references/README.md +25 -0
- package/references/code-review-axes.md +82 -0
- package/references/definition-of-done.md +74 -0
- package/references/performance-checklist.md +53 -0
- package/references/security-checklist.md +101 -0
- package/references/testing-patterns.md +51 -0
- package/skills/README.md +43 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +89 -0
- package/skills/core/thread-state/SKILL.md +86 -0
- package/skills/governance/decision-triage/SKILL.md +65 -0
- package/skills/governance/narrative-guardian/SKILL.md +66 -0
- package/skills/governance/roadmap-keeper/SKILL.md +60 -0
- package/skills/governance/scope-check-capability-map/SKILL.md +61 -0
- package/skills/observability/thread-tracing/SKILL.md +63 -0
- package/skills/phases/check-quality/SKILL.md +87 -0
- package/skills/phases/go-implementation/SKILL.md +70 -0
- package/skills/phases/goal-definition/SKILL.md +69 -0
- package/skills/phases/master-metrics/SKILL.md +65 -0
- package/skills/phases/plan-specification/SKILL.md +67 -0
- package/skills/phases/ship-release/SKILL.md +68 -0
- package/skills/reviewers/code-reviewer/SKILL.md +61 -0
- package/skills/reviewers/security-auditor/SKILL.md +68 -0
- package/skills/reviewers/test-engineer/SKILL.md +66 -0
- package/skills/reviewers/web-performance-auditor/SKILL.md +63 -0
package/dist/policies.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Policies executaveis do manifesto (bloco B1).
|
|
4
|
+
*
|
|
5
|
+
* O bloco `policies:` do `orkastery.yaml` deixa de ser prosa e passa a ser codigo:
|
|
6
|
+
* cada policy conhecida tem um gate (`when`), uma severidade (`block` ou `warn`) e um
|
|
7
|
+
* avaliador deterministico. Policy `block` violada reprova em QUALQUER modo, inclusive
|
|
8
|
+
* `#Auto`, porque o modo afrouxa a pausa e nunca a verificacao.
|
|
9
|
+
*
|
|
10
|
+
* Policy declarada no manifesto que o `ork` nao conhece nao e silenciosamente ignorada:
|
|
11
|
+
* ela volta na lista de desconhecidas, para o `doctor` e o CLI dizerem que ela nao vale.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.POLICIES_CONHECIDAS = void 0;
|
|
15
|
+
exports.procurarSegredos = procurarSegredos;
|
|
16
|
+
exports.policiesDesconhecidas = policiesDesconhecidas;
|
|
17
|
+
exports.avaliarPolicies = avaliarPolicies;
|
|
18
|
+
exports.bloqueantes = bloqueantes;
|
|
19
|
+
exports.motivoDominante = motivoDominante;
|
|
20
|
+
exports.textoDeViolacoes = textoDeViolacoes;
|
|
21
|
+
/**
|
|
22
|
+
* Variaveis que REDIRECIONAM o despacho do `claude` para um provider pago.
|
|
23
|
+
* Mesma lista do `ork doctor`: presenca delas com `subscription-only` e bloqueante.
|
|
24
|
+
*/
|
|
25
|
+
const ENVS_QUE_REDIRECIONAM = [
|
|
26
|
+
'ANTHROPIC_API_KEY',
|
|
27
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
28
|
+
'ANTHROPIC_BASE_URL',
|
|
29
|
+
'CLAUDE_CODE_USE_BEDROCK',
|
|
30
|
+
'CLAUDE_CODE_USE_VERTEX',
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Padroes de segredo procurados no prompt antes do despacho.
|
|
34
|
+
* O `ork` reporta o NOME do padrao e a posicao, nunca o trecho casado: relatorio de
|
|
35
|
+
* vazamento que imprime o segredo e um segundo vazamento.
|
|
36
|
+
*/
|
|
37
|
+
const PADROES_DE_SEGREDO = [
|
|
38
|
+
{ nome: 'chave da Anthropic', regex: /sk-ant-[A-Za-z0-9_-]{16,}/ },
|
|
39
|
+
{ nome: 'chave da OpenAI', regex: /\bsk-[A-Za-z0-9]{32,}\b/ },
|
|
40
|
+
{ nome: 'chave de acesso AWS', regex: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
41
|
+
{ nome: 'token do GitHub', regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/ },
|
|
42
|
+
{ nome: 'chave privada PEM', regex: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/ },
|
|
43
|
+
{
|
|
44
|
+
nome: 'segredo atribuido em texto',
|
|
45
|
+
regex: /\b(?:api[_-]?key|secret|token|senha|password)\s*[:=]\s*['"][^'"\s]{16,}['"]/i,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Procura os padroes de segredo em um texto qualquer (prompt de fase, template de prompt).
|
|
50
|
+
* Devolve o NOME do padrao e a linha, nunca o trecho: relatorio que imprime o segredo vaza de novo.
|
|
51
|
+
*/
|
|
52
|
+
function procurarSegredos(texto) {
|
|
53
|
+
const achados = [];
|
|
54
|
+
for (const padrao of PADROES_DE_SEGREDO) {
|
|
55
|
+
const casado = padrao.regex.exec(texto);
|
|
56
|
+
if (!casado)
|
|
57
|
+
continue;
|
|
58
|
+
achados.push({ nome: padrao.nome, linha: texto.slice(0, casado.index).split('\n').length });
|
|
59
|
+
}
|
|
60
|
+
return achados;
|
|
61
|
+
}
|
|
62
|
+
/** Catalogo das policies que o `ork` sabe executar, com o gate em que cada uma vale. */
|
|
63
|
+
exports.POLICIES_CONHECIDAS = {
|
|
64
|
+
provider: {
|
|
65
|
+
quando: ['phase.dispatch', 'ship'],
|
|
66
|
+
descricao: 'proibe despacho por provider pago quando a politica e subscription-only',
|
|
67
|
+
},
|
|
68
|
+
segredo_em_prompt: {
|
|
69
|
+
quando: ['phase.dispatch'],
|
|
70
|
+
descricao: 'proibe despachar prompt que carrega credencial',
|
|
71
|
+
},
|
|
72
|
+
push_direto_na_base: {
|
|
73
|
+
quando: ['ship'],
|
|
74
|
+
descricao: 'proibe entregar sem branch de thread (push direto na base)',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
function severidade(bruta) {
|
|
78
|
+
if (bruta === 'block' || bruta === 'warn' || bruta === 'off')
|
|
79
|
+
return bruta;
|
|
80
|
+
return 'warn';
|
|
81
|
+
}
|
|
82
|
+
/** Policies declaradas no manifesto que o `ork` nao sabe executar. */
|
|
83
|
+
function policiesDesconhecidas(manifesto) {
|
|
84
|
+
return Object.keys(manifesto.policies ?? {}).filter((p) => !(p in exports.POLICIES_CONHECIDAS));
|
|
85
|
+
}
|
|
86
|
+
/** Avalia as policies aplicaveis ao gate informado. Nunca lanca: devolve as violacoes. */
|
|
87
|
+
function avaliarPolicies(manifesto, ctx) {
|
|
88
|
+
const declaradas = manifesto.policies ?? {};
|
|
89
|
+
const violacoes = [];
|
|
90
|
+
for (const [nome, bruta] of Object.entries(declaradas)) {
|
|
91
|
+
const conhecida = exports.POLICIES_CONHECIDAS[nome];
|
|
92
|
+
if (!conhecida || !conhecida.quando.includes(ctx.gate))
|
|
93
|
+
continue;
|
|
94
|
+
const sev = severidade(bruta);
|
|
95
|
+
if (sev === 'off')
|
|
96
|
+
continue;
|
|
97
|
+
if (nome === 'provider') {
|
|
98
|
+
if (manifesto.runtime.provider_policy !== 'subscription-only')
|
|
99
|
+
continue;
|
|
100
|
+
const ativas = ENVS_QUE_REDIRECIONAM.filter((e) => (process.env[e] ?? '').trim() !== '');
|
|
101
|
+
if (ativas.length > 0) {
|
|
102
|
+
violacoes.push({
|
|
103
|
+
policy: nome,
|
|
104
|
+
severidade: sev,
|
|
105
|
+
// Bloco B3: a policy `provider` deixa de sair como `policy.violation` generica e
|
|
106
|
+
// passa a carregar o motivo de CUSTO. E o que permite a politica de retry recusar
|
|
107
|
+
// a reexecucao automatica dela sem precisar reabrir o nome da policy em cada gate.
|
|
108
|
+
motivo: 'cost.violation',
|
|
109
|
+
detalhe: `${ativas.join(', ')} redireciona o despacho do claude para provider pago`,
|
|
110
|
+
correcao: `remova do ambiente: ${ativas.join(', ')} (o despacho usa a assinatura Claude local)`,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (nome === 'segredo_em_prompt') {
|
|
116
|
+
for (const achado of procurarSegredos(ctx.prompt ?? '')) {
|
|
117
|
+
violacoes.push({
|
|
118
|
+
policy: nome,
|
|
119
|
+
severidade: sev,
|
|
120
|
+
motivo: 'policy.violation',
|
|
121
|
+
detalhe: `padrao "${achado.nome}" casou na linha ${achado.linha} do prompt (trecho omitido de proposito)`,
|
|
122
|
+
correcao: 'tire a credencial do pedido; referencie a variavel de ambiente pelo nome',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (nome === 'push_direto_na_base') {
|
|
128
|
+
const de = ctx.de ?? '';
|
|
129
|
+
const para = ctx.para ?? '';
|
|
130
|
+
const base = ctx.baseBranch ?? '';
|
|
131
|
+
if (de && para && de === para) {
|
|
132
|
+
violacoes.push({
|
|
133
|
+
policy: nome,
|
|
134
|
+
severidade: sev,
|
|
135
|
+
motivo: 'policy.violation',
|
|
136
|
+
detalhe: `origem e destino sao a mesma branch ("${de}"): isso e push direto na base, nao merge de thread`,
|
|
137
|
+
correcao: 'entregue a partir da branch da thread: ork ship <thread> --para ' + (base || 'main'),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else if (de && base && de === base) {
|
|
141
|
+
violacoes.push({
|
|
142
|
+
policy: nome,
|
|
143
|
+
severidade: sev,
|
|
144
|
+
motivo: 'policy.violation',
|
|
145
|
+
detalhe: `a origem "${de}" e a propria branch base do projeto: nenhuma branch de thread foi usada`,
|
|
146
|
+
correcao: 'crie a thread com --worktree auto e entregue a branch ork/<slug>',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return violacoes;
|
|
153
|
+
}
|
|
154
|
+
/** So as violacoes com severidade `block` (as que reprovam em qualquer modo). */
|
|
155
|
+
function bloqueantes(violacoes) {
|
|
156
|
+
return violacoes.filter((v) => v.severidade === 'block');
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* O motivo tipado que representa um conjunto de violacoes bloqueantes.
|
|
160
|
+
*
|
|
161
|
+
* Custo vence: se qualquer violacao do lote for de custo, o lote inteiro sai como
|
|
162
|
+
* `cost.violation`, porque e o unico motivo que NUNCA pode ser reexecutado sozinho.
|
|
163
|
+
* Sem este helper, cada gate carimbaria `policy.violation` na mao e a regra de custo
|
|
164
|
+
* viraria uma segunda regra paralela, mais frouxa, em cada arquivo.
|
|
165
|
+
*/
|
|
166
|
+
function motivoDominante(violacoes) {
|
|
167
|
+
return violacoes.some((v) => v.motivo === 'cost.violation')
|
|
168
|
+
? 'cost.violation'
|
|
169
|
+
: 'policy.violation';
|
|
170
|
+
}
|
|
171
|
+
/** Texto das violacoes para a saida do CLI. */
|
|
172
|
+
function textoDeViolacoes(violacoes) {
|
|
173
|
+
return violacoes
|
|
174
|
+
.map((v) => ` [${v.severidade}] policy ${v.policy}: ${v.detalhe}\n` + ` correcao: ${v.correcao}`)
|
|
175
|
+
.join('\n');
|
|
176
|
+
}
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Prompts como templates versionados (bloco B4).
|
|
4
|
+
*
|
|
5
|
+
* Ate o B2 o prompt de fase era montado por concatenacao dentro do `phase.ts`: mudar uma
|
|
6
|
+
* linha do contrato de fase era mudar codigo, e nao havia como um projeto revisar o prompt
|
|
7
|
+
* sem abrir o compilador. Aqui o prompt vira TEMPLATE VERSIONADO:
|
|
8
|
+
*
|
|
9
|
+
* - o template embutido no `ork` e a fonte de verdade quando o projeto nao tem opiniao;
|
|
10
|
+
* - `<raiz>/prompts/<id>.md` sobrescreve o embutido de mesmo id, versionado no git do projeto;
|
|
11
|
+
* - `ork prompt lint` reprova template quebrado ANTES de ele virar despacho;
|
|
12
|
+
* - `ork prompt render` mostra o prompt exato, com o mesmo sha256 que iria para o ledger.
|
|
13
|
+
*
|
|
14
|
+
* A regra de ouro do bloco continua valendo: o template e dado, o renderizador e codigo, e
|
|
15
|
+
* nenhuma regra de negocio nova entra aqui. O que o template faz e dizer com que texto o
|
|
16
|
+
* nucleo despacha; quem decide o que despachar continua sendo o `ork`.
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.TEMPLATES_EMBUTIDOS = exports.TEMPLATE_FASE_PADRAO = exports.CONTRATO_DE_FASE = exports.REGRA_CENTRAL = exports.SECOES_OBRIGATORIAS = exports.LIMITE_TEMPLATE_BYTES = void 0;
|
|
53
|
+
exports.dirDeTemplates = dirDeTemplates;
|
|
54
|
+
exports.variaveisUsadas = variaveisUsadas;
|
|
55
|
+
exports.renderizar = renderizar;
|
|
56
|
+
exports.parseTemplate = parseTemplate;
|
|
57
|
+
exports.lintTemplate = lintTemplate;
|
|
58
|
+
exports.carregarTemplatesDe = carregarTemplatesDe;
|
|
59
|
+
exports.carregarTemplates = carregarTemplates;
|
|
60
|
+
exports.exigirTemplate = exigirTemplate;
|
|
61
|
+
exports.templateDaFase = templateDaFase;
|
|
62
|
+
exports.textoDoLint = textoDoLint;
|
|
63
|
+
exports.tabelaDeTemplates = tabelaDeTemplates;
|
|
64
|
+
exports.threadDeExemplo = threadDeExemplo;
|
|
65
|
+
const fs = __importStar(require("node:fs"));
|
|
66
|
+
const path = __importStar(require("node:path"));
|
|
67
|
+
const modos_1 = require("./modos");
|
|
68
|
+
const policies_1 = require("./policies");
|
|
69
|
+
const types_1 = require("./types");
|
|
70
|
+
/** Limite duro de um template, o mesmo do manifesto: prosa longa nao vira prompt. */
|
|
71
|
+
exports.LIMITE_TEMPLATE_BYTES = 16384;
|
|
72
|
+
/** Onde um projeto guarda os templates que sobrescrevem os embutidos. */
|
|
73
|
+
function dirDeTemplates(raiz) {
|
|
74
|
+
return path.join(raiz, 'prompts');
|
|
75
|
+
}
|
|
76
|
+
/** Toda `{{variavel}}` usada no corpo, sem repeticao, na ordem de aparicao. */
|
|
77
|
+
function variaveisUsadas(corpo) {
|
|
78
|
+
const achadas = [];
|
|
79
|
+
const regex = /\{\{\s*([a-z0-9_]+)\s*\}\}/g;
|
|
80
|
+
let casado;
|
|
81
|
+
while ((casado = regex.exec(corpo)) !== null) {
|
|
82
|
+
if (!achadas.includes(casado[1]))
|
|
83
|
+
achadas.push(casado[1]);
|
|
84
|
+
}
|
|
85
|
+
return achadas;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Renderiza o template com os valores informados.
|
|
89
|
+
*
|
|
90
|
+
* Duas regras, ambas deterministicas e testadas:
|
|
91
|
+
* 1. variavel usada e nao informada e ERRO, nunca string vazia silenciosa;
|
|
92
|
+
* 2. linha cujo conteudo inteiro e uma unica variavel que resolveu vazio SOME, para que um
|
|
93
|
+
* campo opcional (a variante de ciclo, por exemplo) nao deixe linha em branco no prompt.
|
|
94
|
+
*/
|
|
95
|
+
function renderizar(template, valores) {
|
|
96
|
+
const faltando = variaveisUsadas(template.corpo).filter((v) => !(v in valores));
|
|
97
|
+
if (faltando.length > 0) {
|
|
98
|
+
throw new Error(`template ${template.id}: variavel sem valor na renderizacao: ${faltando.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
const linhas = template.corpo.split('\n');
|
|
101
|
+
const saida = [];
|
|
102
|
+
for (const linha of linhas) {
|
|
103
|
+
const soUmaVariavel = /^\{\{\s*([a-z0-9_]+)\s*\}\}$/.exec(linha);
|
|
104
|
+
if (soUmaVariavel && valores[soUmaVariavel[1]] === '')
|
|
105
|
+
continue;
|
|
106
|
+
saida.push(linha.replace(/\{\{\s*([a-z0-9_]+)\s*\}\}/g, (_, nome) => valores[nome]));
|
|
107
|
+
}
|
|
108
|
+
return saida.join('\n');
|
|
109
|
+
}
|
|
110
|
+
function parseListaInline(bruto) {
|
|
111
|
+
const limpo = bruto.trim();
|
|
112
|
+
if (limpo === '' || limpo === '[]')
|
|
113
|
+
return [];
|
|
114
|
+
const interno = limpo.startsWith('[') && limpo.endsWith(']') ? limpo.slice(1, -1) : limpo;
|
|
115
|
+
return interno
|
|
116
|
+
.split(',')
|
|
117
|
+
.map((p) => p.trim().replace(/^["']|["']$/g, ''))
|
|
118
|
+
.filter((p) => p.length > 0);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Le o frontmatter e o corpo de um template.
|
|
122
|
+
*
|
|
123
|
+
* O parser aceita o subconjunto que os templates usam (`chave: valor` e lista inline), e
|
|
124
|
+
* reprova em vez de adivinhar: frontmatter ausente e erro, nao template sem metadados.
|
|
125
|
+
*/
|
|
126
|
+
function parseTemplate(bruto, origem) {
|
|
127
|
+
const casado = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(bruto);
|
|
128
|
+
if (!casado) {
|
|
129
|
+
throw new Error(`template em ${origem}: frontmatter ausente (esperado bloco --- ... ---)`);
|
|
130
|
+
}
|
|
131
|
+
const campos = {};
|
|
132
|
+
for (const linha of casado[1].split('\n')) {
|
|
133
|
+
if (linha.trim() === '' || linha.trimStart().startsWith('#'))
|
|
134
|
+
continue;
|
|
135
|
+
const sep = linha.indexOf(':');
|
|
136
|
+
if (sep < 0)
|
|
137
|
+
continue;
|
|
138
|
+
campos[linha.slice(0, sep).trim()] = linha
|
|
139
|
+
.slice(sep + 1)
|
|
140
|
+
.trim()
|
|
141
|
+
.replace(/^["']|["']$/g, '');
|
|
142
|
+
}
|
|
143
|
+
const versao = Number(campos.versao ?? '0');
|
|
144
|
+
return {
|
|
145
|
+
id: campos.id ?? '',
|
|
146
|
+
versao: Number.isFinite(versao) ? versao : 0,
|
|
147
|
+
descricao: campos.descricao ?? '',
|
|
148
|
+
fases: parseListaInline(campos.fases ?? ''),
|
|
149
|
+
variaveis: parseListaInline(campos.variaveis ?? ''),
|
|
150
|
+
corpo: casado[2].replace(/\n$/, ''),
|
|
151
|
+
origem,
|
|
152
|
+
bruto,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Secoes que todo template de fase precisa carregar para o prompt continuar sendo um prompt. */
|
|
156
|
+
exports.SECOES_OBRIGATORIAS = [
|
|
157
|
+
'## Ciclo canonico',
|
|
158
|
+
'## Modo de conducao',
|
|
159
|
+
'## Contexto da thread',
|
|
160
|
+
'## Pedido do builder',
|
|
161
|
+
'## Regras de evidencia',
|
|
162
|
+
];
|
|
163
|
+
/** A frase que nenhum template pode perder, porque ela e o contrato dos 5 modos. */
|
|
164
|
+
exports.REGRA_CENTRAL = 'REGRA CENTRAL: o modo afrouxa a pausa, NUNCA a verificacao.';
|
|
165
|
+
/** O contrato do prompt de fase (o do B4). */
|
|
166
|
+
exports.CONTRATO_DE_FASE = {
|
|
167
|
+
nome: 'fase',
|
|
168
|
+
secoes: exports.SECOES_OBRIGATORIAS,
|
|
169
|
+
regraCentral: exports.REGRA_CENTRAL,
|
|
170
|
+
variavelObrigatoria: 'pedido',
|
|
171
|
+
validaFases: true,
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Lint de um template. Devolve os problemas; lista vazia quer dizer aprovado.
|
|
175
|
+
*
|
|
176
|
+
* O lint e o que torna "prompt versionado" diferente de "arquivo de texto solto": ele reprova
|
|
177
|
+
* template que perdeu a regra dos modos, que esqueceu o pedido do builder, que declara variavel
|
|
178
|
+
* que nao usa (ou usa variavel que nao declara) e que carrega credencial.
|
|
179
|
+
*/
|
|
180
|
+
function lintTemplate(t, contrato = exports.CONTRATO_DE_FASE) {
|
|
181
|
+
const problemas = [];
|
|
182
|
+
const erro = (regra, detalhe) => {
|
|
183
|
+
problemas.push({ template: t.id || t.origem, regra, gravidade: 'erro', detalhe });
|
|
184
|
+
};
|
|
185
|
+
const aviso = (regra, detalhe) => {
|
|
186
|
+
problemas.push({ template: t.id || t.origem, regra, gravidade: 'aviso', detalhe });
|
|
187
|
+
};
|
|
188
|
+
if (!t.id)
|
|
189
|
+
erro('id', 'frontmatter sem `id`');
|
|
190
|
+
if (t.origem !== 'embutido') {
|
|
191
|
+
const esperado = path.basename(t.origem, '.md');
|
|
192
|
+
if (t.id && t.id !== esperado) {
|
|
193
|
+
erro('id', `o id "${t.id}" nao bate com o nome do arquivo "${esperado}.md"`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!Number.isInteger(t.versao) || t.versao < 1) {
|
|
197
|
+
erro('versao', `versao "${t.versao}" invalida: esperado inteiro a partir de 1`);
|
|
198
|
+
}
|
|
199
|
+
if (t.descricao.trim() === '')
|
|
200
|
+
erro('descricao', 'frontmatter sem `descricao`');
|
|
201
|
+
if (contrato.validaFases) {
|
|
202
|
+
for (const fase of t.fases) {
|
|
203
|
+
if (!types_1.FASES.includes(fase)) {
|
|
204
|
+
erro('fases', `"${fase}" nao e fase do ciclo canonico (${types_1.FASES.join(', ')})`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const usadas = variaveisUsadas(t.corpo);
|
|
209
|
+
const naoDeclaradas = usadas.filter((v) => !t.variaveis.includes(v));
|
|
210
|
+
if (naoDeclaradas.length > 0) {
|
|
211
|
+
erro('variaveis', `usadas no corpo e nao declaradas: ${naoDeclaradas.join(', ')}`);
|
|
212
|
+
}
|
|
213
|
+
const naoUsadas = t.variaveis.filter((v) => !usadas.includes(v));
|
|
214
|
+
if (naoUsadas.length > 0) {
|
|
215
|
+
erro('variaveis', `declaradas e nao usadas: ${naoUsadas.join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
for (const secao of contrato.secoes) {
|
|
218
|
+
if (!t.corpo.includes(secao))
|
|
219
|
+
erro('secoes', `secao obrigatoria ausente: ${secao}`);
|
|
220
|
+
}
|
|
221
|
+
if (!t.corpo.includes(contrato.regraCentral)) {
|
|
222
|
+
erro('regra-central', `o template perdeu a linha: ${contrato.regraCentral}`);
|
|
223
|
+
}
|
|
224
|
+
if (contrato.variavelObrigatoria && !usadas.includes(contrato.variavelObrigatoria)) {
|
|
225
|
+
erro(contrato.variavelObrigatoria, `o template nao usa {{${contrato.variavelObrigatoria}}}: despacharia uma ${contrato.nome} sem a demanda do builder`);
|
|
226
|
+
}
|
|
227
|
+
for (const achado of (0, policies_1.procurarSegredos)(t.bruto)) {
|
|
228
|
+
erro('segredo', `padrao "${achado.nome}" casou na linha ${achado.linha} do template (trecho omitido de proposito)`);
|
|
229
|
+
}
|
|
230
|
+
const bytes = Buffer.byteLength(t.bruto, 'utf8');
|
|
231
|
+
if (bytes > exports.LIMITE_TEMPLATE_BYTES) {
|
|
232
|
+
erro('tamanho', `${bytes} bytes acima do limite de ${exports.LIMITE_TEMPLATE_BYTES}`);
|
|
233
|
+
}
|
|
234
|
+
if (t.bruto.includes('—')) {
|
|
235
|
+
aviso('idioma', 'o template usa travessao longo (U+2014); o padrao do projeto e hifen ou virgula');
|
|
236
|
+
}
|
|
237
|
+
return problemas;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* O template embutido da fase.
|
|
241
|
+
*
|
|
242
|
+
* Ele e a fonte de verdade do prompt do `ork` e a copia versionada em `prompts/fase-padrao.md`
|
|
243
|
+
* precisa ser identica byte a byte (ha teste que prova isso). Renderizado, ele produz
|
|
244
|
+
* exatamente o prompt que o B0 montava por concatenacao: mudar o texto aqui e mudar contrato.
|
|
245
|
+
*/
|
|
246
|
+
exports.TEMPLATE_FASE_PADRAO = `---
|
|
247
|
+
id: fase-padrao
|
|
248
|
+
versao: 2
|
|
249
|
+
descricao: Prompt canonico de uma fase do ciclo GOAL..MASTER, com nomenclatura, modo de conducao, contexto da thread, memoria injetada e regras de evidencia.
|
|
250
|
+
fases: [GOAL, PLAN, GO, CHECK, SHIP, MASTER]
|
|
251
|
+
variaveis: [fase, thread, bloco, nome, ciclo_canonico, tag, blocos, pausas, linha_variante, regra_de_pausa, invariantes, slug, projeto_nome, projeto_abbrev, base_branch, base_commit, diretorio, pedido, memoria_injetada, regras_de_evidencia]
|
|
252
|
+
---
|
|
253
|
+
# Orkastery, fase {{fase}} da thread {{thread}}
|
|
254
|
+
|
|
255
|
+
Voce conduz o bloco {{bloco}} da thread "{{nome}}".
|
|
256
|
+
|
|
257
|
+
## Ciclo canonico
|
|
258
|
+
{{ciclo_canonico}}
|
|
259
|
+
|
|
260
|
+
## Modo de conducao: {{tag}}
|
|
261
|
+
Blocos desta thread: {{blocos}}
|
|
262
|
+
Pausas humanas desta thread: {{pausas}}
|
|
263
|
+
{{linha_variante}}
|
|
264
|
+
{{regra_de_pausa}}
|
|
265
|
+
|
|
266
|
+
REGRA CENTRAL: o modo afrouxa a pausa, NUNCA a verificacao.
|
|
267
|
+
{{invariantes}}
|
|
268
|
+
|
|
269
|
+
## Contexto da thread
|
|
270
|
+
- thread: {{thread}} (slug {{slug}})
|
|
271
|
+
- projeto: {{projeto_nome}} (abbrev {{projeto_abbrev}})
|
|
272
|
+
- base carimbada: {{base_branch}} @ {{base_commit}}
|
|
273
|
+
- diretorio de trabalho: {{diretorio}}
|
|
274
|
+
- estado da thread: .orkastery/threads/{{thread}}/thread.json
|
|
275
|
+
- ledger: .orkastery/threads/{{thread}}/ledger.jsonl
|
|
276
|
+
|
|
277
|
+
## Pedido do builder
|
|
278
|
+
{{pedido}}
|
|
279
|
+
{{memoria_injetada}}
|
|
280
|
+
|
|
281
|
+
## Regras de evidencia
|
|
282
|
+
{{regras_de_evidencia}}`;
|
|
283
|
+
/** Os templates que vem dentro do `ork`, por id. */
|
|
284
|
+
exports.TEMPLATES_EMBUTIDOS = {
|
|
285
|
+
'fase-padrao': exports.TEMPLATE_FASE_PADRAO,
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Carrega templates de um conjunto de embutidos, sobrescritos pelos `.md` de um diretorio.
|
|
289
|
+
*
|
|
290
|
+
* Parametrizada por diretorio para que os prompts de FASE (`prompts/`) e os prompts de
|
|
291
|
+
* AUDITORIA (`auditors/`, bloco B5) usem a mesma carga, com a mesma precedencia e a mesma
|
|
292
|
+
* regra de README, em vez de duas implementacoes que divergem com o tempo.
|
|
293
|
+
*/
|
|
294
|
+
function carregarTemplatesDe(embutidos, dir) {
|
|
295
|
+
const porId = new Map();
|
|
296
|
+
for (const [id, bruto] of Object.entries(embutidos)) {
|
|
297
|
+
porId.set(id, parseTemplate(bruto, 'embutido'));
|
|
298
|
+
}
|
|
299
|
+
if (dir && fs.existsSync(dir)) {
|
|
300
|
+
for (const arquivo of fs.readdirSync(dir).sort()) {
|
|
301
|
+
if (!arquivo.endsWith('.md'))
|
|
302
|
+
continue;
|
|
303
|
+
// O README do diretorio documenta os templates; ele nao e um deles. Qualquer OUTRO
|
|
304
|
+
// `.md` sem frontmatter continua sendo erro, para template quebrado nao virar arquivo
|
|
305
|
+
// silenciosamente ignorado.
|
|
306
|
+
if (arquivo === 'README.md')
|
|
307
|
+
continue;
|
|
308
|
+
const caminho = path.join(dir, arquivo);
|
|
309
|
+
const t = parseTemplate(fs.readFileSync(caminho, 'utf8'), caminho);
|
|
310
|
+
porId.set(t.id || path.basename(arquivo, '.md'), t);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return [...porId.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Todos os templates que valem para este projeto: os embutidos, sobrescritos por
|
|
317
|
+
* `<raiz>/prompts/<id>.md` quando o projeto versiona o seu proprio.
|
|
318
|
+
*/
|
|
319
|
+
function carregarTemplates(raiz) {
|
|
320
|
+
return carregarTemplatesDe(exports.TEMPLATES_EMBUTIDOS, raiz ? dirDeTemplates(raiz) : undefined);
|
|
321
|
+
}
|
|
322
|
+
/** Um template por id, com erro tipado quando ele nao existe. */
|
|
323
|
+
function exigirTemplate(id, raiz) {
|
|
324
|
+
const achado = carregarTemplates(raiz).find((t) => t.id === id);
|
|
325
|
+
if (!achado) {
|
|
326
|
+
const ids = carregarTemplates(raiz).map((t) => t.id).join(', ');
|
|
327
|
+
throw new Error(`template "${id}" nao existe (conhecidos: ${ids})`);
|
|
328
|
+
}
|
|
329
|
+
return achado;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* O template que conduz a fase: `fase-<minuscula>` quando o projeto versionou um so para ela,
|
|
333
|
+
* senao o `fase-padrao`. E o unico ponto de resolucao, para nao haver duas regras de escolha.
|
|
334
|
+
*/
|
|
335
|
+
function templateDaFase(fase, raiz) {
|
|
336
|
+
const templates = carregarTemplates(raiz);
|
|
337
|
+
const especifico = templates.find((t) => t.id === `fase-${fase.toLowerCase()}`);
|
|
338
|
+
if (especifico)
|
|
339
|
+
return especifico;
|
|
340
|
+
const padrao = templates.find((t) => t.id === 'fase-padrao');
|
|
341
|
+
if (!padrao)
|
|
342
|
+
throw new Error('template `fase-padrao` ausente: o `ork` nao tem prompt de fase');
|
|
343
|
+
return padrao;
|
|
344
|
+
}
|
|
345
|
+
/** Texto de `ork prompt lint` para a saida do CLI. */
|
|
346
|
+
function textoDoLint(problemas, quantos) {
|
|
347
|
+
const linhas = [];
|
|
348
|
+
const erros = problemas.filter((p) => p.gravidade === 'erro');
|
|
349
|
+
const avisos = problemas.filter((p) => p.gravidade === 'aviso');
|
|
350
|
+
for (const p of problemas) {
|
|
351
|
+
linhas.push(` [${p.gravidade}] ${p.template} :: ${p.regra}`);
|
|
352
|
+
linhas.push(` ${p.detalhe}`);
|
|
353
|
+
}
|
|
354
|
+
if (problemas.length === 0) {
|
|
355
|
+
linhas.push(` nenhum problema em ${quantos} template(s)`);
|
|
356
|
+
}
|
|
357
|
+
linhas.push('');
|
|
358
|
+
linhas.push(`templates: ${quantos} | erros: ${erros.length} | avisos: ${avisos.length}`);
|
|
359
|
+
return linhas.join('\n');
|
|
360
|
+
}
|
|
361
|
+
/** Texto de `ork prompt list`. */
|
|
362
|
+
function tabelaDeTemplates(templates) {
|
|
363
|
+
const linhas = ['Templates de prompt (o do projeto sobrescreve o embutido de mesmo id)', ''];
|
|
364
|
+
for (const t of templates) {
|
|
365
|
+
linhas.push(` ${t.id.padEnd(16)} v${t.versao} origem: ${t.origem}`);
|
|
366
|
+
linhas.push(` ${' '.repeat(16)} fases: ${t.fases.length > 0 ? t.fases.join(' ') : 'todas'}`);
|
|
367
|
+
linhas.push(` ${' '.repeat(16)} ${t.descricao}`);
|
|
368
|
+
}
|
|
369
|
+
linhas.push('');
|
|
370
|
+
linhas.push('Para versionar o seu: grave em prompts/<id>.md e rode `ork prompt lint`.');
|
|
371
|
+
return linhas.join('\n');
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Uma thread de exemplo, em memoria, para `ork prompt render --exemplo`.
|
|
375
|
+
*
|
|
376
|
+
* Ela existe para que o builder possa ver o prompt exato de um modo ANTES de criar thread
|
|
377
|
+
* nenhuma, e para que o lint tenha um alvo de renderizacao sem depender de estado em disco.
|
|
378
|
+
* Nada aqui e gravado: o exemplo nunca vira thread.
|
|
379
|
+
*/
|
|
380
|
+
function threadDeExemplo(modo, fase, projeto) {
|
|
381
|
+
const def = (0, modos_1.definicaoDoModo)(modo);
|
|
382
|
+
const p = projeto ?? { name: 'exemplo', abbrev: 'exe' };
|
|
383
|
+
const agora = '1970-01-01T00:00:00.000Z';
|
|
384
|
+
return {
|
|
385
|
+
id: `${p.abbrev}-exemplo`,
|
|
386
|
+
slug: `${p.abbrev}-exemplo-${def.blocos[0].slugFases}`,
|
|
387
|
+
nome: 'exemplo de prompt',
|
|
388
|
+
assunto: 'exemplo',
|
|
389
|
+
modo,
|
|
390
|
+
fases: [...types_1.FASES],
|
|
391
|
+
blocos: def.blocos,
|
|
392
|
+
faseAtual: fase,
|
|
393
|
+
status: 'aberta',
|
|
394
|
+
criadaEm: agora,
|
|
395
|
+
atualizadaEm: agora,
|
|
396
|
+
projeto: p,
|
|
397
|
+
base: { branch: 'main', commit: '0'.repeat(40) },
|
|
398
|
+
worktree: null,
|
|
399
|
+
sessoes: [],
|
|
400
|
+
decisoes: [],
|
|
401
|
+
claims: [],
|
|
402
|
+
leases: [],
|
|
403
|
+
baseline: null,
|
|
404
|
+
variante: null,
|
|
405
|
+
};
|
|
406
|
+
}
|