novahiz 0.5.2 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/README.md +4 -4
  3. package/adapters/opencode/commands/novahiz-upgrade.md +13 -0
  4. package/adapters/opencode/instructions.md +21 -9
  5. package/adapters/opencode/novahiz-plugin.ts +173 -31
  6. package/catalog/categories.json +14 -0
  7. package/catalog/providers.json +25 -0
  8. package/catalog/rules.json +19 -0
  9. package/catalog/vault-structure.json +366 -0
  10. package/dist/cli.js +19 -39
  11. package/dist/commands/docs.js +55 -0
  12. package/dist/commands/gate.js +54 -0
  13. package/dist/commands/second-memory.js +1116 -67
  14. package/dist/commands/stitch.js +298 -0
  15. package/dist/commands/upgrade.js +161 -0
  16. package/dist/gate.js +85 -1
  17. package/dist/spec.js +28 -1
  18. package/install/install.mjs +148 -16
  19. package/install/ui.d.mts +35 -0
  20. package/install/ui.mjs +246 -0
  21. package/mcp/novahiz-docs/index.mjs +63 -0
  22. package/mcp/novahiz-docs/src/query.ts +1 -1
  23. package/mcp/stitch-template/recommandation.md +14 -0
  24. package/package.json +1 -1
  25. package/skills/novahiz-second-memory/SKILL.md +189 -277
  26. package/skills/novahiz-second-memory/references/arborescence.md +131 -0
  27. package/skills/novahiz-second-memory/references/templates.md +153 -0
  28. package/skills/novahiz-stitch-fidelity/SKILL.md +113 -0
  29. package/skills/novahiz-stitch-fidelity/defaults/verify.config.defaults.json +29 -0
  30. package/skills/novahiz-stitch-fidelity/references/capture-recipes.md +74 -0
  31. package/skills/novahiz-stitch-fidelity/references/judgment-grid.md +67 -0
  32. package/skills/novahiz-stitch-fidelity/scripts/stitch_capture.mjs +598 -0
  33. package/skills/novahiz-stitch-fidelity/scripts/stitch_composite.mjs +271 -0
  34. package/skills/stitch-design-fidelity/SKILL.md +140 -0
  35. package/skills/stitch-design-fidelity/evals.json +23 -0
  36. package/skills/stitch-design-fidelity/references/anti-ai-patterns.md +115 -0
  37. package/skills/stitch-design-fidelity/references/design-system.md +192 -0
  38. package/skills/stitch-design-fidelity/references/prompt-methodology.md +143 -0
  39. package/skills/stitch-design-fidelity/references/stitch-fidelity.md +65 -0
  40. package/skills/stitch-design-fidelity/references/stitch-mcp.md +100 -0
  41. package/skills/stitch-design-fidelity/scripts/validate-frontmatter.py +11 -0
  42. package/src/cli.ts +18 -39
  43. package/src/commands/docs.ts +62 -0
  44. package/src/commands/gate.ts +54 -0
  45. package/src/commands/second-memory.ts +1164 -66
  46. package/src/commands/stitch.ts +295 -0
  47. package/src/commands/upgrade.ts +174 -0
  48. package/src/gate.ts +77 -1
  49. package/src/spec.ts +51 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,93 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.7.0] - 2026-10-08
