novahiz 0.3.4 → 0.3.6

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.
Files changed (44) hide show
  1. package/README.md +98 -64
  2. package/adapters/claude/agent/novahiz.md +23 -0
  3. package/adapters/opencode/instructions.md +3 -3
  4. package/adapters/opencode/novahiz.ts +29 -8
  5. package/catalog/categories.json +60 -1
  6. package/catalog/providers.json +2 -2
  7. package/dist/cli.js +9 -3
  8. package/dist/commands/doctor.js +457 -15
  9. package/dist/commands/hook.js +204 -0
  10. package/dist/commands/init.js +53 -2
  11. package/dist/gate-repair.js +5 -2
  12. package/dist/hook.js +176 -0
  13. package/dist/impeccable.js +17 -0
  14. package/dist/memory.js +17 -1
  15. package/dist/spec.js +39 -7
  16. package/docs/CONFIGURATION.md +2 -2
  17. package/docs/HARNESSES.md +25 -6
  18. package/docs/INSTALL.md +3 -3
  19. package/docs/PLUGIN.md +9 -1
  20. package/docs/PROVIDERS.md +1 -1
  21. package/docs/ROADMAPS.md +6 -4
  22. package/install/bootstrap.mjs +4 -3
  23. package/install/hooks.mjs +171 -0
  24. package/install/install.mjs +174 -29
  25. package/install/lib.mjs +7 -1
  26. package/install/prompt.mjs +30 -0
  27. package/install/uninstall.mjs +1 -0
  28. package/novahiz.config.example.json +1 -1
  29. package/package.json +3 -1
  30. package/skills/memory/SKILL.md +1 -1
  31. package/skills/novahiz-converge/SKILL.md +10 -0
  32. package/skills/novahiz-init/SKILL.md +6 -0
  33. package/skills/novahiz-memory/SKILL.md +1 -1
  34. package/skills/novahiz-release/SKILL.md +59 -0
  35. package/src/cli.ts +9 -3
  36. package/src/commands/doctor.ts +473 -15
  37. package/src/commands/hook.ts +238 -0
  38. package/src/commands/init.ts +56 -2
  39. package/src/gate-repair.ts +5 -2
  40. package/src/gate.ts +1 -1
  41. package/src/hook.ts +203 -0
  42. package/src/impeccable.ts +18 -0
  43. package/src/memory.ts +19 -1
  44. package/src/spec.ts +38 -7
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Novahiz: Agent Governance Toolkit
2
2
 
3
- > **Zero-dependency enforcement layer for AI coding agents** — classifies prompts, assigns execution roadmaps, blocks unsafe edits, and injects session-level skills, all deterministically without model calls.
3
+ > **Zero-dependency enforcement layer for AI coding agents** — classifies prompts, assigns execution roadmaps, blocks unsafe edits until the right skills are loaded, and persists decisions across sessions — all deterministically, without model calls.
4
4
 
5
- 17 categories, 95 skills, 11 gate rules, 7 MCP providers — all deterministic, all local, all JSON.
5
+ 17 categories, 96 skills, 11 gate rules, 7 MCP providers — all deterministic, all local, all JSON.
6
6
 
7
7
  ```
8
8
  ┌─────────────────────────────────────────────────────────────────────┐
@@ -18,41 +18,22 @@
18
18
 
19
19
  ---
20
20
 
21
- ## What it does
21
+ ## How a session runs
22
+
23
+ 1. **Classify** — every prompt is scored against 17 categories (deterministic keyword matching, no model call). The result carries up to three categories, a primary one, a confidence, a **tier** (`trivial` / `lite` / `full`), and the skills the gate will expect.
24
+ 2. **Roadmap** — the primary category selects an ordered roadmap, and the plugin injects its checklist into the session: the agent walks plan → clarify → tasks → analyse → implement → converge instead of improvising an order.
25
+ 3. **Gate on every write** — `edit` / `write` / `patch` / `bash` / `shell` calls are checked against file class, active rules, roadmap skills and content (placeholder tokens): **allow** or **block**, locally, with no model call in the decision path.
26
+ 4. **Auto-repair, not a dead end** — a block names the exact missing skills and the retry rule: load each one, retry the same call once. A skill absent from the installed index is reported, never enforced. `NOVAHIZ_GATE=off` is the only escape hatch, and it is loud.
27
+ 5. **Verify and converge** — roadmaps end in `verify` steps that require proof; `novahiz-converge` grades the code against the original request and turns every remainder into a traceable ledger step.
28
+ 6. **Persist** — decisions, root causes and next steps survive the session through the memory layer (below), and `novahiz report` closes the loop with a session summary.
22
29
 
23
30
  ```
