novahiz 0.7.0 → 0.8.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 CHANGED
@@ -5,6 +5,72 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.8.0] - 2026-10-09
9
+
10
+ Minor release: total Impeccable coverage on the design roadmap, a repair
11
+ command for the optional skill packs, and an honest diagnosis of why packs go
12
+ missing on fresh installs.
13
+
14
+ ### Added
15
+
16
+ - **Design-ui roadmap: total Impeccable coverage** (`catalog/categories.json`):
17
+ 23 `kind: skill` steps carry the whole official playbook — init, document,
18
+ shape, critique, then the targeted fixes (layout, typeset, colorize,
19
+ animate, adapt, bolder, quieter, clarify, delight, distill, extract,
20
+ onboard, optimize, overdrive, generate), then audit, harden, polish and the
21
+ detect → verify loop. Labels match the official descriptions published on
22
+ https://impeccable.style/cheatsheet. Every step is `optional: true` (a
23
+ checklist the agent follows, gate enforcement stays with R14);
24
+ `impeccable-live` is excluded on purpose (live browser session, not a
25
+ code path) and the deprecated `craft` alias is dropped. Rollback:
26
+ `categories.json.pre-impeccable-full.bak`.
27
+ - **`/novahiz-skills-update` command**
28
+ (`adapters/opencode/commands/novahiz-skills-update.md`): reinstalls every
29
+ optional skill pack — dart, flutter, expo, impeccable — cleanly from their
30
+ official providers (`novahiz deps --install --yes`), refreshes the
31
+ Impeccable CLI/engine from npm, resyncs the skill index and verifies with
32
+ doctor. The one command a community member runs when packs are missing.
33
+
34
+ ### Changed
35
+
36
+ - **`doctor` now names the git root cause** (`src/commands/doctor.ts`): a new
37
+ non-blocking `git (skill packs)` row — the official skills CLI behind every
38
+ `providers.json` `kind: skill` entry (dart-lang/skills, flutter/agent-plugins,
39
+ expo/skills, pbakaus/impeccable) requires git; without it those installs fail
40
+ silently on a fresh setup, which is exactly how a machine ends up with 33
41
+ pack skills missing and no explanation. Present → "installable"; missing →
42
+ install hint pointing at `/novahiz-skills-update`.
43
+ - **`Referenced skills` hint** (`src/commands/doctor.ts`): the
44
+ "novahiz-install --dart-skills --flutter-skills" hint now points at
45
+ `/novahiz-skills-update, or novahiz deps --install`, the actual repair path.
46
+
47
+ ## [0.7.1] - 2026-10-08
48
+
49
+ Patch release: two installation-level repairs found while cutting 0.7.0 — the
50
+ memory MCP degraded on machines without a home catalog, and the docs reading
51
+ layer collapsed under the SQLite planner of node 22.
52
+
53
+ ### Fixed
54
+
55
+ - **`packageRoot()` walk-up** (`src/spec.ts`): the community-path assumption
56
+ (entry script exactly one directory below the package root) held for `bin/`
57
+ and `src/` but not `mcp/<server>/index.mjs` — two levels down it resolved to
58
+ `mcp/catalog/` (absent), so on any machine whose home has no catalog yet
59
+ (CI, fresh `npm install -g novahiz` before the installer ran) every memory
60
+ call returned `failed: true` « Invalid or missing catalog file ». The
61
+ resolver now walks up to the nearest `package.json`, preferring
62
+ `name: "novahiz"`, pinned by a regression test that reproduces the exact CI
63
+ condition (`tests/memory-p5.test.ts`, `NOVAHIZ_HOME` empty) — red CI on
64
+ Linux since 0.7.0, dead memory tools on the documented community path.
65
+ - **Docs search plan pinned with CROSS JOIN** (`mcp/novahiz-docs/src/store.ts`):
66
+ SQLite 3.51.3 (node:sqlite of node 22.23.x) reorders the plain JOIN — it
67
+ drives from `chunks(library)` and re-evaluates the full FTS MATCH for every
68
+ chunk of the library (50 × ~6 ms ≈ 315 ms instead of ~14 ms), collapsing
69
+ the 100 ms reading budget (742-1085 ms observed on CI runners). SQLite
70
+ never reorders a CROSS JOIN (`optoverview` 7.1.2, `lang_select` 2.2), so
71
+ the plan is now pinned on every runtime: 315.8 → 13.3 ms on 3.51.3,
72
+ unchanged 14.1 ms on 3.53.3 — same result set, only the plan changes.
73
+
8
74
  ## [0.7.0] - 2026-10-08