9
+
10
+ Feature release: the Obsidian vault becomes a first-class citizen of the
11
+ workflow, and the install ships a self-sufficient runtime — the MCP trio is
12
+ wired statically, the docs index is seeded, and the LSP section is declared.
13
+
14
+ ### Added
15
+
16
+ - **Vault auto-consult**: the plugin reads the `second-memory` vault during
17
+ session setup (`memory.auto.vault`), so decisions and next steps from the
18
+ vault reach the model without a manual read. `instructions.md` now states
19
+ the Obsidian/second-memory consultation rules (T2, T3).
20
+ - **`novahiz docs` command** (`src/commands/docs.ts`): `status` reports the
21
+ local documentation index per library, `ingest <id>|--all [--dry]` fills it
22
+ over the network. Backed by new CLI modes on the docs MCP itself
23
+ (`node mcp/novahiz-docs/index.mjs --ingest … | --status`) — `src/ingest.ts`
24
+ finally has an entry point (D1).
25
+ - **Docs index seed at install**: the installer fills the core bouquet
26
+ (react, nextjs, typescript, nodejs, tailwindcss, dart, flutter, expo) in a
27
+ non-blocking step; offline installs still succeed and the fill command stays
28
+ available. `read_docs` now names the command when a library is unindexed.
29
+ - **`novahiz second-memory search`** (T1) and **`doctor --apply` regenerates a
30
+ drifted `STRUCTURE.md`** (T6, `structureDrift`).
31
+ - **`stitchConfigured()`**: `novahiz init` recommends `novahiz stitch` only
32
+ when the API key is absent (T5); new `src/commands/stitch.ts`.
33
+ - **`novahiz upgrade`** command and the `/novahiz-upgrade` in-session command
34
+ (`src/commands/upgrade.ts`, `adapters/opencode/commands/novahiz-upgrade.md`).
35
+ - **Installer UI**: dedicated `install/ui.mjs` (+ `.d.mts`), plain/ASCII banner,
36
+ step layout — `tests/installer-ui.test.ts`.
37
+ - **Obsidian vault step (10/10)**: the installer creates the `second-memory`
38
+ vault structure and its ten community plugins when missing (T7).
39
+
40
+ ### Fixed
41
+
42
+ - **MCP trio wired statically (D2)**: `novahiz-core`, `novahiz-gate` and
43
+ `novahiz-scan` are written into the generated `opencode.jsonc`, and the
44
+ plugin transform registers them *before* the awaited `providers --mcp-json`
45
+ spawn — a transform landing after MCP resolution never binds servers (audit
46
+ 2026-10-08: no connection, no warning). The trio now connects on install;
47
+ the plugin skips ids already present, so no duplicates.
48
+ - **Plugin renamed `novahiz` → `novahiz-workflow`** (descriptive id in the
49
+ image of `novahiz-token-economy`), and the live plugin copy is resynced from
50
+ the source on install — the running session no longer lags the repo.
51
+ - **Gate tests are hermetic to `NOVAHIZ_GATE`**: an operator running
52
+ `novahiz gate off` no longer turns two MCP gate tests red — child processes
53
+ get a sanitized environment.
54
+ - **`tsconfig.build.json` restored**: `npm run prepare` referenced a file that
55
+ was missing from the working copy.
56
+
57
+ ### Changed
58
+
59
+ - **LSP declared**: the live opencode config carries an `lsp` object with five
60
+ servers verified present on the machine (dart, typescript, bash, yaml, json);
61
+ the generated config writes `lsp: true` (portable — no dead entries on user
62
+ machines). Verified against OpenCode v2.0.24: the config is accepted and
63
+ preserved (`config.get` echoes it), but V2 has no LSP runtime yet — no server
64
+ is started until upstream ships one.
65
+
66
+ ## [0.6.0] - 2026-10-06
67
+
68
+ Feature release: the gate learns project scope, and Stitch design projects
69
+ get a capture → composite → fidelity-verdict loop.
70
+
71
+ ### Added
72
+
73
+ - **`R16-stitch-fidelity` gate rule**: editing a UI file (`*.tsx`, `*.jsx`,
74
+ `*.vue`, `*.svelte`, `*.dart`, `*.css`, `*.scss`, `*.html`) now requires
75
+ the `novahiz-stitch-fidelity` skill — but only inside a project that
76
+ carries `stitch/**` references. This is the new `projectGlobs` selector
77
+ (`src/spec.ts`, `src/gate.ts`): nearest ancestor of the edited file
78
+ holding `.git`, `package.json` or `pubspec.yaml`, globs written from that
79
+ root. Projects without Stitch references are unaffected.
80
+ - **`novahiz-stitch-fidelity` skill**: `scripts/stitch_capture.mjs` (adb:
81
+ uiautomator dump, screencap, input, adb.autoStart), `scripts/stitch_composite.mjs`
82
+ (screenshot ↔ design overlay), the fidelity judgment grid, capture
83
+ recipes and a `verify.config.defaults.json`.
84
+ - **`stitch-fidelity` provider** (`kind: commands`): adb prerequisite with
85
+ per-OS bootstrap (winget / brew / apt), categories `design-ui`, `expo`,
86
+ `flutter`; `stitch_capture.mjs --check` reports the seven readiness items.
87
+ - Optional `stitch-verify` roadmap step (`kind: verify`) in two roadmaps.
88
+ - Tests: `tests/gate-stitch-fidelity.test.ts` — project-scoped enforcement.
89
+
90
+ ### Changed
91
+
92
+ - README headline and counts refreshed: 99 skills, 13 gate rules, 8 MCP
93
+ providers, version line at 0.6.0 (was advertising 96/11/7 and 0.4.1).
94
+
8
95
  ## [0.5.2] - 2026-10-05
9
96
 