24
- ┌──────────────────────────────────────────────────────────────────────────┐
25
- │ HOW Novahiz WORKS │
26
- │ │
27
- │ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────────────┐ │
28
- │ │ USER │───▶│ CLASSIFY │───▶│ INJECT │───▶│ MODEL │ │
29
- │ │ PROMPT │ │ │ │ ENFORCE │ │ RESPONSE │ │
30
- │ └──────────┘ │ keywords │ │ block + │ └──────┬───────┘ │
31
- │ │ priority │ │ roadmap │ │ │
32
- │ │ roadmap │ │ ledger │ ▼ │
33
- │ └────────────┘ └──────────┘ ┌──────────────┐ │
34
- │ │ │ TOOL CALL │ │
35
- │ │ │ (edit/write) │ │
36
- │ │ └──────┬───────┘ │
37
- │ │ │ │
38
- │ │ ┌────────────┐ │ │
39
- │ └────────▶│ GATE │◀───────────┘ │
40
- │ │ │ │
41
- │ │ file path │ │
42
- │ │ skills │ │
43
- │ │ content │ │
44
- │ └─────┬──────┘ │
45
- │ │ │
46
- │ ▼ │
47
- │ ┌──────────┐ │
48
- │ │ allow / │ │
49
- │ │ BLOCK │ │
50
- │ └──────────┘ │
51
- │ │
52
- └──────────────────────────────────────────────────────────────────────────┘
31
+ PROMPT ─▶ CLASSIFY ─▶ ROADMAP ─▶ work ─▶ GATE ─▶ allow ─▶ VERIFY ─▶ MEMORY
32
+ │ ▲ │
33
+ └─ tier, skills ────│ └─ block: load named skills, retry once
53
34
  ```
54
35
 
55
- **17 categories**, **95 skills**, **11 gate rules**, **7 MCP providers** — all deterministic, all local, all JSON.
36
+ **17 categories**, **96 skills**, **11 gate rules**, **7 MCP providers** — all deterministic, all local, all JSON.
56
37
 
57
38
  ---
58
39
 
@@ -61,14 +42,15 @@
61
42
  ### Option 1 — One-liner (recommended)
62
43
 
63
44
  ```bash
64
- npm install -g Novahiz
45
+ npm install -g novahiz
46
+ novahiz-install --yes
65
47
  ```
66
48
 
67
- This installs Novahiz globally and auto-configures opencode (skills, plugin, MCP servers, config). Then verify:
49
+ `npm install -g novahiz` installs the CLI. On npm 11+, lifecycle scripts are gated behind an allow-scripts confirmation, so configuration is an explicit second step: `novahiz-install` asks which harnesses to configure (opencode and Claude Code when their config is present, Codex too; `--yes` accepts the detected defaults, `--harness claude` forces a list) — skills, plugin/agent, hooks, MCP servers, config. Then verify:
68
50
 
69
51
  ```bash
70
- npx Novahiz doctor # 12 health checks
71
- npx Novahiz classify "fix the auth bug"
52
+ novahiz doctor # health checks — 13 base, 17 with Claude Code, 19 with impeccable
53
+ novahiz classify "fix the auth bug"
72
54
  ```
73
55
 
74
56
  ### Option 2 — From source
@@ -76,14 +58,14 @@ npx Novahiz classify "fix the auth bug"
76
58
  ```bash
77
59
  git clone https://github.com/novahiz/novahiz.git
78
60
  cd novahiz
79
- npm install && npm run build
80
- node ./install/install.mjs
61
+ npm install # `prepare` typechecks and builds dist/
62
+ node ./install/install.mjs # add --yes to accept the detected defaults
81
63
 
82
64
  # Verify
