novahiz 0.9.7 → 0.9.8

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,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.9.8] - 2026-10-15
9
+
10
+ ### Removed
11
+
12
+ - **The 7 `eas-*` skills from the catalogue** - `test` and `devops` listed them
13
+ as `defaultSkills` (`eas-app-stores`, `eas-hosting`, `eas-observe`,
14
+ `eas-simulator`, `eas-update`, `eas-update-insights`, `eas-workflows`) while
15
+ the expo pack deliberately excludes them: `providers.json` says so in the
16
+ provider's own purpose. No provider could install a single one, so `doctor`
17
+ named them and `novahiz deps --install` returned `install: []`. 0.9.5 made
18
+ that row tell the truth instead of blaming the reader; the references
19
+ themselves stayed, and the row reports without blocking, which is why nothing
20
+ had failed since. `referencedSkills` now spans 51 ids and none of them is
21
+ undeliverable, so `doctor` reports `51 present` with no caveat. The seven
22
+ inert `overrides.json` entries went with them; the `devops` keywords `eas`,
23
+ `eas build` and `eas update` stay, since they route a deployment question to
24
+ a roadmap that still applies.
25
+
26
+ ### Added
27
+
28
+ - **A test holding the catalogue to that invariant** -
29
+ `undeliverableReferences(spec, packageRootDir)` is exported from
30
+ `doctor.ts`, and `tests/doctor-referenced-skills.test.ts` runs it against the
31
+ shipped catalogue: it must return `[]`, no `defaultSkills` may carry an
32
+ `eas-*`, and a reintroduced one must be named. Checked by injection: putting
33
+ `eas-simulator` back into `test.defaultSkills` turns both assertions red.
34
+
8
35
  ## [0.9.7] - 2026-10-15
9
36
 
10
37
  ### Added
@@ -554,8 +554,7 @@
554
554
  "defaultSkills": [
555
555
  "flutter-add-widget-test",
556
556
  "dart-add-unit-test",
557
- "expo-skill-eval",
558
- "eas-simulator"
557
+ "expo-skill-eval"
559
558
  ],
560
559
  "roadmap": {
561
560
  "id": "testing",
@@ -1329,14 +1328,7 @@
1329
1328
  "tag a version",
1330
1329
  "changelog"
1331
1330
  ],
1332
- "defaultSkills": [
1333
- "eas-workflows",
1334
- "eas-app-stores",
1335
- "eas-hosting",
1336
- "eas-observe",
1337
- "eas-update",
1338
- "eas-update-insights"
1339
- ],
1331
+ "defaultSkills": [],
1340
1332
  "roadmap": {
1341
1333
  "id": "devops",
1342
1334
  "steps": [
@@ -634,83 +634,6 @@
634
634
  "docs-writing"
635
635
  ]
636
636
  },