9
75
 
10
76
  Feature release: the Obsidian vault becomes a first-class citizen of the
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Reinstall every optional skill pack (dart, flutter, expo, impeccable) cleanly from their official providers. Arguments: $ARGUMENTS
3
+ ---
4
+
5
+ 1. Locate the Novahiz home: `NOVAHIZ_HOME` if set, otherwise `~/.config/novahiz`.
6
+
7
+ 2. Confirm `git` is on the PATH (`git --version`). The official skills CLI
8
+ behind every skill provider needs it; without it the install fails
9
+ silently. If git is missing, tell the user to install it first
10
+ (Windows: `winget install Git.Git`) and stop.
11
+
12
+ 3. Reinstall/update every provider from the official registry
13
+ (`catalog/providers.json` - dart-lang/skills, flutter/agent-plugins,
14
+ expo/skills, pbakaus/impeccable, plus the provider MCP servers):
15
+
16
+ `node <home>/src/cli.ts deps --install --yes`
17
+
18
+ 4. Refresh the Impeccable CLI and engine from npm (official package):
19
+
20
+ `npm install -g impeccable`
21
+ `impeccable install --global --yes --force`
22
+
23
+ 5. Realign the skill index:
24
+
25
+ `node <home>/src/cli.ts sync`
26
+
27
+ 6. Verify with `node <home>/src/cli.ts doctor`: the `Referenced skills` row
28
+ must show every pack present (the 7 `eas-*` paid-service skills stay
29
+ excluded by design). Report what was reinstalled.
@@ -832,9 +832,27 @@
832
832
  ],
833
833
  "optional": true
834
834
  },