83
- npx Novahiz doctor
65
+ node ./dist/cli.js doctor
84
66
  ```
85
67
 
86
- > Requires **Node.js >= 22.18**. The installer auto-installs opencode if it's missing.
68
+ > Requires **Node.js >= 22.18**. The installer auto-installs the CLI of each selected harness when it is entirely missing (`opencode-ai`, `@anthropic-ai/claude-code`, `@openai/codex`), non-blocking.
87
69
 
88
70
  ---
89
71
 
@@ -169,7 +151,13 @@ flowchart TD
169
151
  | R13-design-craft | A design-ui prompt or a style file (css/scss/less/html) | novahiz-humanizer, ui-slop-remover, ui-craft-rules |
170
152
  | R14-impeccable | A design-ui prompt or a style file (css/scss/less/html) | impeccable |
171
153
 
172
- `novahiz-humanizer` and `ui-slop-remover` are required only on frontend design tasks (R13); `impeccable` loads the same way (R14) so critique, audit and polish playbooks stay reachable.
154
+ `novahiz-humanizer` and `ui-slop-remover` are required only on frontend design tasks (R13); `impeccable` loads the same way (R14) so the shape, critique, audit, harden and polish playbooks stay reachable, and the design-ui roadmap carries a deterministic `impeccable detect` verify step before ship. `novahiz init` and `novahiz doctor` report the impeccable `PRODUCT.md` / `DESIGN.md` context files as advisory rows.
155
+
156
+ ### Auto-repair
157
+
158
+ A block is never a dead end. The refusal carries an AUTO-REPAIR recipe: load every skill it names with the skill loader, then retry the exact same call once — no alternate tool, no shell write, no editing around it. If the same skills are reported missing again, the load did not register: run `novahiz doctor`, report honestly, and stop. The only sanctioned bypass is `NOVAHIZ_GATE=off` (see Configuration), declared out loud.
159
+
160
+ A required skill missing from the installed index is never enforced silently: the gate appends `required skill not in index, not enforced — run novahiz sync to realign` to its reasons instead of blocking forever. An unreadable index makes the gate stricter, never laxer.
173
161
 
174
162
  ---
175
163
 
@@ -177,6 +165,21 @@ flowchart TD
177
165
 
178
166
  Each category has an ordered execution roadmap. The gate enforces non-optional `skill` steps.
179
167
 
168
+ ### The six-stage pipeline
169
+
170
+ Eight categories (`code`, `debug`, `browser`, `design-ui`, `database-supabase`, `planning`, `devops`, `data`) share one pipeline — and stages 1–4 write no application file, they produce a plan and decisions:
171
+
172
+ | # | Stage | Skill | Produces |
173
+ |---|-------|-------|----------|
174
+ | 1 | Plan | `novahiz-plan` | direction, scope, dependency order, slicing strategy, risks |
175
+ | 2 | Clarify | `novahiz-clarify` | the open questions, answered, and the decisions they freeze |
176
+ | 3 | Tasks | `novahiz-task` | atomic tasks, each with acceptance criteria and proof |
177
+ | 4 | Analyse | `novahiz-analyse` | the files and symbols that carry the logic, and the unknowns |
178
+ | 5 | Implement | `novahiz-implement` | increments that leave the system working |
179
+ | 6 | Converge | `novahiz-converge` | the gap between intent and code, as traceable remaining tasks |
180
+
181
+ Clarify sends the work back to plan when an answer changes the architecture; converge sends it back to tasks when it finds a gap. `flutter` and `expo` keep the same six stages and insert their own quality skills around implement. The classifier's **tier** filters the rest: `trivial` runs almost nothing, `lite` keeps implement and converge, `full` walks the whole roadmap. Full reference: [docs/ROADMAPS.md](docs/ROADMAPS.md).
182
+
180
183
  ```mermaid
181
184
  flowchart LR
182
185
  subgraph "Feature (code)"
@@ -240,9 +243,39 @@ flowchart TD
240
243
 
241
244
  ---
242
245
 
246
+ ## Memory
247
+
248
+ Session memory is a two-layer system: a bounded, machine-readable workspace, and a human-readable notebook that is dual-written.
249
+
250
+ | Layer | Where | Behavior |
251
+ |-------|-------|----------|
252
+ | Session slots | `project-memory/` under the project root | `index.json` + fixed-size slots, compact → archive → rotate |
253
+ | Notebook | `MEMORY.md` + Obsidian vault page | dual-write at task end; the vault folder comes from `_meta/routing.md` |
254
+
255
+ ### Session slots — `project-memory/`
256
+
257
+ - Lives under the project root: `index.json` plus `slots/`, and `novahiz init` seeds it with a baseline slot.
258
+ - Slots are fixed-size (**8000 chars / 200 lines**): a full slot is compacted, archived, and replaced — memory stays bounded no matter how long the project runs.
259
+ - MCP tools operate it: `memory_init`, `memory_list`, `memory_get`, `memory_write` (appends and rotates), `memory_rebuild` (reindexes from the markdown).
260
+ - This is where decisions, root causes and next steps go when a complex task ends.
261
+ - `novahiz doctor` checks both the memory root and the memory tools.
262
+
263
+ ### Dual-write — `MEMORY.md` + vault
264
+
265
+ The `novahiz-memory` skill writes the closing state of a task in two places at once:
266
+
267
+ | Medium | Destination |
268
+ |--------|-------------|
269
+ | Project | `MEMORY.md` at the project root — what works now, what changed, what stays open |
270
+ | Vault | an Obsidian page — folder chosen **only** by the `_meta/routing.md` routing table |
271
+
272
+ Rules: mandatory frontmatter (title, category, tags, sources, created, updated, summary), `[[wikilinks]]` from the taxonomy, and enrich the existing page instead of creating a second one. Ambiguous routing → ask, never guess.
273
+
274
+ ---
275
+
243
276
  ## Installed skills
244
277
 
245
- Novahiz ships with 95 skills across all categories:
278
+ Novahiz ships with 96 skills across all categories:
246
279
 
247
280
  | Category | Skills | Purpose |
248
281
  |----------|--------|---------|