637
- "eas-app-stores": {
638
- "power": 4,
639
- "stars": null,
640
- "tags": [
641
- "eas",
642
- "expo"
643
- ],
644
- "categories": [
645
- "expo"
646
- ]
647
- },
648
- "eas-hosting": {
649
- "power": 4,
650
- "stars": null,
651
- "tags": [
652
- "eas",
653
- "expo"
654
- ],
655
- "categories": [
656
- "expo"
657
- ]
658
- },
659
- "eas-observe": {
660
- "power": 4,
661
- "stars": null,
662
- "tags": [
663
- "eas",
664
- "expo"
665
- ],
666
- "categories": [
667
- "expo"
668
- ]
669
- },
670
- "eas-simulator": {
671
- "power": 4,
672
- "stars": null,
673
- "tags": [
674
- "eas",
675
- "expo"
676
- ],
677
- "categories": [
678
- "expo"
679
- ]
680
- },
681
- "eas-update": {
682
- "power": 4,
683
- "stars": null,
684
- "tags": [
685
- "eas",
686
- "expo"
687
- ],
688
- "categories": [
689
- "expo"
690
- ]
691
- },
692
- "eas-update-insights": {
693
- "power": 4,
694
- "stars": null,
695
- "tags": [
696
- "eas",
697
- "expo"
698
- ],
699
- "categories": [
700
- "expo"
701
- ]
702
- },
703
- "eas-workflows": {
704
- "power": 4,
705
- "stars": null,
706
- "tags": [
707
- "eas",
708
- "expo"
709
- ],
710
- "categories": [
711
- "expo"
712
- ]
713
- },
714
637
  "expo-animation": {
715
638
  "power": 4,
716
639
  "stars": null,
@@ -443,22 +443,15 @@ function referencedSkills(spec) {
443
443
  * `indexSkills` is null when the index is unreadable — the index check already
444
444
  * reports that, so the row stays quiet instead of double-failing.
445
445
  * Only skills the package ships (packageRoot/skills) can make a fresh install
446
- * broken. The remaining references — the dart/eas/expo/flutter packs and
447
- * impeccable — are optional delivery downloaded on request and are simply not
446
+ * broken. The remaining references, the dart/expo/flutter packs and
447
+ * impeccable, are optional delivery downloaded on request and are simply not
448
448
  * present on a machine that never asked for them; the gate already degrades
449
449
  * them to "not enforced". Blocking on them turned every healthy fresh install
450
450
  * red (`missing from index: dart-...` right after novahiz-install --yes). */
451
451
  export function referencedSkillsCheck(spec, indexSkills, packageRootDir) {
452
452
  const referenced = referencedSkills(spec);
453
453
  const absent = indexSkills ? referenced.filter((id) => !indexSkills.has(id)) : [];
454
- const shipped = new Set();
455
- const shippedDir = join(packageRootDir, "skills");
456
- if (existsSync(shippedDir)) {
457
- for (const entry of readdirSync(shippedDir, { withFileTypes: true })) {
458
- if (entry.isDirectory())
459
- shipped.add(entry.name);
460
- }
461
- }
454
+ const shipped = shippedSkills(packageRootDir);
462
455
  const absentShipped = absent.filter((id) => shipped.has(id));
463
456
  const absentPack = absent.filter((id) => !shipped.has(id));
464
457
  // Only promise the update command when a pack can actually deliver what is
@@ -493,11 +486,14 @@ export function referencedSkillsCheck(spec, indexSkills, packageRootDir) {
493
486
  * argv: an explicit `--skill a b c` names them, and `--skill *` delivers the
494
487
  * whole repo, whose ids start with the repo's leading segment with any hyphen
495
488
  * suffix dropped (`flutter/skills` -> `flutter-`, `dart-lang/skills` ->
496
- * `dart-`). Measured 2026-10-15: the 7 skills doctor reported as "pack skills
497
- * not installed" are `eas-*`, referenced as `defaultSkills` by the `test` and
498
- * `devops` categories while no provider ships them, so
499
- * "run /novahiz-skills-update, or novahiz deps --install" could not have
500
- * installed a single one. Advice no command can honour is a defect. */
489
+ * `dart-`). Measured 2026-10-15 on the 0.9.5 install: the 7 skills doctor
490
+ * reported as "pack skills not installed" were `eas-*`, referenced as
491
+ * `defaultSkills` by `test` and `devops` while the expo pack deliberately
492
+ * excludes them, so "run /novahiz-skills-update, or novahiz deps --install"
493
+ * could not have installed a single one. Advice no command can honour is a
494
+ * defect. Those references are gone from the catalog; the guard stays, since
495
+ * the same situation reappears whenever a pack is disabled or its `--skill`
496
+ * list stops naming an id the catalog still asks for. */
501
497
  function packDelivery(spec) {
502
498
  const exact = new Set();
503
499
  const prefixes = [];
@@ -526,6 +522,27 @@ function packDelivery(spec) {
526
522
  }
527
523
  return { exact, prefixes };
528
524
  }
525
+ /** Skill ids the package itself carries in `packageRootDir/skills`. */
526
+ function shippedSkills(packageRootDir) {
527
+ const shipped = new Set();
528
+ const shippedDir = join(packageRootDir, "skills");
529
+ if (existsSync(shippedDir)) {
530
+ for (const entry of readdirSync(shippedDir, { withFileTypes: true })) {
531
+ if (entry.isDirectory())
532
+ shipped.add(entry.name);
533
+ }
534
+ }
535
+ return shipped;
536
+ }
537
+ /** Referenced skill ids nothing in the system can supply: the package ships
538
+ * none of them and no enabled pack can install them. Exported so the catalog
539
+ * is held to this invariant in tests, since the row above reports it without
540
+ * blocking and therefore never failed on it. */
541
+ export function undeliverableReferences(spec, packageRootDir) {
542
+ const shipped = shippedSkills(packageRootDir);
543
+ const { exact, prefixes } = packDelivery(spec);
544
+ return referencedSkills(spec).filter((id) => !shipped.has(id) && !exact.has(id) && !prefixes.some((prefix) => id.startsWith(prefix)));
545
+ }
529
546
  /** Impeccable design-context checks: PRODUCT.md and DESIGN.md resolution
530
547
  * (root -> .agents/context -> docs/). Rows appear only when the impeccable
531
548
  * skill is installed (opt-in) and are never blocking: a missing record is a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "novahiz",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
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",
@@ -439,8 +439,8 @@ function referencedSkills(spec: ReturnType<typeof loadSpec>): string[] {
439
439
  * `indexSkills` is null when the index is unreadable — the index check already
440
440
  * reports that, so the row stays quiet instead of double-failing.
441
441
  * Only skills the package ships (packageRoot/skills) can make a fresh install
442
- * broken. The remaining references — the dart/eas/expo/flutter packs and
443
- * impeccable — are optional delivery downloaded on request and are simply not
442
+ * broken. The remaining references, the dart/expo/flutter packs and
443
+ * impeccable, are optional delivery downloaded on request and are simply not
444
444
  * present on a machine that never asked for them; the gate already degrades
445
445
  * them to "not enforced". Blocking on them turned every healthy fresh install
446
446
  * red (`missing from index: dart-...` right after novahiz-install --yes). */
@@ -451,13 +451,7 @@ export function referencedSkillsCheck(
451
451
  ): DoctorCheck {
452
452
  const referenced = referencedSkills(spec);
453
453
  const absent = indexSkills ? referenced.filter((id) => !indexSkills.has(id)) : [];
454
- const shipped = new Set<string>();
455
- const shippedDir = join(packageRootDir, "skills");
456
- if (existsSync(shippedDir)) {
457
- for (const entry of readdirSync(shippedDir, { withFileTypes: true })) {
458
- if (entry.isDirectory()) shipped.add(entry.name);
459
- }
460
- }
454
+ const shipped = shippedSkills(packageRootDir);
461
455
  const absentShipped = absent.filter((id) => shipped.has(id));
462
456
  const absentPack = absent.filter((id) => !shipped.has(id));
463
457
  // Only promise the update command when a pack can actually deliver what is
@@ -491,11 +485,14 @@ export function referencedSkillsCheck(
491
485
  * argv: an explicit `--skill a b c` names them, and `--skill *` delivers the
492
486
  * whole repo, whose ids start with the repo's leading segment with any hyphen
493
487
  * suffix dropped (`flutter/skills` -> `flutter-`, `dart-lang/skills` ->
494
- * `dart-`). Measured 2026-10-15: the 7 skills doctor reported as "pack skills
495
- * not installed" are `eas-*`, referenced as `defaultSkills` by the `test` and
496
- * `devops` categories while no provider ships them, so
497
- * "run /novahiz-skills-update, or novahiz deps --install" could not have
498
- * installed a single one. Advice no command can honour is a defect. */
488
+ * `dart-`). Measured 2026-10-15 on the 0.9.5 install: the 7 skills doctor
489
+ * reported as "pack skills not installed" were `eas-*`, referenced as
490
+ * `defaultSkills` by `test` and `devops` while the expo pack deliberately
491
+ * excludes them, so "run /novahiz-skills-update, or novahiz deps --install"
492
+ * could not have installed a single one. Advice no command can honour is a
493
+ * defect. Those references are gone from the catalog; the guard stays, since
494
+ * the same situation reappears whenever a pack is disabled or its `--skill`
495
+ * list stops naming an id the catalog still asks for. */
499
496
  function packDelivery(spec: ReturnType<typeof loadSpec>): { exact: Set<string>; prefixes: string[] } {
500
497
  const exact = new Set<string>();
501
498
  const prefixes: string[] = [];
@@ -518,6 +515,33 @@ function packDelivery(spec: ReturnType<typeof loadSpec>): { exact: Set<string>;
518
515
  return { exact, prefixes };
519
516
  }
520
517
 
518
+ /** Skill ids the package itself carries in `packageRootDir/skills`. */
519
+ function shippedSkills(packageRootDir: string): Set<string> {
520
+ const shipped = new Set<string>();
521
+ const shippedDir = join(packageRootDir, "skills");
522
+ if (existsSync(shippedDir)) {
523
+ for (const entry of readdirSync(shippedDir, { withFileTypes: true })) {
524
+ if (entry.isDirectory()) shipped.add(entry.name);
525
+ }
526
+ }
527
+ return shipped;
528
+ }
529
+
530
+ /** Referenced skill ids nothing in the system can supply: the package ships
531
+ * none of them and no enabled pack can install them. Exported so the catalog
532
+ * is held to this invariant in tests, since the row above reports it without
533
+ * blocking and therefore never failed on it. */
534
+ export function undeliverableReferences(
535
+ spec: ReturnType<typeof loadSpec>,
536
+ packageRootDir: string
537
+ ): string[] {
538
+ const shipped = shippedSkills(packageRootDir);
539
+ const { exact, prefixes } = packDelivery(spec);
540
+ return referencedSkills(spec).filter(
541
+ (id) => !shipped.has(id) && !exact.has(id) && !prefixes.some((prefix) => id.startsWith(prefix))
542
+ );
543
+ }
544
+
521
545
  /** Impeccable design-context checks: PRODUCT.md and DESIGN.md resolution
522
546
  * (root -> .agents/context -> docs/). Rows appear only when the impeccable
523
547
  * skill is installed (opt-in) and are never blocking: a missing record is a