wendkeep 0.6.1 → 0.7.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 +20 -0
- package/README.md +36 -3
- package/bin/wendkeep.mjs +14 -1
- package/hooks/change-core.mjs +11 -0
- package/package.json +2 -1
- package/schema/wendkeep.sensors.schema.json +34 -0
- package/src/change.mjs +82 -3
- package/src/dotcontext-seed.mjs +1 -1
- package/src/sensors.mjs +23 -0
- package/src/spec.mjs +53 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to **wendkeep** are documented here. Format based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.7.0] — 2026-07-05
|
|
8
|
+
|
|
9
|
+
Ergonomics: the loop without hand-editing files.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`change status`** — one screen: tasks (done/open with `[req:]`/`[sensor:]`), sensor
|
|
13
|
+
evidence, verdict state (ok / stale / incomplete / absent), mutation round.
|
|
14
|
+
- **`change done <id>` / `undone <id>`** — tick tasks from the CLI (exact-id anchored).
|
|
15
|
+
- **`change diff`** — dry-run preview of the spec promotion (`+` ADDED / `~` MODIFIED /
|
|
16
|
+
`-` REMOVED / `!` warnings) without touching `07-Specs`.
|
|
17
|
+
- **`spec list` / `spec show <capability>`** — read-only views over the living specs.
|
|
18
|
+
- **`sensors list`** — the sensors from `wendkeep.sensors.json`; a **JSON Schema** for the
|
|
19
|
+
file now ships in the package (`schema/`) and the init seed points `$schema` at it.
|
|
20
|
+
- README: "the loop in five minutes" worked example.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- `change` subcommands without a positional argument no longer mistake the `--vault` value
|
|
24
|
+
for a slug.
|
|
25
|
+
|
|
7
26
|
## [0.6.1] — 2026-07-05
|
|
8
27
|
|
|
9
28
|
Hardening: CI + real-world gate holes found by self-audit.
|
|
@@ -141,6 +160,7 @@ Initial release — the capture engine, extracted from a system in daily product
|
|
|
141
160
|
- `wendkeep init` (cross-platform installer) + optional `@bitbonsai/mcpvault` MCP server.
|
|
142
161
|
|
|
143
162
|
<!-- Only v0.4.0+ is tagged in git (history starts here); older versions link to npm. -->
|
|
163
|
+
[0.7.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.7.0
|
|
144
164
|
[0.6.1]: https://github.com/rogersialves/wendkeep/releases/tag/v0.6.1
|
|
145
165
|
[0.6.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.6.0
|
|
146
166
|
[0.5.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.5.0
|
package/README.md
CHANGED
|
@@ -70,8 +70,11 @@ No re‑copying, no snapshot to re‑sync — the package is the single source o
|
|
|
70
70
|
|---|---|
|
|
71
71
|
| `wendkeep init` | Set up wendkeep in a project (vault taxonomy + settings + MCP + skills). |
|
|
72
72
|
| `wendkeep hook <name>` | Run a session hook; invoked by `settings.json` (reads agent JSON on stdin). |
|
|
73
|
-
| `wendkeep change <sub>` | Change lifecycle: `new
|
|
74
|
-
| `wendkeep verify [--
|
|
73
|
+
| `wendkeep change <sub>` | Change lifecycle: `new [--simple]` / `list` / `show` / `status` / `done <id>` / `undone <id>` / `diff` / `archive [--force]`. |
|
|
74
|
+
| `wendkeep verify [--deep]` | Run the change's task sensors; `--deep` assembles the independent-verification package. |
|
|
75
|
+
| `wendkeep spec <sub>` | Living specs: `list` / `show <capability>`. |
|
|
76
|
+
| `wendkeep sensors list` | Show the sensors from `wendkeep.sensors.json` (JSON Schema included in the package). |
|
|
77
|
+
| `wendkeep lesson add "t" "l"` | Record a project-local lesson (injected at the next SessionStart). |
|
|
75
78
|
| `wendkeep sync-defs` | Copy `.brain/agents\|skills` into the project (`.codex/agents`, `.claude/skills`). |
|
|
76
79
|
| `wendkeep validate-memory [path]` | Validate `.brain/CORE.md` (cap 25, 3 sections, no secrets/PII). |
|
|
77
80
|
| `wendkeep doctor [--vault P]` | Run a vault health check (integrity of sessions, registry, links). |
|
|
@@ -90,7 +93,37 @@ explore → propose → apply (TDD) → verify → archive
|
|
|
90
93
|
- **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.
|
|
91
94
|
- **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/`.
|
|
92
95
|
|
|
93
|
-
`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.
|
|
96
|
+
`wendkeep init` also seeds **native process skills** (`wk-workflow`, `wk-tdd`, `wk-debugging`, `wk-brainstorming`, `wk-planning`, `wk-verify`) 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.
|
|
97
|
+
|
|
98
|
+
### The loop in five minutes
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx wendkeep init --yes # vault + hooks + sensors + skills
|
|
102
|
+
npx wendkeep change new dark-mode # proposta/design/tarefas — change is now active
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Edit `tarefas.md` — tag proof and requirement per task:
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
- [ ] 1.1 toggle persists across sessions [req:UI-1] [sensor:tests]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Declare the capability in `proposta.md` (`specs: [ui]`) and author its delta in
|
|
112
|
+
`specs/ui/spec.md` (`## ADDED Requirements` → `### Requisito: UI-1 — dark mode toggle`). Then:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx wendkeep change status # one screen: tasks / sensors / verdict
|
|
116
|
+
npx wendkeep change done 1.1 # tick a task from the CLI
|
|
117
|
+
npx wendkeep verify # run the declared sensors -> evidencia.json
|
|
118
|
+
npx wendkeep verify --deep # assemble the verification package
|
|
119
|
+
# the wk-verify skill (fresh, read-only pass) writes verdict.json
|
|
120
|
+
npx wendkeep change diff # preview what will land in 07-Specs
|
|
121
|
+
npx wendkeep change archive dark-mode # gate: sensors + verdict + no open tasks
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The archive promotes the delta into the living `07-Specs/ui.md`, mints an ADR, and the
|
|
125
|
+
Obsidian graph now links *session ↔ change ↔ requirement ↔ decision*. A change that names
|
|
126
|
+
no `[req:]` skips the independent verdict — the sensor gate is its proof.
|
|
94
127
|
|
|
95
128
|
## How it works
|
|
96
129
|
|
package/bin/wendkeep.mjs
CHANGED
|
@@ -43,7 +43,10 @@ Usage:
|
|
|
43
43
|
agent's JSON on stdin. Names: ${RUNNABLE_HOOKS.join(', ')}.
|
|
44
44
|
|
|
45
45
|
wendkeep doctor [--vault P] Run a vault health check.
|
|
46
|
-
wendkeep change <sub> Change lifecycle: new
|
|
46
|
+
wendkeep change <sub> Change lifecycle: new [--simple] | list | show | status |
|
|
47
|
+
done <id> | undone <id> | diff | archive [--force].
|
|
48
|
+
wendkeep spec <sub> Living specs: list | show <capability>.
|
|
49
|
+
wendkeep sensors list Show the sensors from wendkeep.sensors.json.
|
|
47
50
|
wendkeep verify [--change s] Run a change's task sensors + record evidence (the gate).
|
|
48
51
|
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
49
52
|
wendkeep validate-memory [path] Validate .brain/CORE.md against the compaction
|
|
@@ -117,6 +120,16 @@ async function main() {
|
|
|
117
120
|
runLesson(rest);
|
|
118
121
|
break;
|
|
119
122
|
}
|
|
123
|
+
case 'spec': {
|
|
124
|
+
const { runSpec } = await import('../src/spec.mjs');
|
|
125
|
+
runSpec(rest);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'sensors': {
|
|
129
|
+
const { runSensors } = await import('../src/sensors.mjs');
|
|
130
|
+
runSensors(rest);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
120
133
|
case '--version':
|
|
121
134
|
case '-v':
|
|
122
135
|
process.stdout.write(`${version()}\n`);
|
package/hooks/change-core.mjs
CHANGED
|
@@ -123,6 +123,17 @@ export function parseTasks(md) {
|
|
|
123
123
|
return tasks;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// Toggle a task checkbox by its exact id (0.7.0 ergonomics). Returns false when absent.
|
|
127
|
+
export function setTaskDone(changeDir, taskId, done = true) {
|
|
128
|
+
const path = join(changeDir, 'tarefas.md');
|
|
129
|
+
const md = readFileSync(path, 'utf8');
|
|
130
|
+
const esc = String(taskId).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
131
|
+
const re = new RegExp(`^(-\\s+\\[)( |x)(\\]\\s+${esc}\\s)`, 'm');
|
|
132
|
+
if (!re.test(md)) return false;
|
|
133
|
+
writeFileSync(path, md.replace(re, `$1${done ? 'x' : ' '}$3`), 'utf8');
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
126
137
|
export function listChanges(vaultBase) {
|
|
127
138
|
const base = join(vaultBase, CHANGES_DIR);
|
|
128
139
|
const active = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"bin",
|
|
12
12
|
"src",
|
|
13
13
|
"hooks",
|
|
14
|
+
"schema",
|
|
14
15
|
"README.md",
|
|
15
16
|
"CHANGELOG.md"
|
|
16
17
|
],
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/rogersialves/wendkeep/main/schema/wendkeep.sensors.schema.json",
|
|
4
|
+
"title": "wendkeep.sensors.json",
|
|
5
|
+
"description": "Native sensor config for the wendkeep harness (harness contract v1.1).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["version", "sensors"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": { "type": "string" },
|
|
10
|
+
"version": { "const": 1 },
|
|
11
|
+
"source": { "type": "string" },
|
|
12
|
+
"sensors": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["id", "command"],
|
|
17
|
+
"properties": {
|
|
18
|
+
"id": { "type": "string", "minLength": 1 },
|
|
19
|
+
"name": { "type": "string" },
|
|
20
|
+
"description": { "type": "string" },
|
|
21
|
+
"severity": { "enum": ["critical", "warning"], "default": "critical" },
|
|
22
|
+
"type": { "enum": ["command", "mutation"], "default": "command" },
|
|
23
|
+
"command": { "type": "string", "minLength": 1 },
|
|
24
|
+
"report": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "type: mutation only — path (project-relative) to the mutation-testing-elements report."
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"additionalProperties": false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": false
|
|
34
|
+
}
|
package/src/change.mjs
CHANGED
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
activeChange,
|
|
7
7
|
listChanges,
|
|
8
8
|
parseTasks,
|
|
9
|
+
setTaskDone,
|
|
9
10
|
archiveChange,
|
|
10
11
|
} from '../hooks/change-core.mjs';
|
|
11
12
|
import { evaluateGate, requiredSensors } from '../hooks/sensors-core.mjs';
|
|
12
|
-
import { evaluateVerdict, tasksHashOf } from '../hooks/spec-core.mjs';
|
|
13
|
+
import { evaluateVerdict, tasksHashOf, parseSpecsList, parseDelta, parseRequirements, applyDelta } from '../hooks/spec-core.mjs';
|
|
13
14
|
import { getNextAdrNumber, readControl } from '../hooks/obsidian-common.mjs';
|
|
14
15
|
|
|
15
16
|
function resolveVault(argv) {
|
|
@@ -27,6 +28,13 @@ function resolveVault(argv) {
|
|
|
27
28
|
return isAbsolute(base) ? base : resolve(process.cwd(), base);
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
function opt(argv, name) {
|
|
32
|
+
const i = argv.indexOf(name);
|
|
33
|
+
if (i >= 0) return argv[i + 1];
|
|
34
|
+
const eq = argv.find((a) => a.startsWith(`${name}=`));
|
|
35
|
+
return eq ? eq.slice(name.length + 1) : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
function today() {
|
|
31
39
|
const d = new Date();
|
|
32
40
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
@@ -35,7 +43,8 @@ function today() {
|
|
|
35
43
|
export function runChange(argv) {
|
|
36
44
|
const [sub, ...rest] = argv;
|
|
37
45
|
const vaultBase = resolveVault(rest);
|
|
38
|
-
const
|
|
46
|
+
const VALUE_FLAGS = new Set(['--vault', '--change', '--project']);
|
|
47
|
+
const slugArg = () => rest.find((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(rest[i - 1]));
|
|
39
48
|
|
|
40
49
|
if (sub === 'new') {
|
|
41
50
|
const slug = slugArg();
|
|
@@ -69,6 +78,76 @@ export function runChange(argv) {
|
|
|
69
78
|
process.exit(0);
|
|
70
79
|
}
|
|
71
80
|
|
|
81
|
+
if (sub === 'status') {
|
|
82
|
+
const slug = slugArg() || activeChange(vaultBase);
|
|
83
|
+
if (!slug) { process.stderr.write('wendkeep change status: no change (arg or active)\n'); process.exit(2); }
|
|
84
|
+
const dir = join(vaultBase, '08-Mudanças', slug);
|
|
85
|
+
let tarefasMd;
|
|
86
|
+
try { tarefasMd = readFileSync(join(dir, 'tarefas.md'), 'utf8'); }
|
|
87
|
+
catch { process.stderr.write(`wendkeep change status: not found: ${slug}\n`); process.exit(2); }
|
|
88
|
+
const tasks = parseTasks(tarefasMd);
|
|
89
|
+
const done = tasks.filter((t) => t.done).length;
|
|
90
|
+
process.stdout.write(`change: ${slug}${slug === activeChange(vaultBase) ? ' (ativa)' : ''}\n`);
|
|
91
|
+
let specs = [];
|
|
92
|
+
try { specs = parseSpecsList(readFileSync(join(dir, 'proposta.md'), 'utf8')); } catch { /* sem proposta */ }
|
|
93
|
+
process.stdout.write(`specs: ${specs.join(', ') || '(nenhuma)'}\n`);
|
|
94
|
+
process.stdout.write(`tarefas: ${done} done / ${tasks.length - done} open\n`);
|
|
95
|
+
for (const t of tasks) {
|
|
96
|
+
process.stdout.write(` [${t.done ? 'x' : ' '}] ${t.id} ${t.text}${t.req ? ` [req:${t.req}]` : ''}${t.sensor ? ` [sensor:${t.sensor}]` : ''}\n`);
|
|
97
|
+
}
|
|
98
|
+
let evidence = null;
|
|
99
|
+
try { evidence = JSON.parse(readFileSync(join(dir, 'evidencia.json'), 'utf8')); } catch { /* sem evidência */ }
|
|
100
|
+
if (evidence) for (const e of evidence) process.stdout.write(` ${e.status === 'green' ? '✓' : '✗'} ${e.id} (${e.severity || 'critical'})\n`);
|
|
101
|
+
else process.stdout.write('evidencia: ausente\n');
|
|
102
|
+
const reqIds = [...new Set(tasks.map((t) => t.req).filter(Boolean))];
|
|
103
|
+
let verdict = null;
|
|
104
|
+
try { verdict = JSON.parse(readFileSync(join(dir, 'verdict.json'), 'utf8')); } catch { /* sem verdict */ }
|
|
105
|
+
if (!reqIds.length) process.stdout.write('verdict: não exigido (sem [req:])\n');
|
|
106
|
+
else if (!verdict) process.stdout.write('verdict: ausente — rode `wendkeep verify --deep` + wk-verify\n');
|
|
107
|
+
else {
|
|
108
|
+
const v = evaluateVerdict(verdict, reqIds, { tasksHash: tasksHashOf(tarefasMd) });
|
|
109
|
+
process.stdout.write(`verdict: ${v.ok ? 'ok' : v.stale ? 'stale — re-verifique' : `incompleto: falta ${v.missing.join(', ')}`}\n`);
|
|
110
|
+
}
|
|
111
|
+
try { process.stdout.write(`mutation-round: ${readFileSync(join(dir, '.mutation-round'), 'utf8').trim()}/3\n`); } catch { /* sem rodadas */ }
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (sub === 'done' || sub === 'undone') {
|
|
116
|
+
const taskId = slugArg();
|
|
117
|
+
if (!taskId) { process.stderr.write(`wendkeep change ${sub}: missing <taskId>\n`); process.exit(2); }
|
|
118
|
+
const slug = opt(rest, '--change') || activeChange(vaultBase);
|
|
119
|
+
if (!slug) { process.stderr.write(`wendkeep change ${sub}: no active change\n`); process.exit(2); }
|
|
120
|
+
const dir = join(vaultBase, '08-Mudanças', slug);
|
|
121
|
+
let ok = false;
|
|
122
|
+
try { ok = setTaskDone(dir, taskId, sub === 'done'); } catch { /* sem tarefas.md */ }
|
|
123
|
+
if (!ok) { process.stderr.write(`wendkeep change ${sub}: task não encontrada: ${taskId}\n`); process.exit(2); }
|
|
124
|
+
process.stdout.write(`task ${taskId}: ${sub === 'done' ? '[x]' : '[ ]'}\n`);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (sub === 'diff') {
|
|
129
|
+
const slug = slugArg() || activeChange(vaultBase);
|
|
130
|
+
if (!slug) { process.stderr.write('wendkeep change diff: no change (arg or active)\n'); process.exit(2); }
|
|
131
|
+
const dir = join(vaultBase, '08-Mudanças', slug);
|
|
132
|
+
let specs = [];
|
|
133
|
+
try { specs = parseSpecsList(readFileSync(join(dir, 'proposta.md'), 'utf8')); }
|
|
134
|
+
catch { process.stderr.write(`wendkeep change diff: not found: ${slug}\n`); process.exit(2); }
|
|
135
|
+
if (!specs.length) { process.stdout.write('diff: sem specs declaradas na proposta\n'); process.exit(0); }
|
|
136
|
+
for (const cap of specs) {
|
|
137
|
+
let delta;
|
|
138
|
+
try { delta = parseDelta(readFileSync(join(dir, 'specs', cap, 'spec.md'), 'utf8')); }
|
|
139
|
+
catch { process.stdout.write(`! sem delta para ${cap}\n`); continue; }
|
|
140
|
+
process.stdout.write(`spec: ${cap}\n`);
|
|
141
|
+
for (const r of delta.added) process.stdout.write(` + ${r.id || r.name} (ADDED)\n`);
|
|
142
|
+
for (const r of delta.modified) process.stdout.write(` ~ ${r.id || r.name} (MODIFIED)\n`);
|
|
143
|
+
for (const k of delta.removed) process.stdout.write(` - ${k} (REMOVED)\n`);
|
|
144
|
+
let living = [];
|
|
145
|
+
try { living = parseRequirements(readFileSync(join(vaultBase, '07-Specs', `${cap}.md`), 'utf8')); } catch { /* nova capability */ }
|
|
146
|
+
for (const w of applyDelta(living, delta).warnings) process.stdout.write(` ! ${w}\n`);
|
|
147
|
+
}
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
72
151
|
if (sub === 'archive') {
|
|
73
152
|
const slug = slugArg() || activeChange(vaultBase);
|
|
74
153
|
if (!slug) { process.stderr.write('wendkeep change archive: missing <slug> and no active change\n'); process.exit(2); }
|
|
@@ -109,6 +188,6 @@ export function runChange(argv) {
|
|
|
109
188
|
process.exit(0);
|
|
110
189
|
}
|
|
111
190
|
|
|
112
|
-
process.stderr.write(`wendkeep change: unknown subcommand "${sub}". Known: new, list, show, archive.\n`);
|
|
191
|
+
process.stderr.write(`wendkeep change: unknown subcommand "${sub}". Known: new, list, show, status, done, undone, diff, archive.\n`);
|
|
113
192
|
process.exit(2);
|
|
114
193
|
}
|
package/src/dotcontext-seed.mjs
CHANGED
|
@@ -37,7 +37,7 @@ export function renderSensorsJson(scripts = {}) {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
return `${JSON.stringify({ version: 1, source: 'manual', sensors }, null, 2)}\n`;
|
|
40
|
+
return `${JSON.stringify({ $schema: 'https://raw.githubusercontent.com/rogersialves/wendkeep/main/schema/wendkeep.sensors.schema.json', version: 1, source: 'manual', sensors }, null, 2)}\n`;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function readProjectScripts(projectPath) {
|
package/src/sensors.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// `wendkeep sensors list` — read-only view over wendkeep.sensors.json (0.7.0).
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { loadSensors } from '../hooks/sensors-core.mjs';
|
|
4
|
+
|
|
5
|
+
export function runSensors(argv) {
|
|
6
|
+
const [sub, ...rest] = argv;
|
|
7
|
+
if (sub !== 'list') {
|
|
8
|
+
process.stderr.write(`wendkeep sensors: unknown subcommand "${sub}". Known: list.\n`);
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
let project;
|
|
12
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
13
|
+
if (rest[i] === '--project') project = rest[++i];
|
|
14
|
+
else if (rest[i].startsWith('--project=')) project = rest[i].slice(10);
|
|
15
|
+
}
|
|
16
|
+
const projectRoot = resolve(project || process.cwd());
|
|
17
|
+
const sensors = loadSensors(projectRoot);
|
|
18
|
+
if (!sensors.length) { process.stdout.write('sensors: (nenhum — crie wendkeep.sensors.json na raiz)\n'); process.exit(0); }
|
|
19
|
+
for (const s of sensors) {
|
|
20
|
+
process.stdout.write(`${s.id}: ${s.type || 'command'} · ${s.severity || 'critical'} · ${s.command}\n`);
|
|
21
|
+
}
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
package/src/spec.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// `wendkeep spec <sub>` — read-only views over the living specs in 07-Specs (0.7.0).
|
|
2
|
+
import { readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { parseRequirements } from '../hooks/spec-core.mjs';
|
|
5
|
+
|
|
6
|
+
function resolveVault(argv) {
|
|
7
|
+
let vault;
|
|
8
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
9
|
+
const a = argv[i];
|
|
10
|
+
if (a === '--vault') vault = argv[++i];
|
|
11
|
+
else if (a.startsWith('--vault=')) vault = a.slice(8);
|
|
12
|
+
}
|
|
13
|
+
const base = vault || process.env.OBSIDIAN_VAULT_PATH;
|
|
14
|
+
if (!base) {
|
|
15
|
+
process.stderr.write('wendkeep spec: no vault. Pass --vault <path> or set OBSIDIAN_VAULT_PATH.\n');
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
return isAbsolute(base) ? base : resolve(process.cwd(), base);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function runSpec(argv) {
|
|
22
|
+
const [sub, ...rest] = argv;
|
|
23
|
+
const vaultBase = resolveVault(rest);
|
|
24
|
+
const specsDir = join(vaultBase, '07-Specs');
|
|
25
|
+
|
|
26
|
+
if (sub === 'list') {
|
|
27
|
+
let files = [];
|
|
28
|
+
try { files = readdirSync(specsDir).filter((f) => f.endsWith('.md') && f !== 'README.md'); } catch { /* sem specs */ }
|
|
29
|
+
if (!files.length) { process.stdout.write('specs: (nenhuma)\n'); process.exit(0); }
|
|
30
|
+
for (const f of files) {
|
|
31
|
+
const md = readFileSync(join(specsDir, f), 'utf8');
|
|
32
|
+
const n = parseRequirements(md).length;
|
|
33
|
+
const upd = md.match(/> Atualizado por .* em (\d{4}-\d{2}-\d{2})/);
|
|
34
|
+
process.stdout.write(`${f.replace(/\.md$/, '')}: ${n} requisito(s)${upd ? ` (atualizado ${upd[1]})` : ''}\n`);
|
|
35
|
+
}
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (sub === 'show') {
|
|
40
|
+
const cap = rest.find((a, i) => !a.startsWith('-') && rest[i - 1] !== '--vault');
|
|
41
|
+
if (!cap) { process.stderr.write('wendkeep spec show: missing <capability>\n'); process.exit(2); }
|
|
42
|
+
let md;
|
|
43
|
+
try { md = readFileSync(join(specsDir, `${cap}.md`), 'utf8'); }
|
|
44
|
+
catch { process.stderr.write(`wendkeep spec show: not found: ${cap}\n`); process.exit(2); }
|
|
45
|
+
const reqs = parseRequirements(md);
|
|
46
|
+
process.stdout.write(`${cap}: ${reqs.length} requisito(s)\n`);
|
|
47
|
+
for (const r of reqs) process.stdout.write(` ${r.id ? `${r.id} — ` : ''}${r.name}\n`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
process.stderr.write(`wendkeep spec: unknown subcommand "${sub}". Known: list, show.\n`);
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|