@@ -255,8 +288,9 @@ Novahiz ships with 95 skills across all categories:
255
288
  | `browser` | novahiz-browser, browser-session, novahiz-web-extract, ... | Web automation, screenshots, extraction |
256
289
  | `audit` | novahiz-security, package-risk-audit, llm-threat-review, ... | Security, compliance, vulnerability |
257
290
  | `expo` | expo-overview, expo-router, expo-module, expo-dev-client, ... | Expo / React Native: routes, native modules, builds |
291
+ | `devops` | eas-workflows, eas-app-stores, novahiz-release, ... | CI/CD, deploys, versioned releases |
258
292
 
259
- Run `npx Novahiz skills --all` to see the full list.
293
+ Run `npx novahiz skills --all` to see the full list.
260
294
 
261
295
  ---
262
296
 
@@ -274,7 +308,7 @@ Novahiz auto-registers external MCP servers based on the prompt category:
274
308
  | cron | `scheduler-mcp` (local venv clone) | MIT | devops |
275
309
  | dart | `dart mcp-server` (Dart SDK) | BSD-3-Clause | code, debug, design-ui, flutter |
276
310
 
277
- Skill packs (installed from official repos, never vendored): `flutter/agent-plugins` (25 skills), `dart-lang/skills` (15 skills), `expo/skills` (17 skills, the `expo-*` group only; `eas-*` paid services excluded), `pbakaus/impeccable` (1 skill, the upstream `impeccable` design skill). See [docs/PROVIDERS.md](docs/PROVIDERS.md).
311
+ Skill packs (installed from official repos, never vendored): `flutter/agent-plugins` (25 skills), `dart-lang/skills` (15 skills), `expo/skills` (19 skills, the `expo-*` group only; `eas-*` paid services excluded), `pbakaus/impeccable` (1 skill, the upstream `impeccable` design skill). See [docs/PROVIDERS.md](docs/PROVIDERS.md).
278
312
 
279
313
  Upstream repositories and full provenance for MCP providers and opencode plugins: [docs/PROVIDERS.md](docs/PROVIDERS.md), [docs/HARNESSES.md](docs/HARNESSES.md), [NOTICE.md](NOTICE.md).
280
314
 
@@ -287,10 +321,10 @@ Upstream repositories and full provenance for MCP providers and opencode plugins
287
321
  NOVAHIZ_GATE=off npx opencode
288
322
 
289
323
  # Override home directory
290
- NOVAHIZ_HOME=/path/to/Novahiz npx Novahiz doctor
324
+ NOVAHIZ_HOME=/path/to/novahiz npx novahiz doctor
291
325
 
292
326
  # Force node version
