diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc8fde03..713c0a50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,8 +153,8 @@ jobs: node-version: '24' package-manager-cache: false # Dependency-free; native Herdr checks run locally, not on headless CI. - - run: npm test --prefix herdr-insights - - run: npm run check --prefix herdr-insights + - run: npm run test:insights + - run: npm run check:insights secrets: uses: ./.github/workflows/secret-scan.yml diff --git a/README.md b/README.md index eaa1e6fb..3c15f368 100644 --- a/README.md +++ b/README.md @@ -86,19 +86,19 @@ pi install npm:@gagansd/pi-automode # Permission gate (opt-in, off by default) | Mod | Description | Install | | :--- | :--- | :--- | -| [Insights](./herdr-insights/) | Local-first token-usage dashboard with model rankings, usage trends, and an activity heatmap. Reads Pi, Codex, Claude Code, and OpenCode sessions. | From a checkout: `npm run install:local --prefix herdr-insights -- --open` | +| [Insights](./herdr-insights/) | Local-first token-usage dashboard with model rankings, usage trends, and an activity heatmap. Reads Pi, Codex, Claude Code, and OpenCode sessions. | From a checkout: `npm run install:local --prefix herdr-insights` | ### Install Insights Requires [Herdr](https://herdr.dev) and **Node.js 22.15+**. No dependencies or account needed. From this repository's root: ```bash -npm run install:local --prefix herdr-insights -- --open +npm run install:local --prefix herdr-insights ``` -The installer validates the mod with `herdr-app check` and opens an **Insights** tab. It installs only `mods/insights/` under `~/.config/herdr-app`, leaving layouts, keys, and terminals unchanged. No Pi extension installation is required. +The installer validates the mod with `herdr-app check` without opening a tab or test window. Add `-- --open` to open one **Insights** tab. It installs only `mods/insights/` under `~/.config/herdr-app`, leaving layouts, keys, and terminals unchanged. No Pi extension installation is required. -See the [Insights README](./herdr-insights/README.md) for standalone checkout instructions, settings, privacy details, and uninstallation, or [RECIPE.md](./herdr-insights/RECIPE.md) for the mod specification. +See the [Insights README](./herdr-insights/README.md) for standalone checkout instructions, settings, privacy details, and uninstallation, [INSTALL.md](./herdr-insights/INSTALL.md) for agent installation instructions, or [RECIPE.md](./herdr-insights/RECIPE.md) for the rebuild specification. --- @@ -124,10 +124,14 @@ After `pi install .`, local edits apply on `/reload`. Insights uses Node.js `22.15+`; its native validation requires Herdr. Run its checks separately from the root Pi extension suite: ```bash -npm test --prefix herdr-insights -npm run check --prefix herdr-insights +npm run test:insights +npm run check:insights ``` +These are developer checks, not installation steps. See the +[Insights development guide](./docs/herdr-insights-development.md) for opt-in +native release validation. + ### Shared token counter After installing root development dependencies, count literal text locally (no API calls): diff --git a/docs/herdr-insights-development.md b/docs/herdr-insights-development.md new file mode 100644 index 00000000..f3a7e26a --- /dev/null +++ b/docs/herdr-insights-development.md @@ -0,0 +1,72 @@ +# Insights development and release validation + +**Not an installation guide.** To install the existing build, use +[`herdr-insights/INSTALL.md`](../herdr-insights/INSTALL.md). The self-contained +[`RECIPE.md`](../herdr-insights/RECIPE.md) remains the Herdr rebuild brief. + +## Repository boundaries + +- `herdr-insights/`: shareable distribution; docs, preview, mod source, and + dependency-free install/uninstall scripts. This folder installs by itself. +- `tests/herdr-insights/`: unit/integration tests, fixtures, and native smoke tests. +- `scripts/herdr-insights/`: developer package/distribution checks. + +Keeping developer tooling outside the shared folder reduces the files an +installing agent needs to review. Herdr's own `AGENTS.md` still controls security +review; this separation does not exempt the mod source from that review. Moving +files out of the distribution does not remove any test coverage. + +## Headless checks + +From the repository root, with Node.js 22.15+ (CI uses Node 24): + +```sh +npm run test:insights +npm run check:insights +``` + +No dependency installation is needed. CI runs both commands, including accounting, +deduplication, configuration, incremental recovery, safe install/uninstall, +distribution structure, PNG, recipe, and installation-boundary regressions. +Neither command launches a Herdr window. Installer tests use a fake Herdr runner. + +## Native release checks — opt-in, not installation steps + +Requires Herdr on a graphical machine. The populated-render harness runs +`herdr-app check` against a temporary mod copy without collecting user transcripts: + +```sh +node tests/herdr-insights/native-loaded-check.mjs --rows 10000 +node tests/herdr-insights/native-loaded-check.mjs --rows 10000 --mode menu +node tests/herdr-insights/native-loaded-check.mjs --rows 10000 --mode settings +``` + +**The async harness opens a separate, visible Herdr window.** Tell the operator +before running it. It refuses to run without `--allow-window`; it is deliberately +not invoked by installation, headless tests, or package checks: + +```sh +node tests/herdr-insights/native-async-check.mjs --allow-window +node tests/herdr-insights/native-async-check.mjs --allow-window --settings --appearance light +node tests/herdr-insights/native-async-check.mjs --allow-window --menu +``` + +It uses a temporary app home with fixture data, captures a screenshot and status, +and stops only the process it started. Synthetic rows must never be installed +into a user's app home. Inspect screenshots and logs for rendering errors. Verify +filters, heatmap modes, settings, refresh, and empty/error states before release. + +Reinstall validation must preserve source transcripts, layouts, keys, local edits, +and `insights.json`; the automated installer tests cover those boundaries. + +## Sharing a build + +Keep `herdr-insights/` self-contained, with `RECIPE.md`, `INSTALL.md`, `README.md`, +`preview.png`, and `mods/insights/`. Do not ship layouts, key snippets, developer +tests/fixtures, or smoke-test tooling in that folder. The installer copies only +the mod tree, generates the local Node path, and records ownership separately. + +For Herdr Community discovery, publish the folder in a public GitHub repo and +add topic `herdr-mod` after merging to the default branch. Allow up to an hour for +indexing and at least 24 hours for picture screening. Do not republish or change +repository visibility as part of an end-user installation. diff --git a/herdr-insights/INSTALL.md b/herdr-insights/INSTALL.md new file mode 100644 index 00000000..1edb8523 --- /dev/null +++ b/herdr-insights/INSTALL.md @@ -0,0 +1,36 @@ +# Install Insights + +Requires Herdr and **Node.js 22.15+**. Reuse an existing checkout when available. +Follow Herdr's `AGENTS.md`; this guide does not override its review requirements. +`RECIPE.md` describes rebuilding, not installation. + +## Install + +From the parent of `herdr-insights/`: + +```sh +npm run install:local --prefix herdr-insights +``` + +Only that folder is needed; no `npm install` is required. The installer copies +the mod, records ownership, configures the local Node path, and runs +`herdr-app check`. Existing usage data, layouts, keys, and terminals stay intact. + +Finish when the installer exits successfully with **`herdr-app check passed`**. +Report failures and stop; do not force overwrites or rebuild to bypass errors. +Do not run developer tests or change tabs/focus during installation. Never +launch a second Herdr instance. + +## Open only if requested + +```sh +herdr-app do tab.new '{"name":"Insights","mod":"insights"}' +``` + +## Uninstall + +Usage data and locally edited/unowned files are preserved: + +```sh +npm run uninstall:local --prefix herdr-insights +``` diff --git a/herdr-insights/README.md b/herdr-insights/README.md index 1d544ffe..c62b7fb5 100644 --- a/herdr-insights/README.md +++ b/herdr-insights/README.md @@ -10,25 +10,24 @@ Reads local sessions from **Pi, Codex, Claude Code, and OpenCode**. ## Install -Installing requires Herdr and **Node.js 22.15+**. No dependencies or account needed. -The uninstaller also runs on older Node versions. - Ask your agent: - -> Read https://github.com/GaganSD/coding-agent-toolkit/tree/main/herdr-insights and install the Insights mod following its RECIPE.md and my Herdr AGENTS.md. +``` +Read https://github.com/GaganSD/coding-agent-toolkit/blob/main/herdr-insights/INSTALL.md and install Insights following it. +``` Or install from a checkout: ```sh git clone https://github.com/GaganSD/coding-agent-toolkit.git cd coding-agent-toolkit -npm run install:local --prefix herdr-insights -- --open +npm run install:local --prefix herdr-insights ``` -The installer runs `herdr-app check`; with `--open` it then opens an **Insights** -tab. It installs owned files only under `mods/insights/` and writes its install -manifest to `data/insights/` in `~/.config/herdr-app`; your layouts, keys, and -terminals are unchanged. Set `HERDR_APP_HOME` to use a different app home. +The installer runs `herdr-app check` without opening a tab or test window. +Follow [INSTALL.md](INSTALL.md) for the installation steps. Add `-- --open` only +if you want it to open one **Insights** tab. It installs owned files only under +`mods/insights/` and writes its install manifest to `data/insights/` in +`~/.config/herdr-app`; your layouts, keys, and terminals are unchanged. Set `HERDR_APP_HOME` to use a different app home. ## Use @@ -46,14 +45,13 @@ response text or modify source transcripts. ## Development -[RECIPE.md](RECIPE.md) describes the mod's behavior, architecture, and rebuild -requirements. The distribution folder is `herdr-insights`; its native mod ID is -`insights` and its display name is **Insights**. No layouts or key bindings ship. +[RECIPE.md](RECIPE.md) is the self-contained rebuild brief required by Herdr. +The distribution folder is `herdr-insights`; its native mod ID is `insights` +and its display name is **Insights**. No layouts or key bindings ship. -```sh -npm test --prefix herdr-insights -npm run check --prefix herdr-insights -``` +Developer tests, fixtures, and release validation live outside this distribution; +see the repo's [development guide](../docs/herdr-insights-development.md). They are +not installation steps. Uninstall while keeping usage data: diff --git a/herdr-insights/RECIPE.md b/herdr-insights/RECIPE.md index 59a90c8e..99b93026 100644 --- a/herdr-insights/RECIPE.md +++ b/herdr-insights/RECIPE.md @@ -6,6 +6,11 @@ and opens in its own tab without changing terminals, layouts, or keys. ![Insights](preview.png) +Installing this existing build? Follow [INSTALL.md](INSTALL.md) and the user's +Herdr `AGENTS.md`. This recipe is a rebuild brief; its **Done when** checks apply +to building/releasing, not routine installation. Installation does not launch +smoke-test windows or open a tab unless requested. + **What you see:** A vertically scrollable dashboard centered at a maximum width of 1180px. Use Herdr theme colors, 1px borders, 8px card corners, 6px control corners, no decorative shadows, and 4/8/12/16/24px spacing. Page padding is 12px @@ -53,9 +58,10 @@ controls preserve custom mappings. Changing source configuration invalidates the scan cache; palette/cache/week-start/refresh changes preserve collected history. **Built from:** Put this distribution in `herdr-insights/`, with `RECIPE.md`, -`README.md`, `preview.png`, and `mods/insights/`. Do not include a `layouts/` folder -or keys snippet: no bindings are needed. The recipe heading and `mod.json` title -are **Insights**; the native mod ID is `insights`. +`README.md`, `INSTALL.md`, `preview.png`, and `mods/insights/`. Keep developer +tests, fixtures, and release-check tooling outside this distribution folder. +Do not include a `layouts/` folder or keys snippet: no bindings are needed. +The recipe heading and `mod.json` title are **Insights**; the native mod ID is `insights`. `mods/insights/mod.json` contains `{"place":"none","title":"Insights"}`. `main.js` default-exports a gpui-kit `View`; use gpui-base flex/controls, `theme()` @@ -115,9 +121,10 @@ a backup before rebuilding; read-only prepare must not repair or create stores. Never write layouts, keys, source transcripts, or an existing user's usage data from the installer. Do not copy an absolute runtime path from another machine. -**Done when:** Package tests and checks pass, including distribution naming, -PNG preview, recipe structure, no layouts, token accounting, deduplication, -configuration validation, incremental recovery, and safe installation/uninstallation. +**Done when:** For a new build or release (not an installation), package tests +and checks pass, including distribution naming, PNG preview, recipe structure, +no layouts, token accounting, deduplication, configuration validation, incremental +recovery, and safe installation/uninstallation. Run `herdr-app check` on the installed mod, plus populated-render and asynchronous native smoke checks in temporary app homes. Inspect a native screenshot and status for render errors. Verify actual filters, heatmap modes, source settings, refresh, diff --git a/herdr-insights/package.json b/herdr-insights/package.json index ae46ff46..eb3a2ee3 100644 --- a/herdr-insights/package.json +++ b/herdr-insights/package.json @@ -9,9 +9,7 @@ "node": ">=22.15" }, "scripts": { - "test": "node --test tests/*.test.mjs", "install:local": "node scripts/install.mjs", - "uninstall:local": "node scripts/uninstall.mjs", - "check": "node scripts/check.mjs" + "uninstall:local": "node scripts/uninstall.mjs" } } diff --git a/llms.txt b/llms.txt index 208d30e7..ec5834fb 100644 --- a/llms.txt +++ b/llms.txt @@ -25,11 +25,13 @@ Packages and mods live at the repository root, not under `packages/`. Pi extensi ## Herdr mods +- [Insights installation](herdr-insights/INSTALL.md): agent installation steps and completion criteria - [Insights README](herdr-insights/README.md): installation, settings, privacy, development, uninstallation - [Insights recipe](herdr-insights/RECIPE.md): native mod specification, architecture, rebuild requirements -- [Insights manifest](herdr-insights/package.json): local install, uninstall, test, and check scripts; not published on npm +- [Insights manifest](herdr-insights/package.json): local install and uninstall scripts; not published on npm +- [Insights development](docs/herdr-insights-development.md): tests and opt-in native release validation -Insights requires Herdr and Node.js 22.15+. From a checkout, run `npm run install:local --prefix herdr-insights -- --open`. The installer validates and opens the mod without changing layouts, keys, or terminals. Session data stays local; no account or API key is required. Run Insights tests separately with `npm test --prefix herdr-insights` and native checks with `npm run check --prefix herdr-insights`. +For installation, follow herdr-insights/INSTALL.md and the user's Herdr AGENTS.md. Insights requires Herdr and Node.js 22.15+. From a checkout, run `npm run install:local --prefix herdr-insights`; it runs `herdr-app check` without opening tabs or test windows. Add `-- --open` only when requested. Layouts, keys, and terminals are unchanged. Session data stays local; no account or API key is required. RECIPE.md is the rebuild brief, not an installation test plan. Developer checks (`npm run test:insights`, `npm run check:insights`) and opt-in native smoke tests live outside the distribution; see docs/herdr-insights-development.md. ## Agent indexes diff --git a/package.json b/package.json index aca0b235..0a3a6053 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "typecheck": "npm run typecheck --prefix pi-slate && npm run typecheck --prefix pi-ask && npm run typecheck --prefix pi-web-search && npm run typecheck --prefix pi-subagents && npm run typecheck --prefix pi-automode", "lint": "eslint . --max-warnings=0", "test:tooling": "node --test scripts/tests/*.test.mjs", + "test:insights": "node --test tests/herdr-insights/*.test.mjs", + "check:insights": "node scripts/herdr-insights/check.mjs", "secrets:scan": "bash scripts/secret-scan.sh", "hooks:install": "bash scripts/install-hooks.sh", "release:prepare": "node scripts/release.mjs prepare", diff --git a/herdr-insights/scripts/check.mjs b/scripts/herdr-insights/check.mjs similarity index 93% rename from herdr-insights/scripts/check.mjs rename to scripts/herdr-insights/check.mjs index 8ad3326a..47c01a12 100644 --- a/herdr-insights/scripts/check.mjs +++ b/scripts/herdr-insights/check.mjs @@ -8,9 +8,10 @@ import { assertSupportedNodeVersion, collectSourceFiles, lstatOrNull, -} from "./lib.mjs"; +} from "../../herdr-insights/scripts/lib.mjs"; import { inspectDistribution } from "./distribution.mjs"; +const TEST_ROOT = path.resolve(PACKAGE_ROOT, "../tests/herdr-insights"); const errors = []; const notes = []; @@ -102,9 +103,7 @@ async function checkPackage() { } const expectedScripts = { - check: "node scripts/check.mjs", "install:local": "node scripts/install.mjs", - test: "node --test tests/*.test.mjs", "uninstall:local": "node scripts/uninstall.mjs", }; for (const [name, expected] of Object.entries(expectedScripts)) { @@ -117,6 +116,7 @@ async function checkPackage() { async function checkFiles() { for (const relative of [ ".gitignore", + "INSTALL.md", "RECIPE.md", "preview.png", "mods/insights/main.js", @@ -124,8 +124,6 @@ async function checkFiles() { "scripts/install.mjs", "scripts/lib.mjs", "scripts/uninstall.mjs", - "tests/install.test.mjs", - "tests/node-version.test.mjs", ]) { const stat = await lstatOrNull(path.join(PACKAGE_ROOT, relative)); if (!stat || !stat.isFile()) fail(`Missing owned file: ${relative}`); @@ -165,8 +163,8 @@ async function checkDocs() { } async function checkScriptsAndTests() { - const directories = ["scripts", "tests"] - .map((relative) => path.join(PACKAGE_ROOT, relative)); + const directories = [path.join(PACKAGE_ROOT, "scripts"), + path.resolve(PACKAGE_ROOT, "../scripts/herdr-insights"), TEST_ROOT]; for (const directory of directories) { const files = (await walkFiles(directory)).filter((file) => /\.(?:mjs|js)$/.test(file), @@ -190,10 +188,10 @@ async function checkScriptsAndTests() { } async function checkTestInventory() { - const tests = (await walkFiles(path.join(PACKAGE_ROOT, "tests"))).filter((file) => + const tests = (await walkFiles(TEST_ROOT)).filter((file) => /install.*\.test\.mjs$/.test(path.basename(file)), ); - if (tests.length === 0) fail("No tests/install*.test.mjs files found"); + if (tests.length === 0) fail("No tests/herdr-insights/install*.test.mjs files found"); } async function checkDistribution() { diff --git a/herdr-insights/scripts/distribution.mjs b/scripts/herdr-insights/distribution.mjs similarity index 71% rename from herdr-insights/scripts/distribution.mjs rename to scripts/herdr-insights/distribution.mjs index 70e324b5..269fbe13 100644 --- a/herdr-insights/scripts/distribution.mjs +++ b/scripts/herdr-insights/distribution.mjs @@ -1,7 +1,7 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import { lstatOrNull } from "./lib.mjs"; +import { lstatOrNull } from "../../herdr-insights/scripts/lib.mjs"; import { validatePng } from "./png.mjs"; export async function inspectDistribution(packageRoot) { @@ -14,6 +14,7 @@ export async function inspectDistribution(packageRoot) { for (const [relative, message] of [ ["layouts", "Herdr mods must not ship layouts/"], ["keys.json", "Herdr mods must not ship keys.json"], + ["tests", "Developer tests and fixtures must stay outside herdr-insights/"], ]) { try { if (await lstatOrNull(path.join(packageRoot, relative))) errors.push(message); @@ -22,6 +23,15 @@ export async function inspectDistribution(packageRoot) { } } + try { + const allowed = new Set(["install.mjs", "lib.mjs", "uninstall.mjs"]); + for (const name of await fs.readdir(path.join(packageRoot, "scripts"))) { + if (!allowed.has(name)) errors.push(`Developer tooling must stay outside herdr-insights/scripts/: ${name}`); + } + } catch (error) { + if (error.code !== "ENOENT") errors.push(`Cannot inspect install scripts: ${error.message}`); + } + try { const preview = await fs.readFile(path.join(packageRoot, "preview.png")); const image = validatePng(preview); diff --git a/herdr-insights/scripts/png.mjs b/scripts/herdr-insights/png.mjs similarity index 100% rename from herdr-insights/scripts/png.mjs rename to scripts/herdr-insights/png.mjs diff --git a/herdr-insights/tests/analytics-ui.test.mjs b/tests/herdr-insights/analytics-ui.test.mjs similarity index 99% rename from herdr-insights/tests/analytics-ui.test.mjs rename to tests/herdr-insights/analytics-ui.test.mjs index 525b94cd..9df70b3b 100644 --- a/herdr-insights/tests/analytics-ui.test.mjs +++ b/tests/herdr-insights/analytics-ui.test.mjs @@ -2,9 +2,9 @@ import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; -const mainSource = await readFile(new URL("../mods/insights/main.js", import.meta.url), "utf8"); -const analyticsSource = await readFile(new URL("../mods/insights/analytics.js", import.meta.url), "utf8"); -const uiSource = await readFile(new URL("../mods/insights/ui.js", import.meta.url), "utf8"); +const mainSource = await readFile(new URL("../../herdr-insights/mods/insights/main.js", import.meta.url), "utf8"); +const analyticsSource = await readFile(new URL("../../herdr-insights/mods/insights/analytics.js", import.meta.url), "utf8"); +const uiSource = await readFile(new URL("../../herdr-insights/mods/insights/ui.js", import.meta.url), "utf8"); const dataUrl = (source) => `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`; const analyticsUrl = dataUrl(analyticsSource); const analytics = await import(analyticsUrl); @@ -551,7 +551,7 @@ test("usage logos prefer model marks, then provider marks, then initials", async "openai.svg", "opencode.svg", "xai.svg", - ].map((file) => readFile(new URL(`../mods/insights/logos/${file}`, import.meta.url)))); + ].map((file) => readFile(new URL(`../../herdr-insights/mods/insights/logos/${file}`, import.meta.url)))); }); test("model usage leaderboard is models-only with ranked change rows", () => { diff --git a/herdr-insights/tests/analytics.test.mjs b/tests/herdr-insights/analytics.test.mjs similarity index 99% rename from herdr-insights/tests/analytics.test.mjs rename to tests/herdr-insights/analytics.test.mjs index 688c8a47..c5c9abfc 100644 --- a/herdr-insights/tests/analytics.test.mjs +++ b/tests/herdr-insights/analytics.test.mjs @@ -4,7 +4,7 @@ import { readFile } from "node:fs/promises"; // Exercise the file as an ES module without relying on a package.json "type". process.env.TZ = "America/New_York"; -const source = await readFile(new URL("../mods/insights/analytics.js", import.meta.url), "utf8"); +const source = await readFile(new URL("../../herdr-insights/mods/insights/analytics.js", import.meta.url), "utf8"); const analytics = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`); const { addDays, diff --git a/herdr-insights/tests/collector-adapters.test.mjs b/tests/herdr-insights/collector-adapters.test.mjs similarity index 99% rename from herdr-insights/tests/collector-adapters.test.mjs rename to tests/herdr-insights/collector-adapters.test.mjs index 7a906741..04d1ecd6 100644 --- a/herdr-insights/tests/collector-adapters.test.mjs +++ b/tests/herdr-insights/collector-adapters.test.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); const fixtures = path.join(here, "fixtures"); async function env() { diff --git a/herdr-insights/tests/collector-cli.test.mjs b/tests/herdr-insights/collector-cli.test.mjs similarity index 97% rename from herdr-insights/tests/collector-cli.test.mjs rename to tests/herdr-insights/collector-cli.test.mjs index 043becb4..b44c46e6 100644 --- a/herdr-insights/tests/collector-cli.test.mjs +++ b/tests/herdr-insights/collector-cli.test.mjs @@ -14,7 +14,7 @@ import { fileURLToPath, pathToFileURL } from "node:url"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const modsDir = path.join(here, "..", "mods", "insights"); +const modsDir = path.join(here, "../../herdr-insights", "mods", "insights"); function run(script, args, { cwd, environment = {} } = {}) { return new Promise((resolve, reject) => { diff --git a/herdr-insights/tests/collector-config.test.mjs b/tests/herdr-insights/collector-config.test.mjs similarity index 98% rename from herdr-insights/tests/collector-config.test.mjs rename to tests/herdr-insights/collector-config.test.mjs index b2a39658..4284ba4f 100644 --- a/herdr-insights/tests/collector-config.test.mjs +++ b/tests/herdr-insights/collector-config.test.mjs @@ -19,12 +19,12 @@ import { DEFAULT_CONFIG, defaultConfig, normalizeConfig, -} from "../mods/insights/core/config.mjs"; -import { emptyStore, newFileEntry, saveStore } from "../mods/insights/core/store.mjs"; -import { DEFAULT_SOURCES } from "../mods/insights/analytics.js"; +} from "../../herdr-insights/mods/insights/core/config.mjs"; +import { emptyStore, newFileEntry, saveStore } from "../../herdr-insights/mods/insights/core/store.mjs"; +import { DEFAULT_SOURCES } from "../../herdr-insights/mods/insights/analytics.js"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); const fixtures = path.join(here, "fixtures"); async function env() { diff --git a/herdr-insights/tests/collector-dedup.test.mjs b/tests/herdr-insights/collector-dedup.test.mjs similarity index 97% rename from herdr-insights/tests/collector-dedup.test.mjs rename to tests/herdr-insights/collector-dedup.test.mjs index d47f7536..144d7570 100644 --- a/herdr-insights/tests/collector-dedup.test.mjs +++ b/tests/herdr-insights/collector-dedup.test.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); const fixtures = path.join(here, "fixtures"); async function env() { diff --git a/herdr-insights/tests/collector-incremental.test.mjs b/tests/herdr-insights/collector-incremental.test.mjs similarity index 99% rename from herdr-insights/tests/collector-incremental.test.mjs rename to tests/herdr-insights/collector-incremental.test.mjs index c2ef2a60..8f93f5a7 100644 --- a/herdr-insights/tests/collector-incremental.test.mjs +++ b/tests/herdr-insights/collector-incremental.test.mjs @@ -17,7 +17,7 @@ import { fileURLToPath } from "node:url"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); const fixtures = path.join(here, "fixtures"); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/herdr-insights/tests/collector-opencode.test.mjs b/tests/herdr-insights/collector-opencode.test.mjs similarity index 98% rename from herdr-insights/tests/collector-opencode.test.mjs rename to tests/herdr-insights/collector-opencode.test.mjs index c7430220..b016c050 100644 --- a/herdr-insights/tests/collector-opencode.test.mjs +++ b/tests/herdr-insights/collector-opencode.test.mjs @@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); function run(args) { return new Promise((resolve, reject) => { diff --git a/herdr-insights/tests/collector-runtime.test.mjs b/tests/herdr-insights/collector-runtime.test.mjs similarity index 96% rename from herdr-insights/tests/collector-runtime.test.mjs rename to tests/herdr-insights/collector-runtime.test.mjs index 2470dff8..053b48f6 100644 --- a/herdr-insights/tests/collector-runtime.test.mjs +++ b/tests/herdr-insights/collector-runtime.test.mjs @@ -8,8 +8,8 @@ import * as zlib from "node:zlib"; import test from "node:test"; const here = path.dirname(fileURLToPath(import.meta.url)); -const collector = path.join(here, "..", "mods", "insights", "collector.mjs"); -const scanModule = path.join(here, "..", "mods", "insights", "core", "scan.mjs"); +const collector = path.join(here, "../../herdr-insights", "mods", "insights", "collector.mjs"); +const scanModule = path.join(here, "../../herdr-insights", "mods", "insights", "core", "scan.mjs"); const fixtures = path.join(here, "fixtures"); function probeWithoutZstd(nodePath) { diff --git a/herdr-insights/tests/distribution.test.mjs b/tests/herdr-insights/distribution.test.mjs similarity index 64% rename from herdr-insights/tests/distribution.test.mjs rename to tests/herdr-insights/distribution.test.mjs index 1ce6a2ce..9c7d8cad 100644 --- a/herdr-insights/tests/distribution.test.mjs +++ b/tests/herdr-insights/distribution.test.mjs @@ -5,10 +5,10 @@ import { fileURLToPath } from "node:url"; import path from "node:path"; import test from "node:test"; -import { inspectDistribution } from "../scripts/distribution.mjs"; -import { validatePng } from "../scripts/png.mjs"; +import { inspectDistribution } from "../../scripts/herdr-insights/distribution.mjs"; +import { validatePng } from "../../scripts/herdr-insights/png.mjs"; -const root = fileURLToPath(new URL("../", import.meta.url)); +const root = fileURLToPath(new URL("../../herdr-insights/", import.meta.url)); const read = (relative) => readFile(path.join(root, relative), "utf8"); test("Herdr distribution has the requested folder and display name, with no layouts or bindings", async () => { @@ -76,6 +76,38 @@ test("Distribution inspection rejects keys.json and layouts without ignoring the assert.match((await inspectDistribution(packageRoot)).errors.join("\n"), /layouts/i); }); +test("shared distribution excludes developer tests, fixtures, and smoke tooling", async () => { + await assert.rejects(lstat(path.join(root, "tests")), { code: "ENOENT" }); + assert.deepEqual((await readdir(path.join(root, "scripts"))).sort(), + ["install.mjs", "lib.mjs", "uninstall.mjs"]); + assert.deepEqual((await inspectDistribution(root)).errors, []); +}); + +test("distribution inspection rejects developer tooling in the shared folder", async (t) => { + const temporary = await mkdtemp(path.join(tmpdir(), "insights-dev-boundary-")); + t.after(async () => rm(temporary, { recursive: true, force: true })); + const packageRoot = path.join(temporary, "herdr-insights"); + await mkdir(path.join(packageRoot, "tests"), { recursive: true }); + await mkdir(path.join(packageRoot, "scripts")); + await writeFile(path.join(packageRoot, "scripts", "native-smoke.mjs"), "// developer only"); + const errors = (await inspectDistribution(packageRoot)).errors.join("\n"); + assert.match(errors, /tests and fixtures must stay outside/); + assert.match(errors, /Developer tooling must stay outside.*native-smoke/); +}); + +test("agent installation entry point separates installation from rebuilding", async () => { + const readme = await read("README.md"); + const install = await read("INSTALL.md"); + const recipe = await read("RECIPE.md"); + assert.match(readme, /blob\/main\/herdr-insights\/INSTALL\.md/); + assert.doesNotMatch(readme, /following its RECIPE\.md/); + assert.match(install, /does not override its review requirements/); + assert.match(install, /npm run install:local --prefix herdr-insights\n/); + assert.match(install, /Do not run developer tests/); + assert.match(install, /Never\nlaunch a second Herdr instance/); + assert.match(recipe, /Done when\*\* checks apply\nto building\/releasing, not routine installation/); +}); + test("Recipe follows Herdr's required order and remains self-contained", async () => { const recipe = await read("RECIPE.md"); let previous = -1; diff --git a/herdr-insights/tests/fixtures/claude-session.jsonl b/tests/herdr-insights/fixtures/claude-session.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/claude-session.jsonl rename to tests/herdr-insights/fixtures/claude-session.jsonl diff --git a/herdr-insights/tests/fixtures/codex-fork.jsonl b/tests/herdr-insights/fixtures/codex-fork.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/codex-fork.jsonl rename to tests/herdr-insights/fixtures/codex-fork.jsonl diff --git a/herdr-insights/tests/fixtures/codex-legacy.jsonl b/tests/herdr-insights/fixtures/codex-legacy.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/codex-legacy.jsonl rename to tests/herdr-insights/fixtures/codex-legacy.jsonl diff --git a/herdr-insights/tests/fixtures/codex-mixed.jsonl b/tests/herdr-insights/fixtures/codex-mixed.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/codex-mixed.jsonl rename to tests/herdr-insights/fixtures/codex-mixed.jsonl diff --git a/herdr-insights/tests/fixtures/codex-modern.jsonl b/tests/herdr-insights/fixtures/codex-modern.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/codex-modern.jsonl rename to tests/herdr-insights/fixtures/codex-modern.jsonl diff --git a/herdr-insights/tests/fixtures/codex-modern.jsonl.zst b/tests/herdr-insights/fixtures/codex-modern.jsonl.zst similarity index 100% rename from herdr-insights/tests/fixtures/codex-modern.jsonl.zst rename to tests/herdr-insights/fixtures/codex-modern.jsonl.zst diff --git a/herdr-insights/tests/fixtures/custom.jsonl b/tests/herdr-insights/fixtures/custom.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/custom.jsonl rename to tests/herdr-insights/fixtures/custom.jsonl diff --git a/herdr-insights/tests/fixtures/pi-fork.jsonl b/tests/herdr-insights/fixtures/pi-fork.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/pi-fork.jsonl rename to tests/herdr-insights/fixtures/pi-fork.jsonl diff --git a/herdr-insights/tests/fixtures/pi-main.jsonl b/tests/herdr-insights/fixtures/pi-main.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/pi-main.jsonl rename to tests/herdr-insights/fixtures/pi-main.jsonl diff --git a/herdr-insights/tests/fixtures/pi-subagent.jsonl b/tests/herdr-insights/fixtures/pi-subagent.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/pi-subagent.jsonl rename to tests/herdr-insights/fixtures/pi-subagent.jsonl diff --git a/herdr-insights/tests/fixtures/pi-usage-carriers.jsonl b/tests/herdr-insights/fixtures/pi-usage-carriers.jsonl similarity index 100% rename from herdr-insights/tests/fixtures/pi-usage-carriers.jsonl rename to tests/herdr-insights/fixtures/pi-usage-carriers.jsonl diff --git a/herdr-insights/tests/install.test.mjs b/tests/herdr-insights/install.test.mjs similarity index 94% rename from herdr-insights/tests/install.test.mjs rename to tests/herdr-insights/install.test.mjs index 72cb6435..ff1db0a7 100644 --- a/herdr-insights/tests/install.test.mjs +++ b/tests/herdr-insights/install.test.mjs @@ -7,15 +7,15 @@ import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; -import { isNodeVersionSupported } from "../scripts/lib.mjs"; +import { isNodeVersionSupported } from "../../herdr-insights/scripts/lib.mjs"; const PACKAGE_ROOT = path.resolve( path.dirname(fileURLToPath(import.meta.url)), - "..", + "../../herdr-insights", ); const INSTALL_SCRIPT = path.join(PACKAGE_ROOT, "scripts", "install.mjs"); const UNINSTALL_SCRIPT = path.join(PACKAGE_ROOT, "scripts", "uninstall.mjs"); -const CHECK_SCRIPT = path.join(PACKAGE_ROOT, "scripts", "check.mjs"); +const CHECK_SCRIPT = fileURLToPath(new URL("../../scripts/herdr-insights/check.mjs", import.meta.url)); const OPEN_JSON = '{"name":"Insights","mod":"insights"}'; const RUNTIME_REL = "runtime.json"; @@ -743,6 +743,46 @@ test("uninstaller remains usable on an older Node runtime", async (t) => { ); }); +test("standalone distribution installs with no repo tooling or window commands", async (t) => { + const root = await temporaryRoot(t); + const distribution = path.join(root, "herdr-insights"); + await fs.cp(PACKAGE_ROOT, distribution, { recursive: true }); + const appHome = path.join(root, "app"); + const fake = await createFakeRunner(root); + const result = spawnSync(process.execPath, [path.join(distribution, "scripts/install.mjs")], { + cwd: distribution, + env: makeEnvironment(root, appHome, fake), + encoding: "utf8", + shell: false, + timeout: 30_000, + }); + assertSuccess(result); + assert.match(result.stdout, /herdr-app check passed/); + assert.deepEqual((await readLog(fake)).map((entry) => entry.args), + [["check", path.join(await fs.realpath(appHome), "mods/insights")]]); + await assert.rejects(fs.lstat(path.join(appHome, "data/insights/insights.json")), { code: "ENOENT" }); + assert.deepEqual((await fs.readdir(root)).sort(), + ["app", "fake bin", "fake herdr log.jsonl", "herdr-insights"]); +}); + +test("async smoke test refuses to open a window without explicit opt-in", async (t) => { + const root = await temporaryRoot(t); + const script = fileURLToPath(new URL("./native-async-check.mjs", import.meta.url)); + const result = spawnSync(process.execPath, [script], { + cwd: root, + env: { ...process.env, TMPDIR: root, TMP: root, TEMP: root, + HERDR_APP_HOME: path.join(root, "must-not-create"), + HERDR_APP_BINARY: path.join(root, "must-not-launch") }, + encoding: "utf8", + shell: false, + timeout: 10_000, + }); + assert.equal(result.status, 2, result.stderr); + assert.match(result.stderr, /separate Herdr window.*--allow-window/); + assert.match(result.stderr, /never during installation/); + assert.deepEqual(await fs.readdir(root), []); +}); + test("the package stays private, dependency-free, and the check script passes", async () => { const packageJson = await readJson(path.join(PACKAGE_ROOT, "package.json")); assert.equal(packageJson.private, true); @@ -757,9 +797,7 @@ test("the package stays private, dependency-free, and the check script passes", assert.equal(packageJson[field], undefined, field); } assert.deepEqual(packageJson.scripts, { - check: "node scripts/check.mjs", "install:local": "node scripts/install.mjs", - test: "node --test tests/*.test.mjs", "uninstall:local": "node scripts/uninstall.mjs", }); assert.equal(packageJson.publishConfig, undefined); diff --git a/herdr-insights/tests/native-async-check.mjs b/tests/herdr-insights/native-async-check.mjs similarity index 92% rename from herdr-insights/tests/native-async-check.mjs rename to tests/herdr-insights/native-async-check.mjs index 7702327c..b735864e 100644 --- a/herdr-insights/tests/native-async-check.mjs +++ b/tests/herdr-insights/native-async-check.mjs @@ -1,5 +1,7 @@ #!/usr/bin/env node -// True asynchronous lifecycle check for the Insights native mod. +// Developer-only asynchronous lifecycle check for the Insights native mod. +// Opens a separate Herdr window: never run during installation. +// Requires --allow-window to acknowledge that visible side effect. // // The older loaded-render check injected applySnapshot in init, which never // exercised the exec -> Task continuation -> timer -> presentation helper path. @@ -11,11 +13,11 @@ // interrupted" signature. // // Usage: -// node tests/native-async-check.mjs -// node tests/native-async-check.mjs --rows 10000 --snapshot /path/to/snapshot.json -// node tests/native-async-check.mjs --store /tmp/store-copy.json --wait 5000 -// node tests/native-async-check.mjs --settings --appearance light --zoom-out 2 -// node tests/native-async-check.mjs --menu --shot /tmp/menu.png --keep +// node tests/herdr-insights/native-async-check.mjs --allow-window +// node tests/herdr-insights/native-async-check.mjs --allow-window --rows 10000 --snapshot /path/to/snapshot.json +// node tests/herdr-insights/native-async-check.mjs --allow-window --store /tmp/store-copy.json --wait 5000 +// node tests/herdr-insights/native-async-check.mjs --allow-window --settings --appearance light --zoom-out 2 +// node tests/herdr-insights/native-async-check.mjs --allow-window --menu --shot /tmp/menu.png --keep // // Only the Herdr process started here is stopped. It creates no terminals, // spaces, tabs, or user-home writes. @@ -35,16 +37,16 @@ import { import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { normalizeConfig, todayKey } from "../mods/insights/analytics.js"; +import { normalizeConfig, todayKey } from "../../herdr-insights/mods/insights/analytics.js"; import { emptyStore, newFileEntry, saveStore, -} from "../mods/insights/core/store.mjs"; -import { run as runPresenter } from "../mods/insights/presenter.mjs"; +} from "../../herdr-insights/mods/insights/core/store.mjs"; +import { run as runPresenter } from "../../herdr-insights/mods/insights/presenter.mjs"; const here = dirname(fileURLToPath(import.meta.url)); -const modSource = resolve(here, "../mods/insights"); +const modSource = resolve(here, "../../herdr-insights/mods/insights"); function argument(name, fallback) { const index = process.argv.indexOf(name); @@ -164,6 +166,10 @@ const appearance = argument("--appearance", null); if (appearance !== null && !["dark", "light"].includes(appearance)) { throw new Error("--appearance must be dark or light"); } +if (!process.argv.includes("--allow-window")) { + console.error("Developer smoke test opens a separate Herdr window. Rerun with --allow-window only for explicit development/release validation; never during installation."); + process.exit(2); +} const home = realpathSync(mkdtempSync(join(tmpdir(), "insights-native-async-"))); const logPath = "/tmp/insights-native-async.log"; const shotPath = argument("--shot", "/tmp/insights-native-async.png"); diff --git a/herdr-insights/tests/native-loaded-check.mjs b/tests/herdr-insights/native-loaded-check.mjs similarity index 91% rename from herdr-insights/tests/native-loaded-check.mjs rename to tests/herdr-insights/native-loaded-check.mjs index edd7cd99..778acd19 100644 --- a/herdr-insights/tests/native-loaded-check.mjs +++ b/tests/herdr-insights/native-loaded-check.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node -// Loaded-render regression check for the Insights native mod. +// Developer-only loaded-render regression check for the Insights native mod. +// Not an installation step; this uses a temporary mod, not the installed build. // // `herdr-app check` normally renders the empty initial state, so it cannot see // a populated dashboard crossing the 50 ms render budget. This harness injects @@ -7,12 +8,12 @@ // first render. It deliberately replaces runtime resolution and refresh timers // in the temporary mod copy, so no collector runs and no user transcript roots // are scanned. The true exec -> Task -> timer lifecycle is covered separately -// by tests/native-async-check.mjs. +// by tests/herdr-insights/native-async-check.mjs. // // Usage: -// node tests/native-loaded-check.mjs -// node tests/native-loaded-check.mjs --rows 10000 --mode menu -// node tests/native-loaded-check.mjs --snapshot /path/to/warm-snapshot.json +// node tests/herdr-insights/native-loaded-check.mjs +// node tests/herdr-insights/native-loaded-check.mjs --rows 10000 --mode menu +// node tests/herdr-insights/native-loaded-check.mjs --snapshot /path/to/warm-snapshot.json // // The snapshot file is read only. Nothing is installed into the user's Herdr // home: the check runs against a temporary HERDR_APP_HOME. @@ -22,10 +23,10 @@ import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:f import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { compactDashboard, deriveDashboard, todayKey } from "../mods/insights/analytics.js"; +import { compactDashboard, deriveDashboard, todayKey } from "../../herdr-insights/mods/insights/analytics.js"; const here = dirname(fileURLToPath(import.meta.url)); -const modSource = resolve(here, "../mods/insights"); +const modSource = resolve(here, "../../herdr-insights/mods/insights"); function argument(name, fallback) { const index = process.argv.indexOf(name); @@ -144,7 +145,7 @@ try { writeFileSync(mainPath, main); const result = spawnSync("herdr-app", ["check", mod], { - cwd: resolve(here, ".."), + cwd: resolve(here, "../../herdr-insights"), env: { ...process.env, HERDR_APP_HOME: home, HOME: home }, encoding: "utf8", timeout: 120_000, diff --git a/herdr-insights/tests/node-version.test.mjs b/tests/herdr-insights/node-version.test.mjs similarity index 94% rename from herdr-insights/tests/node-version.test.mjs rename to tests/herdr-insights/node-version.test.mjs index 1bd66e50..67d25625 100644 --- a/herdr-insights/tests/node-version.test.mjs +++ b/tests/herdr-insights/node-version.test.mjs @@ -5,7 +5,7 @@ import { MIN_NODE_VERSION, assertSupportedNodeVersion, isNodeVersionSupported, -} from "../scripts/lib.mjs"; +} from "../../herdr-insights/scripts/lib.mjs"; test("Node version gate accepts 22.15+ and rejects older runtimes", () => { assert.equal(MIN_NODE_VERSION, "22.15"); diff --git a/herdr-insights/tests/presentation.test.mjs b/tests/herdr-insights/presentation.test.mjs similarity index 96% rename from herdr-insights/tests/presentation.test.mjs rename to tests/herdr-insights/presentation.test.mjs index fee2438f..77f91a74 100644 --- a/herdr-insights/tests/presentation.test.mjs +++ b/tests/herdr-insights/presentation.test.mjs @@ -5,13 +5,13 @@ import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { isPresentation, normalizeConfig } from "../mods/insights/analytics.js"; -import { run as runPresenter } from "../mods/insights/presenter.mjs"; +import { isPresentation, normalizeConfig } from "../../herdr-insights/mods/insights/analytics.js"; +import { run as runPresenter } from "../../herdr-insights/mods/insights/presenter.mjs"; import { emptyStore, newFileEntry, saveStore, -} from "../mods/insights/core/store.mjs"; +} from "../../herdr-insights/mods/insights/core/store.mjs"; const TODAY = "2026-03-04"; const OPTIONS = { diff --git a/herdr-insights/tests/ship-readiness.test.mjs b/tests/herdr-insights/ship-readiness.test.mjs similarity index 96% rename from herdr-insights/tests/ship-readiness.test.mjs rename to tests/herdr-insights/ship-readiness.test.mjs index 28d83bc8..78041d71 100644 --- a/herdr-insights/tests/ship-readiness.test.mjs +++ b/tests/herdr-insights/ship-readiness.test.mjs @@ -5,13 +5,13 @@ import { hostname, tmpdir } from "node:os"; import path from "node:path"; import { registerHooks } from "node:module"; import test from "node:test"; -import { progressWriter } from "../mods/insights/collector.mjs"; -import { compactDashboard, deriveDashboard } from "../mods/insights/analytics.js"; -import { MAX_LINE_BYTES, withStoreLock } from "../mods/insights/core/util.mjs"; -import { normalizeConfig } from "../mods/insights/core/config.mjs"; -import { scanSources } from "../mods/insights/core/scan.mjs"; -import { run as present } from "../mods/insights/presenter.mjs"; -import { emptyStore, getOrCreateRow, newFileEntry, resetFileEntry, snapshotRows } from "../mods/insights/core/store.mjs"; +import { progressWriter } from "../../herdr-insights/mods/insights/collector.mjs"; +import { compactDashboard, deriveDashboard } from "../../herdr-insights/mods/insights/analytics.js"; +import { MAX_LINE_BYTES, withStoreLock } from "../../herdr-insights/mods/insights/core/util.mjs"; +import { normalizeConfig } from "../../herdr-insights/mods/insights/core/config.mjs"; +import { scanSources } from "../../herdr-insights/mods/insights/core/scan.mjs"; +import { run as present } from "../../herdr-insights/mods/insights/presenter.mjs"; +import { emptyStore, getOrCreateRow, newFileEntry, resetFileEntry, snapshotRows } from "../../herdr-insights/mods/insights/core/store.mjs"; async function fixture(t) { const root = await mkdtemp(path.join(tmpdir(), "insights-ship-")); diff --git a/herdr-insights/tests/ui-system.test.mjs b/tests/herdr-insights/ui-system.test.mjs similarity index 96% rename from herdr-insights/tests/ui-system.test.mjs rename to tests/herdr-insights/ui-system.test.mjs index fb738d90..b86d6a8e 100644 --- a/herdr-insights/tests/ui-system.test.mjs +++ b/tests/herdr-insights/ui-system.test.mjs @@ -2,10 +2,10 @@ import test from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; -import { PACKAGE_ROOT, collectSourceFiles } from "../scripts/lib.mjs"; +import { PACKAGE_ROOT, collectSourceFiles } from "../../herdr-insights/scripts/lib.mjs"; -const uiSource = await readFile(new URL("../mods/insights/ui.js", import.meta.url), "utf8"); -const mainSource = await readFile(new URL("../mods/insights/main.js", import.meta.url), "utf8"); +const uiSource = await readFile(new URL("../../herdr-insights/mods/insights/ui.js", import.meta.url), "utf8"); +const mainSource = await readFile(new URL("../../herdr-insights/mods/insights/main.js", import.meta.url), "utf8"); function createElement(type) { const node = { type, calls: [], childNodes: [], style: {} };