835
+ {
836
+ "id": "impeccable-init",
837
+ "label": "Impeccable init (give Impeccable the project context: PRODUCT.md audience and goals)",
838
+ "kind": "skill",
839
+ "requireSkills": [
840
+ "impeccable"
841
+ ],
842
+ "optional": true
843
+ },
844
+ {
845
+ "id": "impeccable-document",
846
+ "label": "Impeccable document (record your visual system in DESIGN.md so future work can follow it)",
847
+ "kind": "skill",
848
+ "requireSkills": [
849
+ "impeccable"
850
+ ],
851
+ "optional": true
852
+ },
835
853
  {
836
854
  "id": "impeccable-shape",
837
- "label": "Impeccable shape (UX/UI design brief before code)",
855
+ "label": "Impeccable shape (turn an idea into a design brief you can build from)",
838
856
  "kind": "skill",
839
857
  "requireSkills": [
840
858
  "impeccable"
@@ -851,7 +869,142 @@
851
869
  },
852
870
  {
853
871
  "id": "impeccable-critique",
854
- "label": "Impeccable critique (systematic after UI work)",
872
+ "label": "Impeccable critique (find what holds the design back and what to improve first)",
873
+ "kind": "skill",
874
+ "requireSkills": [
875
+ "impeccable"
876
+ ],
877
+ "optional": true
878
+ },
879
+ {
880
+ "id": "impeccable-layout",
881
+ "label": "Impeccable layout (arrange the page so people know where to look and what belongs together)",
882
+ "kind": "skill",
883
+ "requireSkills": [
884
+ "impeccable"
885
+ ],
886
+ "optional": true
887
+ },
888
+ {
889
+ "id": "impeccable-typeset",
890
+ "label": "Impeccable typeset (make text easier to read, scan and recognize)",
891
+ "kind": "skill",
892
+ "requireSkills": [
893
+ "impeccable"
894
+ ],
895
+ "optional": true
896
+ },
897
+ {
898
+ "id": "impeccable-colorize",
899
+ "label": "Impeccable colorize (color clarifies actions, states and the design's character)",
900
+ "kind": "skill",
901
+ "requireSkills": [
902
+ "impeccable"
903
+ ],
904
+ "optional": true
905
+ },
906
+ {
907
+ "id": "impeccable-animate",
908
+ "label": "Impeccable animate (motion clarifies changes, relationships and feedback)",
909
+ "kind": "skill",
910
+ "requireSkills": [
911
+ "impeccable"
912
+ ],
913
+ "optional": true
914
+ },
915
+ {
916
+ "id": "impeccable-adapt",
917
+ "label": "Impeccable adapt (make the design work on a different screen, device or platform)",
918
+ "kind": "skill",
919
+ "requireSkills": [
920
+ "impeccable"
921
+ ],
922
+ "optional": true
923
+ },
924
+ {
925
+ "id": "impeccable-bolder",
926
+ "label": "Impeccable bolder (more presence, still true to the design)",
927
+ "kind": "skill",
928
+ "requireSkills": [
929
+ "impeccable"
930
+ ],
931
+ "optional": true
932
+ },
933
+ {
934
+ "id": "impeccable-quieter",
935
+ "label": "Impeccable quieter (reduce visual noise, keep the personality)",
936
+ "kind": "skill",
937
+ "requireSkills": [
938
+ "impeccable"
939
+ ],
940
+ "optional": true
941
+ },
942
+ {
943
+ "id": "impeccable-clarify",
944
+ "label": "Impeccable clarify (help people understand what happened and what to do next)",
945
+ "kind": "skill",
946
+ "requireSkills": [
947
+ "impeccable"
948
+ ],
949
+ "optional": true
950
+ },
951
+ {
952
+ "id": "impeccable-delight",
953
+ "label": "Impeccable delight (meaningful moments: surprising, satisfying, unmistakably yours)",
954
+ "kind": "skill",
955
+ "requireSkills": [
956
+ "impeccable"
957
+ ],
958
+ "optional": true
959
+ },
960
+ {
961
+ "id": "impeccable-distill",
962
+ "label": "Impeccable distill (reduce clutter so the important things are easier to find)",
963
+ "kind": "skill",
964
+ "requireSkills": [
965
+ "impeccable"
966
+ ],
967
+ "optional": true
968
+ },
969
+ {
970
+ "id": "impeccable-extract",
971
+ "label": "Impeccable extract (turn repeated patterns into shared components and styles)",
972
+ "kind": "skill",
973
+ "requireSkills": [
974
+ "impeccable"
975
+ ],
976
+ "optional": true
977
+ },
978
+ {
979
+ "id": "impeccable-onboard",
980
+ "label": "Impeccable onboard (help new users reach their first useful result)",
981
+ "kind": "skill",
982
+ "requireSkills": [
983
+ "impeccable"
984
+ ],
985
+ "optional": true
986
+ },
987
+ {
988
+ "id": "impeccable-optimize",
989
+ "label": "Impeccable optimize (find what slows the interface, fix it, measure the result)",
990
+ "kind": "skill",
991
+ "requireSkills": [
992
+ "impeccable"
993
+ ],
994
+ "optional": true
995
+ },
996
+ {
997
+ "id": "impeccable-overdrive",
998
+ "label": "Impeccable overdrive (push past the expected, make it feel extraordinary)",
999
+ "kind": "skill",
1000
+ "requireSkills": [
1001
+ "impeccable"
1002
+ ],
1003
+ "optional": true
1004
+ },
1005
+ {
1006
+ "id": "impeccable-generate",
1007
+ "label": "Impeccable generate (name an element, compare variations, keep the favorite)",
855
1008
  "kind": "skill",
856
1009
  "requireSkills": [
857
1010
  "impeccable"
@@ -860,7 +1013,7 @@
860
1013
  },
861
1014
  {
862
1015
  "id": "impeccable-audit",
863
- "label": "Impeccable audit (a11y/perf/responsive, when the UI warrants it)",
1016
+ "label": "Impeccable audit (find implementation problems and prioritize the fixes)",
864
1017
  "kind": "skill",
865
1018
  "requireSkills": [
866
1019
  "impeccable"
@@ -869,7 +1022,7 @@
869
1022
  },
870
1023
  {
871
1024
  "id": "impeccable-harden",
872
- "label": "Impeccable harden (errors, i18n, edge cases before polish)",
1025
+ "label": "Impeccable harden (keep the interface usable when data, connections or actions go wrong)",
873
1026
  "kind": "skill",
874
1027
  "requireSkills": [
875
1028
  "impeccable"
@@ -878,7 +1031,7 @@
878
1031
  },
879
1032
  {
880
1033
  "id": "impeccable-polish",
881
- "label": "Impeccable polish (final pass before ship)",
1034
+ "label": "Impeccable polish (make the page feel finished and work better)",
882
1035
  "kind": "skill",
883
1036
  "requireSkills": [
884
1037
  "impeccable"
@@ -469,7 +469,7 @@ export function referencedSkillsCheck(spec, indexSkills, packageRootDir) {
469
469
  ? `${referenced.length} present`
470
470
  : absentShipped.length > 0
471
471
  ? `missing from index: ${absent.join(", ")}`
472
- : `${referenced.length - absent.length} present, ${absentPack} pack skills not installed (novahiz-install --dart-skills --flutter-skills)`,
472
+ : `${referenced.length - absent.length} present, ${absentPack} pack skills not installed (run /novahiz-skills-update, or novahiz deps --install)`,
473
473
  blocking: absentShipped.length > 0
474
474
  };
475
475
  }
@@ -537,6 +537,20 @@ export async function commandDoctor(parsed) {
537
537
  : "all present",
538
538
  blocking: missingCli.length > 0
539
539
  });
540
+ // git backs the official skills CLI behind every providers.json kind:skill
541
+ // entry (dart, flutter, expo, impeccable). Without git those installs fail
542
+ // silently on a fresh setup, leaving the pack skills missing with no
543
+ // explanation - surface the root cause here as a non-blocking warning.
544
+ const hasGit = hasCommand("git");
545
+ checks.push({
546
+ id: "git",
547
+ label: "git (skill packs)",
548
+ ok: hasGit,
549
+ detail: hasGit
550
+ ? "present - provider skill packs (dart, flutter, expo, impeccable) installable"
551
+ : "missing - provider skill packs cannot install; run: winget install Git.Git (or install git, then rerun /novahiz-skills-update)",
552
+ blocking: false
553
+ });
540
554
  // Two probes, one per live enforcement layer, both deterministic against the
541
555
  // current ruleset (the old single probe fed README.md with empty categories,
542
556
  // which matches no rule since the prose content rules were removed):
package/dist/spec.js CHANGED
@@ -282,15 +282,40 @@ function readCatalog(path) {
282
282
  // gates lifecycle scripts, and the postinstall only previews anyway), so a
283
283
  // brand-new machine has no catalog in its home yet — every command used to die
284
284
  // with ENOENT right after the documented install. The package ships its own
285
- // catalog/: fall back to it before failing. The entry script (bin/novahiz.mjs,
286
- // dist/cli.js or src/cli.ts) always sits exactly one directory below the
287
- // package root, so dirname(argv[1])/.. resolves in every supported shape.
285
+ // catalog/: fall back to it before failing.
286
+ // Audit 2026-10-08: the old one-level assumption (dirname(argv[1])/..) held for
287
+ // bin/ and src/ but NOT for mcp/<server>/index.mjs — two levels down it pointed
288
+ // at mcp/catalog/ (absent), so on any machine without a home catalog (CI,
289
+ // fresh npm-global) the memory MCP degraded on every call: red CI on Linux,
290
+ // memory_write/search dead for the community path. Walk up instead: the package
291
+ // root is the nearest ancestor carrying a package.json, preferring name
292
+ // "novahiz"; a stray nested manifest is kept only as a last resort.
288
293
  // Exported: doctor resolves the shipped skills/ the same way.
289
294
  export function packageRoot() {
290
295
  const entry = process.argv[1];
291
296
  if (!entry || entry.length === 0)
292
297
  return null;
293
- return join(dirname(resolve(entry)), "..");
298
+ let dir = dirname(resolve(entry));
299
+ let loose = null;
300
+ for (let depth = 0; depth < 6; depth += 1) {
301
+ const manifest = join(dir, "package.json");
302
+ if (existsSync(manifest)) {
303
+ try {
304
+ const name = JSON.parse(readFileSync(manifest, "utf8")).name;
305
+ if (name === "novahiz")
306
+ return dir;
307
+ loose ??= dir;
308
+ }
309
+ catch {
310
+ // manifest illisible: on remonte quand meme.
311
+ }
312
+ }
313
+ const parent = dirname(dir);
314
+ if (parent === dir)
315
+ break;
316
+ dir = parent;
317
+ }
318
+ return loose;
294
319
  }
295
320
  // The home copy always wins when present, because that is what the installer
296
321
  // refreshes.
@@ -148,10 +148,19 @@ export function search(db: DatabaseSync, query: string, options: SearchOptions =
148
148
  if (match === null) return [];
149
149
  const limit = Math.min(Math.max(Math.trunc(options.limit ?? 5), 1), 50);
150
150
  const filterLibrary = options.library !== undefined && options.library.length > 0;
151
+ // CROSS JOIN et non JOIN : SQLite ne reordonne JAMAIS les operandes d'un
152
+ // CROSS JOIN (optoverview 7.1.2 / lang_select 2.2 — le mecanisme documente
153
+ // pour imposer l'ordre des boucles imbriquees). Avec un JOIN simple, le
154
+ // planner de SQLite 3.51.3 (node 22.23.x) sort chunks (index library) en
155
+ // boucle externe et re-evalue le MATCH complet pour chaque chunk : 50 x
156
+ // ~6 ms = ~315 ms au lieu de ~14 ms — le budget de lecture de 100 ms
157
+ // s'ecroule, CI rouge sur node 22. Le resultat est identique (jointure
158
+ // interne), seul le plan change. Bench du 2026-10-08 : 315.8 -> 13.3 ms
159
+ // sous 3.51.3, 14.0 -> 14.1 ms sous 3.53.3.
151
160
  const sql =
152
161
  "SELECT c.library, c.version, c.source_url, c.license, c.fetched_at, c.heading_path, c.ord, " +
153
162
  "snippet(chunks_fts, 0, '[', ']', '…', 16) AS snippet, bm25(chunks_fts) AS score " +
154
- "FROM chunks_fts JOIN chunks c ON c.id = chunks_fts.rowid " +
163
+ "FROM chunks_fts CROSS JOIN chunks c ON c.id = chunks_fts.rowid " +
155
164
  `WHERE chunks_fts MATCH ?${filterLibrary ? " AND c.library = ?" : ""} ` +
156
165
  "ORDER BY bm25(chunks_fts) LIMIT ?";
157
166
  const params: Array<string | number> = [match];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novahiz",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Enforce AI agent skills before every file edit: prompt classifier, skill catalog, and pre-edit gate.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -469,7 +469,7 @@ export function referencedSkillsCheck(
469
469
  ? `${referenced.length} present`
470
470
  : absentShipped.length > 0
471
471
  ? `missing from index: ${absent.join(", ")}`
472
- : `${referenced.length - absent.length} present, ${absentPack} pack skills not installed (novahiz-install --dart-skills --flutter-skills)`,
472
+ : `${referenced.length - absent.length} present, ${absentPack} pack skills not installed (run /novahiz-skills-update, or novahiz deps --install)`,
473
473
  blocking: absentShipped.length > 0
474
474
  };
475
475
  }
@@ -544,6 +544,21 @@ export async function commandDoctor(parsed: Parsed): Promise<void> {
544
544
  blocking: missingCli.length > 0
545
545
  });
546
546
 
547
+ // git backs the official skills CLI behind every providers.json kind:skill
548
+ // entry (dart, flutter, expo, impeccable). Without git those installs fail
549
+ // silently on a fresh setup, leaving the pack skills missing with no
550
+ // explanation - surface the root cause here as a non-blocking warning.
551
+ const hasGit = hasCommand("git");
552
+ checks.push({
553
+ id: "git",
554
+ label: "git (skill packs)",
555
+ ok: hasGit,
556
+ detail: hasGit
557
+ ? "present - provider skill packs (dart, flutter, expo, impeccable) installable"
558
+ : "missing - provider skill packs cannot install; run: winget install Git.Git (or install git, then rerun /novahiz-skills-update)",
559
+ blocking: false
560
+ });
561
+
547
562
  // Two probes, one per live enforcement layer, both deterministic against the
548
563
  // current ruleset (the old single probe fed README.md with empty categories,
549
564
  // which matches no rule since the prose content rules were removed):
package/src/spec.ts CHANGED
@@ -474,14 +474,36 @@ function readCatalog<T>(path: string): T {
474
474
  // gates lifecycle scripts, and the postinstall only previews anyway), so a
475
475
  // brand-new machine has no catalog in its home yet — every command used to die
476
476
  // with ENOENT right after the documented install. The package ships its own
477
- // catalog/: fall back to it before failing. The entry script (bin/novahiz.mjs,
478
- // dist/cli.js or src/cli.ts) always sits exactly one directory below the
479
- // package root, so dirname(argv[1])/.. resolves in every supported shape.
477
+ // catalog/: fall back to it before failing.
478
+ // Audit 2026-10-08: the old one-level assumption (dirname(argv[1])/..) held for
479
+ // bin/ and src/ but NOT for mcp/<server>/index.mjs — two levels down it pointed
480
+ // at mcp/catalog/ (absent), so on any machine without a home catalog (CI,
481
+ // fresh npm-global) the memory MCP degraded on every call: red CI on Linux,
482
+ // memory_write/search dead for the community path. Walk up instead: the package
483
+ // root is the nearest ancestor carrying a package.json, preferring name
484
+ // "novahiz"; a stray nested manifest is kept only as a last resort.
480
485
  // Exported: doctor resolves the shipped skills/ the same way.
481
486
  export function packageRoot(): string | null {
482
487
  const entry = process.argv[1];
483
488
  if (!entry || entry.length === 0) return null;
484
- return join(dirname(resolve(entry)), "..");
489
+ let dir = dirname(resolve(entry));
490
+ let loose: string | null = null;
491
+ for (let depth = 0; depth < 6; depth += 1) {
492
+ const manifest = join(dir, "package.json");
493
+ if (existsSync(manifest)) {
494
+ try {
495
+ const name = (JSON.parse(readFileSync(manifest, "utf8")) as { name?: string }).name;
496
+ if (name === "novahiz") return dir;
497
+ loose ??= dir;
498
+ } catch {
499
+ // manifest illisible: on remonte quand meme.
500
+ }
501
+ }
502
+ const parent = dirname(dir);
503
+ if (parent === dir) break;
504
+ dir = parent;
505
+ }
506
+ return loose;
485
507
  }
486
508
 
487
509
  // The home copy always wins when present, because that is what the installer