293
- NOVAHIZ_NODE=/usr/local/bin/node npx Novahiz doctor
327
+ NOVAHIZ_NODE=/usr/local/bin/node npx novahiz doctor
294
328
  ```
295
329
 
296
330
  See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for all options.
@@ -301,23 +335,23 @@ See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for all options.
301
335
 
302
336
  | Command | Purpose |
303
337
  |---------|---------|
304
- | `Novahiz init` | One-shot setup |
305
- | `Novahiz doctor` | 12-check health diagnostic |
306
- | `Novahiz status` | Current classification + gate state |
307
- | `Novahiz classify <text>` | Classify a prompt |
308
- | `Novahiz gate` | Check if an edit is allowed |
309
- | `Novahiz task new <title>` | Start a tracked task |
310
- | `Novahiz task status` | Task progress |
311
- | `Novahiz task done <id>` | Mark a todo complete |
312
- | `Novahiz report` | Session report |
313
- | `Novahiz skills` | List loaded or available skills |
314
- | `Novahiz catalog <query>` | Search the skill catalog |
315
- | `Novahiz roadmap` | Show execution roadmap |
316
- | `Novahiz dispatch` | Generate work packets |
317
- | `Novahiz sync` | Rebuild installed-skills index |
318
- | `Novahiz clean` | Remove old logs |
319
- | `Novahiz upgrade` | Pull latest + rebuild |
320
- | `Novahiz version` | Print version |
338
+ | `novahiz init` | One-shot setup |
339
+ | `novahiz doctor` | 13-check health diagnostic (14 with `--deep`; 17 with Claude Code, 19 with impeccable) |
340
+ | `novahiz status` | Current classification + gate state |
341
+ | `novahiz classify <text>` | Classify a prompt |
342
+ | `novahiz gate` | Check if an edit is allowed |
343
+ | `novahiz task new <title>` | Start a tracked task |
344
+ | `novahiz task status` | Task progress |
345
+ | `novahiz task done <id>` | Mark a todo complete |
346
+ | `novahiz report` | Session report |
347
+ | `novahiz skills` | List loaded or available skills |
348
+ | `novahiz catalog <query>` | Search the skill catalog |
349
+ | `novahiz roadmap` | Show execution roadmap |
350
+ | `novahiz dispatch` | Generate work packets |
351
+ | `novahiz sync` | Rebuild installed-skills index |
352
+ | `novahiz clean` | Remove old logs |
353
+ | `novahiz upgrade` | Pull latest + rebuild |
354
+ | `novahiz version` | Print version |
321
355
 
322
356
  See [docs/CLI.md](docs/CLI.md) for full reference.
323
357
 
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: novahiz
3
+ description: Novahiz deterministic workflow. Classifies the request, loads the required skills, then works under the Novahiz gate. Use for any multi-step task in a project that has a novahiz.config.json, or when the user asks for the Novahiz pipeline.
4
+ ---
5
+
6
+ You are Novahiz-Agent, running inside Claude Code with the Novahiz gate and MCP server active.
7
+
8
+ Work in this order for every request:
9
+
10
+ 1. Classify. Call the `novahiz_classify` MCP tool with the user request, or run `node <novahiz-home>/src/cli.ts classify "<request>"`. Read the returned categories and the required skills.
11
+ 2. Load skills. Call the `Skill` tool for every required skill before touching any file. That is the signal the Novahiz hook records; a skill loaded any other way does not count. If the `Skill` tool is unavailable, read `<novahiz-home>/skills/<name>/SKILL.md` instead, which the hook also accepts. The gate enforces this: edits, writes, and shell writes are blocked until the required skills are loaded.
12
+ 3. Plan. For anything beyond a trivial change, write the plan before the code.
13
+ 4. Execute. Prefer small reversible edits. Keep the architecture modular and maintainable.
14
+ 5. Verify. Run the relevant tests or commands. Report what you ran and what it returned.
15
+ 6. Report. State what changed, what is proven, what is uncertain, and the honest next step.
16
+
17
+ Rules:
18
+
19
+ - novahiz-humanizer, ui-slop-remover and ui-craft-rules are required only for frontend design tasks; impeccable covers the same design selectors.
20
+ - Load the Supabase skills for any Supabase work.
21
+ - Be honest. Avoid false good ideas. Zero simulation: never pretend to have run, tested, or verified something you did not.
22
+ - Criticize the request when it is inconsistent, ambiguous, risky, or suboptimal, and propose an alternative.
23
+ - If the gate blocks you, load every skill it names with the `Skill` tool, then retry the same call once. If the same skills are reported missing again, run `novahiz doctor`, report honestly, and stop. Never bypass the gate with a shell write or by editing around the block.
@@ -5,10 +5,10 @@
5
5
  Obsidian (`C:\Users\hiz\Documents\Novahiz`) is the user's second memory.
6
6
 
7
7
  ### Rules
8
- 1. When the user asks to **save / memorize / update obsidian memory**, load the `memory-save` skill and follow its procedure without exception.
8
+ 1. When the user asks to **save / memorize / update obsidian memory**, load the `memory` skill (alias for `novahiz-memory`) and follow its procedure without exception.
9
9
  2. Determine the target folder **only** from the `Novahiz\_meta\routing.md` table (source of truth). Never guess. When ambiguous, ask the user.
10
10
  3. Display the chosen path before writing.
11
- 4. Never write to `index.md`, `log.md`, `hot.md`, `.manifest.json`, `_meta/`, or `.obsidian/` **except through a dedicated maintenance skill** (wiki-ingest/wiki-lint/wiki-status for index/log/hot/manifest; graph-colorize for `.obsidian/graph.json`, with mandatory backup). The `memory-save` skill writes only to the targeted content page.
11
+ 4. Never write to `index.md`, `log.md`, `hot.md`, `.manifest.json`, `_meta/`, or `.obsidian/`. No installed skill covers maintenance of those files: if the user asks for it, do it step by step in front of them, with a backup first for anything under `.obsidian/`. The `memory` skill writes only to the targeted content page.
12
12
  5. Never create a root folder on your own. Every new category requires user agreement and an update to `routing.md`.
13
13
  6. Include mandatory frontmatter: `title, category, tags, sources, created, updated, summary`. Use tags from `_meta/taxonomy.md`.
14
14
  7. Link pages with `[[wikilinks]]`. Merge rather than duplicate.
@@ -29,7 +29,7 @@ Playwright uses a **persistent profile** that preserves data across sessions (co
29
29
  ## Behavioral & Quality Rules
30
30
 
31
31
  1. **Mandatory design skills on frontend design tasks** — `novahiz-humanizer`, `ui-slop-remover` and `ui-craft-rules` are required only on frontend design work (design-ui prompts and style files). Load them with `skill({name})` before any design edit. Outside design, they are not required by the gate. Browser tasks (navigation, search, extraction) proceed as direct actions without a roadmap.
32
- 2. **Impeccable after UI work** — `impeccable` is installed and required on the same design selectors (gate rule R14). Whenever a page, component, section, or screen design is created or substantially changed, run its critique systematically afterwards, an audit when the change warrants it (a11y, performance, responsive), and a polish pass before shipping. The design-ui roadmap carries optional `impeccable-critique`, `impeccable-audit`, and `impeccable-polish` steps for exactly this.
32
+ 2. **Impeccable after UI work** — `impeccable` is installed and required on the same design selectors (gate rule R14). Whenever a page, component, section, or screen design is created or substantially changed, run its critique systematically afterwards, an audit when the change warrants it (a11y, performance, responsive), harden errors and edge cases, and a polish pass before shipping; a deterministic `impeccable detect` scan backs the verify step. The design-ui roadmap carries optional `impeccable-shape`, `impeccable-critique`, `impeccable-audit`, `impeccable-harden`, `impeccable-polish`, and `impeccable-detect` steps for exactly this.
33
33
  3. **Supabase** — On any Supabase task (database, auth, RLS, Edge Functions, migrations, Storage, Realtime, CLI/MCP), load the `novahiz-supabase` and `novahiz-postgres` skills before acting.
34
34
  5. **Honesty and critical thinking** — Always be honest. Avoid false good ideas. Maintain critical thinking. **Zero simulation objective:** never claim to have executed, tested, or verified what was not. Explicitly report uncertainties and assumptions.
35
35
  6. **Propose next steps** — After completing a task, always honestly propose the relevant next step. Do not invent unnecessary work or mask failures.
@@ -399,19 +399,27 @@ const DISABLED = ["off", "0", "false", "no", "disabled"].includes(ESCAPE);
399
399
  const GATE_TOOLS = new Set(
400
400
  (Array.isArray(GATE.tools) && GATE.tools.length > 0
401
401
  ? GATE.tools
402
- // MINEUR#8: cron tools that carry/execute shell commands are gated too —
403
- // cron_add_command_task was a bash-gate bypass. Keep in sync with
404
- // DEFAULT_CONFIG.gate.tools (src/spec.ts) and novahiz.config.json.
405
- : ["edit", "write", "patch", "apply_patch", "bash", "shell", "cron_add_command_task", "cron_update_command_task", "cron_update_task", "cron_run_task_now"]
402
+ // MINEUR#8 + 0.3.6 hardening: every cron tool that carries, creates, or
403
+ // executes shell commands is gated — cron_add_command_task was a bash-gate
404
+ // bypass, and cron_add_task / cron_add_ai_task / cron_add_http_task each
405
+ // accept a `command` field (HTTP/AI tasks run shell_command type too).
406
+ // Keep in sync with DEFAULT_CONFIG.gate.tools (src/spec.ts), install/lib.mjs,
407
+ // novahiz.config.example.json, the live novahiz.config.json, and
408
+ // docs/CONFIGURATION.md.
409
+ : ["edit", "write", "patch", "apply_patch", "bash", "shell", "cron_add_command_task", "cron_add_task", "cron_add_ai_task", "cron_add_http_task", "cron_update_command_task", "cron_update_task", "cron_run_task_now"]
406
410
  ).map((tool) => tool.toLowerCase())
407
411
  );
408
412
 
409
413
  type RunResult = { status: number; stdout: string; stderr: string; spawnError?: string };
410
414
 
411
415
  // C1: timeout prevents a hung CLI from freezing the whole OpenCode process.
416
+ // Raised 10 s -> 30 s (audit 2026-09-25, MEDIUM): gate/CLI runs legitimately
417
+ // exceeded 10 s on 23-24/09 and the cap turned them into 14 spurious
418
+ // fail-closed refusals. Still bounded, so a truly hung CLI cannot freeze
419
+ // OpenCode for more than 30 s.
412
420
  // C2: maxBuffer caps output; oversized output is treated as a gate failure,
413
421
  // never as truncated-then-allowed.
414
- const RUN_TIMEOUT_MS = 10_000;
422
+ const RUN_TIMEOUT_MS = 30_000;
415
423
  const RUN_MAX_BUFFER = 1_048_576;
416
424
 
417
425
  function run(args: string[], input?: string): RunResult {
@@ -492,12 +500,15 @@ function buildRepairDirective(failure: GateFailure, attempt: number): string {
492
500
  }
493
501
 
494
502
  if (attempt <= 1) {
495
- const steps = missing.map((skill, index) => ` ${index + 1}. skill({name:"${skill}"})`).join("\n");
503
+ // Always add novahiz-gate: it explains why the block happened and how to
504
+ // satisfy the rule, which is what the agent needs before retrying.
505
+ const loads = missing.includes("novahiz-gate") ? missing : [...missing, "novahiz-gate"];
506
+ const steps = loads.map((skill, index) => ` ${index + 1}. skill({name:"${skill}"})`).join("\n");
496
507
  return [
497
508
  `${head} Missing skills: ${missing.join(", ")}.`,
498
509
  "AUTO-REPAIR — execute now, do not ask the user, do not stop:",
499
510
  steps,
500
- ` ${missing.length + 1}. Retry this exact ${failure.tool} call once, then continue the user's task where it left off.`,
511
+ ` ${loads.length + 1}. Retry this exact ${failure.tool} call once, then continue the user's task where it left off.`,
501
512
  "Never bypass the gate: no NOVAHIZ_GATE, no alternate tool, no shell write, no editing around the block."
