novahiz 0.9.9 → 0.11.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 +103 -0
- package/README.md +22 -17
- package/adapters/opencode/agent/novahiz.md +1 -0
- package/adapters/opencode/novahiz-plugin.ts +11 -3
- package/bin/novahiz.mjs +41 -15
- package/catalog/categories.json +209 -2
- package/catalog/rules.json +2 -35
- package/dist/classify.js +5 -1
- package/dist/cli.js +6 -4
- package/dist/commands/doctor.js +3 -1
- package/dist/commands/gate.js +36 -1
- package/dist/commands/inspect.js +73 -6
- package/dist/complexity.js +12 -6
- package/dist/db.js +8 -2
- package/dist/deps.js +47 -3
- package/dist/exec.js +1 -1
- package/dist/gate.js +16 -18
- package/dist/prompt-rewriter.js +1 -0
- package/dist/readonly.js +119 -0
- package/dist/session.js +63 -0
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/CATALOG.md +1 -1
- package/docs/CLASSIFICATION.md +16 -3
- package/docs/GATE.md +7 -15
- package/docs/INSTALL.md +28 -3
- package/docs/PROVIDERS.md +3 -3
- package/docs/ROADMAPS.md +2 -2
- package/docs/RULES.md +5 -5
- package/install/install.mjs +103 -29
- package/install/lib.d.mts +29 -1
- package/install/lib.mjs +67 -11
- package/package.json +4 -5
- package/skills/novahiz-capture/SKILL.md +123 -0
- package/skills/novahiz-capture/scripts/shot-spec.mjs +113 -0
- package/skills/novahiz-gate/SKILL.md +2 -2
- package/skills/novahiz-planner/SKILL.md +1 -1
- package/src/classify.ts +5 -1
- package/src/cli.ts +6 -4
- package/src/commands/doctor.ts +3 -1
- package/src/commands/gate.ts +36 -1
- package/src/commands/inspect.ts +74 -9
- package/src/complexity.ts +12 -6
- package/src/db.ts +8 -2
- package/src/deps.ts +47 -3
- package/src/exec.ts +1 -1
- package/src/gate.ts +16 -16
- package/src/prompt-rewriter.ts +1 -0
- package/src/readonly.ts +123 -0
- package/src/session.ts +99 -0
- package/bin/veille-mensuelle.ps1 +0 -26
- package/install/bootstrap.mjs +0 -324
- package/install/install.ps1 +0 -3
- package/install/install.sh +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,109 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.11.0] - 2026-10-22
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`npm install -g novahiz` is the whole install instruction** - the installer
|
|
13
|
+
now has two modes and two moments. `--core` is the local half: home, skills,
|
|
14
|
+
plugin, agent, commands, config, catalog. No network, no `sudo`, so it is safe
|
|
15
|
+
from a postinstall, a CI job or an agent with no terminal. `--extras` is the
|
|
16
|
+
network/system half (providers and toolchain, global MCP servers, docs index
|
|
17
|
+
seed, Obsidian vault, npm refresh) and is replayed at the first real `novahiz`
|
|
18
|
+
call. Without a flag the installer still does everything in one pass, so
|
|
19
|
+
`novahiz-install` keeps its historical behaviour.
|
|
20
|
+
- **The first `novahiz` run finishes the setup** - npm 11.16+ blocks install
|
|
21
|
+
scripts unless `allowScripts` covers the package, and a global install has no
|
|
22
|
+
`package.json` to record that approval in. `bin/novahiz.mjs` therefore runs
|
|
23
|
+
`--core` when no manifest exists, then `--extras` in the same invocation, and
|
|
24
|
+
clears `extrasPending`. `NOVAHIZ_NO_EXTRAS=1` and `NOVAHIZ_AUTOINSTALL=0`
|
|
25
|
+
remain the opt-outs.
|
|
26
|
+
- **Linux and macOS compatibility** - `which()` and `commandExists()` scan
|
|
27
|
+
`PATH` (plus `PATHEXT` on Windows) instead of spawning `which`/`where`, which
|
|
28
|
+
Debian minimal and Alpine do not ship. The opencode `shell` setting follows
|
|
29
|
+
`$SHELL` on macOS/Linux (zsh by default on macOS) instead of hardcoding
|
|
30
|
+
`bash`, and keeps the `pwsh`/`powershell` probe on Windows. CI gains an
|
|
31
|
+
`install` job that runs the core installer and the resulting CLI on ubuntu,
|
|
32
|
+
macos and windows.
|
|
33
|
+
- **Nothing privileged is executed any more** - the core toolchain bootstrap no
|
|
34
|
+
longer spawns `sudo apt-get install` or `sh -c "curl … | sh"`. Both are
|
|
35
|
+
printed as the exact manual command (`manual core python - run: …`), so a CI
|
|
36
|
+
job or an agent can no longer hang on a password prompt. `brew` and `winget`
|
|
37
|
+
(structured argv, no privilege escalation) stay executable.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- **Four redundant entry points** - `install/bootstrap.mjs`,
|
|
42
|
+
`install/install.sh`, `install/install.ps1` and `bin/veille-mensuelle.ps1`,
|
|
43
|
+
together with the `novahiz-bootstrap` bin and the `bootstrap` / `bootstrap:dry`
|
|
44
|
+
scripts. `novahiz-install` is the single installer; `npm run setup:core` and
|
|
45
|
+
`npm run setup:extras` cover the two modes.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **`novahiz --version` printed the usage** - the `command === undefined` branch
|
|
50
|
+
ran before the `--version` flag check.
|
|
51
|
+
|
|
52
|
+
## [0.10.0] - 2026-10-22
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **The gate and the injected block now agree on the pipeline** - `R6-Novahiz`
|
|
57
|
+
required plan/clarify/analyse/implement/converge on every workflow prompt,
|
|
58
|
+
while the roadmap loop only enforced `kind: "skill"`. A lite or Flutter task
|
|
59
|
+
could therefore announce nothing (or five skills) and be blocked for seven.
|
|
60
|
+
`R6` is removed and every non-optional roadmap step that carries
|
|
61
|
+
`requireSkills` is enforced, whatever its kind (`skill`, `edit`, `verify`,
|
|
62
|
+
`advisory`). `R10-security` is removed too: the audit roadmap already names
|
|
63
|
+
`novahiz-security`. `R11-accessibility` is narrowed to `design-ui`. The
|
|
64
|
+
catalogue now holds 13 rules instead of 15.
|
|
65
|
+
- **A trivial follow-up no longer disarms the gate** - the plugin kept the
|
|
66
|
+
classification in memory only. `continue` / `ok`, or a plugin restart, fell
|
|
67
|
+
back to `general/trivial` and the gate allowed every edit with `missing: []`.
|
|
68
|
+
The last non-trivial classification is now persisted in the `sessions` table
|
|
69
|
+
(schema v3 adds the `prompt` and `tier` columns), `classify --session`
|
|
70
|
+
returns the effective classification, and the gate CLI reads that state from
|
|
71
|
+
the database when categories and prompt are empty.
|
|
72
|
+
- **`doctor` reports a schema mismatch** - the schema check hardcoded
|
|
73
|
+
`ok: true`; it now compares `PRAGMA user_version` with `SCHEMA_VERSION`.
|
|
74
|
+
- **The gate no longer blocks provably read-only shell commands** -
|
|
75
|
+
`git status`, `git log`, `git diff`, `ls`, `Get-Content` and their
|
|
76
|
+
equivalents are exempt (`src/readonly.ts`). The gate used to ask for the
|
|
77
|
+
design skills before it would let an agent read a file.
|
|
78
|
+
- **French UI prompts are no longer fail-open** - `ajoute` / `ajouter` now
|
|
79
|
+
score as lite and `implémente` as full, so a French additive request reaches
|
|
80
|
+
the pipeline instead of falling through to trivial. `design-ui` recognises
|
|
81
|
+
`bouton`, `écran`, `connexion`, `formulaire`, `menu`, `champ`, `icône`,
|
|
82
|
+
`carte`, `modal`, `header`, `sidebar` and their English forms. Numeric
|
|
83
|
+
micro-changes (`ajoute 8px de marge`) stay trivial, and debug words
|
|
84
|
+
(`bug`, `error`, `crash`, ...) subtract from `design-ui` so
|
|
85
|
+
`fix the login bug` stays in `debug` instead of dragging the design pipeline
|
|
86
|
+
into a fix.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- The gate CLI falls back to the persisted session state when the caller
|
|
91
|
+
passes no categories and no prompt, instead of letting the decision rest on
|
|
92
|
+
an empty classification.
|
|
93
|
+
- The plugin asks `classify --session` and, for a trivial continuation,
|
|
94
|
+
inherits the previous task's prompt so the tier does not drop back to
|
|
95
|
+
trivial mid-task.
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **Session state persistence** - `src/session.ts` stores the categories, tier
|
|
100
|
+
and prompt of the last non-trivial task, with a TTL that keeps a stale
|
|
101
|
+
session from being mistaken for a continuation.
|
|
102
|
+
- **A read-only allowlist for the gate** - `src/readonly.ts` lists the shell
|
|
103
|
+
commands the gate can prove do not write.
|
|
104
|
+
- **The `novahiz-capture` skill** - reliable screenshot capture for the
|
|
105
|
+
physical Playwright browser, when a plain capture comes back stale, blank
|
|
106
|
+
or cropped.
|
|
107
|
+
- **Three test files** - `tests/session-state.test.ts`, `tests/readonly.test.ts`
|
|
108
|
+
and `tests/gate-alignment.test.ts` pin the session state, the read-only
|
|
109
|
+
exemption and the alignment between the classifier and the gate.
|
|
110
|
+
|
|
8
111
|
## [0.9.9] - 2026-10-16
|
|
9
112
|
|
|
10
113
|
### Changed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
17 catégories, 94 skills, 13 gate rules, 8 MCP providers — tout déterministe, tout local, tout JSON.
|
|
6
6
|
|
|
7
|
-
> **Version 0.
|
|
7
|
+
> **Version 0.11.0** — `npm install -g novahiz` pose le CLI, et le premier `novahiz` lancé termine l'installation (core puis extras). `CHANGELOG.md` et `scripts/` sont dans le tarball.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
┌──────────────────────────────────────────────────────────────────────────┐
|
|
@@ -41,28 +41,33 @@
|
|
|
41
41
|
|
|
42
42
|
## Démarrage rapide
|
|
43
43
|
|
|
44
|
-
### Option 1 —
|
|
44
|
+
### Option 1 — npm (recommandé)
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
npm install -g
|
|
47
|
+
npm install -g novahiz
|
|
48
|
+
novahiz doctor
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
`npm install -g` pose le CLI. Le premier `novahiz` lancé termine le reste, en deux passes : le **core** (skills, plugin, agent, commands, config, catalogue — local, sans réseau ni sudo), puis les **extras** (providers et toolchain, MCP globaux dont Playwright, vault Obsidian, rafraîchissement npm). Une seule fois.
|
|
51
52
|
|
|
52
|
-
**Pourquoi
|
|
53
|
+
**Pourquoi au premier lancement et pas dans le postinstall ?** npm 11.16+ ne lance un script de lifecycle que si le paquet y est explicitement autorisé, et une installation globale n'a pas de `package.json` où l'inscrire : npm avertit (`npm warn allow-scripts novahiz`) et saute le script. Le CLI fait donc le même travail à son premier appel, ce qui fonctionne quelle que soit la version de npm. Pour que tout se passe pendant le `npm install` :
|
|
53
54
|
|
|
54
55
|
```bash
|
|
55
|
-
|
|
56
|
-
npm config set allow-scripts=novahiz --location=user
|
|
57
|
-
|
|
58
|
-
# paquet déjà installé sans configuration : une commande suffit
|
|
59
|
-
novahiz-install --yes
|
|
56
|
+
npm install -g --allow-scripts=novahiz novahiz
|
|
60
57
|
```
|
|
61
58
|
|
|
62
|
-
|
|
59
|
+
Variantes :
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
NOVAHIZ_NO_EXTRAS=1 novahiz doctor # core seulement, aucun accès réseau
|
|
63
|
+
NOVAHIZ_AUTOINSTALL=0 novahiz doctor # ni core ni extras
|
|
64
|
+
novahiz-install --extras # rejouer les extras plus tard
|
|
65
|
+
```
|
|
63
66
|
|
|
64
67
|
> Piège : `npm install -g --allow-scripts=novahiz` **sans** le nom du paquet installe le dossier courant et échoue avec `ENOENT: package.json`. Le nom du paquet est obligatoire : `npm install -g --allow-scripts=novahiz novahiz`.
|
|
65
68
|
|
|
69
|
+
> Linux et macOS : les commandes système qui demandent `sudo` ou qui exécutent un script téléchargé ne sont jamais lancées. L'installeur affiche la ligne exacte (`sudo apt-get install -y python3`, `brew install uv`, …) et vous décidez. C'est ce qui rend l'installation utilisable sans terminal interactif.
|
|
70
|
+
|
|
66
71
|
> Linux : le serveur Playwright MCP s'exécute sous WebKit (règle maison : Chromium interdit). WebKit exige ses dépendances système : si le navigateur ne démarre pas, `npx playwright install-deps webkit` (sudo requis sur Debian/Ubuntu).
|
|
67
72
|
|
|
68
73
|
Puis vérifiez :
|
|
@@ -125,12 +130,10 @@ flowchart TD
|
|
|
125
130
|
C --> D[R13 : prompt design-ui ou fichier style ?<br/>exiger humanizer + ui-slop + ui-craft]
|
|
126
131
|
C --> F[R3 : le prompt était Supabase ?<br/>exiger novahiz-supabase + postgres]
|
|
127
132
|
C --> G[R4 : le prompt était navigateur ?<br/>exiger novahiz-browser]
|
|
128
|
-
C --> H[R6 : prompt de workflow ?<br/>exiger plan/clarify/analyse/implement/converge]
|
|
129
133
|
|
|
130
|
-
D --> I{Application de la roadmap}
|
|
134
|
+
D --> I{Application de la roadmap<br/>étapes non optionnelles}
|
|
131
135
|
F --> I
|
|
132
136
|
G --> I
|
|
133
|
-
H --> I
|
|
134
137
|
|
|
135
138
|
I --> J{Détection de placeholder<br/>tokens TODO / FIXME / placeholder}
|
|
136
139
|
|
|
@@ -160,18 +163,20 @@ flowchart TD
|
|
|
160
163
|
|-------|------------------|-------|
|
|
161
164
|
| R3-supabase | Un chemin Supabase ou un prompt Supabase | novahiz-supabase, novahiz-postgres |
|
|
162
165
|
| R4-playwright | Une catégorie prompt navigateur, ou un chemin de test navigateur (`**/*.spec.ts`, `**/e2e/**`, `**/playwright/**`, …) | novahiz-browser |
|
|
163
|
-
| R6-Novahiz | Un prompt dans une catégorie de workflow | novahiz-plan, -clarify, -analyse, -implement, -converge |
|
|
164
166
|
| R7-assessment | Un prompt d'assessment | novahiz-assess-intake, -research, -define, -shape, -decide |
|
|
165
167
|
| R8-docs | Une édition sous `novahiz-docs/**/*.md` | novahiz-docs |
|
|
166
168
|
| R9-code-review | Un prompt de review ou un fichier de code en review | novahiz-code-review |
|
|
167
|
-
|
|
|
168
|
-
| R11-accessibility | Un prompt design-ui ou audit | novahiz-wcag-audit |
|
|
169
|
+
| R11-accessibility | Un prompt design-ui | novahiz-wcag-audit |
|
|
169
170
|
| R12-web-extract | Un prompt de recherche | novahiz-web-extract |
|
|
170
171
|
| R13-design-craft | Un prompt design-ui ou un fichier style (css/scss/less/html) | novahiz-humanizer, ui-slop-remover, ui-craft-rules |
|
|
171
172
|
| R14-impeccable | Un prompt design-ui ou un fichier style (css/scss/less/html) | impeccable |
|
|
172
173
|
|
|
173
174
|
`novahiz-humanizer` et `ui-slop-remover` ne sont exigées que sur les tâches de design frontend (R13) ; `impeccable` se charge de la même façon (R14) pour garder les playbooks shape, critique, audit, harden et polish accessibles, et la roadmap design-ui porte une étape de vérification `impeccable detect` déterministe avant ship. `novahiz init` et `novahiz doctor` affichent les fichiers de contexte `PRODUCT.md` / `DESIGN.md` d'impeccable en lignes advisory.
|
|
174
175
|
|
|
176
|
+
Le pipeline (plan, clarify, analyse, implement, converge) n'est plus une règle : il vient des étapes non optionnelles de la roadmap de la catégorie principale, quel que soit leur `kind` (`skill`, `edit`, `verify`, `advisory`). Une seule source de vérité, donc le bloc affiché et le gate ne peuvent plus diverger sur ce point.
|
|
177
|
+
|
|
178
|
+
Les commandes shell prouvées en lecture seule (`git status`, `git log`, `git diff`, `ls`, `Get-ChildItem`, `Get-Content`, `rg`, `Select-String`, …) sont exemptées avant toute règle ou roadmap. Dès qu'une commande chaîne, redirige, substitue ou porte un flag d'écriture, l'exemption tombe et le gate s'applique normalement.
|
|
179
|
+
|
|
175
180
|
### Gate reload
|
|
176
181
|
|
|
177
182
|
Un blocage n'est jamais une impasse. Le refus embarque une recette GATE RELOAD : charger chaque skill nommée via le loader, puis rejouer l'appel exact une fois — aucun outil alternatif, aucune écriture shell, aucun contournement d'édition. Si les mêmes skills sont de nouveau signalées manquantes, le chargement n'a pas été enregistré : exécuter `novahiz doctor`, signaler honnêtement, et s'arrêter. La seule dérogation sanctionnée est `NOVAHIZ_GATE=off` (voir Configuration), déclarée à voix haute.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
description: Novahiz primary agent. Classifies the request, loads the required skills, then works under the Novahiz gate.
|
|
3
3
|
mode: primary
|
|
4
4
|
temperature: 0.2
|
|
5
|
+
color: "#22c55e"
|
|
5
6
|
# opencode denies `question` by default. The built-in build and plan agents
|
|
6
7
|
# re-allow it; a custom agent inherits the denial unless it asks. Without this
|
|
7
8
|
# block the pipeline cannot ask a clarifying question or validate a plan.
|
|
@@ -1633,7 +1633,7 @@ async function setup(ctx: Context): Promise<Cleanup> {
|
|
|
1633
1633
|
// P0-B: the prompt travels on stdin. On argv it allowed option
|
|
1634
1634
|
// injection (--home), broke past the Windows 32k limit, and was
|
|
1635
1635
|
// readable in the process list.
|
|
1636
|
-
const result = await run(["classify", "--stdin"], classifyText);
|
|
1636
|
+
const result = await run(["classify", "--stdin", "--session", sessionID], classifyText);
|
|
1637
1637
|
if (result.status !== 0) {
|
|
1638
1638
|
classifyFailedBySession.set(sessionID, `classify exit ${result.status}`);
|
|
1639
1639
|
await log("warn", `Classify failed (exit ${result.status}), gate tool calls refused for this session: ${result.stderr.trim().slice(0, 200)}`);
|
|
@@ -1645,6 +1645,7 @@ async function setup(ctx: Context): Promise<Cleanup> {
|
|
|
1645
1645
|
requiredSkills?: string[];
|
|
1646
1646
|
enforcedSkills?: string[];
|
|
1647
1647
|
providers?: string[];
|
|
1648
|
+
continuedFrom?: string;
|
|
1648
1649
|
roadmaps?: { id: string; steps: { label: string; kind: string; requireSkills?: string[] }[] }[];
|
|
1649
1650
|
};
|
|
1650
1651
|
try {
|
|
@@ -1666,7 +1667,11 @@ async function setup(ctx: Context): Promise<Cleanup> {
|
|
|
1666
1667
|
categoriesBySession.set(sessionID, categories);
|
|
1667
1668
|
// P1-4: the gate takes the last prompt as its tier seed via --prompt even
|
|
1668
1669
|
// when memory auto is off — set unconditionally, not inside MEMORY_AUTO.
|
|
1669
|
-
|
|
1670
|
+
// A trivial follow-up ("continue") inherits the previous task's prompt:
|
|
1671
|
+
// using the literal "continue" here would retier the session as trivial
|
|
1672
|
+
// and skip the roadmap the classify call just restored.
|
|
1673
|
+
const effectivePrompt = parsed.continuedFrom ?? text;
|
|
1674
|
+
lastPromptBySession.set(sessionID, effectivePrompt.slice(0, 300));
|
|
1670
1675
|
const primary = parsed.primary ?? categories[0] ?? null;
|
|
1671
1676
|
const enforced = parsed.enforcedSkills ?? [];
|
|
1672
1677
|
const required = parsed.requiredSkills ?? [];
|
|
@@ -1677,6 +1682,9 @@ async function setup(ctx: Context): Promise<Cleanup> {
|
|
|
1677
1682
|
"[Novahiz enforcement]",
|
|
1678
1683
|
`Categories detected: ${categories.join(", ") || "none"}${primary ? ` (primary: ${primary})` : ""}`
|
|
1679
1684
|
];
|
|
1685
|
+
if (parsed.continuedFrom) {
|
|
1686
|
+
lines.push(`Continuing the previous task: ${parsed.continuedFrom.slice(0, 160)}`);
|
|
1687
|
+
}
|
|
1680
1688
|
if (rewrite.sourceLanguage !== "en") {
|
|
1681
1689
|
lines.push(`User language: ${rewrite.sourceLanguage} — respond in this language, not English.`);
|
|
1682
1690
|
}
|
|
@@ -1699,7 +1707,7 @@ async function setup(ctx: Context): Promise<Cleanup> {
|
|
|
1699
1707
|
await log("warn", "Ledger state is invalid JSON, enforcement injected without the task summary");
|
|
1700
1708
|
}
|
|
1701
1709
|
}
|
|
1702
|
-
lines.push("The gate blocks edit/write/patch/apply_patch/bash/shell until the required skills are loaded via skill({id:\"...\"}).");
|
|
1710
|
+
lines.push("The gate blocks edit/write/patch/apply_patch/bash/shell until the required skills are loaded via skill({id:\"...\"}). Read-only shell commands (status/log/diff/ls/Get-Content/...) are exempt.");
|
|
1703
1711
|
lines.push("The gate is content-aware: novahiz-humanizer, ui-slop-remover and ui-craft-rules are required only on frontend design tasks (R13), and impeccable on the same design selectors (R14).");
|
|
1704
1712
|
lines.push("Config edited = opencode restart required (config read at import).");
|
|
1705
1713
|
lines.push("Memory lives in project-memory/ under the project root (cwd): index.json + fixed-size slots (8000 chars / 200 lines) with compact → archive → new-slot rotation. Use the MCP memory_* tools: memory_search to find relevant slots first, then memory_get to read, memory_write to append, memory_update to correct, memory_archive to retire.");
|
package/bin/novahiz.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
@@ -21,40 +21,66 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|
|
21
21
|
const root = join(here, "..");
|
|
22
22
|
const underNodeModules = root.replace(/\\/g, "/").toLowerCase().includes("/node_modules/");
|
|
23
23
|
const dist = join(root, "dist", "cli.js");
|
|
24
|
-
const src = join(root, "src", "cli.ts");
|
|
24
|
+
const src = join(root, "src", "cli.ts"); console.error("DEBUG", JSON.stringify({underNodeModules, root, auto: process.env.NOVAHIZ_AUTOINSTALL, noextras: process.env.NOVAHIZ_NO_EXTRAS}));
|
|
25
25
|
|
|
26
26
|
// npm 11.16+ blocks install scripts unless `allowScripts` covers them, and a
|
|
27
27
|
// global install (`npm install -g`) has no package.json to record that
|
|
28
28
|
// approval in — so the postinstall can be skipped silently and the machine is
|
|
29
|
-
// left half configured.
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
29
|
+
// left half configured. The first real `novahiz` run therefore finishes the
|
|
30
|
+
// job, in two passes:
|
|
31
|
+
// 1. no install manifest -> install the core (skills, plugin, agent, config);
|
|
32
|
+
// 2. manifest says extrasPending -> run the network/system extras once
|
|
33
|
+
// (providers, global MCP servers, Obsidian vault, npm refresh).
|
|
34
|
+
// Opt out with NOVAHIZ_AUTOINSTALL=0 (both passes) or NOVAHIZ_NO_EXTRAS=1
|
|
35
|
+
// (extras only). A repo checkout keeps its explicit `novahiz-install`.
|
|
36
|
+
function readManifest(path) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
39
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
if (underNodeModules && process.env.NOVAHIZ_AUTOINSTALL !== "0") {
|
|
35
46
|
const args = process.argv.slice(2);
|
|
36
47
|
const infoOnly = args.length > 0 && args.every((arg) => ["-v", "-V", "--version", "--help", "-h"].includes(arg));
|
|
37
48
|
const home = process.env.NOVAHIZ_HOME ? resolve(process.env.NOVAHIZ_HOME) : join(homedir(), ".config", "novahiz");
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
43
|
-
const setup = spawnSync(process.execPath, [
|
|
49
|
+
const manifestPath = join(home, ".novahiz-install.json");
|
|
50
|
+
const manifest = readManifest(manifestPath);
|
|
51
|
+
const installer = join(root, "install", "install.mjs");
|
|
52
|
+
const runInstaller = (mode, reason) => {
|
|
53
|
+
process.stderr.write(`novahiz: ${reason}\n`);
|
|
54
|
+
const setup = spawnSync(process.execPath, [installer, "--yes", mode], {
|
|
44
55
|
stdio: "inherit",
|
|
45
56
|
cwd: root,
|
|
46
57
|
env: process.env
|
|
47
58
|
});
|
|
48
59
|
if (setup.status !== 0) {
|
|
49
|
-
process.stderr.write(
|
|
60
|
+
process.stderr.write(`novahiz: setup (${mode}) did not complete; re-run it with \`novahiz-install ${mode}\`.\n`);
|
|
61
|
+
}
|
|
62
|
+
return setup.status === 0;
|
|
63
|
+
};
|
|
64
|
+
if (!infoOnly && manifest === null) {
|
|
65
|
+
const coreOk = runInstaller("--core", "first run - installing the core (the npm postinstall was skipped or did not finish).");
|
|
66
|
+
// Meme lancement, deux passes : le premier appel doit tout laisser pret, pas
|
|
67
|
+
// seulement le core. Les extras restent optionnels via NOVAHIZ_NO_EXTRAS=1.
|
|
68
|
+
if (coreOk && process.env.NOVAHIZ_NO_EXTRAS !== "1") {
|
|
69
|
+
runInstaller("--extras", "installing the extras (providers, global MCP servers, Obsidian vault). One time only; skip with NOVAHIZ_NO_EXTRAS=1.");
|
|
50
70
|
}
|
|
71
|
+
} else if (!infoOnly && manifest?.extrasPending === true && process.env.NOVAHIZ_NO_EXTRAS !== "1") {
|
|
72
|
+
runInstaller("--extras", "finishing the setup installed earlier (providers, global MCP servers, Obsidian vault). One time only; skip with NOVAHIZ_NO_EXTRAS=1.");
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
let target;
|
|
55
77
|
if (underNodeModules) {
|
|
56
78
|
if (!existsSync(dist)) {
|
|
57
|
-
process.stderr.write(
|
|
79
|
+
process.stderr.write(
|
|
80
|
+
"novahiz: dist/cli.js is missing from this package copy.\n" +
|
|
81
|
+
"novahiz: npm blocked the `prepare` script (allow-scripts). Reinstall with:\n" +
|
|
82
|
+
"novahiz: npm install -g --allow-scripts=novahiz novahiz\n"
|
|
83
|
+
);
|
|
58
84
|
process.exit(1);
|
|
59
85
|
}
|
|
60
86
|
target = dist;
|
package/catalog/categories.json
CHANGED
|
@@ -756,7 +756,206 @@
|
|
|
756
756
|
"accessibility",
|
|
757
757
|
"a11y",
|
|
758
758
|
"dashboard",
|
|
759
|
-
|
|
759
|
+
{
|
|
760
|
+
"term": "form",
|
|
761
|
+
"weight": 2
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
"term": "forms",
|
|
765
|
+
"weight": 2
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
"term": "formulaire",
|
|
769
|
+
"weight": 2
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
"term": "formulaires",
|
|
773
|
+
"weight": 2
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
"term": "bouton",
|
|
777
|
+
"weight": 2
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
"term": "boutons",
|
|
781
|
+
"weight": 2
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
"term": "button",
|
|
785
|
+
"weight": 2
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
"term": "buttons",
|
|
789
|
+
"weight": 2
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
"term": "écran",
|
|
793
|
+
"weight": 2
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
"term": "screen",
|
|
797
|
+
"weight": 2
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
"term": "screens",
|
|
801
|
+
"weight": 2
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
"term": "connexion",
|
|
805
|
+
"weight": 1
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"term": "login",
|
|
809
|
+
"weight": 1
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"term": "sign in",
|
|
813
|
+
"weight": 1
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
"term": "signin",
|
|
817
|
+
"weight": 1
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"term": "sign up",
|
|
821
|
+
"weight": 1
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
"term": "signup",
|
|
825
|
+
"weight": 1
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"term": "inscription",
|
|
829
|
+
"weight": 1
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"term": "mot de passe",
|
|
833
|
+
"weight": 1
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
"term": "password",
|
|
837
|
+
"weight": 1
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
"term": "champ",
|
|
841
|
+
"weight": 1
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"term": "field",
|
|
845
|
+
"weight": 1
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
"term": "input",
|
|
849
|
+
"weight": 1
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
"term": "menu",
|
|
853
|
+
"weight": 1
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"term": "icône",
|
|
857
|
+
"weight": 1
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
"term": "icon",
|
|
861
|
+
"weight": 1
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
"term": "carte",
|
|
865
|
+
"weight": 1
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
"term": "card",
|
|
869
|
+
"weight": 1
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
"term": "modal",
|
|
873
|
+
"weight": 1
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
"term": "dialog",
|
|
877
|
+
"weight": 1
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
"term": "dialogue",
|
|
881
|
+
"weight": 1
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
"term": "header",
|
|
885
|
+
"weight": 1
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"term": "footer",
|
|
889
|
+
"weight": 1
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
"term": "sidebar",
|
|
893
|
+
"weight": 1
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"term": "composant",
|
|
897
|
+
"weight": 1
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
"term": "component",
|
|
901
|
+
"weight": 1
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
"term": "onglet",
|
|
905
|
+
"weight": 1
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
"term": "tab",
|
|
909
|
+
"weight": 1
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"term": "avatar",
|
|
913
|
+
"weight": 1
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
"term": "badge",
|
|
917
|
+
"weight": 1
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
"term": "tooltip",
|
|
921
|
+
"weight": 1
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
"term": "toast",
|
|
925
|
+
"weight": 1
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
"term": "snackbar",
|
|
929
|
+
"weight": 1
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
"term": "drawer",
|
|
933
|
+
"weight": 1
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
"term": "panneau",
|
|
937
|
+
"weight": 1
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
"term": "panel",
|
|
941
|
+
"weight": 1
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"term": "accueil",
|
|
945
|
+
"weight": 1
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
"term": "welcome",
|
|
949
|
+
"weight": 1
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
"term": "home",
|
|
953
|
+
"weight": 1
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
"term": "homepage",
|
|
957
|
+
"weight": 1
|
|
958
|
+
},
|
|
760
959
|
"onboarding",
|
|
761
960
|
"frontend design",
|
|
762
961
|
"ui component",
|
|
@@ -773,7 +972,15 @@
|
|
|
773
972
|
"negativeKeywords": [
|
|
774
973
|
"migration",
|
|
775
974
|
"rls",
|
|
776
|
-
"edge function"
|
|
975
|
+
"edge function",
|
|
976
|
+
"bug",
|
|
977
|
+
"error",
|
|
978
|
+
"crash",
|
|
979
|
+
"exception",
|
|
980
|
+
"regression",
|
|
981
|
+
"stacktrace",
|
|
982
|
+
"panic",
|
|
983
|
+
"broken"
|
|
777
984
|
],
|
|
778
985
|
"defaultSkills": [
|
|
779
986
|
"novahiz-humanizer",
|
package/catalog/rules.json
CHANGED
|
@@ -26,30 +26,6 @@
|
|
|
26
26
|
},
|
|
27
27
|
"require": ["novahiz-browser"]
|
|
28
28
|
},
|
|
29
|
-
{
|
|
30
|
-
"id": "R6-Novahiz",
|
|
31
|
-
"description": "Load Novahiz pipeline skills for any task triggering the workflow.",
|
|
32
|
-
"when": {
|
|
33
|
-
"promptCategories": [
|
|
34
|
-
"code",
|
|
35
|
-
"debug",
|
|
36
|
-
"review",
|
|
37
|
-
"audit",
|
|
38
|
-
"browser",
|
|
39
|
-
"design-ui",
|
|
40
|
-
"database-supabase",
|
|
41
|
-
"docs-writing",
|
|
42
|
-
"planning",
|
|
43
|
-
"devops",
|
|
44
|
-
"data",
|
|
45
|
-
"general",
|
|
46
|
-
"flutter",
|
|
47
|
-
"expo",
|
|
48
|
-
"test"
|
|
49
|
-
]
|
|
50
|
-
},
|
|
51
|
-
"require": ["novahiz-plan", "novahiz-clarify", "novahiz-analyse", "novahiz-implement", "novahiz-converge"]
|
|
52
|
-
},
|
|
53
29
|
{
|
|
54
30
|
"id": "R7-assessment",
|
|
55
31
|
"description": "Load Idea Assessment pipeline skills.",
|
|
@@ -80,21 +56,12 @@
|
|
|
80
56
|
},
|
|
81
57
|
"require": ["novahiz-code-review"]
|
|
82
58
|
},
|
|
83
|
-
{
|
|
84
|
-
"id": "R10-security",
|
|
85
|
-
"description": "Load novahiz-security on any security audit or hardening task.",
|
|
86
|
-
"when": {
|
|
87
|
-
"match": "any",
|
|
88
|
-
"promptCategories": ["audit"]
|
|
89
|
-
},
|
|
90
|
-
"require": ["novahiz-security"]
|
|
91
|
-
},
|
|
92
59
|
{
|
|
93
60
|
"id": "R11-accessibility",
|
|
94
|
-
"description": "Load novahiz-wcag-audit on design-ui work
|
|
61
|
+
"description": "Load novahiz-wcag-audit on design-ui work, where accessibility is in scope.",
|
|
95
62
|
"when": {
|
|
96
63
|
"match": "any",
|
|
97
|
-
"promptCategories": ["design-ui"
|
|
64
|
+
"promptCategories": ["design-ui"],
|
|
98
65
|
"pathExcludes": ["**/tests/**", "**/__tests__/**", "**/*.test.*", "**/*.spec.*"]
|
|
99
66
|
},
|
|
100
67
|
"require": ["novahiz-wcag-audit"]
|
package/dist/classify.js
CHANGED
|
@@ -58,7 +58,11 @@ function skillsOfCategory(category) {
|
|
|
58
58
|
function enforcedOfCategory(category) {
|
|
59
59
|
const skills = [];
|
|
60
60
|
for (const step of category.roadmap?.steps ?? []) {
|
|
61
|
-
|
|
61
|
+
// Every non-optional step counts, whatever its kind: `edit:implement` and
|
|
62
|
+
// `verify:converge` carry skills too. Restricting to kind "skill" made
|
|
63
|
+
// lite/debug roadmaps advertise nothing while the gate demanded R6's
|
|
64
|
+
// implement/converge — the block and the gate disagreed.
|
|
65
|
+
if (step.optional)
|
|
62
66
|
continue;
|
|
63
67
|
for (const skill of step.requireSkills ?? []) {
|
|
64
68
|
if (!skills.includes(skill))
|
package/dist/cli.js
CHANGED
|
@@ -121,14 +121,16 @@ async function main(argv) {
|
|
|
121
121
|
process.env.NOVAHIZ_HOME = resolve(expandHome(parsed.flags.home));
|
|
122
122
|
}
|
|
123
123
|
const command = parsed.positionals[0];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
124
|
+
// `--version` sans commande tombe sur `command === undefined` et affichait
|
|
125
|
+
// l'aide : le test du drapeau doit passer avant celui de la commande.
|
|
128
126
|
if (parsed.flags.version || parsed.flags.v) {
|
|
129
127
|
printVersion();
|
|
130
128
|
return;
|
|
131
129
|
}
|
|
130
|
+
if (command === undefined || command === "help" || command === "--help" || command === "-h") {
|
|
131
|
+
usage();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
132
134
|
switch (command) {
|
|
133
135
|
case "init":
|
|
134
136
|
return commandInit(parsed);
|