10
97
  Maintenance release closing the three leftovers traced right after `0.5.1`:
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  > **Couche d'application sans dépendance pour les agents de code IA** — classe les prompts, attribue des roadmaps d'exécution, bloque les éditions non sûres tant que les bonnes skills ne sont pas chargées, et persiste les décisions d'une session à l'autre — tout cela de façon déterministe, sans appel de modèle.
4
4
 
5
- 17 catégories, 96 skills, 11 gate rules, 7 MCP providers — tout déterministe, tout local, tout JSON.
5
+ 17 catégories, 99 skills, 13 gate rules, 8 MCP providers — tout déterministe, tout local, tout JSON.
6
6
 
7
- > **Version 0.4.1** — publiée sur npm (`npm install -g --allow-scripts=novahiz novahiz` installe et configure tout : core, plugin, agent, skills packs et Playwright MCP). `CHANGELOG.md` et `scripts/` sont dans le tarball.
7
+ > **Version 0.6.0** — publiée sur npm (`npm install -g --allow-scripts=novahiz novahiz` installe et configure tout : core, plugin, agent, skills packs et Playwright MCP). `CHANGELOG.md` et `scripts/` sont dans le tarball.
8
8
 
9
9
  ```
10
10
  ┌──────────────────────────────────────────────────────────────────────────┐
@@ -35,7 +35,7 @@
35
35
  └─ tier, skills ──────│ └─ block : charger les skills nommées, retry une fois
36
36
  ```
37
37
 
38
- **17 catégories**, **96 skills**, **11 gate rules**, **7 MCP providers** — tout déterministe, tout local, tout JSON.
38
+ **17 catégories**, **99 skills**, **13 gate rules**, **8 MCP providers** — tout déterministe, tout local, tout JSON.
39
39
 
40
40
  ---
41
41
 
@@ -310,7 +310,7 @@ Deux pièces complémentaires : la session consomme moins sans perdre en puissan
310
310
 
311
311
  ## Skills installées
312
312
 
313
- Novahiz livre 98 skills couvrant toutes les catégories :
313
+ Novahiz livre 99 skills couvrant toutes les catégories :
314
314
 
315
315
  | Catégorie | Skills | Objectif |
316
316
  |-----------|--------|----------|
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Check for a new Novahiz version and update it via npm. Arguments: $ARGUMENTS
3
+ ---
4
+
5
+ 1. Run the version check. Prefer the global CLI when available: `novahiz upgrade`. If `novahiz` is not on PATH, locate the Novahiz home (`NOVAHIZ_HOME` if set, otherwise `~/.config/novahiz`) and run `node src/cli.ts upgrade` from there.
6
+
7
+ 2. If the output reports "up to date", confirm it in one sentence and stop.
8
+
9
+ 3. If an update is available, report the installed -> latest versions, then ask the user for explicit approval. Never run the update without that approval (skip this confirmation only when `$ARGUMENTS` contains `--yes`).
10
+
11
+ 4. After approval, run `novahiz upgrade --apply` (same CLI used in step 1). This runs `npm install -g novahiz@latest` for npm installs, or `git pull --ff-only` plus a catalog rebuild for source checkouts. Report the exact result, including any npm/git error verbatim — never claim success you did not observe.
12
+
13
+ 5. Finish by telling the user to restart OpenCode completely so the new version loads. Offer `novahiz doctor` as the follow-up check.
@@ -1,17 +1,29 @@
1
1
  # System Instructions
2
2
 
3
- ## Obsidian Memory
3
+ ## Obsidian Memory — the `second-memory` vault
4
4
 
5
- Obsidian (`C:\Users\hiz\Documents\Novahiz`) is the user's second memory.
5
+ The system's official vault is **`~/Documents/second-memory`** (`NOVAHIZ_SM_VAULT` overrides it for tests). It is created by the installer together with the plugins and skills: it is part of Novahiz itself, not an optional add-on. The old `~/Documents/Novahiz` path does not exist — never reference it.
6
+
7
+ ### Automatic consultation (already done for you)
8
+ The plugin auto-consults the vault (`memory.auto.vault`, enabled by default): every `every` prompts (default 3) it runs `novahiz second-memory search <prompt head> --k=3 --min-score=0.25 --json` and injects the top hits under `[Novahiz memory] vault second-memory auto-consulted`. Treat those hits as leads, not conclusions: open the referenced note with the read tool before relying on a snippet.
9
+
10
+ ### When to consult the vault proactively
11
+ Search it yourself (`novahiz second-memory search "<terms>"`, then read the note) when:
12
+ - the user asks what was decided or learned earlier (project, strategy, preference, decision);
13
+ - you are about to redo work that has likely already happened (design, migration, analysis, troubleshooting);
14
+ - the topic matches a vault domain (Trading, AI, Design, DevOps, Security, Business, Learning, Wiki, Projects, Journal);
15
+ - the user says « dans ma mémoire », « dans le vault », « comme on avait décidé », "second memory";
16
+ - an answer must survive this session.
17
+ Do **not** consult it for trivial edits, unrelated questions, or when the prompt already carries the needed context.
6
18
 