502
513
  ].join("\n");
503
514
  }
@@ -723,7 +734,17 @@ export const NovahizPlugin: Plugin = async ({ client }) => {
723
734
  const sessionID = input.sessionID;
724
735
  if (!sessionID) return;
725
736
  const block = enforcementBySession.get(sessionID);
726
- if (block) output.system.push(block);
737
+ if (!block) return;
738
+ // The output array can still hold the block from a previous turn:
739
+ // pushing again duplicated "[Novahiz enforcement]" once per turn.
740
+ // Drop every stale copy, then inject exactly one fresh block.
741
+ for (let i = output.system.length - 1; i >= 0; i--) {
742
+ const entry = output.system[i];
743
+ if (typeof entry === "string" && entry.startsWith("[Novahiz enforcement]")) {
744
+ output.system.splice(i, 1);
745
+ }
746
+ }
747
+ output.system.push(block);
727
748
  } catch (error) {
728
749
  await log("warn", `system.transform hook failed: ${String(error).slice(0, 200)}`);
729
750
  }
@@ -507,6 +507,14 @@
507
507
  "id": "report",
508
508
  "label": "Report findings",
509
509
  "kind": "advisory"
510
+ },
511
+ {
512
+ "id": "audit",
513
+ "label": "Session compliance audit",
514
+ "kind": "advisory",
515
+ "requireSkills": [
516
+ "novahiz-audit"
517
+ ]
510
518
  }
