@orkastery/cli 0.2.0 → 0.3.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 +1 -1
- package/README.md +86 -65
- package/adapters/README.md +30 -15
- package/adapters/claude-code/.claude-plugin/plugin.json +11 -5
- package/adapters/claude-code/README.md +110 -8
- package/adapters/claude-code/commands/onboarding.md +21 -0
- package/adapters/claude-code/commands/ork.md +99 -27
- package/adapters/claude-code/hooks/hooks.json +6 -1
- package/adapters/claude-code/hooks/ork-sensor.js +66 -0
- package/adapters/codex/skills/ork/SKILL.md +57 -0
- package/adapters/hermes/README.md +163 -0
- package/adapters/hermes/bin/ork-abrir-thread.sh +2 -0
- package/adapters/hermes/bin/ork-brain.sh +4 -0
- package/adapters/hermes/bin/ork-hitl-answer.py +82 -0
- package/adapters/hermes/bin/ork-maestro.sh +6 -0
- package/adapters/hermes/bin/ork-master-enviar.py +84 -0
- package/adapters/hermes/bin/ork-objective-message.sh +17 -0
- package/adapters/hermes/bin/ork-objective-status.sh +12 -0
- package/adapters/hermes/bin/ork-pulse-enviar.py +47 -0
- package/adapters/hermes/hermes.plugin.json +26 -4
- package/adapters/hermes/hitl-ingress/__init__.py +356 -0
- package/adapters/hermes/hitl-ingress/plugin.yaml +4 -0
- package/adapters/hermes/skills/orkastery-devmaster/SKILL.md +75 -22
- package/adapters/openclaw/README.md +135 -26
- package/adapters/openclaw/bin/ork-brain.sh +4 -0
- package/adapters/openclaw/construir.sh +19 -0
- package/adapters/openclaw/dist/hitl-ingress.js +199 -0
- package/adapters/openclaw/dist/index.js +437 -0
- package/adapters/openclaw/openclaw.plugin.json +39 -134
- package/adapters/openclaw/package.json +26 -0
- package/adapters/openclaw/src/hitl-ingress.ts +180 -0
- package/adapters/openclaw/src/index.ts +458 -0
- package/adapters/openclaw/src/tipos-openclaw.d.ts +58 -0
- package/adapters/openclaw/tsconfig.json +15 -0
- package/assets/docs/markdownlint-cli2.jsonc +22 -0
- package/assets/docs/padroes/documentacao-de-produto.md +133 -0
- package/assets/docs/padroes/roadmap-de-produto.md +98 -0
- package/assets/docs/produto/README.md +14 -0
- package/assets/docs/produto/_modelo-feature.md +59 -0
- package/assets/docs/roadmap/README.md +14 -0
- package/assets/docs/roadmap/_modelo-item.md +73 -0
- package/assets/orkmind-native-schema.json +32 -0
- package/assets/orkmind_bridge.py +357 -0
- package/assets/orkmind_fixture.py +67 -0
- package/assets/orkmind_prospective.py +138 -0
- package/assets/reference-tariffs-i07.json +23 -0
- package/dist/adapters/claude-bg.js +488 -39
- package/dist/adapters/codex-controller-sensor.js +449 -0
- package/dist/adapters/codex-controller-worker.js +361 -0
- package/dist/adapters/codex-controller.js +252 -0
- package/dist/adapters/codex-events.js +205 -0
- package/dist/adapters/codex-question.js +26 -0
- package/dist/adapters/codex-runner.js +133 -0
- package/dist/adapters/codex.js +394 -0
- package/dist/agents-md.js +84 -0
- package/dist/auditoria.js +11 -8
- package/dist/auditrun.js +5 -4
- package/dist/board.js +173 -22
- package/dist/branch-de-estado.js +136 -0
- package/dist/canarios-hitl.js +177 -0
- package/dist/canarios-i43.js +543 -0
- package/dist/canarios-pulse.js +147 -0
- package/dist/canarios-sensores.js +129 -0
- package/dist/canarios.js +111 -2
- package/dist/catalogo.js +9 -0
- package/dist/ci.js +216 -0
- package/dist/ciclos.js +2 -1
- package/dist/claim-lint.js +64 -0
- package/dist/claims.js +60 -0
- package/dist/company-brain-capture.js +195 -0
- package/dist/company-brain-cli.js +123 -0
- package/dist/company-brain-client.js +61 -0
- package/dist/company-brain-contract.js +166 -0
- package/dist/company-brain-journal.js +192 -0
- package/dist/company-brain-mcp.js +33 -0
- package/dist/company-brain-migration.js +60 -0
- package/dist/company-brain-source.js +177 -0
- package/dist/company-brain-worker.js +18 -0
- package/dist/conducao-texto.js +61 -0
- package/dist/conducao.js +876 -0
- package/dist/contrato-publico.js +39 -0
- package/dist/creation-operation-store.js +251 -0
- package/dist/creation-operation.js +148 -0
- package/dist/decisao-autonoma.js +183 -0
- package/dist/delegation.js +79 -0
- package/dist/demo.js +120 -0
- package/dist/docs.js +828 -0
- package/dist/doctor.js +183 -24
- package/dist/entrega-pr.js +117 -0
- package/dist/escopo-escrita.js +71 -0
- package/dist/estado-thread.js +222 -0
- package/dist/evalrunner.js +12 -0
- package/dist/fabrica-estado.js +329 -0
- package/dist/fabrica-publicar.js +76 -0
- package/dist/fix.js +21 -0
- package/dist/gates.js +55 -9
- package/dist/handoff.js +39 -23
- package/dist/hitl-canais.js +333 -0
- package/dist/hitl-classificacao.js +131 -0
- package/dist/hitl-contract.js +465 -0
- package/dist/hitl-estado.js +136 -0
- package/dist/hitl-gates.js +554 -0
- package/dist/hitl-ingress-receipt.js +382 -0
- package/dist/hitl-local-atestado.js +57 -0
- package/dist/hitl-local-receipt.js +328 -0
- package/dist/hitl-local.js +143 -0
- package/dist/hitl-lock.js +139 -0
- package/dist/hitl-lote.js +223 -0
- package/dist/hitl-native-offer.js +97 -0
- package/dist/hitl-native.js +65 -0
- package/dist/hitl-presentation.js +209 -0
- package/dist/hitl-public-receipt.js +176 -0
- package/dist/hitl-resumo.js +209 -0
- package/dist/hitl-sessions.js +475 -0
- package/dist/hitl.js +711 -0
- package/dist/horario.js +269 -0
- package/dist/hosts.js +198 -22
- package/dist/index.js +1736 -93
- package/dist/indice.js +99 -0
- package/dist/init.js +28 -3
- package/dist/integracoes-locais.js +17 -0
- package/dist/leases.js +65 -21
- package/dist/ledger-stats.js +272 -0
- package/dist/ledger.js +107 -2
- package/dist/licoes.js +215 -0
- package/dist/liveness.js +218 -0
- package/dist/maestro-actions.js +51 -0
- package/dist/maestro-authority.js +152 -0
- package/dist/maestro-cli.js +97 -0
- package/dist/maestro-contract.js +55 -0
- package/dist/maestro-discovery.js +132 -0
- package/dist/maestro-runtime.js +268 -0
- package/dist/maestro-snapshot.js +86 -0
- package/dist/maestro-sources.js +224 -0
- package/dist/manifest.js +165 -6
- package/dist/maquina.js +102 -0
- package/dist/master-audit.js +101 -0
- package/dist/master-batch.js +43 -0
- package/dist/master-digest.js +149 -0
- package/dist/master-migracao.js +155 -0
- package/dist/master.js +361 -77
- package/dist/mcp-artifacts.js +234 -0
- package/dist/mcp-git.js +433 -0
- package/dist/mcp-install.js +310 -0
- package/dist/mcp-maestro.js +25 -0
- package/dist/mcp-server.js +469 -0
- package/dist/mcp-ship.js +436 -0
- package/dist/mcp-verify.js +173 -0
- package/dist/memoria-humana.js +207 -0
- package/dist/memoria.js +314 -78
- package/dist/memory-migration.js +279 -0
- package/dist/memory-prospective.js +148 -0
- package/dist/modos-migracao.js +191 -0
- package/dist/modos.js +151 -15
- package/dist/monitor-lock.js +110 -0
- package/dist/objective.js +449 -0
- package/dist/ocupacao.js +267 -0
- package/dist/onboarding.js +303 -0
- package/dist/orkmind.js +389 -90
- package/dist/orquestracao.js +96 -69
- package/dist/phase.js +596 -150
- package/dist/playbook-capabilities.js +150 -0
- package/dist/playbook-contracts.js +188 -0
- package/dist/portfolio-context.js +61 -0
- package/dist/portfolio.js +180 -0
- package/dist/preflight.js +207 -0
- package/dist/process-audit.js +112 -0
- package/dist/project-state.js +93 -0
- package/dist/prompts.js +25 -5
- package/dist/prova-minima.js +112 -0
- package/dist/pulse-cadencia.js +163 -0
- package/dist/pulse-consentimento.js +260 -0
- package/dist/pulse-delivery.js +323 -0
- package/dist/pulse-resposta.js +542 -0
- package/dist/pulse.js +262 -0
- package/dist/ratelimit.js +35 -2
- package/dist/recall.js +80 -3
- package/dist/redacao-saida.js +45 -0
- package/dist/redacao-url.js +66 -0
- package/dist/retry.js +637 -115
- package/dist/roadmap-reservas.js +243 -0
- package/dist/runtime-ambiente.js +38 -0
- package/dist/runtime-context.js +215 -0
- package/dist/runtime-profiles.js +869 -0
- package/dist/runtimes.js +103 -0
- package/dist/sandbox.js +9 -0
- package/dist/session-events.js +203 -0
- package/dist/session-watcher-claude.js +637 -0
- package/dist/session-watcher.js +683 -0
- package/dist/sessoes-adopt.js +154 -0
- package/dist/sessoes-inventario.js +159 -0
- package/dist/sessoes.js +16 -35
- package/dist/setup.js +691 -0
- package/dist/ship.js +150 -15
- package/dist/slug.js +1 -1
- package/dist/thread-close.js +115 -0
- package/dist/thread.js +142 -28
- package/dist/tokens.js +1 -1
- package/dist/util.js +4 -2
- package/dist/verify-sandbox.js +265 -0
- package/dist/verify.js +270 -25
- package/dist/versao.js +60 -0
- package/dist/worktree.js +31 -2
- package/dist/write-activation.js +291 -0
- package/dist/yaml.js +2 -0
- package/eval/casos/onboarding.json +102 -0
- package/eval/casos/orkastery-bootstrap.json +35 -1
- package/eval/casos/scope-check-capability-map.json +45 -7
- package/eval/casos/ship-release.json +2 -2
- package/eval/fixtures/b0-slug-e-modos/caso.json +35 -22
- package/eval/fixtures/b2-master-log/caso.json +1 -1
- package/eval/fixtures/fx-adapter-editado-detectado/caso.json +19 -0
- package/eval/fixtures/fx-auto-quiet/caso.json +11 -0
- package/eval/fixtures/fx-blanket-approve/caso.json +10 -0
- package/eval/fixtures/fx-check-runtime-cruzado/caso.json +20 -0
- package/eval/fixtures/fx-codex-dry/caso.json +18 -0
- package/eval/fixtures/fx-donewhen-executavel/caso.json +15 -0
- package/eval/fixtures/fx-estado-dividido/caso.json +15 -0
- package/eval/fixtures/fx-fase-orfa/caso.json +116 -0
- package/eval/fixtures/fx-happy/caso.json +6 -4
- package/eval/fixtures/fx-hitl-latency/caso.json +20 -0
- package/eval/fixtures/fx-indice-reversao/caso.json +25 -0
- package/eval/fixtures/fx-listagem-abertas/caso.json +16 -0
- package/eval/fixtures/fx-maestro-bootstrap/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-escritor/caso.json +21 -0
- package/eval/fixtures/fx-modo-aposentado-leitor/caso.json +16 -0
- package/eval/fixtures/fx-objective-oscillation/caso.json +14 -0
- package/eval/fixtures/fx-omnicanal/caso.json +17 -0
- package/eval/fixtures/fx-sensores-runtime/caso.json +17 -0
- package/monitor/company-brain.cjs +17 -0
- package/monitor/pulse-scope.cjs +45 -0
- package/monitor/pulse.cron +18 -0
- package/monitor/varredura-pulse.sh +27 -0
- package/package.json +25 -4
- package/schemas/claims.schema.json +25 -0
- package/schemas/company-brain.schema.json +1158 -0
- package/schemas/creation-operation.schema.json +365 -0
- package/schemas/maestro-snapshot.schema.json +2716 -0
- package/skills/core/onboarding/SKILL.md +50 -0
- package/skills/core/orkastery-bootstrap/SKILL.md +95 -65
- package/skills/core/thread-state/SKILL.md +3 -1
- package/skills/governance/scope-check-capability-map/SKILL.md +17 -7
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validarArquivosEstadoMcp = validarArquivosEstadoMcp;
|
|
37
|
+
exports.lerArtefatoMcp = lerArtefatoMcp;
|
|
38
|
+
exports.escreverArtefatoMcp = escreverArtefatoMcp;
|
|
39
|
+
exports.listarClaimsMcp = listarClaimsMcp;
|
|
40
|
+
exports.adicionarClaimMcp = adicionarClaimMcp;
|
|
41
|
+
/** Documentos do agente, separados dos recibos oficiais do nucleo. Backend Linux como HITL. */
|
|
42
|
+
const fs = __importStar(require("node:fs"));
|
|
43
|
+
const path = __importStar(require("node:path"));
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const thread_1 = require("./thread");
|
|
46
|
+
const hitl_gates_1 = require("./hitl-gates");
|
|
47
|
+
const leases_1 = require("./leases");
|
|
48
|
+
const claims_1 = require("./claims");
|
|
49
|
+
const fases = { goal: 'GOAL', plan: 'PLAN', check: 'CHECK' };
|
|
50
|
+
const limite = 128 * 1024;
|
|
51
|
+
const hash = (bytes) => (0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex');
|
|
52
|
+
/** Core legado usa nomes de arquivos: este preflight recusa links existentes, nao promete CAS contra writers hostis. */
|
|
53
|
+
function validarArquivosEstadoMcp(raiz, id) {
|
|
54
|
+
validarId(id);
|
|
55
|
+
for (const nome of ['thread.json', 'ledger.jsonl', 'claims.jsonl']) {
|
|
56
|
+
let fd;
|
|
57
|
+
try {
|
|
58
|
+
fd = fs.openSync(path.join((0, thread_1.dirThread)(raiz, id), nome), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
if (e.code === 'ENOENT' && nome !== 'thread.json')
|
|
62
|
+
continue;
|
|
63
|
+
throw Error('mcp.state.unsafe');
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const st = fs.fstatSync(fd);
|
|
67
|
+
if (!st.isFile() || st.nlink !== 1)
|
|
68
|
+
throw Error('mcp.state.unsafe');
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
fs.closeSync(fd);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function validarId(id) {
|
|
76
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(id))
|
|
77
|
+
throw Error('mcp.artifact.thread.invalid');
|
|
78
|
+
}
|
|
79
|
+
function validarTipo(tipo) {
|
|
80
|
+
if (!Object.hasOwn(fases, tipo))
|
|
81
|
+
throw Error('mcp.artifact.kind.invalid');
|
|
82
|
+
}
|
|
83
|
+
/** Ancora cada componente existente com O_NOFOLLOW, inclusive docs. Nao segue link de worktree. */
|
|
84
|
+
function comDocs(raiz, id, acao, criar = false) {
|
|
85
|
+
validarId(id);
|
|
86
|
+
if (process.platform !== 'linux')
|
|
87
|
+
throw Error('runtime.unavailable: documentos MCP exigem descritores Linux');
|
|
88
|
+
const canonico = path.resolve(raiz), destino = path.resolve((0, thread_1.dirThread)(canonico, id), 'docs');
|
|
89
|
+
const rel = path.relative(canonico, destino);
|
|
90
|
+
if (rel.startsWith('..') || path.isAbsolute(rel))
|
|
91
|
+
throw Error('mcp.artifact.scope');
|
|
92
|
+
const fds = [];
|
|
93
|
+
try {
|
|
94
|
+
let fd = fs.openSync(canonico, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
95
|
+
fds.push(fd);
|
|
96
|
+
const partes = rel.split(path.sep);
|
|
97
|
+
for (const [i, parte] of partes.entries()) {
|
|
98
|
+
const alvo = `/proc/self/fd/${fd}/${parte}`;
|
|
99
|
+
try {
|
|
100
|
+
fd = fs.openSync(alvo, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
if (e.code !== 'ENOENT' || i !== partes.length - 1)
|
|
104
|
+
throw e;
|
|
105
|
+
if (!criar)
|
|
106
|
+
return acao(null);
|
|
107
|
+
fs.mkdirSync(alvo, { mode: 0o700 });
|
|
108
|
+
fd = fs.openSync(alvo, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
|
|
109
|
+
}
|
|
110
|
+
fds.push(fd);
|
|
111
|
+
}
|
|
112
|
+
return acao(`/proc/self/fd/${fd}`);
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
for (const fd of fds.reverse())
|
|
116
|
+
fs.closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function lerDocumento(file) {
|
|
120
|
+
let fd;
|
|
121
|
+
try {
|
|
122
|
+
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
if (e.code === 'ENOENT')
|
|
126
|
+
return null;
|
|
127
|
+
throw Error('mcp.artifact.unsafe');
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const st = fs.fstatSync(fd);
|
|
131
|
+
if (!st.isFile() || st.nlink !== 1 || st.size > limite)
|
|
132
|
+
throw Error('mcp.artifact.unsafe');
|
|
133
|
+
const bytes = fs.readFileSync(fd);
|
|
134
|
+
if (bytes.length > limite)
|
|
135
|
+
throw Error('mcp.artifact.too-large');
|
|
136
|
+
return bytes;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
fs.closeSync(fd);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function comEscrita(raiz, id, executar) {
|
|
143
|
+
validarId(id);
|
|
144
|
+
validarArquivosEstadoMcp(raiz, id);
|
|
145
|
+
// A mesma exclusao usada por despacho/HITL impede duas revisoes MCP simultaneas.
|
|
146
|
+
return (0, hitl_gates_1.comLockHitl)(raiz, id, () => {
|
|
147
|
+
validarArquivosEstadoMcp(raiz, id);
|
|
148
|
+
const pastaLeases = path.join(raiz, '.orkastery', 'leases');
|
|
149
|
+
const st = fs.lstatSync(pastaLeases, { throwIfNoEntry: false });
|
|
150
|
+
if (st) {
|
|
151
|
+
if (!st.isDirectory() || st.isSymbolicLink() || fs.realpathSync(pastaLeases) !== pastaLeases)
|
|
152
|
+
throw Error('mcp.state.unsafe: leases');
|
|
153
|
+
for (const nome of fs.readdirSync(pastaLeases)) {
|
|
154
|
+
const s = fs.lstatSync(path.join(pastaLeases, nome));
|
|
155
|
+
if (!s.isFile() || s.isSymbolicLink() || s.nlink !== 1)
|
|
156
|
+
throw Error('mcp.state.unsafe: lease ou fila');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const nome = `path:.orkastery/threads/${id}`;
|
|
160
|
+
if ((0, leases_1.lerLease)(raiz, nome) || (0, leases_1.leasesColidentes)(raiz, nome).length)
|
|
161
|
+
throw Error('lease.busy: estado canonico ocupado');
|
|
162
|
+
const r = (0, leases_1.adquirirRegiao)(raiz, nome, { thread: id, motivo: 'MCP documento ou claim', ttlMs: 30000, retomarVencido: false });
|
|
163
|
+
if (!r.ok || !r.lease)
|
|
164
|
+
throw Error('lease.busy: estado canonico ocupado');
|
|
165
|
+
try {
|
|
166
|
+
return executar();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
const atual = (0, leases_1.lerLease)(raiz, nome);
|
|
170
|
+
if (atual?.adquiridoEm === r.lease.adquiridoEm && atual.pid === r.lease.pid && atual.thread === id)
|
|
171
|
+
(0, leases_1.liberar)(raiz, nome, id);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function lerArtefatoMcp(raiz, id, tipo) {
|
|
176
|
+
validarTipo(tipo);
|
|
177
|
+
validarArquivosEstadoMcp(raiz, id);
|
|
178
|
+
(0, thread_1.lerThread)(raiz, id);
|
|
179
|
+
return comDocs(raiz, id, dir => {
|
|
180
|
+
const bytes = dir ? lerDocumento(path.join(dir, tipo + '.md')) : null;
|
|
181
|
+
return { tipo, arquivo: `docs/${tipo}.md`, sha256: bytes ? hash(bytes) : null, conteudo: bytes?.toString('utf8') ?? null };
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function escreverArtefatoMcp(raiz, id, tipo, conteudo, esperado) {
|
|
185
|
+
validarTipo(tipo);
|
|
186
|
+
validarId(id);
|
|
187
|
+
if (typeof conteudo !== 'string' || Buffer.byteLength(conteudo) > limite)
|
|
188
|
+
throw Error('mcp.artifact.too-large');
|
|
189
|
+
if (esperado !== null && !/^[a-f0-9]{64}$/.test(esperado))
|
|
190
|
+
throw Error('mcp.artifact.expected.invalid');
|
|
191
|
+
// Valida a cadeia antes de qualquer lock/escrita no estado canônico.
|
|
192
|
+
return comDocs(raiz, id, () => comEscrita(raiz, id, () => {
|
|
193
|
+
const t = (0, thread_1.lerThread)(raiz, id);
|
|
194
|
+
if (t.status === 'fechada' || !(0, thread_1.blocoDaThread)(t, t.faseAtual).fases.includes(fases[tipo]))
|
|
195
|
+
throw Error('mcp.artifact.phase');
|
|
196
|
+
return comDocs(raiz, id, dir => {
|
|
197
|
+
if (!dir)
|
|
198
|
+
throw Error('mcp.artifact.directory');
|
|
199
|
+
const file = path.join(dir, tipo + '.md'), antes = lerDocumento(file);
|
|
200
|
+
if ((antes ? hash(antes) : null) !== esperado)
|
|
201
|
+
throw Error('mcp.artifact.stale');
|
|
202
|
+
const temp = path.join(dir, '.mcp-document-' + (0, node_crypto_1.randomUUID)());
|
|
203
|
+
try {
|
|
204
|
+
fs.writeFileSync(temp, conteudo, { flag: 'wx', mode: 0o600 });
|
|
205
|
+
const atual = lerDocumento(file);
|
|
206
|
+
if ((atual ? hash(atual) : null) !== esperado)
|
|
207
|
+
throw Error('mcp.artifact.stale');
|
|
208
|
+
fs.renameSync(temp, file);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
fs.rmSync(temp, { force: true });
|
|
212
|
+
}
|
|
213
|
+
return { tipo, arquivo: `docs/${tipo}.md`, sha256: hash(conteudo), autoria: 'agente', reciboOficial: false };
|
|
214
|
+
}, true);
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
function listarClaimsMcp(raiz, id) {
|
|
218
|
+
return comDocs(raiz, id, () => { validarArquivosEstadoMcp(raiz, id); (0, thread_1.lerThread)(raiz, id); return (0, claims_1.lerClaims)(raiz, id); });
|
|
219
|
+
}
|
|
220
|
+
function adicionarClaimMcp(raiz, id, entrada) {
|
|
221
|
+
validarId(id);
|
|
222
|
+
if (!entrada.arquivo || entrada.arquivo.length > 500 || path.isAbsolute(entrada.arquivo) ||
|
|
223
|
+
entrada.arquivo.split(/[\\/]/).some(p => !p || p === '..' || p === '.' || p === '.git'))
|
|
224
|
+
throw Error('mcp.claim.path.invalid');
|
|
225
|
+
if (!entrada.alegacao.trim() || entrada.alegacao.length > 8192 || entrada.verificar.length > 20 ||
|
|
226
|
+
entrada.verificar.some(c => typeof c !== 'string' || !c.trim() || c.length > 8192))
|
|
227
|
+
throw Error('mcp.claim.invalid');
|
|
228
|
+
return comDocs(raiz, id, () => comEscrita(raiz, id, () => {
|
|
229
|
+
if ((0, thread_1.lerThread)(raiz, id).status === 'fechada')
|
|
230
|
+
throw Error('mcp.claim.closed');
|
|
231
|
+
// Strings sao dados da claim; a reexecucao MCP deve usar o executor confinado.
|
|
232
|
+
return (0, claims_1.adicionarClaim)(raiz, id, entrada);
|
|
233
|
+
}));
|
|
234
|
+
}
|
package/dist/mcp-git.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.estadoGitMcp = estadoGitMcp;
|
|
37
|
+
exports.commitMcp = commitMcp;
|
|
38
|
+
exports.perfilGitMcp = perfil;
|
|
39
|
+
exports.gitPassivoMcp = git;
|
|
40
|
+
exports.caminhoFisicoGitMcp = fisico;
|
|
41
|
+
exports.metadadosGitMcp = arvoreMetadados;
|
|
42
|
+
exports.configuracaoPassivaGitMcp = configPassiva;
|
|
43
|
+
exports.ambienteGitMcp = ambiente;
|
|
44
|
+
/** Commit estreito no núcleo instalado. Repositórios com hooks/helpers ficam
|
|
45
|
+
* explicitamente indisponíveis; nenhum hook ou configuração é desativado. */
|
|
46
|
+
const fs = __importStar(require("node:fs"));
|
|
47
|
+
const path = __importStar(require("node:path"));
|
|
48
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
|
+
const node_child_process_1 = require("node:child_process");
|
|
50
|
+
const manifest_1 = require("./manifest");
|
|
51
|
+
const thread_1 = require("./thread");
|
|
52
|
+
const claims_1 = require("./claims");
|
|
53
|
+
const worktree_1 = require("./worktree");
|
|
54
|
+
const mcp_artifacts_1 = require("./mcp-artifacts");
|
|
55
|
+
const estado_thread_1 = require("./estado-thread");
|
|
56
|
+
const leases_1 = require("./leases");
|
|
57
|
+
const ledger_1 = require("./ledger");
|
|
58
|
+
const contrato_publico_1 = require("./contrato-publico");
|
|
59
|
+
const prova_minima_1 = require("./prova-minima");
|
|
60
|
+
const GIT = '/usr/bin/git', LIMITE = 262144, TTL = 120000;
|
|
61
|
+
function falha(codigo) { throw Error('mcp.git.' + codigo); }
|
|
62
|
+
function validar(p) {
|
|
63
|
+
if (!p || typeof p !== 'object' || Object.keys(p).sort().join(',') !== 'expectedHead,mensagem,paths,threadId' ||
|
|
64
|
+
typeof p.threadId !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(p.threadId) ||
|
|
65
|
+
typeof p.expectedHead !== 'string' || !/^[a-f0-9]{40}$/.test(p.expectedHead) ||
|
|
66
|
+
typeof p.mensagem !== 'string' || !p.mensagem.trim() || p.mensagem.length > 2048 || /[\x00-\x08\x0b-\x1f\x7f]/.test(p.mensagem) ||
|
|
67
|
+
!Array.isArray(p.paths) || !p.paths.length || p.paths.length > 32 || new Set(p.paths).size !== p.paths.length)
|
|
68
|
+
falha('request.invalid');
|
|
69
|
+
for (const f of p.paths)
|
|
70
|
+
if (typeof f !== 'string' || !f || f.length > 512 || path.isAbsolute(f) ||
|
|
71
|
+
path.normalize(f) !== f || /[\x00-\x1f\x7f\\]/.test(f) || f.split('/').some(x => !x || x === '.' || x === '..' ||
|
|
72
|
+
['.git', '.orkastery', '.codex', '.agents', '.claude'].includes(x) || x === '.env' || x.startsWith('.env.')))
|
|
73
|
+
falha('path.invalid');
|
|
74
|
+
}
|
|
75
|
+
function dentro(root, file) { return file.startsWith(root + path.sep); }
|
|
76
|
+
/**
|
|
77
|
+
* Confere que o caminho e fisicamente o que diz ser, dentro da worktree.
|
|
78
|
+
*
|
|
79
|
+
* I-43: um caminho AUSENTE passa, e so quando e arquivo. Sem isso o nucleo nao
|
|
80
|
+
* conseguia commitar uma DELECAO pelo proprio transporte: `fs.lstatSync` estourava
|
|
81
|
+
* antes do `git add`, e um GO que remove um arquivo ficava sem como entregar a
|
|
82
|
+
* remocao. Foi exatamente o que travou a saida de `fx-objective-oscillation`.
|
|
83
|
+
*
|
|
84
|
+
* O buraco que isso poderia abrir esta fechado por tres lados:
|
|
85
|
+
* - `validar()` ja recusou `..`, caminho absoluto, `.git`, `.orkastery` e afins
|
|
86
|
+
* ANTES daqui, entao "ausente" nunca significa "fora do escopo";
|
|
87
|
+
* - `git add` recusa pathspec que nunca existiu, entao caminho inventado continua
|
|
88
|
+
* reprovando, so que com outra mensagem;
|
|
89
|
+
* - o `lstat` (que NAO segue symlink) continua sendo feito quando o caminho existe,
|
|
90
|
+
* entao symlink pendurado nao escapa pela porta do ausente: ele tem lstat valido,
|
|
91
|
+
* nao e arquivo regular, e cai em `path.unsafe` como antes.
|
|
92
|
+
* E a selecao do indice e do commit continua conferida contra `p.paths` depois.
|
|
93
|
+
*/
|
|
94
|
+
function fisico(root, file, arquivo = false) {
|
|
95
|
+
if (file !== root && !dentro(root, file))
|
|
96
|
+
falha('scope.invalid');
|
|
97
|
+
let s;
|
|
98
|
+
try {
|
|
99
|
+
s = fs.lstatSync(file);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
if (arquivo)
|
|
103
|
+
return;
|
|
104
|
+
falha('path.unsafe');
|
|
105
|
+
}
|
|
106
|
+
if (fs.realpathSync(file) !== file)
|
|
107
|
+
falha('path.symlink');
|
|
108
|
+
if (arquivo ? !s.isFile() || s.nlink !== 1 : !s.isDirectory())
|
|
109
|
+
falha('path.unsafe');
|
|
110
|
+
}
|
|
111
|
+
function ambiente(env = process.env) {
|
|
112
|
+
// HOME preserva configurações efetivas do usuário; chaves desconhecidas recusam.
|
|
113
|
+
// Variáveis GIT/SSH/GPG vindas do cliente não são transportadas ao worker.
|
|
114
|
+
return { HOME: env.HOME, USER: env.USER, LOGNAME: env.LOGNAME,
|
|
115
|
+
XDG_CONFIG_HOME: env.XDG_CONFIG_HOME, PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', TMPDIR: '/tmp' };
|
|
116
|
+
}
|
|
117
|
+
function git(wt, args, input) {
|
|
118
|
+
const r = (0, node_child_process_1.spawnSync)(GIT, ['--no-pager', '--literal-pathspecs', ...args], { cwd: wt, env: ambiente(),
|
|
119
|
+
encoding: 'utf8', input, timeout: 10000, killSignal: 'SIGKILL', maxBuffer: LIMITE });
|
|
120
|
+
if (r.status !== 0 || r.signal || r.error)
|
|
121
|
+
falha('command.failed');
|
|
122
|
+
return r.stdout;
|
|
123
|
+
}
|
|
124
|
+
/** Lista positiva de configuração passiva. Não é blacklist de shell. */
|
|
125
|
+
function configPassiva(chave, valor, credenciaisInertes = false) {
|
|
126
|
+
if (credenciaisInertes && (chave === 'credential.helper' || /^credential\..+\.helper$/.test(chave)))
|
|
127
|
+
return valor.length <= 8192 && !valor.includes('\0');
|
|
128
|
+
// Definição não executa nada: a ausência de uso efetivo é provada abaixo.
|
|
129
|
+
if (/^filter\.[a-zA-Z0-9._-]+\.(clean|smudge|process|required)$/.test(chave))
|
|
130
|
+
return valor.length <= 8192 && !valor.includes('\0');
|
|
131
|
+
if (['user.name', 'user.email'].includes(chave))
|
|
132
|
+
return !!valor.trim() && valor.length <= 512 && !/[\x00-\x1f]/.test(valor);
|
|
133
|
+
if (['core.filemode', 'core.logallrefupdates', 'core.ignorecase', 'core.precomposeunicode'].includes(chave))
|
|
134
|
+
return /^(true|false)$/.test(valor);
|
|
135
|
+
if (chave === 'core.repositoryformatversion')
|
|
136
|
+
return valor === '0';
|
|
137
|
+
if (['core.bare', 'commit.gpgsign', 'tag.gpgsign', 'core.fsmonitor'].includes(chave))
|
|
138
|
+
return valor === 'false';
|
|
139
|
+
if (chave === 'core.autocrlf')
|
|
140
|
+
return ['true', 'false', 'input'].includes(valor);
|
|
141
|
+
if (chave === 'core.eol')
|
|
142
|
+
return ['lf', 'crlf', 'native'].includes(valor);
|
|
143
|
+
if (chave === 'init.defaultbranch')
|
|
144
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(valor);
|
|
145
|
+
if (/^remote\.[a-zA-Z0-9._-]+\.(url|fetch)$/.test(chave) || /^branch\.[a-zA-Z0-9._/-]+\.(remote|merge)$/.test(chave))
|
|
146
|
+
return valor.length <= 2048 && !/[\x00-\x1f]/.test(valor);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
/** Metadados graváveis pelo Git/API não podem redirecionar escrita para fora. */
|
|
150
|
+
function arvoreMetadados(root, alvo) {
|
|
151
|
+
if (!fs.lstatSync(alvo, { throwIfNoEntry: false }))
|
|
152
|
+
return;
|
|
153
|
+
let vistos = 0;
|
|
154
|
+
const visitar = (f, profundidade) => {
|
|
155
|
+
if (++vistos > 8192 || profundidade > 32)
|
|
156
|
+
falha('metadata.unsupported');
|
|
157
|
+
const st = fs.lstatSync(f);
|
|
158
|
+
if (st.isSymbolicLink() || (!st.isDirectory() && (!st.isFile() || st.nlink !== 1)))
|
|
159
|
+
falha('metadata.unsafe');
|
|
160
|
+
if (f !== root && !dentro(root, f))
|
|
161
|
+
falha('metadata.unsafe');
|
|
162
|
+
if (st.isDirectory())
|
|
163
|
+
for (const n of fs.readdirSync(f))
|
|
164
|
+
visitar(path.join(f, n), profundidade + 1);
|
|
165
|
+
};
|
|
166
|
+
if (fs.realpathSync(alvo) !== alvo)
|
|
167
|
+
falha('metadata.unsafe');
|
|
168
|
+
visitar(alvo, 0);
|
|
169
|
+
}
|
|
170
|
+
/** Cobre também refresh do índice e checkout/diff internos da API de estado.
|
|
171
|
+
* --cached e worktree devem concordar na ausência de filtros, sem executar driver.
|
|
172
|
+
* Não suporta .gitattributes materializado pela API: a ordem de checkout poderia
|
|
173
|
+
* produzir um estado intermediário diferente dos dois snapshots verificados. */
|
|
174
|
+
function atributosPassivos(wt, id, selecionados) {
|
|
175
|
+
const index = git(wt, ['ls-files', '--stage', '-z']).split('\0').filter(Boolean);
|
|
176
|
+
if (index.some(entry => entry.startsWith('160000 ')))
|
|
177
|
+
falha('execution-profile.unsupported: gitlinks');
|
|
178
|
+
const tracked = git(wt, ['ls-files', '-z']).split('\0').filter(Boolean);
|
|
179
|
+
const prefixo = `.orkastery/threads/${id}/`;
|
|
180
|
+
if (tracked.some(f => f.startsWith(prefixo) && path.posix.basename(f) === '.gitattributes'))
|
|
181
|
+
falha('execution-profile.unsupported: atributos de estado materializaveis');
|
|
182
|
+
const nomes = [...new Set([...tracked, ...selecionados])].sort();
|
|
183
|
+
if (nomes.length > 4096 || nomes.some(f => !f || f.includes('\0')))
|
|
184
|
+
falha('execution-profile.unsupported: inventario de atributos');
|
|
185
|
+
const input = nomes.join('\0') + '\0', resultados = [];
|
|
186
|
+
for (const cached of [false, true]) {
|
|
187
|
+
const raw = git(wt, ['check-attr', ...(cached ? ['--cached'] : []), '-z', '--stdin', 'filter'], input);
|
|
188
|
+
const partes = raw.split('\0');
|
|
189
|
+
if (partes.pop() !== '' || partes.length !== nomes.length * 3)
|
|
190
|
+
falha('execution-profile.unsupported: atributos inconclusivos');
|
|
191
|
+
for (let i = 0; i < nomes.length; i++)
|
|
192
|
+
if (partes[i * 3] !== nomes[i] || partes[i * 3 + 1] !== 'filter' || !['unspecified', 'unset'].includes(partes[i * 3 + 2]))
|
|
193
|
+
falha('execution-profile.unsupported: filtro efetivo preservado');
|
|
194
|
+
resultados.push(raw);
|
|
195
|
+
}
|
|
196
|
+
return resultados.join('\0');
|
|
197
|
+
}
|
|
198
|
+
function perfil(wt, comum, gitdir, id, selecionados) {
|
|
199
|
+
fisico(path.dirname(comum), comum);
|
|
200
|
+
fisico(comum, gitdir);
|
|
201
|
+
for (const nome of ['objects', 'refs', 'logs', 'info'])
|
|
202
|
+
arvoreMetadados(comum, path.join(comum, nome));
|
|
203
|
+
for (const nome of ['index', 'logs', 'refs'])
|
|
204
|
+
arvoreMetadados(gitdir, path.join(gitdir, nome));
|
|
205
|
+
const estado = path.join(path.dirname(comum), '.orkastery');
|
|
206
|
+
for (const nome of ['leases', 'state-backups']) {
|
|
207
|
+
const f = path.join(estado, nome);
|
|
208
|
+
if (fs.lstatSync(f, { throwIfNoEntry: false }))
|
|
209
|
+
fisico(estado, f);
|
|
210
|
+
}
|
|
211
|
+
arvoreMetadados(estado, path.join(estado, 'leases'));
|
|
212
|
+
for (const nome of ['packed-refs', 'shallow'])
|
|
213
|
+
arvoreMetadados(comum, path.join(comum, nome));
|
|
214
|
+
const alternates = path.join(comum, 'objects/info/alternates');
|
|
215
|
+
if (fs.existsSync(alternates) && fs.statSync(alternates).size)
|
|
216
|
+
falha('metadata.unsupported: alternates');
|
|
217
|
+
for (const base of [comum, gitdir]) {
|
|
218
|
+
for (const nome of ['config', 'config.worktree', 'commondir', 'HEAD']) {
|
|
219
|
+
const f = path.join(base, nome);
|
|
220
|
+
if (fs.existsSync(f))
|
|
221
|
+
fisico(base, f, true);
|
|
222
|
+
}
|
|
223
|
+
const hooks = path.join(base, 'hooks');
|
|
224
|
+
if (fs.lstatSync(hooks, { throwIfNoEntry: false })) {
|
|
225
|
+
fisico(base, hooks);
|
|
226
|
+
for (const nome of fs.readdirSync(hooks)) {
|
|
227
|
+
const f = path.join(hooks, nome), s = fs.lstatSync(f);
|
|
228
|
+
if (!s.isFile() || s.isSymbolicLink() || (!nome.endsWith('.sample') && (s.mode & 0o111) !== 0))
|
|
229
|
+
falha('execution-profile.unsupported: hooks preservados');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const raw = git(wt, ['config', '--null', '--list']);
|
|
234
|
+
for (const item of raw.split('\0').filter(Boolean)) {
|
|
235
|
+
const i = item.indexOf('\n');
|
|
236
|
+
if (i < 0 || !configPassiva(item.slice(0, i).toLowerCase(), item.slice(i + 1), true))
|
|
237
|
+
falha('execution-profile.unsupported: configuracao preservada');
|
|
238
|
+
}
|
|
239
|
+
const atributos = atributosPassivos(wt, id, selecionados);
|
|
240
|
+
return (0, node_crypto_1.createHash)('sha256').update(raw).update('\0').update(atributos).digest('hex');
|
|
241
|
+
}
|
|
242
|
+
/** Consulta local e passiva; os HEADs sao observacoes, nunca autorizacao ou reserva. */
|
|
243
|
+
function estadoGitMcp(raiz, threadId) {
|
|
244
|
+
if (!path.isAbsolute(raiz) || fs.realpathSync(raiz) !== raiz ||
|
|
245
|
+
typeof threadId !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/.test(threadId))
|
|
246
|
+
falha('request.invalid');
|
|
247
|
+
const c = (0, manifest_1.exigirManifesto)(raiz);
|
|
248
|
+
if (c.raiz !== raiz)
|
|
249
|
+
falha('project.invalid');
|
|
250
|
+
fisico(raiz, (0, thread_1.dirThread)(raiz, threadId));
|
|
251
|
+
(0, mcp_artifacts_1.validarArquivosEstadoMcp)(raiz, threadId);
|
|
252
|
+
const t = (0, thread_1.lerThread)(raiz, threadId), wt = t.worktree;
|
|
253
|
+
if (t.id !== threadId || !wt || wt === raiz)
|
|
254
|
+
falha('thread.worktree.required');
|
|
255
|
+
fisico(raiz, wt);
|
|
256
|
+
const marcador = path.join(wt, '.git');
|
|
257
|
+
fisico(wt, marcador, true);
|
|
258
|
+
const match = /^gitdir: (.+)\s*$/.exec(fs.readFileSync(marcador, 'utf8'));
|
|
259
|
+
if (!match)
|
|
260
|
+
falha('worktree.invalid');
|
|
261
|
+
const comum = path.join(raiz, '.git'), gitdir = path.resolve(wt, match[1].trim());
|
|
262
|
+
perfil(wt, comum, gitdir, t.id, []);
|
|
263
|
+
if ((0, estado_thread_1.raizDoEstado)(wt) !== raiz)
|
|
264
|
+
falha('worktree.invalid');
|
|
265
|
+
const branch = (0, worktree_1.branchDaWorktree)(t), destino = c.manifesto.worktree.base_branch;
|
|
266
|
+
if (git(wt, ['symbolic-ref', '--quiet', '--short', 'HEAD']).trim() !== branch)
|
|
267
|
+
falha('branch.invalid');
|
|
268
|
+
const head = git(wt, ['rev-parse', '--verify', 'HEAD']).trim();
|
|
269
|
+
const source = git(raiz, ['rev-parse', '--verify', '--end-of-options', `refs/heads/${branch}`]).trim();
|
|
270
|
+
const destination = git(raiz, ['rev-parse', '--verify', '--end-of-options', `refs/heads/${destino}`]).trim();
|
|
271
|
+
if (head !== source || ![source, destination].every(x => /^[a-f0-9]{40}$/.test(x)))
|
|
272
|
+
falha('head.invalid');
|
|
273
|
+
return { threadId, source: { branch, head: source }, destination: { branch: destino, head: destination },
|
|
274
|
+
scope: 'local-only', observedAt: new Date().toISOString() };
|
|
275
|
+
}
|
|
276
|
+
/** Somente o worker dedicado chama este corpo; não altera env do servidor. */
|
|
277
|
+
function executar(raiz, p) {
|
|
278
|
+
const proprias = [];
|
|
279
|
+
let commit = null, estadoAuditado = false;
|
|
280
|
+
try {
|
|
281
|
+
validar(p);
|
|
282
|
+
if (!path.isAbsolute(raiz) || fs.realpathSync(raiz) !== raiz || (0, manifest_1.exigirManifesto)(raiz).raiz !== raiz)
|
|
283
|
+
falha('project.invalid');
|
|
284
|
+
fisico(raiz, (0, thread_1.dirThread)(raiz, p.threadId));
|
|
285
|
+
(0, mcp_artifacts_1.validarArquivosEstadoMcp)(raiz, p.threadId);
|
|
286
|
+
const t = (0, thread_1.lerThread)(raiz, p.threadId), wt = t.worktree;
|
|
287
|
+
if (!wt || t.status === 'fechada' || !(0, thread_1.blocoDaThread)(t, t.faseAtual).fases.includes('GO') || wt === raiz)
|
|
288
|
+
falha('thread.invalid');
|
|
289
|
+
// I-42 (D7): ciclo sem PLAN nem CHECK nao commita contrato publico; a mudanca vira iniciativa.
|
|
290
|
+
if ((0, prova_minima_1.cicloSemCheck)(t) && (0, contrato_publico_1.contratosTocados)(p.paths).length)
|
|
291
|
+
falha('contract.protected');
|
|
292
|
+
fisico(raiz, wt);
|
|
293
|
+
fisico(raiz, (0, thread_1.dirThread)(raiz, t.id));
|
|
294
|
+
const marcador = path.join(wt, '.git');
|
|
295
|
+
fisico(wt, marcador, true);
|
|
296
|
+
const match = /^gitdir: (.+)\s*$/.exec(fs.readFileSync(marcador, 'utf8'));
|
|
297
|
+
if (!match)
|
|
298
|
+
falha('worktree.invalid');
|
|
299
|
+
const comum = path.join(raiz, '.git'), gitdir = path.resolve(wt, match[1].trim());
|
|
300
|
+
fisico(comum, gitdir);
|
|
301
|
+
if ((0, estado_thread_1.raizDoEstado)(wt) !== raiz)
|
|
302
|
+
falha('worktree.invalid');
|
|
303
|
+
const originalPerfil = perfil(wt, comum, gitdir, t.id, p.paths);
|
|
304
|
+
const conferir = () => {
|
|
305
|
+
(0, mcp_artifacts_1.validarArquivosEstadoMcp)(raiz, t.id);
|
|
306
|
+
if (perfil(wt, comum, gitdir, t.id, p.paths) !== originalPerfil)
|
|
307
|
+
falha('execution-profile.changed');
|
|
308
|
+
if (git(wt, ['rev-parse', 'HEAD']).trim() !== p.expectedHead)
|
|
309
|
+
falha('head.stale');
|
|
310
|
+
if (git(wt, ['symbolic-ref', '--quiet', '--short', 'HEAD']).trim() !== (0, worktree_1.branchDaWorktree)(t))
|
|
311
|
+
falha('branch.invalid');
|
|
312
|
+
for (const nome of ['MERGE_HEAD', 'CHERRY_PICK_HEAD', 'REVERT_HEAD', 'rebase-merge', 'rebase-apply'])
|
|
313
|
+
if (fs.existsSync(path.join(gitdir, nome)))
|
|
314
|
+
falha('operation.in-progress');
|
|
315
|
+
const claims = (0, claims_1.lerClaims)(raiz, t.id);
|
|
316
|
+
for (const f of p.paths) {
|
|
317
|
+
fisico(wt, path.join(wt, f), true);
|
|
318
|
+
if (!claims.some(c => c.estado !== 'retirada' && c.arquivo === f && c.verificar.length > 0))
|
|
319
|
+
falha('claim.missing');
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
conferir();
|
|
323
|
+
if (git(wt, ['diff', '--cached', '--name-only', '-z']).length)
|
|
324
|
+
falha('index.not-empty');
|
|
325
|
+
const regioes = [`worktree-write:${t.id}`, ...p.paths.map(f => 'path:' + f)];
|
|
326
|
+
for (const nome of regioes) {
|
|
327
|
+
if ((0, leases_1.lerLease)(raiz, nome) || (0, leases_1.leasesColidentes)(raiz, nome).length)
|
|
328
|
+
falha('lease.busy');
|
|
329
|
+
const r = (0, leases_1.adquirirRegiao)(raiz, nome, { thread: t.id, motivo: 'MCP git_commit delimitado', ttlMs: TTL, retomarVencido: false });
|
|
330
|
+
if (!r.ok || !r.lease)
|
|
331
|
+
falha('lease.busy');
|
|
332
|
+
proprias.push(r.lease);
|
|
333
|
+
}
|
|
334
|
+
conferir();
|
|
335
|
+
if (git(wt, ['diff', '--cached', '--name-only', '-z']).length)
|
|
336
|
+
falha('index.not-empty');
|
|
337
|
+
(0, estado_thread_1.comEstadoParaGit)(raiz, t.id, wt, () => {
|
|
338
|
+
conferir();
|
|
339
|
+
git(wt, ['add', '--', ...p.paths]);
|
|
340
|
+
const staged = git(wt, ['diff', '--cached', '--name-only', '-z']).split('\0').filter(Boolean).sort();
|
|
341
|
+
if (!staged.length || staged.some(f => !p.paths.includes(f)))
|
|
342
|
+
falha('index.selection');
|
|
343
|
+
conferir();
|
|
344
|
+
git(wt, ['commit', '-m', p.mensagem, '--', ...p.paths]);
|
|
345
|
+
commit = git(wt, ['rev-parse', 'HEAD']).trim();
|
|
346
|
+
});
|
|
347
|
+
if (perfil(wt, comum, gitdir, t.id, p.paths) !== originalPerfil)
|
|
348
|
+
falha('execution-profile.changed');
|
|
349
|
+
estadoAuditado = (0, estado_thread_1.auditarEstado)(raiz, t.id, wt).nivel === 'ok';
|
|
350
|
+
if (!estadoAuditado)
|
|
351
|
+
falha('state.audit-failed');
|
|
352
|
+
if (!commit || git(wt, ['rev-parse', 'HEAD^']).trim() !== p.expectedHead)
|
|
353
|
+
falha('commit.parent-invalid');
|
|
354
|
+
const alterados = git(wt, ['diff-tree', '--no-commit-id', '--name-only', '-r', '-z', commit]).split('\0').filter(Boolean);
|
|
355
|
+
if (!alterados.length || alterados.some(f => !p.paths.includes(f)))
|
|
356
|
+
falha('commit.paths-invalid');
|
|
357
|
+
(0, ledger_1.registrar)((0, thread_1.dirThread)(raiz, t.id), t.id, 'mcp_git_committed', { commit, paths: alterados, origem: 'mcp.git', estadoAuditado });
|
|
358
|
+
return { ok: true, commit, erro: null, estadoAuditado };
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
const mensagem = e.message;
|
|
362
|
+
return { ok: false, commit, erro: mensagem.startsWith('mcp.git.') ? mensagem : 'mcp.git.operation.failed: estado e indice preservados', estadoAuditado };
|
|
363
|
+
}
|
|
364
|
+
finally {
|
|
365
|
+
for (const l of proprias.reverse())
|
|
366
|
+
if (JSON.stringify((0, leases_1.lerLease)(raiz, l.nome)) === JSON.stringify(l))
|
|
367
|
+
(0, leases_1.liberar)(raiz, l.nome, l.thread);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Entrada do servidor: argumentos nunca escolhem executável/env/cwd/opções Git. */
|
|
371
|
+
async function commitMcp(raiz, pedido, signal) {
|
|
372
|
+
validar(pedido);
|
|
373
|
+
if (signal?.aborted)
|
|
374
|
+
return { ok: false, commit: null, erro: 'mcp.git.cancelled', estadoAuditado: false };
|
|
375
|
+
return new Promise(resolve => {
|
|
376
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [__filename, '--worker'], { env: ambiente(), detached: true, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
377
|
+
let stdout = '', erro = false, fechou = false, inicio;
|
|
378
|
+
const identidade = () => { try {
|
|
379
|
+
const st = fs.readFileSync(`/proc/${child.pid}/stat`, 'utf8');
|
|
380
|
+
return st.slice(st.lastIndexOf(')') + 2).split(' ')[19];
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return undefined;
|
|
384
|
+
} };
|
|
385
|
+
const terminar = () => { erro = true; if (!fechou && inicio && identidade() === inicio) {
|
|
386
|
+
try {
|
|
387
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
388
|
+
}
|
|
389
|
+
catch { }
|
|
390
|
+
} };
|
|
391
|
+
child.once('spawn', () => { inicio = identidade(); if (!inicio) {
|
|
392
|
+
erro = true;
|
|
393
|
+
child.stdin.end();
|
|
394
|
+
return;
|
|
395
|
+
} child.stdin.end(JSON.stringify({ raiz, pedido })); });
|
|
396
|
+
child.stdin.on('error', () => { erro = true; });
|
|
397
|
+
child.on('error', () => { erro = true; });
|
|
398
|
+
child.stdout.on('data', (b) => { if (Buffer.byteLength(stdout) + b.length > LIMITE)
|
|
399
|
+
terminar();
|
|
400
|
+
else
|
|
401
|
+
stdout += b.toString(); });
|
|
402
|
+
child.stderr.on('data', () => { erro = true; });
|
|
403
|
+
signal?.addEventListener('abort', terminar, { once: true });
|
|
404
|
+
const timer = setTimeout(terminar, 45000);
|
|
405
|
+
child.on('close', (code, signalExit) => {
|
|
406
|
+
fechou = true;
|
|
407
|
+
if (code !== 0 || signalExit)
|
|
408
|
+
erro = true;
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
signal?.removeEventListener('abort', terminar);
|
|
411
|
+
if (!erro)
|
|
412
|
+
try {
|
|
413
|
+
const r = JSON.parse(stdout);
|
|
414
|
+
if (typeof r.ok === 'boolean' && (r.commit === null || /^[a-f0-9]{40}$/.test(r.commit)))
|
|
415
|
+
return resolve(r);
|
|
416
|
+
}
|
|
417
|
+
catch { }
|
|
418
|
+
resolve({ ok: false, commit: null, erro: 'mcp.git.worker.incomplete: conferir HEAD, indice e leases antes de repetir', estadoAuditado: false });
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
if (require.main === module && process.argv[2] === '--worker') {
|
|
423
|
+
try {
|
|
424
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
425
|
+
if (Buffer.byteLength(raw) > LIMITE)
|
|
426
|
+
falha('request.invalid');
|
|
427
|
+
const p = JSON.parse(raw);
|
|
428
|
+
process.stdout.write(JSON.stringify(executar(p.raiz, p.pedido)));
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
process.stdout.write(JSON.stringify({ ok: false, commit: null, erro: 'mcp.git.worker.invalid', estadoAuditado: false }));
|
|
432
|
+
}
|
|
433
|
+
}
|