7
19
  ### Rules
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
- 2. Determine the target folder **only** from the `Novahiz\_meta\routing.md` table (source of truth). Never guess. When ambiguous, ask the user.
10
- 3. Display the chosen path before writing.
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
- 5. Never create a root folder on your own. Every new category requires user agreement and an update to `routing.md`.
13
- 6. Include mandatory frontmatter: `title, category, tags, sources, created, updated, summary`. Use tags from `_meta/taxonomy.md`.
14
- 7. Link pages with `[[wikilinks]]`. Merge rather than duplicate.
20
+ 1. When the user asks to **save / memorize / update** memory, load the `memory` skill (alias for `novahiz-memory`) and follow its procedure without exception. For vault-only writes and structure questions, load `novahiz-second-memory` instead: it owns the vault's fixed, predefined arborescence.
21
+ 2. Vault routing comes from the predefined structure (`catalog/vault-structure.json`, surfaced by the `novahiz-second-memory` skill) — never guess a location. When ambiguous, ask the user, and display the chosen path before writing.
22
+ 3. Search before you write: `novahiz second-memory search "<terms>"` — merge into an existing note rather than creating a duplicate. Link pages with `[[wikilinks]]`.
23
+ 4. Never write to `index.md`, `log.md`, `hot.md`, `.manifest.json`, `_meta/`, or `.obsidian/`. If the user asks for it, do it step by step in front of them, with a backup first for anything under `.obsidian/`.
24
+ 5. Never create a root domain on your own: every new domain requires user agreement and a structure update.
25
+ 6. Include mandatory frontmatter: `title, category, tags, sources, created, updated, summary`.
26
+ 7. Structure maintenance is CLI work: `novahiz second-memory [init|doctor|fix|sync|status]` (dry-run by default, `--apply` to write).
15
27
 
16
28
  ## Playwright Browser — Persistent Profile
17
29
 
@@ -397,10 +397,23 @@ type MemoryAutoReadConfig = {
397
397
  // sessions precedentes + resumes de pertinence compris. Defaut 1500.
398
398
  budgetTokens: number;
399
399
  };
400
+ // V-AUTO: consultation automatique du vault second-memory (vault Obsidian
401
+ // officiel du systeme, cree a l'installation). every = cadence en occurrences
402
+ // de prompt (1 = chaque prompt, defaut 3 = "de temps en temps"), budgetTokens
403
+ // = plafond d'injection du bloc vault (~4 chars/token). Miroir de
404
+ // MemoryAutoVaultConfig dans src/spec.ts.
405
+ type MemoryAutoVaultConfig = {
406
+ enabled: boolean;
407
+ k: number;
408
+ minScore: number;
409
+ every: number;
410
+ budgetTokens: number;
411
+ };
400
412
  type MemoryAutoConfig = {
401
413
  enabled: boolean;
402
414
  write: MemoryAutoWriteConfig;
403
415
  read: MemoryAutoReadConfig;
416
+ vault: MemoryAutoVaultConfig;
404
417
  };
405
418
  type NovahizConfig = { gate?: GateConfig; memory?: unknown };
406
419
 
@@ -442,7 +455,18 @@ export function resolveMemoryAuto(raw: unknown, envValue: string | undefined): M
442
455
  postCompaction: bool(readRaw.postCompaction, true),
443
456
  budgetTokens: inRange(readRaw.budgetTokens, 100, 4000) ? Math.trunc(readRaw.budgetTokens) : 1500
444
457
  };
445
- return { enabled: true, write, read };
458
+ // V-AUTO: vault second-memory — defauts full-on aussi (actif partout par
459
+ // defaut, comme write/read), kill-switch partage NOVAHIZ_MEM_AUTO + reset
460
+ // local memory.auto.vault.enabled=false. Miroir de src/spec.ts.
461
+ const vaultRaw = auto.vault && typeof auto.vault === "object" ? (auto.vault as Record<string, unknown>) : {};
462
+ const vault: MemoryAutoVaultConfig = {
463
+ enabled: bool(vaultRaw.enabled, true),
464
+ k: inRange(vaultRaw.k, 1, 10) ? Math.trunc(vaultRaw.k) : 3,
465
+ minScore: inRange(vaultRaw.minScore, 0, 1) ? vaultRaw.minScore : 0.25,
466
+ every: inRange(vaultRaw.every, 1, 100) ? Math.trunc(vaultRaw.every) : 3,
467
+ budgetTokens: inRange(vaultRaw.budgetTokens, 100, 2000) ? Math.trunc(vaultRaw.budgetTokens) : 600
468
+ };
469
+ return { enabled: true, write, read, vault };
446
470
  }