511
519
  ]
512
520
  }
@@ -791,6 +799,14 @@
791
799
  "novahiz-task"
792
800
  ]
793
801
  },
802
+ {
803
+ "id": "tokens",
804
+ "label": "Design tokens (when a token system is involved)",
805
+ "kind": "advisory",
806
+ "requireSkills": [
807
+ "design-token-pipeline"
808
+ ]
809
+ },
794
810
  {
795
811
  "id": "design-craft",
796
812
  "label": "Apply design craft, humanizer and anti-slop pass",
@@ -802,6 +818,15 @@
802
818
  ],
803
819
  "optional": true
804
820
  },
821
+ {
822
+ "id": "impeccable-shape",
823
+ "label": "Impeccable shape (UX/UI design brief before code)",
824
+ "kind": "skill",
825
+ "requireSkills": [
826
+ "impeccable"
827
+ ],
828
+ "optional": true
829
+ },
805
830
  {
806
831
  "id": "implement",
807
832
  "label": "Implement",
@@ -828,6 +853,15 @@
828
853
  ],
829
854
  "optional": true
830
855
  },
856
+ {
857
+ "id": "impeccable-harden",
858
+ "label": "Impeccable harden (errors, i18n, edge cases before polish)",
859
+ "kind": "skill",
860
+ "requireSkills": [
861
+ "impeccable"
862
+ ],
863
+ "optional": true
864
+ },
831
865
  {
832
866
  "id": "impeccable-polish",
833
867
  "label": "Impeccable polish (final pass before ship)",
@@ -837,6 +871,15 @@
837
871
  ],
838
872
  "optional": true
839
873
  },
874
+ {
875
+ "id": "impeccable-detect",
876
+ "label": "Impeccable detector scan (deterministic UI check, exit 0 = clean)",
877
+ "kind": "verify",
878
+ "requireSkills": [
879
+ "impeccable"
880
+ ],
881
+ "optional": true
882
+ },
840
883
  {
841
884
  "id": "responsive",
842
885
  "label": "Verify responsive behavior",
@@ -1106,7 +1149,15 @@
1106
1149
  "nginx",
1107
1150
  "infra",
1108
1151
  "monitoring",
1109
- "eas"
1152
+ "eas",
1153
+ "release",
1154
+ "cut a release",
1155
+ "publish",
1156
+ "npm publish",
1157
+ "version bump",
1158
+ "bump version",
1159
+ "tag a version",
1160
+ "changelog"
1110
1161
  ],
1111
1162
  "defaultSkills": [
1112
1163
  "eas-workflows",
@@ -1168,6 +1219,14 @@
1168
1219
  "requireSkills": [
1169
1220
  "novahiz-converge"
1170
1221
  ]
1222
+ },
1223
+ {
1224
+ "id": "release",
1225
+ "label": "Ship a release",
1226
+ "kind": "advisory",
1227
+ "requireSkills": [
1228
+ "novahiz-release"
1229
+ ]
1171
1230
  }
1172
1231
  ]
1173
1232
  }
