wendkeep 0.3.0 → 0.4.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/README.md +23 -3
- package/hooks/change-core.mjs +35 -4
- package/hooks/sensors-core.mjs +12 -4
- package/hooks/spec-core.mjs +97 -0
- package/package.json +1 -1
- package/src/change.mjs +2 -0
- package/src/init.mjs +7 -1
- package/src/taxonomy.mjs +1 -0
- package/src/verify.mjs +7 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# wendkeep
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**A persistent‑memory harness for AI coding agents, built on your Obsidian vault.** Every Claude Code / Codex session is captured turn‑by‑turn into local Markdown — with token/cost tracking, auto‑extracted decisions, bugs and learnings, and a curated memory layer injected back at the start of the next session. On top of that memory core sits a native, zero‑dependency **change lifecycle** (spec → change → TDD → sensor‑gated archive) that keeps intent, work and proof wikilinked in one graph. 100% local, open‑core.
|
|
4
4
|
|
|
5
5
|
> Status: `v0.1` — extracted from a system in daily production use. The capture engine, cost tracking and graph wiring are battle‑tested; the cross‑platform installer (`wendkeep init`) is the new part. See [`docs/`](docs/) for the project's strategy and decision log.
|
|
6
6
|
|
|
@@ -41,7 +41,8 @@ npx wendkeep init
|
|
|
41
41
|
4. Offer to pin **companion** plugins/MCP (multi-choice; `context-mode` + `dotcontext` pre-checked). Each is wired through the most agent-agnostic path it supports — `context-mode` and `dotcontext` as `.mcp.json` MCP servers (any agent), `understand-anything` via a `understand-inject` SessionStart hook that injects its domain graph when generated, and the Claude Code plugin layer (`extraKnownMarketplaces` + `enabledPlugins`) as a bonus. **`dotcontext`** (execution/PREVC harness) also gets its lifecycle hooks wired (SessionStart-last + Stop + PostToolUse, pinned CLI) and a starter `.context/config/sensors.json` seeded with a `wendkeep validate-memory` sensor plus one per detected `package.json` script (test/typecheck/lint/build) — add the `.context/runtime|cache|logs|plans|agents|skills/` paths to `.gitignore` (wendkeep doesn't touch git). If dotcontext is already configured globally (`~/.claude.json`), the project MCP entry is **skipped automatically** to avoid a duplicate; tune with `--dotcontext-mcp <auto|project|none>` and `--dotcontext-hooks <full|light|none>` (light drops PostToolUse). Control selection with `--companions <csv>` or `--no-companions`. (`caveman` additionally runs its own cross-agent installer on non-Claude agents.)
|
|
42
42
|
5. Install a **color system** into the vault's `.obsidian/`: a CSS snippet that accents notes by type (session/decision/bug/learning, via the `cssclasses` the hooks emit) plus graph color groups by folder. Non-destructive merge into `appearance.json`/`graph.json`; skip with `--no-colors`.
|
|
43
43
|
6. Seed the **curated memory layer**: `.brain/CORE.md` (the hand-curated hot layer, with the 3 required sections) and `.brain/COMPACTION_PROTOCOL.md` (the protocol guide). The auto layers (`DIGEST.md`, `index.jsonl`) are generated by the hooks. Validate the curated layer with `wendkeep validate-memory` (cap 25 lines, 3 sections, no secrets/PII).
|
|
44
|
-
7. Seed the **definitions layer**: `.brain/agents/` + `.brain/skills/` (versioned source of truth
|
|
44
|
+
7. Seed the **definitions + skills layer**: `.brain/agents/` + `.brain/skills/` (versioned source of truth), including the native process skills `wk-workflow` / `wk-tdd` / `wk-debugging` / `wk-brainstorming` / `wk-planning`. `init` runs `wendkeep sync-defs` for you, delivering them to `.codex/agents/` + `.claude/skills/` (re‑run `sync-defs` after editing `.brain`).
|
|
45
|
+
8. Seed the **change/spec lifecycle**: the `07-Specs/` + `08-Mudanças/` folders and a native `wendkeep.sensors.json` (a `validate-memory` sensor plus one per detected `package.json` script). Drives `wendkeep change` / `wendkeep verify` — see **Change lifecycle** below.
|
|
45
46
|
|
|
46
47
|
```bash
|
|
47
48
|
npx wendkeep init --vault "~/vaults/work" --project . --yes # non-interactive (companions: context-mode,dotcontext)
|
|
@@ -65,11 +66,30 @@ No re‑copying, no snapshot to re‑sync — the package is the single source o
|
|
|
65
66
|
|
|
66
67
|
| Command | What it does |
|
|
67
68
|
|---|---|
|
|
68
|
-
| `wendkeep init` | Set up wendkeep in a project (vault taxonomy + settings + MCP). |
|
|
69
|
+
| `wendkeep init` | Set up wendkeep in a project (vault taxonomy + settings + MCP + skills). |
|
|
69
70
|
| `wendkeep hook <name>` | Run a session hook; invoked by `settings.json` (reads agent JSON on stdin). |
|
|
71
|
+
| `wendkeep change <sub>` | Change lifecycle: `new <slug>` / `list` / `show <slug>` / `archive <slug>`. |
|
|
72
|
+
| `wendkeep verify [--change s]` | Run the active (or named) change's task sensors; record evidence (the gate). |
|
|
73
|
+
| `wendkeep sync-defs` | Copy `.brain/agents\|skills` into the project (`.codex/agents`, `.claude/skills`). |
|
|
74
|
+
| `wendkeep validate-memory [path]` | Validate `.brain/CORE.md` (cap 25, 3 sections, no secrets/PII). |
|
|
70
75
|
| `wendkeep doctor [--vault P]` | Run a vault health check (integrity of sessions, registry, links). |
|
|
71
76
|
| `wendkeep --version` / `--help` | Version / usage. |
|
|
72
77
|
|
|
78
|
+
## Change lifecycle — the a2 loop (spec‑driven, native)
|
|
79
|
+
|
|
80
|
+
Beyond capturing sessions, wendkeep is a **harness**: a native, zero‑dependency loop that keeps *intent* (specs), *work* (changes) and *proof* (sensors) together in the vault, wikilinked into the Obsidian graph.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
explore → propose → apply (TDD) → verify → archive
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **Propose** — `wendkeep change new <slug>` scaffolds `08-Mudanças/<slug>/` (`proposta.md`, `design.md`, `tarefas.md`, and a `specs/` delta). The change becomes *active* and is injected at the next `SessionStart`, so the agent resumes work‑in‑progress.
|
|
87
|
+
- **Apply** — implement each `tarefas.md` task. Tag a task that needs machine proof with `[sensor:<id>]`.
|
|
88
|
+
- **Verify** — `wendkeep verify` runs the sensors your tasks declared (from `wendkeep.sensors.json` at the project root) and records `evidencia.json`. A critical red fails the gate; a `warning` red is advisory.
|
|
89
|
+
- **Archive** — `wendkeep change archive <slug>` **gates** on the evidence (blocks unless every declared critical sensor is green), promotes each capability's spec delta (`ADDED`/`MODIFIED`/`REMOVED`) into the living `07-Specs/<capability>.md`, moves the change to `_arquivo/`, and mints an ADR in `04-Decisões/`.
|
|
90
|
+
|
|
91
|
+
`wendkeep init` also seeds **native process skills** (`wk-workflow`, `wk-tdd`, `wk-debugging`, `wk-brainstorming`, `wk-planning`) into `.brain/skills` and delivers them to `.claude/skills` — the *how* layer, zero‑dep. Optional companions (`context-mode`, `dotcontext`, `understand-anything`, `caveman`) remain an opt‑in extra layer.
|
|
92
|
+
|
|
73
93
|
## How it works
|
|
74
94
|
|
|
75
95
|
```
|
package/hooks/change-core.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { ensureDir, wikilinkFromRel } from './obsidian-common.mjs';
|
|
7
|
+
import { parseSpecsList, promoteSpecs } from './spec-core.mjs';
|
|
7
8
|
|
|
8
9
|
export const CHANGES_DIR = '08-Mudanças';
|
|
9
10
|
export const SPECS_DIR = '07-Specs';
|
|
@@ -48,7 +49,15 @@ specs: []
|
|
|
48
49
|
|
|
49
50
|
- [ ] 1.1 (primeira tarefa)
|
|
50
51
|
`;
|
|
51
|
-
|
|
52
|
+
const specDelta = `## ADDED Requirements
|
|
53
|
+
### Requisito: (nome)
|
|
54
|
+
(comportamento / cenários)
|
|
55
|
+
|
|
56
|
+
## MODIFIED Requirements
|
|
57
|
+
|
|
58
|
+
## REMOVED Requirements
|
|
59
|
+
`;
|
|
60
|
+
return { proposta, design, tarefas, specDelta };
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
export function activeChange(vaultBase) {
|
|
@@ -82,6 +91,12 @@ export function newChange(vaultBase, slug, { sessionRel = '', dateStr }) {
|
|
|
82
91
|
write('proposta.md', files.proposta);
|
|
83
92
|
write('design.md', files.design);
|
|
84
93
|
write('tarefas.md', files.tarefas);
|
|
94
|
+
// Seed an example spec delta so the promotion format is discoverable.
|
|
95
|
+
const exampleDelta = join(dir, 'specs', 'exemplo', 'spec.md');
|
|
96
|
+
if (!existsSync(exampleDelta)) {
|
|
97
|
+
mkdirSync(join(dir, 'specs', 'exemplo'), { recursive: true });
|
|
98
|
+
writeFileSync(exampleDelta, files.specDelta, 'utf8');
|
|
99
|
+
}
|
|
85
100
|
setActiveChange(vaultBase, slug);
|
|
86
101
|
return { rel: changeDirRel(slug), created: !existed };
|
|
87
102
|
}
|
|
@@ -147,6 +162,20 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
|
|
|
147
162
|
if (!verdict.ok) return { ok: false, failing: verdict.failing || [] };
|
|
148
163
|
|
|
149
164
|
const destRel = join(CHANGES_DIR, ARCHIVE_DIR, `${dateStr}-${slug}`);
|
|
165
|
+
const changeWikilink = wikilinkFromRel(join(destRel, 'proposta'));
|
|
166
|
+
|
|
167
|
+
// Promote spec deltas into the living 07-Specs BEFORE moving (deltas live in src).
|
|
168
|
+
let promoted = [];
|
|
169
|
+
let specWarnings = [];
|
|
170
|
+
try {
|
|
171
|
+
const specs = parseSpecsList(readFileSync(join(src, 'proposta.md'), 'utf8'));
|
|
172
|
+
if (specs.length) {
|
|
173
|
+
const res = promoteSpecs(vaultBase, src, specs, { changeWikilink, dateStr });
|
|
174
|
+
promoted = res.promoted;
|
|
175
|
+
specWarnings = res.warnings;
|
|
176
|
+
}
|
|
177
|
+
} catch { /* proposta ilegível — segue só com ADR */ }
|
|
178
|
+
|
|
150
179
|
ensureDir(join(vaultBase, CHANGES_DIR, ARCHIVE_DIR));
|
|
151
180
|
renameSync(src, join(vaultBase, destRel));
|
|
152
181
|
|
|
@@ -155,7 +184,9 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
|
|
|
155
184
|
ensureDir(join(vaultBase, adrDirRel));
|
|
156
185
|
const num = String(adrNum).padStart(3, '0');
|
|
157
186
|
const adrRel = join(adrDirRel, `ADR-${num}-${slug}.md`);
|
|
158
|
-
const
|
|
187
|
+
const capLine = promoted.length
|
|
188
|
+
? `\n\nCapabilities: ${promoted.map((c) => wikilinkFromRel(join(SPECS_DIR, c))).join(', ')}.`
|
|
189
|
+
: '';
|
|
159
190
|
writeFileSync(join(vaultBase, adrRel), `---
|
|
160
191
|
type: decision
|
|
161
192
|
status: accepted
|
|
@@ -170,9 +201,9 @@ tags:
|
|
|
170
201
|
|
|
171
202
|
## Decisão
|
|
172
203
|
|
|
173
|
-
Mudança ${changeWikilink} concluída e arquivada
|
|
204
|
+
Mudança ${changeWikilink} concluída e arquivada.${capLine}
|
|
174
205
|
`, 'utf8');
|
|
175
206
|
|
|
176
207
|
clearActiveChange(vaultBase);
|
|
177
|
-
return { ok: true, failing: [], archivedRel: destRel, adrRel };
|
|
208
|
+
return { ok: true, failing: [], archivedRel: destRel, adrRel, promoted, specWarnings };
|
|
178
209
|
}
|
package/hooks/sensors-core.mjs
CHANGED
|
@@ -24,15 +24,23 @@ export function runSensors(sensors, ids, { spawn = spawnSync, cwd, now } = {}) {
|
|
|
24
24
|
const evidence = [];
|
|
25
25
|
for (const id of ids) {
|
|
26
26
|
const s = byId[id];
|
|
27
|
-
if (!s) { evidence.push({ id, status: 'red', ts, note: 'sensor não definido' }); continue; }
|
|
27
|
+
if (!s) { evidence.push({ id, status: 'red', ts, severity: 'critical', note: 'sensor não definido' }); continue; }
|
|
28
28
|
const r = spawn(s.command, [], { cwd, shell: true, stdio: 'ignore' });
|
|
29
|
-
evidence.push({ id, status: (r.status ?? 1) === 0 ? 'green' : 'red', ts });
|
|
29
|
+
evidence.push({ id, status: (r.status ?? 1) === 0 ? 'green' : 'red', ts, severity: s.severity || 'critical' });
|
|
30
30
|
}
|
|
31
31
|
return evidence;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// A required sensor blocks the gate when it is missing (never verified) or red at a
|
|
35
|
+
// non-warning severity. Warnings are advisory: a red warning does not block archive.
|
|
36
|
+
// Severity comes from the evidence entry (written by runSensors); absent -> critical.
|
|
34
37
|
export function evaluateGate(evidence, requiredIds) {
|
|
35
|
-
const byId = Object.fromEntries((evidence || []).map((e) => [e.id, e
|
|
36
|
-
const failing = (requiredIds || []).filter((id) =>
|
|
38
|
+
const byId = Object.fromEntries((evidence || []).map((e) => [e.id, e]));
|
|
39
|
+
const failing = (requiredIds || []).filter((id) => {
|
|
40
|
+
const e = byId[id];
|
|
41
|
+
if (!e) return true; // never verified
|
|
42
|
+
if (e.status === 'green') return false;
|
|
43
|
+
return (e.severity || 'critical') !== 'warning';
|
|
44
|
+
});
|
|
37
45
|
return { ok: failing.length === 0, failing };
|
|
38
46
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// hooks/spec-core.mjs — living spec (07-Specs) + change delta merge (OpenSpec native).
|
|
2
|
+
// Pure parsing/merge + promoteSpecs (fs). No import from change-core (avoids a cycle).
|
|
3
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { ensureDir } from './obsidian-common.mjs';
|
|
6
|
+
|
|
7
|
+
const SPECS_DIR = '07-Specs';
|
|
8
|
+
const REQ_RE = /^### Requisito:\s*(.+)$/gm;
|
|
9
|
+
|
|
10
|
+
export function parseRequirements(md) {
|
|
11
|
+
const text = String(md);
|
|
12
|
+
const matches = [...text.matchAll(REQ_RE)];
|
|
13
|
+
const reqs = [];
|
|
14
|
+
for (let i = 0; i < matches.length; i += 1) {
|
|
15
|
+
const name = matches[i][1].trim();
|
|
16
|
+
const start = matches[i].index + matches[i][0].length;
|
|
17
|
+
const end = i + 1 < matches.length ? matches[i + 1].index : text.length;
|
|
18
|
+
const body = text.slice(start, end).replace(/(\n>[^\n]*)+\s*$/, '').trim();
|
|
19
|
+
reqs.push({ name, body });
|
|
20
|
+
}
|
|
21
|
+
return reqs;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseDelta(md) {
|
|
25
|
+
const text = String(md);
|
|
26
|
+
const grab = (label) => {
|
|
27
|
+
const m = text.match(new RegExp(`^##\\s+${label} Requirements\\s*$`, 'm'));
|
|
28
|
+
if (!m) return '';
|
|
29
|
+
const rest = text.slice(m.index + m[0].length);
|
|
30
|
+
const next = rest.search(/^##\s+\w+ Requirements\s*$/m);
|
|
31
|
+
return next === -1 ? rest : rest.slice(0, next);
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
added: parseRequirements(grab('ADDED')),
|
|
35
|
+
modified: parseRequirements(grab('MODIFIED')),
|
|
36
|
+
removed: parseRequirements(grab('REMOVED')).map((r) => r.name),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function applyDelta(reqs, delta) {
|
|
41
|
+
const order = reqs.map((r) => r.name);
|
|
42
|
+
const map = new Map(reqs.map((r) => [r.name, r.body]));
|
|
43
|
+
const warnings = [];
|
|
44
|
+
for (const r of delta.added || []) {
|
|
45
|
+
if (map.has(r.name)) warnings.push(`ADDED já existe: ${r.name}`); else order.push(r.name);
|
|
46
|
+
map.set(r.name, r.body);
|
|
47
|
+
}
|
|
48
|
+
for (const r of delta.modified || []) {
|
|
49
|
+
if (!map.has(r.name)) { warnings.push(`MODIFIED inexistente: ${r.name}`); order.push(r.name); }
|
|
50
|
+
map.set(r.name, r.body);
|
|
51
|
+
}
|
|
52
|
+
for (const name of delta.removed || []) {
|
|
53
|
+
if (!map.has(name)) warnings.push(`REMOVED inexistente: ${name}`);
|
|
54
|
+
map.delete(name);
|
|
55
|
+
}
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const out = order
|
|
58
|
+
.filter((n) => map.has(n) && !seen.has(n) && seen.add(n))
|
|
59
|
+
.map((n) => ({ name: n, body: map.get(n) }));
|
|
60
|
+
return { reqs: out, warnings };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function renderSpec(capability, reqs, { footer } = {}) {
|
|
64
|
+
const blocks = reqs.map((r) => `### Requisito: ${r.name}\n${r.body}`).join('\n\n');
|
|
65
|
+
const foot = footer ? `\n\n> ${footer}\n` : '\n';
|
|
66
|
+
return `---\ntype: spec\ncssclasses:\n - topic-spec\ntags:\n - spec\n---\n\n# ${capability}\n\n## Requisitos\n\n${blocks}${foot}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function parseSpecsList(propostaMd) {
|
|
70
|
+
const text = String(propostaMd);
|
|
71
|
+
const inline = text.match(/^specs:\s*\[(.*?)\]\s*$/m);
|
|
72
|
+
if (inline) return inline[1].split(',').map((s) => s.trim().replace(/['"]/g, '')).filter(Boolean);
|
|
73
|
+
const block = text.match(/^specs:\s*\n((?:[ \t]+-[ \t]*.+\n?)+)/m);
|
|
74
|
+
if (block) return block[1].split('\n').map((l) => l.replace(/^[ \t]*-[ \t]*/, '').trim().replace(/['"]/g, '')).filter(Boolean);
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Merge each capability's delta (in the change) into the living spec in 07-Specs.
|
|
79
|
+
export function promoteSpecs(vaultBase, changeDir, specs, { changeWikilink, dateStr } = {}) {
|
|
80
|
+
const promoted = [];
|
|
81
|
+
const warnings = [];
|
|
82
|
+
for (const cap of specs) {
|
|
83
|
+
let delta;
|
|
84
|
+
try { delta = parseDelta(readFileSync(join(changeDir, 'specs', cap, 'spec.md'), 'utf8')); }
|
|
85
|
+
catch { warnings.push(`sem delta para ${cap}`); continue; }
|
|
86
|
+
const livePath = join(vaultBase, SPECS_DIR, `${cap}.md`);
|
|
87
|
+
let current = [];
|
|
88
|
+
try { current = parseRequirements(readFileSync(livePath, 'utf8')); } catch { /* nova capability */ }
|
|
89
|
+
const applied = applyDelta(current, delta);
|
|
90
|
+
warnings.push(...applied.warnings.map((w) => `${cap}: ${w}`));
|
|
91
|
+
ensureDir(join(vaultBase, SPECS_DIR));
|
|
92
|
+
const footer = changeWikilink ? `Atualizado por ${changeWikilink} em ${dateStr}.` : '';
|
|
93
|
+
writeFileSync(livePath, renderSpec(cap, applied.reqs, { footer }), 'utf8');
|
|
94
|
+
promoted.push(cap);
|
|
95
|
+
}
|
|
96
|
+
return { promoted, warnings };
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Automatically capture AI coding agent sessions (Claude Code, Codex) as local Markdown in your Obsidian vault — turn-by-turn history, cost/token tracking, auto-extracted decisions/bugs/learnings, rendered in the graph. Local-first.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/change.mjs
CHANGED
|
@@ -82,6 +82,8 @@ export function runChange(argv) {
|
|
|
82
82
|
process.exit(1);
|
|
83
83
|
}
|
|
84
84
|
process.stdout.write(`archived: ${r.archivedRel}; ADR: ${r.adrRel}\n`);
|
|
85
|
+
if (r.promoted && r.promoted.length) process.stdout.write(`specs promovidas: ${r.promoted.join(', ')}\n`);
|
|
86
|
+
if (r.specWarnings && r.specWarnings.length) for (const w of r.specWarnings) process.stderr.write(` aviso spec: ${w}\n`);
|
|
85
87
|
process.exit(0);
|
|
86
88
|
}
|
|
87
89
|
|
package/src/init.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
mergeGraphColorGroups,
|
|
32
32
|
} from './vault-theme.mjs';
|
|
33
33
|
import { renderCoreSkeleton, renderCompactionProtocol } from './validate-core.mjs';
|
|
34
|
-
import { seedDefinitions } from './sync-defs.mjs';
|
|
34
|
+
import { seedDefinitions, syncDefs } from './sync-defs.mjs';
|
|
35
35
|
import { seedWkSkills } from './skills-seed.mjs';
|
|
36
36
|
import { seedDotcontext, globalHasDotcontext, resolveDotcontextSkipMcp, renderSensorsJson } from './dotcontext-seed.mjs';
|
|
37
37
|
|
|
@@ -295,6 +295,12 @@ export async function runInit(argv) {
|
|
|
295
295
|
writeFileSync(sensorsFile, renderSensorsJson(scripts), 'utf8');
|
|
296
296
|
}
|
|
297
297
|
log(` [1/4] vault taxonomy: ${VAULT_FOLDERS.length} folders (${created} created)${readmeNote}, .brain + change/spec + sensors seeded`);
|
|
298
|
+
// Deliver the seeded defs (agents + wk process skills) to the project so they're
|
|
299
|
+
// usable immediately — no separate `wendkeep sync-defs` step needed.
|
|
300
|
+
const synced = syncDefs(vaultPath, projectPath);
|
|
301
|
+
if (synced.skills.length || synced.agents.length) {
|
|
302
|
+
log(` defs delivered: ${synced.skills.length} skill(s) -> .claude/skills, ${synced.agents.length} agent(s) -> .codex/agents`);
|
|
303
|
+
}
|
|
298
304
|
|
|
299
305
|
// 2. .claude/settings.json --------------------------------------------------
|
|
300
306
|
const settingsPath = join(projectPath, '.claude', 'settings.json');
|
package/src/taxonomy.mjs
CHANGED
package/src/verify.mjs
CHANGED
|
@@ -31,10 +31,13 @@ export function runVerify(argv) {
|
|
|
31
31
|
const evidence = runSensors(sensors, ids, { cwd: projectRoot });
|
|
32
32
|
writeFileSync(join(changeDir, 'evidencia.json'), `${JSON.stringify(evidence, null, 2)}\n`, 'utf8');
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const { ok, failing } = evaluateGate(evidence,
|
|
37
|
-
for (const e of evidence)
|
|
34
|
+
// Same rule as the archive gate: evidence carries severity, so evaluateGate blocks
|
|
35
|
+
// only on critical/missing — a red warning is advisory and passes verify.
|
|
36
|
+
const { ok, failing } = evaluateGate(evidence, ids);
|
|
37
|
+
for (const e of evidence) {
|
|
38
|
+
const mark = e.status === 'green' ? '✓' : (e.severity === 'warning' ? '!' : '✗');
|
|
39
|
+
process.stdout.write(` ${mark} ${e.id}${e.severity === 'warning' && e.status === 'red' ? ' (warning)' : ''}\n`);
|
|
40
|
+
}
|
|
38
41
|
if (!ok) { process.stderr.write(`verify: critical sensors red: ${failing.join(', ')}\n`); process.exit(1); }
|
|
39
42
|
process.stdout.write(`verify OK (${ids.length} sensor(s))\n`);
|
|
40
43
|
process.exit(0);
|