wendkeep 0.80.2 → 0.85.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/CHANGELOG.md +103 -0
- package/README.en.md +28 -11
- package/README.md +28 -11
- package/docs/en/commands/capabilities.md +82 -0
- package/docs/en/commands/getting-started.md +3 -1
- package/docs/en/commands/mcp.md +99 -0
- package/docs/en/commands/portable.md +88 -0
- package/docs/en/commands/sync-protocol.md +58 -0
- package/docs/en/commands/tdd.md +96 -0
- package/docs/en/commands/verify.md +5 -0
- package/docs/pt-BR/commands/capabilities.md +82 -0
- package/docs/pt-BR/commands/getting-started.md +3 -2
- package/docs/pt-BR/commands/mcp.md +99 -0
- package/docs/pt-BR/commands/portable.md +87 -0
- package/docs/pt-BR/commands/sync-protocol.md +58 -0
- package/docs/pt-BR/commands/tdd.md +96 -0
- package/docs/pt-BR/commands/verify.md +5 -0
- package/hooks/active-context-store.mjs +2 -0
- package/hooks/change-core.mjs +5 -0
- package/hooks/project-scope.mjs +2 -1
- package/hooks/session-ensure.mjs +23 -7
- package/hooks/session-start.mjs +20 -5
- package/package.json +3 -3
- package/packages/cli/src/index.mjs +42 -2
- package/packages/harness/src/sensors-core.mjs +16 -3
- package/packages/integrations/src/capabilities.mjs +220 -0
- package/packages/integrations/src/index.mjs +1 -0
- package/packages/mcp/src/audit.mjs +49 -0
- package/packages/mcp/src/cli.mjs +78 -0
- package/packages/mcp/src/config.mjs +22 -1
- package/packages/mcp/src/effects.mjs +115 -0
- package/packages/mcp/src/executor.mjs +354 -0
- package/packages/mcp/src/index.mjs +7 -0
- package/packages/mcp/src/server.mjs +342 -0
- package/packages/mcp/src/stdio.mjs +38 -0
- package/packages/mcp/src/sync.mjs +56 -0
- package/packages/pi/package.json +2 -1
- package/packages/pi/src/index.mjs +29 -0
- package/schema/handoff-contract-v1.schema.json +4 -0
- package/schema/host-capability-manifest-v1.schema.json +46 -0
- package/schema/host-coverage-v1.schema.json +55 -0
- package/schema/mcp-effect-manifest-v1.schema.json +36 -0
- package/schema/mcp-tool-input-v1.schema.json +32 -0
- package/schema/mcp-tool-result-v1.schema.json +22 -0
- package/schema/portable-active-work-v1.schema.json +38 -0
- package/schema/portable-state-v1.schema.json +36 -0
- package/schema/sync-event-v1.schema.json +25 -0
- package/schema/sync-private-envelope-v1.schema.json +16 -0
- package/schema/sync-state-v1.schema.json +18 -0
- package/schema/task-contract-v1.schema.json +2 -0
- package/schema/tdd-attestation-v1.schema.json +39 -0
- package/schema/wendkeep.evidence-envelope-v2.schema.json +17 -0
- package/schema/wendkeep.sensors.schema.json +19 -0
- package/src/active-context-runtime.mjs +1 -0
- package/src/capabilities.mjs +50 -0
- package/src/doctor.mjs +28 -0
- package/src/evidence-envelope.mjs +12 -6
- package/src/host-capabilities.mjs +34 -0
- package/src/init.mjs +3 -3
- package/src/mcp.mjs +7 -0
- package/src/observer-snapshot.mjs +25 -0
- package/src/portable.mjs +558 -0
- package/src/skills-seed.mjs +26 -0
- package/src/sync-adapters.mjs +188 -0
- package/src/sync-outbox.mjs +155 -0
- package/src/sync-protocol-cli.mjs +277 -0
- package/src/sync-protocol.mjs +368 -0
- package/src/sync.mjs +8 -0
- package/src/task-contracts.mjs +67 -2
- package/src/task.mjs +5 -1
- package/src/tdd-attestation-store.mjs +98 -0
- package/src/tdd-attestation.mjs +254 -0
- package/src/tdd.mjs +198 -0
- package/src/vault-readme.mjs +4 -4
- package/src/verify.mjs +24 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Atestação TDD causal
|
|
2
|
+
|
|
3
|
+
**PT-BR** · [English](../../en/commands/tdd.md)
|
|
4
|
+
|
|
5
|
+
## Objetivo
|
|
6
|
+
|
|
7
|
+
`wendkeep tdd` registra uma prova auditável de que um teste ligado a tarefa e requisito foi
|
|
8
|
+
observado em RED e depois em GREEN no mesmo projeto, repositório, worktree, work session e change.
|
|
9
|
+
Ele complementa cobertura, mutação, sensores e revisão; não substitui nenhum deles.
|
|
10
|
+
|
|
11
|
+
## Quando usar
|
|
12
|
+
|
|
13
|
+
Use antes e depois de implementar comportamento testável ligado a uma tarefa `[tdd]`.
|
|
14
|
+
|
|
15
|
+
## Quando não usar
|
|
16
|
+
|
|
17
|
+
Não use para health checks, testes já verdes ou falhas de ambiente. Use sensores/doctor para
|
|
18
|
+
infraestrutura e um waiver humano apenas quando o comportamento realmente não for testável.
|
|
19
|
+
|
|
20
|
+
## Pré-requisitos
|
|
21
|
+
|
|
22
|
+
Change ativa, active context causal, tarefa com `[req:ID]`, teste relativo ao projeto e comando
|
|
23
|
+
determinístico que possa ser repetido em RED e GREEN.
|
|
24
|
+
|
|
25
|
+
## Sintaxe
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
wendkeep tdd red <task-id> --requirement <ID> --test <path> --command "<comando>" --session <id>
|
|
29
|
+
wendkeep tdd green <task-id> --command "<comando>" --session <id>
|
|
30
|
+
wendkeep tdd status <task-id> --session <id> [--json]
|
|
31
|
+
wendkeep tdd waive <task-id> --requirement <ID> --reason "<motivo>" --authority "<humano>" --session <id>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`--test` pode ser repetido. Todos os paths são relativos ao projeto. `--change <slug>`,
|
|
35
|
+
`--project <raiz>`, `--vault <cofre>` e `--json` seguem as convenções dos demais comandos.
|
|
36
|
+
|
|
37
|
+
## Contrato RED → GREEN
|
|
38
|
+
|
|
39
|
+
- RED válido é uma falha comportamental. Teste já verde, erro de sintaxe/import, módulo ausente,
|
|
40
|
+
configuração inválida ou comando inexistente gera `invalid`.
|
|
41
|
+
- GREEN precisa manter a identidade causal e a branch do RED, executar com sucesso e observar
|
|
42
|
+
uma mudança de produção posterior ao RED. GREEN de outra worktree, tarefa ou requisito não fecha.
|
|
43
|
+
- Mudança de path do teste fica em `review_flags`. Refactor ou commit depois do GREEN torna a prova
|
|
44
|
+
stale até nova execução de `tdd green`; o GREEN anterior permanece em `green_history`.
|
|
45
|
+
- Waiver exige motivo e autoridade humana explícita. Não existe waiver silencioso.
|
|
46
|
+
|
|
47
|
+
O store `08-Mudanças/<slug>/tdd-attestations.json` guarda digests SHA-256, cauda sanitizada e
|
|
48
|
+
limitada a 2.000 caracteres e paths relativos — nunca a saída completa. O Evidence Envelope,
|
|
49
|
+
`verificacao.json`, handoff e Observer expõem a atestação e seu ID ao revisor.
|
|
50
|
+
|
|
51
|
+
## Gate por perfil
|
|
52
|
+
|
|
53
|
+
- `OFF` e `FLOW`: atestação opcional.
|
|
54
|
+
- `GUIDE`: recomendada para comportamento testável.
|
|
55
|
+
- `GOVERN`: obrigatória quando a tarefa possui `[tdd]`.
|
|
56
|
+
- `ASSURE`: obrigatória para tarefa executável com requisito ou sensor, salvo waiver explícito.
|
|
57
|
+
|
|
58
|
+
Marque a tarefa assim:
|
|
59
|
+
|
|
60
|
+
```markdown
|
|
61
|
+
- [ ] 1.1 persiste a preferência [req:UI-1] [sensor:tests] [tdd]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Atestação stale/invalid, mutante sobrevivente ou ausência de GREEN/waiver produz
|
|
65
|
+
`TASK_TDD_ATTESTATION_MISSING_OR_INVALID` no Task Contract e bloqueia Execute → Verify.
|
|
66
|
+
|
|
67
|
+
## Exemplos
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
wendkeep tdd red 1.1 --requirement UI-1 --test tests/ui.test.mjs --command "npm test" --session abc
|
|
71
|
+
wendkeep tdd green 1.1 --command "npm test" --session abc
|
|
72
|
+
wendkeep tdd status 1.1 --session abc --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Resultado esperado
|
|
76
|
+
|
|
77
|
+
Uma entrada causal em `tdd-attestations.json`, referenciada pelo Task Contract e pelas superfícies
|
|
78
|
+
de evidência, com estado atual e histórico de revalidação auditáveis.
|
|
79
|
+
|
|
80
|
+
## Opções e códigos de saída
|
|
81
|
+
|
|
82
|
+
- `0`: estado observado é válido (`red-observed`, `green-observed` ou `waived`; status verde/waived).
|
|
83
|
+
- `1`: observação executou, mas ficou `invalid`, RED ainda não chegou a GREEN ou status está stale.
|
|
84
|
+
- `2`: uso, contexto, identidade, store ou autoridade de waiver inválidos.
|
|
85
|
+
|
|
86
|
+
## Erros comuns e diagnóstico
|
|
87
|
+
|
|
88
|
+
- `TDD_RED_ALREADY_GREEN`: escreva primeiro o teste discriminante que falha.
|
|
89
|
+
- `TDD_RED_INFRASTRUCTURE_FAILURE`: corrija import, sintaxe, configuração ou comando e repita RED.
|
|
90
|
+
- `TDD_GREEN_STALE_AFTER_REFACTOR`: repita GREEN no checkout atual.
|
|
91
|
+
- `TDD_IMPLEMENTATION_NOT_AFTER_RED`: a prova não observou diff de produção posterior ao RED.
|
|
92
|
+
|
|
93
|
+
## Próximos passos
|
|
94
|
+
|
|
95
|
+
Rode [verify](verify.md), faça o passe independente e continue o
|
|
96
|
+
[ciclo de changes](changes-and-verification.md).
|
|
@@ -88,6 +88,11 @@ mesmo diretório e rename atômico. No deep, `verificacao.json` e `verdict.json`
|
|
|
88
88
|
`evidenceEnvelopeId` e `evidenceBinding` completo; o verificador independente deve preservar ambos
|
|
89
89
|
no verdict.
|
|
90
90
|
|
|
91
|
+
O envelope carrega `tdd_attestations`, e `verificacao.json`, `tddAttestations`. Em GOVERN, uma tarefa marcada
|
|
92
|
+
`[tdd]` precisa de GREEN atual ou waiver explícito; em ASSURE, isso vale para comportamento
|
|
93
|
+
testável. Refactor/commit posterior ou mutante sobrevivente invalida o GREEN no Task Contract.
|
|
94
|
+
Veja [Atestação TDD causal](tdd.md).
|
|
95
|
+
|
|
91
96
|
Evidência v1 continua legível como `legacy-unbound`, nunca como autoridade equivalente. Rode
|
|
92
97
|
`wendkeep change status <slug>` para ver `bound`, `stale` ou `context-mismatch`.
|
|
93
98
|
|
|
@@ -740,6 +740,7 @@ export function setActiveContextChange(vaultBase, identity, slug, options = {})
|
|
|
740
740
|
}
|
|
741
741
|
return mutateActiveContext(vaultBase, identity, (context) => ({
|
|
742
742
|
...context,
|
|
743
|
+
...(identity?.hostCoverage ? { host_coverage: structuredClone(identity.hostCoverage) } : {}),
|
|
743
744
|
change_slug: normalizedSlug,
|
|
744
745
|
state: 'active',
|
|
745
746
|
}), options);
|
|
@@ -760,6 +761,7 @@ export function setActiveContextDelivery(vaultBase, identity, deliveryId, option
|
|
|
760
761
|
}
|
|
761
762
|
return mutateActiveContext(vaultBase, identity, (context) => ({
|
|
762
763
|
...context,
|
|
764
|
+
...(identity?.hostCoverage ? { host_coverage: structuredClone(identity.hostCoverage) } : {}),
|
|
763
765
|
delivery_id: normalizedId,
|
|
764
766
|
state: 'active',
|
|
765
767
|
}), options);
|
package/hooks/change-core.mjs
CHANGED
|
@@ -286,6 +286,7 @@ export function parseTasks(md) {
|
|
|
286
286
|
const dependencyReG = /\[depends:\s*([\w.-]+)\]/g;
|
|
287
287
|
const artifactReG = /\[artifact:\s*([\w.-]+)\]/g;
|
|
288
288
|
const phaseReG = /\[phase:\s*([\w.-]+)\]/g;
|
|
289
|
+
const tddReG = /\[tdd\]/gi;
|
|
289
290
|
let m;
|
|
290
291
|
while ((m = re.exec(String(md))) !== null) {
|
|
291
292
|
let text = m[3].trim();
|
|
@@ -294,12 +295,15 @@ export function parseTasks(md) {
|
|
|
294
295
|
const dependencies = [...new Set([...text.matchAll(dependencyReG)].map((entry) => entry[1]))];
|
|
295
296
|
const artifacts = [...new Set([...text.matchAll(artifactReG)].map((entry) => entry[1]))];
|
|
296
297
|
const phases = [...new Set([...text.matchAll(phaseReG)].map((entry) => entry[1]))];
|
|
298
|
+
const tdd = tddReG.test(text);
|
|
299
|
+
tddReG.lastIndex = 0;
|
|
297
300
|
const sensor = sensors[0];
|
|
298
301
|
if (sensors.length) text = text.replace(sensorReG, '');
|
|
299
302
|
if (reqs.length) text = text.replace(reqReG, '');
|
|
300
303
|
if (dependencies.length) text = text.replace(dependencyReG, '');
|
|
301
304
|
if (artifacts.length) text = text.replace(artifactReG, '');
|
|
302
305
|
if (phases.length) text = text.replace(phaseReG, '');
|
|
306
|
+
if (tdd) text = text.replace(tddReG, '');
|
|
303
307
|
text = text.replace(/\s+/g, ' ').trim();
|
|
304
308
|
// `sensor` stays as alias of the first id — older consumers keep working.
|
|
305
309
|
// `req` stays as alias of the first id — older consumers keep working.
|
|
@@ -312,6 +316,7 @@ export function parseTasks(md) {
|
|
|
312
316
|
...(dependencies.length ? { dependencies } : {}),
|
|
313
317
|
...(artifacts.length ? { artifacts } : {}),
|
|
314
318
|
...(phases.length ? { phase: phases[0] } : {}),
|
|
319
|
+
...(tdd ? { tdd: true } : {}),
|
|
315
320
|
});
|
|
316
321
|
}
|
|
317
322
|
return tasks;
|
package/hooks/project-scope.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs';
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { resolveMcpToolEffect } from '../packages/mcp/src/effects.mjs';
|
|
4
5
|
|
|
5
6
|
const TOOL_CWD_FIELDS = ['cwd', 'workdir', 'work_dir', 'working_directory', 'directory'];
|
|
6
7
|
const SAFE_GIT_COMMANDS = new Set([
|
|
@@ -396,7 +397,7 @@ export function concurrentScopeConflicts(expectedScope, activeSessions = [], cur
|
|
|
396
397
|
|
|
397
398
|
function toolIsMutable(input) {
|
|
398
399
|
const name = input?.tool_name || input?.toolName || '';
|
|
399
|
-
if (/^mcp__/i.test(name)) return
|
|
400
|
+
if (/^mcp__/i.test(name)) return resolveMcpToolEffect(name).effect !== 'read';
|
|
400
401
|
return MUTABLE_TOOL_NAMES.has(name) || /^mcp__.*(?:write|edit|delete|move|rename|apply)/i.test(name);
|
|
401
402
|
}
|
|
402
403
|
|
package/hooks/session-ensure.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import { resolveSessionIdentity, sessionWorkSessionPatch } from './session-ident
|
|
|
36
36
|
import { readCodexRolloutMeta } from './codex-rollout-meta.mjs';
|
|
37
37
|
import { mutateSessionNote } from './session-note-io.mjs';
|
|
38
38
|
import { captureProjectScope, projectScopePatch } from './project-scope.mjs';
|
|
39
|
+
import { buildHostCoverage } from '../src/host-capabilities.mjs';
|
|
39
40
|
|
|
40
41
|
function sessionIdFromInput(input) {
|
|
41
42
|
return input.session_id || input.sessionId || input.codex_session_id || '';
|
|
@@ -162,13 +163,16 @@ function allocateSessionPath(vaultBase, now, summary = 'session') {
|
|
|
162
163
|
};
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
function buildAdditionalContext({ relPath, startedAt, vaultBase }) {
|
|
166
|
+
function buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage = null }) {
|
|
166
167
|
const controlRel = toVaultRelative(vaultBase, controlPath(vaultBase));
|
|
167
168
|
return [
|
|
168
169
|
'<obsidian_session>',
|
|
169
170
|
`Sessão Obsidian ativa: ${relPath}`,
|
|
170
171
|
`Controle atualizado: ${controlRel}`,
|
|
171
172
|
`Início: ${startedAt}`,
|
|
173
|
+
'<host_capability_coverage>',
|
|
174
|
+
`Host: ${hostCoverage?.host_id || 'unknown'}; coverage: ${hostCoverage?.degraded ? 'degraded' : 'complete'}.`,
|
|
175
|
+
'</host_capability_coverage>',
|
|
172
176
|
'',
|
|
173
177
|
'Use a sessão ativa como log desta conversa.',
|
|
174
178
|
'Antes de registrar informações, leia `.brain/CURRENT_SESSION.md` no vault.',
|
|
@@ -273,7 +277,7 @@ function findSessionForInput(vaultBase, input, control) {
|
|
|
273
277
|
return { sessionId, relPath: '', startedAt: '', fromRegistry: false };
|
|
274
278
|
}
|
|
275
279
|
|
|
276
|
-
function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, input, now, identity, scopePatch = {} }) {
|
|
280
|
+
function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, input, now, identity, scopePatch = {}, hostCoverage = null }) {
|
|
277
281
|
const sessionPath = join(vaultBase, relPath);
|
|
278
282
|
if (!existsSync(sessionPath)) return false;
|
|
279
283
|
|
|
@@ -296,6 +300,7 @@ function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, inp
|
|
|
296
300
|
transcript_path: identity.transcriptPath,
|
|
297
301
|
transcript_id: identity.transcriptId,
|
|
298
302
|
provider: identity.provider,
|
|
303
|
+
host_coverage: hostCoverage,
|
|
299
304
|
...workSessionPatch(vaultBase, sessionId, input),
|
|
300
305
|
...scopePatch,
|
|
301
306
|
...causalTurnPatch(input, now),
|
|
@@ -303,7 +308,7 @@ function activateExistingSession({ vaultBase, relPath, startedAt, sessionId, inp
|
|
|
303
308
|
return true;
|
|
304
309
|
}
|
|
305
310
|
|
|
306
|
-
function createSession({ vaultBase, sessionId, input, now, identity, scopePatch = {} }) {
|
|
311
|
+
function createSession({ vaultBase, sessionId, input, now, identity, scopePatch = {}, hostCoverage = null }) {
|
|
307
312
|
const summary = sessionSummaryFromInput(input);
|
|
308
313
|
const { absPath, relPath } = allocateSessionPath(vaultBase, now, summary);
|
|
309
314
|
const startedAt = formatLocalIso(now);
|
|
@@ -325,6 +330,7 @@ function createSession({ vaultBase, sessionId, input, now, identity, scopePatch
|
|
|
325
330
|
transcript_path: identity.transcriptPath,
|
|
326
331
|
transcript_id: identity.transcriptId,
|
|
327
332
|
provider: identity.provider,
|
|
333
|
+
host_coverage: hostCoverage,
|
|
328
334
|
...workSessionPatch(vaultBase, sessionId, input),
|
|
329
335
|
...scopePatch,
|
|
330
336
|
...causalTurnPatch(input, now),
|
|
@@ -332,11 +338,11 @@ function createSession({ vaultBase, sessionId, input, now, identity, scopePatch
|
|
|
332
338
|
return { relPath, startedAt };
|
|
333
339
|
}
|
|
334
340
|
|
|
335
|
-
function outputActiveContext({ relPath, startedAt, vaultBase, message }) {
|
|
341
|
+
function outputActiveContext({ relPath, startedAt, vaultBase, message, hostCoverage = null }) {
|
|
336
342
|
writeHookOutput({
|
|
337
343
|
hookSpecificOutput: {
|
|
338
344
|
hookEventName: 'UserPromptSubmit',
|
|
339
|
-
additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase }),
|
|
345
|
+
additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage }),
|
|
340
346
|
},
|
|
341
347
|
systemMessage: message,
|
|
342
348
|
});
|
|
@@ -359,6 +365,11 @@ function main() {
|
|
|
359
365
|
return;
|
|
360
366
|
}
|
|
361
367
|
const sessionId = identity.canonicalConversationId;
|
|
368
|
+
const hostCoverage = buildHostCoverage({
|
|
369
|
+
hostId: identity.provider,
|
|
370
|
+
hostVersion: input.host_version || input.hostVersion || process.env.WENDKEEP_HOST_VERSION || '',
|
|
371
|
+
observedAt: now.toISOString(),
|
|
372
|
+
});
|
|
362
373
|
|
|
363
374
|
// UserPromptSubmit also fires inside Codex subagents. The child belongs to the parent's
|
|
364
375
|
// observability graph, but it is not a new main turn: registering it through causalTurnPatch
|
|
@@ -369,6 +380,7 @@ function main() {
|
|
|
369
380
|
upsertSessionRegistry(vaultBase, sessionId, {
|
|
370
381
|
transcript_paths: [identity.transcriptPath],
|
|
371
382
|
provider: identity.provider,
|
|
383
|
+
host_coverage: hostCoverage,
|
|
372
384
|
...workSessionPatch(vaultBase, sessionId, input),
|
|
373
385
|
});
|
|
374
386
|
}
|
|
@@ -407,6 +419,7 @@ function main() {
|
|
|
407
419
|
transcript_path: identity.transcriptPath,
|
|
408
420
|
transcript_id: identity.transcriptId,
|
|
409
421
|
provider: identity.provider,
|
|
422
|
+
host_coverage: hostCoverage,
|
|
410
423
|
...workSessionPatch(vaultBase, sessionId, input),
|
|
411
424
|
...scopePatch,
|
|
412
425
|
...causalTurnPatch(input, now),
|
|
@@ -453,6 +466,7 @@ function main() {
|
|
|
453
466
|
transcript_path: identity.transcriptPath,
|
|
454
467
|
transcript_id: identity.transcriptId,
|
|
455
468
|
provider: identity.provider,
|
|
469
|
+
host_coverage: hostCoverage,
|
|
456
470
|
...workSessionPatch(vaultBase, sessionId || control.session_id, input),
|
|
457
471
|
...scopePatch,
|
|
458
472
|
...causalTurnPatch(input, now),
|
|
@@ -466,22 +480,24 @@ function main() {
|
|
|
466
480
|
const resolvedTarget = registered?.session_file
|
|
467
481
|
? { sessionId, relPath: registered.session_file, startedAt: registered.started_at || '' }
|
|
468
482
|
: { sessionId, relPath: '', startedAt: '' };
|
|
469
|
-
if (resolvedTarget.relPath && activateExistingSession({ vaultBase, relPath: resolvedTarget.relPath, startedAt: resolvedTarget.startedAt, sessionId, input, now, identity, scopePatch })) {
|
|
483
|
+
if (resolvedTarget.relPath && activateExistingSession({ vaultBase, relPath: resolvedTarget.relPath, startedAt: resolvedTarget.startedAt, sessionId, input, now, identity, scopePatch, hostCoverage })) {
|
|
470
484
|
outputActiveContext({
|
|
471
485
|
relPath: resolvedTarget.relPath,
|
|
472
486
|
startedAt: resolvedTarget.startedAt || formatLocalIso(now),
|
|
473
487
|
vaultBase,
|
|
474
488
|
message: `Sessão Obsidian reaberta em ${resolvedTarget.relPath}. ${basename(controlPath(vaultBase))} atualizado.`,
|
|
489
|
+
hostCoverage,
|
|
475
490
|
});
|
|
476
491
|
return;
|
|
477
492
|
}
|
|
478
493
|
|
|
479
|
-
const created = createSession({ vaultBase, sessionId, input, now, identity, scopePatch });
|
|
494
|
+
const created = createSession({ vaultBase, sessionId, input, now, identity, scopePatch, hostCoverage });
|
|
480
495
|
outputActiveContext({
|
|
481
496
|
relPath: created.relPath,
|
|
482
497
|
startedAt: created.startedAt,
|
|
483
498
|
vaultBase,
|
|
484
499
|
message: `Sessão Obsidian criada em ${created.relPath}. ${basename(controlPath(vaultBase))} atualizado.`,
|
|
500
|
+
hostCoverage,
|
|
485
501
|
});
|
|
486
502
|
}
|
|
487
503
|
|
package/hooks/session-start.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from './obsidian-common.mjs';
|
|
35
35
|
import { resolveSessionIdentity, sessionWorkSessionPatch } from './session-identity.mjs';
|
|
36
36
|
import { captureProjectScope, projectScopePatch } from './project-scope.mjs';
|
|
37
|
+
import { buildHostCoverage } from '../src/host-capabilities.mjs';
|
|
37
38
|
|
|
38
39
|
export function buildSessionContent({ relPath, now, summary = 'session', provider: providerId, sessionId = '' }) {
|
|
39
40
|
const date = formatDate(now);
|
|
@@ -133,13 +134,21 @@ export function allocateSessionPath(vaultBase, now, summary = 'session') {
|
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
function buildAdditionalContext({ relPath, startedAt, vaultBase }) {
|
|
137
|
+
function buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage = null }) {
|
|
137
138
|
const controlRel = toVaultRelative(vaultBase, controlPath(vaultBase));
|
|
139
|
+
const degradation = (hostCoverage?.degradations || [])
|
|
140
|
+
.filter((item) => item.capability)
|
|
141
|
+
.map((item) => `${item.capability}=${item.state}`)
|
|
142
|
+
.join(', ');
|
|
138
143
|
return [
|
|
139
144
|
'<obsidian_session>',
|
|
140
145
|
`Sessão Obsidian ativa: ${relPath}`,
|
|
141
146
|
`Controle atualizado: ${controlRel}`,
|
|
142
147
|
`Início: ${startedAt}`,
|
|
148
|
+
'<host_capability_coverage>',
|
|
149
|
+
`Host: ${hostCoverage?.host_id || 'unknown'}; coverage: ${hostCoverage?.degraded ? 'degraded' : 'complete'}.`,
|
|
150
|
+
...(degradation ? [`Lacunas declaradas: ${degradation}. Não fabrique autoridade ausente.`] : []),
|
|
151
|
+
'</host_capability_coverage>',
|
|
143
152
|
'',
|
|
144
153
|
'Use a sessão ativa como log desta conversa.',
|
|
145
154
|
'Antes de registrar informações, leia `.brain/CURRENT_SESSION.md` no vault.',
|
|
@@ -159,6 +168,11 @@ function main() {
|
|
|
159
168
|
warnIfDefaultVault(input);
|
|
160
169
|
const now = new Date();
|
|
161
170
|
const provider = providerMeta();
|
|
171
|
+
const hostCoverage = buildHostCoverage({
|
|
172
|
+
hostId: provider.id,
|
|
173
|
+
hostVersion: input.host_version || input.hostVersion || process.env.WENDKEEP_HOST_VERSION || '',
|
|
174
|
+
observedAt: now.toISOString(),
|
|
175
|
+
});
|
|
162
176
|
const identity = resolveSessionIdentity(vaultBase, input, provider.id);
|
|
163
177
|
if (identity.state !== 'resolved') {
|
|
164
178
|
writeHookOutput({
|
|
@@ -194,6 +208,7 @@ function main() {
|
|
|
194
208
|
const activationStartedAt = formatLocalIso(now);
|
|
195
209
|
const registerActivation = (canonicalSessionId, patch) => upsertSessionRegistry(vaultBase, canonicalSessionId, {
|
|
196
210
|
...patch,
|
|
211
|
+
host_coverage: hostCoverage,
|
|
197
212
|
...workSessionPatchFor(canonicalSessionId),
|
|
198
213
|
...scopePatchFor(canonicalSessionId),
|
|
199
214
|
activation_id: activationId,
|
|
@@ -232,7 +247,7 @@ function main() {
|
|
|
232
247
|
additionalContext: buildAdditionalContext({
|
|
233
248
|
relPath: control.session_file,
|
|
234
249
|
startedAt: control.started_at,
|
|
235
|
-
vaultBase,
|
|
250
|
+
vaultBase, hostCoverage,
|
|
236
251
|
}),
|
|
237
252
|
},
|
|
238
253
|
});
|
|
@@ -275,7 +290,7 @@ function main() {
|
|
|
275
290
|
writeHookOutput({
|
|
276
291
|
hookSpecificOutput: {
|
|
277
292
|
hookEventName: 'SessionStart',
|
|
278
|
-
additionalContext: buildAdditionalContext({ relPath: known.session_file, startedAt, vaultBase }),
|
|
293
|
+
additionalContext: buildAdditionalContext({ relPath: known.session_file, startedAt, vaultBase, hostCoverage }),
|
|
279
294
|
},
|
|
280
295
|
});
|
|
281
296
|
return;
|
|
@@ -318,7 +333,7 @@ function main() {
|
|
|
318
333
|
writeHookOutput({
|
|
319
334
|
hookSpecificOutput: {
|
|
320
335
|
hookEventName: 'SessionStart',
|
|
321
|
-
additionalContext: buildAdditionalContext({ relPath: match.session_file, startedAt, vaultBase }),
|
|
336
|
+
additionalContext: buildAdditionalContext({ relPath: match.session_file, startedAt, vaultBase, hostCoverage }),
|
|
322
337
|
},
|
|
323
338
|
});
|
|
324
339
|
return;
|
|
@@ -349,7 +364,7 @@ function main() {
|
|
|
349
364
|
writeHookOutput({
|
|
350
365
|
hookSpecificOutput: {
|
|
351
366
|
hookEventName: 'SessionStart',
|
|
352
|
-
additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase }),
|
|
367
|
+
additionalContext: buildAdditionalContext({ relPath, startedAt, vaultBase, hostCoverage }),
|
|
353
368
|
},
|
|
354
369
|
systemMessage: [
|
|
355
370
|
`Sessão ${provider.label} criada em ${relPath}.`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"node": ">=18"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"precheck": "node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs",
|
|
45
|
-
"check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
|
|
44
|
+
"precheck": "node --check src/capabilities.mjs && node --check src/host-capabilities.mjs && node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/integrations/src/capabilities.mjs && node --check packages/pi/src/index.mjs && node --check src/sync-protocol.mjs && node --check src/sync-outbox.mjs && node --check src/sync-adapters.mjs && node --check src/sync-protocol-cli.mjs && node --check packages/mcp/src/sync.mjs && node --check src/portable.mjs && node --check src/tdd.mjs && node --check src/tdd-attestation.mjs && node --check src/tdd-attestation-store.mjs",
|
|
45
|
+
"check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/mcp.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/audit.mjs && node --check packages/mcp/src/cli.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/effects.mjs && node --check packages/mcp/src/executor.mjs && node --check packages/mcp/src/server.mjs && node --check packages/mcp/src/stdio.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
|
|
46
46
|
"test": "node --test --test-concurrency=2",
|
|
47
47
|
"test:core": "node scripts/run-scope.mjs core",
|
|
48
48
|
"release": "node scripts/release.mjs",
|
|
@@ -28,7 +28,7 @@ Usage:
|
|
|
28
28
|
--vault <path> Obsidian vault folder (default: <project>/.<project-name>-vault).
|
|
29
29
|
--project <path> Project root to wire (default: current directory).
|
|
30
30
|
--profile <name> Operating profile: OFF, FLOW, GUIDE, GOVERN (default), or ASSURE.
|
|
31
|
-
--no-mcp Do not add the
|
|
31
|
+
--no-mcp Do not add the native WendKeep MCP server to .mcp.json.
|
|
32
32
|
--companions <csv> Companion plugins/MCP to pin: context-mode,caveman,understand-anything
|
|
33
33
|
(default: none — opt in explicitly). dotcontext is legacy — the native a2 loop replaces it.
|
|
34
34
|
--no-companions Skip companion plugins/MCP entirely.
|
|
@@ -49,8 +49,16 @@ Usage:
|
|
|
49
49
|
package first (npm i -D wendkeep@latest); a running process
|
|
50
50
|
cannot replace itself. · --vault P · --profile <name> · --yes
|
|
51
51
|
· --vscode-worktree-tasks.
|
|
52
|
+
wendkeep sync status [...] Optional local-first protocol: status | push | pull | conflicts | resolve.
|
|
53
|
+
Uses explicit CAS/conflicts and an offline outbox; --remote path or --url HTTPS.
|
|
52
54
|
|
|
53
55
|
wendkeep doctor [--vault P] Health check. --scope core|runtime · --strict for CI/release.
|
|
56
|
+
wendkeep portable <sub> Shared authored state: status | export | import | diff.
|
|
57
|
+
Default file: .wendkeep/portable/state.json; private runtime stays local.
|
|
58
|
+
wendkeep mcp <serve|config> Native semantic MCP over stdio, or client config generation.
|
|
59
|
+
serve: --vault P · --timeout-ms N.
|
|
60
|
+
config: --client generic|claude|codex|cursor · --vault P.
|
|
61
|
+
wendkeep capabilities [...] Show declared lifecycle/effect coverage by host. --host <id> · --host-version <v> · --json.
|
|
54
62
|
wendkeep observer <sub> Local multi-project Observer: serve | register | publish | reconcile | status.
|
|
55
63
|
wendkeep worktree create <slug> [--base ref] [--branch name] [--open vscode|none] [--json]
|
|
56
64
|
wendkeep worktree list [--json]
|
|
@@ -66,6 +74,8 @@ Usage:
|
|
|
66
74
|
Repair revalidates orphan/removed contexts or expired request leases without deleting history.
|
|
67
75
|
wendkeep task <sub> Typed task contracts: list | show | evaluate | claim | release.
|
|
68
76
|
Resolves the change from the causal active context; supports --session/--change/--json.
|
|
77
|
+
wendkeep tdd <sub> Causal TDD attestations: red | green | status | waive.
|
|
78
|
+
Binds task, requirement, test paths, worktree and work session.
|
|
69
79
|
wendkeep change <sub> Change lifecycle: new [--simple|--guide] | use | bind <slug> --session <id> | continue | list | show |
|
|
70
80
|
status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
|
|
71
81
|
--session <id> selects the causal active_context for implicit change operations.
|
|
@@ -213,6 +223,15 @@ async function main(argv) {
|
|
|
213
223
|
} else if (cmd === 'context') {
|
|
214
224
|
const { CONTEXT_HELP } = await import('../../../src/context.mjs');
|
|
215
225
|
process.stdout.write(CONTEXT_HELP);
|
|
226
|
+
} else if (cmd === 'portable') {
|
|
227
|
+
const { PORTABLE_HELP } = await import('../../../src/portable.mjs');
|
|
228
|
+
process.stdout.write(PORTABLE_HELP);
|
|
229
|
+
} else if (cmd === 'mcp') {
|
|
230
|
+
const { MCP_HELP } = await import('../../../src/mcp.mjs');
|
|
231
|
+
process.stdout.write(MCP_HELP);
|
|
232
|
+
} else if (cmd === 'capabilities') {
|
|
233
|
+
const { CAPABILITIES_HELP } = await import('../../../src/capabilities.mjs');
|
|
234
|
+
process.stdout.write(CAPABILITIES_HELP);
|
|
216
235
|
} else {
|
|
217
236
|
process.stdout.write(HELP);
|
|
218
237
|
}
|
|
@@ -226,7 +245,7 @@ async function main(argv) {
|
|
|
226
245
|
// `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
|
|
227
246
|
// here would prevent that repair step from reporting a corrupt binding as its own
|
|
228
247
|
// first-stage failure (and could never make it as far as the guarded init).
|
|
229
|
-
&& !['init', 'sync', 'worktree', 'hook', 'observer', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
|
|
248
|
+
&& !['init', 'sync', 'worktree', 'hook', 'observer', 'mcp', 'capabilities', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
|
|
230
249
|
await preferProjectVault(rest);
|
|
231
250
|
}
|
|
232
251
|
switch (cmd) {
|
|
@@ -243,12 +262,28 @@ async function main(argv) {
|
|
|
243
262
|
process.exit(runDoctor(rest));
|
|
244
263
|
break;
|
|
245
264
|
}
|
|
265
|
+
case 'portable': {
|
|
266
|
+
const { runPortable } = await import('../../../src/portable.mjs');
|
|
267
|
+
process.exit(runPortable(rest));
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
246
270
|
case 'observer': {
|
|
247
271
|
const { runObserver } = await import('../../../src/observer.mjs');
|
|
248
272
|
const observerExitCode = await runObserver(rest);
|
|
249
273
|
if (rest[0] !== 'serve') process.exit(observerExitCode);
|
|
250
274
|
break;
|
|
251
275
|
}
|
|
276
|
+
case 'mcp': {
|
|
277
|
+
const { runMcp } = await import('../../../src/mcp.mjs');
|
|
278
|
+
const mcpExitCode = await runMcp(rest);
|
|
279
|
+
if (mcpExitCode) process.exit(mcpExitCode);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 'capabilities': {
|
|
283
|
+
const { runCapabilities } = await import('../../../src/capabilities.mjs');
|
|
284
|
+
process.exit(runCapabilities(rest));
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
252
287
|
case 'worktree': {
|
|
253
288
|
const { runWorktree } = await import('../../../src/worktree.mjs');
|
|
254
289
|
process.exit(await runWorktree(rest));
|
|
@@ -299,6 +334,11 @@ async function main(argv) {
|
|
|
299
334
|
process.exit(runTask(rest));
|
|
300
335
|
break;
|
|
301
336
|
}
|
|
337
|
+
case 'tdd': {
|
|
338
|
+
const { runTdd } = await import('../../../src/tdd.mjs');
|
|
339
|
+
process.exit(runTdd(rest));
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
302
342
|
case 'session': {
|
|
303
343
|
const { runSession } = await import('../../../src/session.mjs');
|
|
304
344
|
runSession(rest);
|
|
@@ -73,12 +73,25 @@ export function loadSensors(projectRoot, file = 'wendkeep.sensors.json') {
|
|
|
73
73
|
// both into [] made every sensor report "sensor não definido" — a misleading diagnosis.
|
|
74
74
|
export function loadSensorsDetailed(projectRoot, file = 'wendkeep.sensors.json') {
|
|
75
75
|
const path = join(projectRoot, file);
|
|
76
|
-
if (!existsSync(path)) return {
|
|
76
|
+
if (!existsSync(path)) return {
|
|
77
|
+
sensors: [], requiredHostCapabilities: [], hostCapabilityWaivers: [], missing: true, error: null, path,
|
|
78
|
+
};
|
|
77
79
|
try {
|
|
78
80
|
const data = JSON.parse(readFileSync(path, 'utf8'));
|
|
79
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
sensors: Array.isArray(data.sensors) ? data.sensors : [],
|
|
83
|
+
requiredHostCapabilities: Array.isArray(data.requires_host_capabilities)
|
|
84
|
+
? data.requires_host_capabilities : [],
|
|
85
|
+
hostCapabilityWaivers: Array.isArray(data.host_capability_waivers)
|
|
86
|
+
? data.host_capability_waivers : [],
|
|
87
|
+
missing: false,
|
|
88
|
+
error: null,
|
|
89
|
+
path,
|
|
90
|
+
};
|
|
80
91
|
} catch (e) {
|
|
81
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
sensors: [], requiredHostCapabilities: [], hostCapabilityWaivers: [], missing: false, error: e.message, path,
|
|
94
|
+
};
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
|