@@ -90,11 +90,11 @@
90
90
  "id": "expo-skills",
91
91
  "label": "Expo agent skills",
92
92
  "kind": "skill",
93
- "install": ["npx", "-y", "skills@1.7.0", "add", "expo/skills", "--skill", "expo-overview", "expo-project-structure", "expo-router", "expo-animation", "expo-native-ui", "expo-design-system", "expo-ui", "expo-data-fetching", "expo-dom", "expo-web-to-native", "expo-module", "expo-brownfield", "expo-dev-client", "expo-examples", "expo-app-clip", "expo-upgrade", "expo-skill-feedback", "-g", "-a", "opencode", "-y"],
93
+ "install": ["npx", "-y", "skills@1.7.0", "add", "expo/skills", "--skill", "expo-overview", "expo-project-structure", "expo-router", "expo-animation", "expo-native-ui", "expo-design-system", "expo-ui", "expo-data-fetching", "expo-dom", "expo-web-to-native", "expo-module", "expo-migrate-module", "expo-brownfield", "expo-dev-client", "expo-examples", "expo-app-clip", "expo-upgrade", "expo-skill-eval", "expo-skill-feedback", "-g", "-a", "opencode", "-y"],
94
94
  "source": "https://github.com/expo/skills",
95
95
  "license": "MIT",
96
96
  "categories": ["code", "expo"],
97
- "purpose": "17 official Expo framework skills (expo-* group); the 7 eas-* paid-service skills are excluded. Installed to ~/.agents/skills; when git clone fails, fetch the tarball from codeload.github.com and copy the skill folders."
97
+ "purpose": "19 official Expo framework skills (expo-* group); the 7 eas-* paid-service skills are excluded. Installed to ~/.agents/skills; when git clone fails, fetch the tarball from codeload.github.com and copy the skill folders."
98
98
  },
99
99
  {
100
100
  "id": "impeccable",
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { parse, print, flagOn } from "./commands/context.js";
6
6
  import { expandHome } from "./spec.js";
7
7
  import { commandCheck, commandSync, commandClassify, commandSkills, commandCategories, commandRules, commandSessionLoad, commandSessionState, commandCatalog, commandRoadmap, commandStep, commandProviders, commandDeps, commandDispatch } from "./commands/inspect.js";
8
8
  import { commandGate } from "./commands/gate.js";
9
+ import { commandHook } from "./commands/hook.js";
9
10
  import { commandReport } from "./commands/report.js";
10
11
  import { commandTask } from "./commands/task.js";
11
12
  import { commandClean } from "./commands/clean.js";
@@ -39,7 +40,7 @@ function usage() {
39
40
  "init Initialize Novahiz in the current project",
40
41
  "setup Install Novahiz (config, skills, catalog)",
41
42
  "autodocs [--flush] Sync docs and memory after major project changes",
42
- "doctor Check that everything works",
43
+ "doctor [--deep] Check that everything works (--deep also probes each MCP server live)",
43
44
  "status Show current classification and gate state",
44
45
  "task new <title> Start a new tracked task",
45
46
  "task status Show task progress",
@@ -59,6 +60,7 @@ function usage() {
59
60
  "Advanced (for power users and adapters):",
60
61
  " classify <text> Classify a prompt",
61
62
  " gate --tool <t> --file <f> Check if an edit is allowed",
63
+ " hook [--harness h] [--event e] Claude/Codex tool-use gate hook",
62
64
  " skills [--category c] List loaded skills",
63
65
  " catalog <query> Search the skill catalog",
64
66
  " roadmap --category c Show execution roadmap",
@@ -130,7 +132,9 @@ function runUpgrade(parsed) {
130
132
  runSync();
131
133
  process.stdout.write("\nUpgraded! Restart opencode to apply changes.\n");
132
134
  }
133
- function main(argv) {
135
+ // Async so `doctor --deep` can await its MCP probes; every other command
136
+ // still returns void, which await handles transparently.
137
+ async function main(argv) {
134
138
  const parsed = parse(argv);
135
139
  if (typeof parsed.flags.home === "string" && parsed.flags.home.length > 0) {
136
140
  process.env.NOVAHIZ_HOME = resolve(expandHome(parsed.flags.home));
@@ -175,6 +179,8 @@ function main(argv) {
175
179
  return commandClassify(parsed);
176
180
  case "gate":
177
181
  return commandGate(parsed);
182
+ case "hook":
183
+ return commandHook(parsed);
178
184
  case "skills":
179
185
  return commandSkills(parsed);
180
186
  case "categories":
@@ -209,7 +215,7 @@ function main(argv) {
209
215
  }
210
216
  }
211
217
  try {
212
- main(process.argv.slice(2));
218
+ await main(process.argv.slice(2));
213
219
  }
214
220
  catch (error) {
215
221
  const message = error instanceof Error ? error.message : String(error);