447
471
 
448
472
  function readConfig(): NovahizConfig {
@@ -826,6 +850,39 @@ export function mergeSummaryWindow(
826
850
  return { ids, details };
827
851
  }
828
852
 
853
+ // V-AUTO: vault officiel du systeme — cree par l'installateur a
854
+ // `~/Documents/second-memory` (NOVAHIZ_SM_VAULT pour tests/overrides).
855
+ export const VAULT_ROOT: string =
856
+ process.env.NOVAHIZ_SM_VAULT && process.env.NOVAHIZ_SM_VAULT.trim().length > 0
857
+ ? process.env.NOVAHIZ_SM_VAULT
858
+ : join(homedir(), "Documents", "second-memory");
859
+
860
+ type VaultHitLike = { rel?: unknown; title?: unknown; score?: unknown; snippet?: unknown };
861
+
862
+ // V-AUTO: formatage des hits `second-memory search --json` en lignes
863
+ // d'injection (une ligne chemin+titre+score, une ligne extrait tronque),
864
+ // filtres k / minScore. Exporte tel quel pour les tests (pure function).
865
+ export function buildVaultLines(
866
+ hits: readonly VaultHitLike[],
867
+ cfg: MemoryAutoVaultConfig
868
+ ): { injectedRels: string[]; details: string[] } {
869
+ const injectedRels: string[] = [];
870
+ const details: string[] = [];
871
+ for (const hit of hits) {
872
+ if (injectedRels.length >= cfg.k) break;
873
+ if (!hit || typeof hit.rel !== "string" || hit.rel.length === 0) continue;
874
+ if (typeof hit.score === "number" && hit.score < cfg.minScore) continue;
875
+ const title = typeof hit.title === "string" && hit.title.length > 0 ? hit.title : hit.rel;
876
+ const score = typeof hit.score === "number" ? hit.score.toFixed(2) : "-";
877
+ injectedRels.push(hit.rel);
878
+ details.push(`- ${hit.rel} [${score}] — ${title}`);
879
+ if (typeof hit.snippet === "string" && hit.snippet.trim().length > 0) {
880
+ details.push(` ${hit.snippet.replace(/\s+/g, " ").trim().slice(0, 220)}`);
881
+ }
882
+ }
883
+ return { injectedRels, details };
884
+ }
885
+
829
886
  // --- P3: heritage inter-sessions (injection au demarrage) -----------------
830
887
  // L'index porte id/title/updated/status/tags (SlotMeta, src/memory.ts); le
831
888
  // plugin installe ne resout pas ../../src/*, donc le type est re-declare ici.
@@ -1047,6 +1104,10 @@ async function setup(ctx: Context): Promise<Cleanup> {
1047
1104
  // chaque prompt, chunk de resumes courant (remplacement post-compaction)
1048
1105
  // et cles de directive T3 deja posees.
1049
1106
  const autoReadBySession = new Map<string, { ids: string[]; details: string[] }>();
1107
+ // V-AUTO: fenetre de notes vault re-injectee entre deux consultations, et
1108
+ // compteur de prompts pour la cadence memory.auto.vault.every.
1109
+ const vaultWindowBySession = new Map<string, { rels: string[]; details: string[] }>();
1110
+ const vaultCountBySession = new Map<string, number>();
1050
1111
  const lastPromptBySession = new Map<string, string>();
1051
1112
  const directivesBySession = new Map<string, string[]>();
1052
1113
  const summaryChunkBySession = new Map<string, string[]>();
@@ -1261,21 +1322,81 @@ async function setup(ctx: Context): Promise<Cleanup> {
1261
1322
  }
1262
1323
  };
1263
1324
 
1264
- // V1 `config` hook → MCP transform. The CLI is queried first: transform
1265
- // callbacks must stay synchronous and free of one-time side effects.
1266
- if (!DISABLED) {
1325
+ // V-AUTO: consultation automatique du vault second-memory. Aller simple via
1326
+ // la CLI (`second-memory search --json`) : le plugin installe ne peut pas
1327
+ // importer src/*, donc le ranking reel est celui de la CLI — une seule
1328
+ // source de verite pour l'agent et pour l'injection. Cadence every (defaut
1329
+ // 3) : le 1er prompt consulte toujours, la fenetre est re-emise entre deux
1330
+ // consultations pour que les notes restent dans le contexte.
1331
+ const buildVaultRead = async (
1332
+ sessionID: string,
1333
+ query: string
1334
+ ): Promise<{ lines: string[]; newCount: number }> => {
1335
+ if (!MEMORY_AUTO || !MEMORY_AUTO.vault.enabled) return { lines: [], newCount: 0 };
1267
1336
  try {
1268
- const providers = await run(["providers", "--mcp-json"]);
1269
- const entries: Record<string, unknown> = {};
1270
- if (providers.status === 0 && providers.stdout.trim().length > 0) {
1271
- try {
1272
- Object.assign(entries, JSON.parse(providers.stdout) as Record<string, unknown>);
1273
- } catch {
1274
- await log("warn", "Providers returned invalid JSON, MCP auto-register skipped");
1337
+ let state = vaultWindowBySession.get(sessionID) ?? { rels: [], details: [] };
1338
+ let newCount = 0;
1339
+ const n = (vaultCountBySession.get(sessionID) ?? 0) + 1;
1340
+ vaultCountBySession.set(sessionID, n);
1341
+ if ((n - 1) % MEMORY_AUTO.vault.every === 0) {
1342
+ if (!existsSync(VAULT_ROOT)) {
1343
+ if (n === 1) void log("info", `vault auto-consult skipped: ${VAULT_ROOT} absent`);
1344
+ } else {
1345
+ const call = await run([
1346
+ "second-memory",
1347
+ "search",
1348
+ autoReadQuery(query, 24),
1349
+ `--k=${MEMORY_AUTO.vault.k}`,
1350
+ `--min-score=${MEMORY_AUTO.vault.minScore}`,
1351
+ "--json"
1352
+ ]);
1353
+ if (call.status !== 0 || call.stdout.trim().length === 0) {
1354
+ void log("info", `vault auto-consult failed: ${call.stderr.slice(0, 200) || `exit ${call.status}`}`);
1355
+ } else {
1356
+ try {
1357
+ const parsed = JSON.parse(call.stdout) as { hits?: unknown };
1358
+ const hits = Array.isArray(parsed.hits) ? (parsed.hits as VaultHitLike[]) : [];
1359
+ const fresh = buildVaultLines(hits, MEMORY_AUTO.vault);
1360
+ if (fresh.injectedRels.length > 0) {
1361
+ newCount = fresh.injectedRels.length;
1362
+ state = { rels: fresh.injectedRels, details: fresh.details };
1363
+ } else {
1364
+ void log(
1365
+ "info",
1366
+ `vault auto-consult: ${hits.length} hit(s), 0 injected (minScore ${MEMORY_AUTO.vault.minScore})`
1367
+ );
1368
+ }
1369
+ } catch {
1370
+ void log("info", "vault auto-consult: invalid JSON from second-memory search");
1371
+ }
1372
+ }
1275
1373
  }
1276
- } else if (providers.status !== 0) {
1277
- await log("warn", `Providers command failed (exit ${providers.status}), MCP auto-register skipped`);
1278
1374
  }
1375
+ if (state.rels.length === 0) return { lines: [], newCount };
1376
+ vaultWindowBySession.set(sessionID, state);
1377
+ const kept = applyInjectionBudget(state.details, MEMORY_AUTO.vault.budgetTokens * 4);
1378
+ const lines = [
1379
+ `[Novahiz memory] vault second-memory auto-consulted (${state.rels.length}/${MEMORY_AUTO.vault.k}):`,
1380
+ ...kept,
1381
+ `Vault: ${VAULT_ROOT} — read a full note with the read tool on <vault>/<rel>; re-search: novahiz second-memory search "<terms>".`
1382
+ ];
1383
+ return { lines, newCount };
1384
+ } catch (error) {
1385
+ void log("info", `vault auto-consult failed: ${String(error).slice(0, 200)}`);
1386
+ return { lines: [], newCount: 0 };
1387
+ }
1388
+ };
1389
+
1390
+ // V1 `config` hook → MCP transform. Audit 2026-10-08 : le transform partait
1391
+ // APRES l'attente du spawn `providers --mcp-json` (~0,5-1s) et arrivait trop
1392
+ // tard — aucun serveur lié, aucun avertissement au log. Le trio runtime part
1393
+ // donc en premier, avant toute await ; le catalogue compose en second
1394
+ // transform (les transforms se cumulent dans leur domaine). Les callbacks
1395
+ // restent synchrones et sans effet de bord un seul. En pratique la config
1396
+ // statique de l'installateur porte deja les entrees : editor.get(id) les
1397
+ // saute, sans doublon.
1398
+ if (!DISABLED) {
1399
+ try {
1279
1400
  await ctx.mcp.transform((editor) => {
1280
1401
  // A server configured by the user wins over the catalog registration.
1281
1402
  // Audit 2026-09-25 (P2): novahiz-scan joined the hard-coded trio — when
@@ -1290,23 +1411,38 @@ async function setup(ctx: Context): Promise<Cleanup> {
1290
1411
  if (!editor.get("novahiz-scan")) {
1291
1412
  editor.set("novahiz-scan", { type: "local", command: [NODE, join(HOME, "mcp", "argus", "src", "cli.mjs")], timeout: MCP_TIMEOUT });
1292
1413
  }
1293
- for (const [id, raw] of Object.entries(entries)) {
1294
- if (editor.get(id)) continue;
1295
- const entry = raw as { type?: unknown; command?: unknown; url?: unknown; enabled?: unknown };
1296
- // V2 replaced `enabled` with `disabled`: an explicit `enabled: false`
1297
- // is carried across instead of silently flipping the server on.
1298
- const disabled = entry.enabled === false ? { disabled: true as const } : {};
1299
- if (entry.type === "remote" && typeof entry.url === "string") {
1300
- editor.set(id, { type: "remote", url: entry.url, timeout: MCP_TIMEOUT, ...disabled });
1301
- } else if (
1302
- entry.type === "local" &&
1303
- Array.isArray(entry.command) &&
1304
- entry.command.every((part) => typeof part === "string")
1305
- ) {
1306
- editor.set(id, { type: "local", command: entry.command as string[], timeout: MCP_TIMEOUT, ...disabled });
1307
- }
1308
- }
1309
1414
  });
1415
+ const providers = await run(["providers", "--mcp-json"]);
1416
+ const entries: Record<string, unknown> = {};
1417
+ if (providers.status === 0 && providers.stdout.trim().length > 0) {
1418
+ try {
1419
+ Object.assign(entries, JSON.parse(providers.stdout) as Record<string, unknown>);
1420
+ } catch {
1421
+ await log("warn", "Providers returned invalid JSON, MCP auto-register skipped");
1422
+ }
1423
+ } else if (providers.status !== 0) {
1424
+ await log("warn", `Providers command failed (exit ${providers.status}), MCP auto-register skipped`);
1425
+ }
1426
+ if (Object.keys(entries).length > 0) {
1427
+ await ctx.mcp.transform((editor) => {
1428
+ for (const [id, raw] of Object.entries(entries)) {
1429
+ if (editor.get(id)) continue;
1430
+ const entry = raw as { type?: unknown; command?: unknown; url?: unknown; enabled?: unknown };
1431
+ // V2 replaced `enabled` with `disabled`: an explicit `enabled: false`
1432
+ // is carried across instead of silently flipping the server on.
1433
+ const disabled = entry.enabled === false ? { disabled: true as const } : {};
1434
+ if (entry.type === "remote" && typeof entry.url === "string") {
1435
+ editor.set(id, { type: "remote", url: entry.url, timeout: MCP_TIMEOUT, ...disabled });
1436
+ } else if (
1437
+ entry.type === "local" &&
1438
+ Array.isArray(entry.command) &&
1439
+ entry.command.every((part) => typeof part === "string")
1440
+ ) {
1441
+ editor.set(id, { type: "local", command: entry.command as string[], timeout: MCP_TIMEOUT, ...disabled });
1442
+ }
1443
+ }
1444
+ });
1445
+ }
1310
1446
  } catch (error) {
1311
1447
  await log("warn", `MCP registration failed: ${String(error).slice(0, 200)}`);
1312
1448
  }
@@ -1454,7 +1590,13 @@ async function setup(ctx: Context): Promise<Cleanup> {
1454
1590
  // chemin lecture a tourne, y compris quand il ressort vide.
1455
1591
  await log("info", `memory auto-read prompt: ${auto.lines.length} line(s), ${auto.newCount} new`);
1456
1592
  lines.push(...auto.lines);
1457
- summaryChunkBySession.set(sessionID, auto.lines);
1593
+ // V-AUTO: bloc vault second-memory — meme contrat que les resumes :
1594
+ // reconstruit a chaque prompt, fenetre persistante entre deux
1595
+ // consultations, log inconditionnel pour prouver la consultation.
1596
+ const vault = await buildVaultRead(sessionID, text);
1597
+ await log("info", `vault auto-consult prompt: ${vault.lines.length} line(s), ${vault.newCount} new`);
1598
+ lines.push(...vault.lines);
1599
+ summaryChunkBySession.set(sessionID, [...auto.lines, ...vault.lines]);
1458
1600
  const directives = directivesBySession.get(sessionID) ?? [];
1459
1601
  lines.push(...directives);
1460
1602
  }
@@ -1712,5 +1854,5 @@ async function setup(ctx: Context): Promise<Cleanup> {
1712
1854
  return () => events.abort();
1713
1855
  }
1714
1856
 
1715
- const novahizPlugin: Plugin = { id: "novahiz", setup };
1857
+ const novahizPlugin: Plugin = { id: "novahiz-workflow", setup };
1716
1858
  export default novahizPlugin;
@@ -198,6 +198,13 @@
198
198
  "kind": "skill",
199
199
  "requireSkills": ["dart-add-unit-test"]
200
200
  },
201
+ {
202
+ "id": "stitch-verify",
203
+ "label": "Verify screens against Stitch references (projects carrying stitch/**)",
204
+ "kind": "verify",
205
+ "requireSkills": ["novahiz-stitch-fidelity"],
206
+ "optional": true
207
+ },
201
208
  {
202
209
  "id": "impeccable-critique",
203
210
  "label": "Impeccable critique (UI work)",
@@ -307,6 +314,13 @@
307
314
  "kind": "edit",
308
315
  "requireSkills": ["novahiz-implement"]
309
316
  },
317
+ {
318
+ "id": "stitch-verify",
319
+ "label": "Verify screens against Stitch references (projects carrying stitch/**)",
320
+ "kind": "verify",
321
+ "requireSkills": ["novahiz-stitch-fidelity"],
322
+ "optional": true
323
+ },
310
324
  {
311
325
  "id": "converge",
312
326
  "label": "Converge",
@@ -108,6 +108,31 @@
108
108
  "categories": ["audit"],
109
109
  "purpose": "Local stdio static-analysis server (zero npm dependency): scans files and directories with local security rules (SQL injection, XSS, command injection, hardcoded secrets, insecure deserialization), creates custom rules behind a ReDoS gate, exports json/sarif/csv/html. Original clean-room implementation for the audit category."
110
110
  },
111
+ {
112
+ "id": "stitch-fidelity",
113
+ "label": "Stitch fidelity capture (adb + Android emulator)",
114
+ "kind": "commands",
115
+ "requires": ["adb"],
116
+ "bootstrap": {
117
+ "win32": [
118
+ "winget install --id Google.PlatformTools",
119
+ "winget install --id Google.AndroidStudio"
120
+ ],
121
+ "darwin": [
122
+ "brew install --cask android-platform-tools",
123
+ "brew install --cask android-commandlinetools"
124
+ ],
125
+ "linux": [
126
+ "sudo apt-get install -y adb",
127
+ "sudo apt-get install -y android-sdk"
128
+ ],
129
+ "default": ["install Android platform-tools (adb) and the Android emulator on PATH"]
130
+ },
131
+ "source": "https://github.com/novahiz/novahiz",
132
+ "license": "Apache-2.0",
133
+ "categories": ["design-ui", "expo", "flutter"],
134
+ "purpose": "Prerequisite for skill novahiz-stitch-fidelity: `adb` drives the capture (uiautomator dump, screencap, input). `emulator` is only needed to auto-start a device when nothing is online (adb.autoStart); `stitch_capture.mjs --check` reports all seven readiness items."
135
+ },
111
136
  {
112
137
  "id": "novahiz-gate",
113
138
  "label": "novahiz-gate",
@@ -156,5 +156,24 @@
156
156
  ]
157
157
  },
158
158
  "require": ["token-economy"]
159
+ },
160
+ {
161
+ "id": "R16-stitch-fidelity",
162
+ "description": "Load novahiz-stitch-fidelity before editing the UI of a project that carries Stitch references (stitch/**): capture, composite and fidelity verdict must be part of that loop.",
163
+ "when": {
164
+ "match": "all",
165
+ "pathGlobs": [
166
+ "**/*.tsx",
167
+ "**/*.jsx",
168
+ "**/*.vue",
169
+ "**/*.svelte",
170
+ "**/*.dart",
171
+ "**/*.css",
172
+ "**/*.scss",
173
+ "**/*.html"
174
+ ],
175
+ "projectGlobs": ["stitch/**"]
176
+ },
177
+ "require": ["novahiz-stitch-fidelity"]
159
178
  }
160
179
  ]