ruvnet-brain 1.14.0-dev โ 1.16.0-dev
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/README.md +6 -3
- package/bin/install.mjs +177 -3
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# ๐ง RuvNet Brain
|
|
6
6
|
|
|
7
|
-
### ๐ง RuvNet Brain โ [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
8
8
|
|
|
9
9
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack โ delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
10
10
|
|
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
[](https://isovision.ai/ruvnet-brain/)
|
|
15
15
|
[](LICENSE)
|
|
16
16
|
[](#testing--proof)
|
|
17
|
-
[](#testing--proof)
|
|
18
18
|
|
|
19
19
|
> **Three independent things version separately here โ by design, not drift. Every number below is live (read straight from its real source, never hand-typed), so none of them can go stale:**
|
|
20
20
|
> - **`plugin`** (badge above) โ the Claude Code plugin itself: SKILL.md, the grounding hooks, the MCP server. Read live from [`plugin/.claude-plugin/plugin.json`](plugin/.claude-plugin/plugin.json). Updates often โ this is where behavior fixes land.
|
|
21
21
|
> - **`installer (npm)`** (badge above) โ the `npx ruvnet-brain` setup script. Read live from the [npm registry](https://www.npmjs.com/package/ruvnet-brain). Only moves when the installer script itself changes โ rare.
|
|
22
22
|
> - **Brain Release** (the downloadable 512MB knowledge bundle, linked from the "download" badge above) โ always resolves to [`releases/latest`](https://github.com/stuinfla/ruvnet-brain/releases/latest), currently `v0.5.0-dev`. Only moves when the underlying knowledge base is rebuilt โ separate again from the two above.
|
|
23
|
+
> - **Update an installed brain once:** `npx ruvnet-brain --update` โ runs the bundle's own self-updater (backs up first, re-verifies, fails loud instead of half-applying).
|
|
24
|
+
> - **Nightly auto-update is OFF by default:** `npx ruvnet-brain --enable-nightly` schedules it (macOS LaunchAgent, 03:47); `npx ruvnet-brain --disable-nightly` removes it; Linux/Windows get the cron line documented in the bundle's `forge-update.mjs`.
|
|
25
|
+
> - **Either way, your copy only advances when a new Release is published** โ the updater pulls `releases/latest`, so running it between releases is a safe no-op.
|
|
23
26
|
|
|
24
27
|
<sub>Built by **[Stuart Kerr](https://isovision.ai)** at [Isovision.ai](https://isovision.ai) ยท free & fair use, to help everyone leverage the high end of agentic coding.</sub>
|
|
25
28
|
|
|
@@ -204,7 +207,7 @@ node plugin/test/run-tests.mjs # full plugin QA over real JSO
|
|
|
204
207
|
| **L1โL4 behavioral harness** | **all pass** | route ยท deep-recall (returns _code_) ยท implement (cites the API) ยท orchestrate (the hook drives the full pipeline) |
|
|
205
208
|
| **Plugin QA** | **26 / 26** | manifests, hook firing, MCP `initialize`/`tools/list`, capability battery |
|
|
206
209
|
| **Clean-room install** | **3 / 3** | download the published 512 MB bundle fresh โ unzip โ query โ grounded, cited answers |
|
|
207
|
-
| **Unit tests** | **
|
|
210
|
+
| **Unit tests** | **208 passing** ยท 10% of ALL source covered | `npm run test:cov` โ the floor fails CI if it slips. 10% is the honest number over every shipped file; the previous "75%" measured a hand-picked 8-file subset |
|
|
208
211
|
| **Grounding proof** | `npx ruvnet-brain --doctor` | asks a real question, then checks the cited path really exists in the on-disk store; a citation that doesn't resolve is reported as **NOT grounded** |
|
|
209
212
|
| **Held-out eval** | **grounded 12/12** ยท routed 10/12 | `npm run eval` โ 12 frozen questions never used for tuning, graded on ground truth, never by a model |
|
|
210
213
|
|
package/bin/install.mjs
CHANGED
|
@@ -65,6 +65,10 @@ const FLAG_DOCTOR = argv.includes('--doctor');
|
|
|
65
65
|
const FLAG_NO_VERIFY = argv.includes('--no-verify');
|
|
66
66
|
const FLAG_PIN = argv.includes('--pin'); // skip the latest-check, use the bundled default
|
|
67
67
|
const FLAG_DEMO = argv.includes('--demo'); // guided, real (non-fabricated) walkthrough of the brain in action
|
|
68
|
+
// โโ freshness flags โ invoke/schedule the SELF-UPDATER the bundle already ships (kb/forge-update.mjs) โโ
|
|
69
|
+
const FLAG_UPDATE = argv.includes('--update'); // one-shot: pull the latest Release bundle into the installed brain now
|
|
70
|
+
const FLAG_ENABLE_NIGHTLY = argv.includes('--enable-nightly'); // schedule that update nightly (macOS LaunchAgent)
|
|
71
|
+
const FLAG_DISABLE_NIGHTLY = argv.includes('--disable-nightly'); // remove the nightly schedule
|
|
68
72
|
// โโ onboarding-experience flags (all optional; every offer is safe to decline) โโ
|
|
69
73
|
const FLAG_YES = argv.includes('--yes') || argv.includes('-y'); // accept every optional offer non-interactively
|
|
70
74
|
const FLAG_WITH_STACK = argv.includes('--with-stack'); // add missing Ruflo/RuVector without prompting
|
|
@@ -725,6 +729,163 @@ async function doctor() {
|
|
|
725
729
|
);
|
|
726
730
|
}
|
|
727
731
|
|
|
732
|
+
// โโ `--update` / `--enable-nightly` / `--disable-nightly`: end-user freshness controls โโโโโโโโโโโโ
|
|
733
|
+
// The brain bundle SHIPS its own self-updater (forge-update.mjs, right in the KB dir): it pulls the
|
|
734
|
+
// canonical Release bundle, backs the current copy up, extracts, and re-verifies with forge-guard โ
|
|
735
|
+
// failing loud with no partial clobber. These flags never reimplement any of that; they only INVOKE
|
|
736
|
+
// it once (--update) or SCHEDULE it per-user (--enable-nightly). Nothing here ever publishes.
|
|
737
|
+
const NIGHTLY_LABEL = 'com.ruvnet.brain-update';
|
|
738
|
+
const resolvedKbDir = () =>
|
|
739
|
+
process.env.RUVNET_BRAIN_KB || path.join(os.homedir(), '.cache', 'ruvnet-brain', 'kb');
|
|
740
|
+
const nightlyPlistPath = () =>
|
|
741
|
+
path.join(os.homedir(), 'Library', 'LaunchAgents', `${NIGHTLY_LABEL}.plist`);
|
|
742
|
+
// RUVNET_BRAIN_TEST=1 โ write/remove the plist but NEVER call launchctl. Tests point HOME at a temp
|
|
743
|
+
// dir; bootstrapping a temp-dir plist into the user's real gui domain would mutate exactly the
|
|
744
|
+
// system state the tests promise not to touch.
|
|
745
|
+
const TEST_MODE = process.env.RUVNET_BRAIN_TEST === '1';
|
|
746
|
+
const xmlEscape = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
747
|
+
|
|
748
|
+
// The same nightly command, cron-flavored โ the pattern forge-update.mjs documents in its header.
|
|
749
|
+
// 03:47 on purpose: an off-hour minute, so it never piles onto the :00 cron rush.
|
|
750
|
+
const cronExample = (kbDir) =>
|
|
751
|
+
`47 3 * * * cd ${kbDir} && ${process.execPath} forge-update.mjs --apply >> ${kbDir}/update.log 2>&1`;
|
|
752
|
+
|
|
753
|
+
function missingUpdaterHelp(kbDir) {
|
|
754
|
+
console.error(`\n${c.red('โ can\'t update:')} ${c.bold('forge-update.mjs')} is missing from ${kbDir}.`);
|
|
755
|
+
console.error(` Either no brain is installed there, or the bundle predates the self-updater.`);
|
|
756
|
+
console.error(` Fix: re-run the installer โ ${c.bold('npx ruvnet-brain')} ${c.dim('(add --force if a brain is already present)')}`);
|
|
757
|
+
console.error(` โ the current bundle ships forge-update.mjs; then this command will work.`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function runUpdate() {
|
|
761
|
+
printBanner('update');
|
|
762
|
+
const kbDir = resolvedKbDir();
|
|
763
|
+
info(`brain dir: ${c.bold(kbDir)}`);
|
|
764
|
+
if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
|
|
765
|
+
missingUpdaterHelp(kbDir);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
768
|
+
info(c.dim("running the bundle's own self-updater (backs up first, re-verifies, never half-applies)โฆ\n"));
|
|
769
|
+
// Relative filename + matching cwd โ same launch convention as smokeQuery(); stdio:'inherit'
|
|
770
|
+
// streams the updater's narration live and unedited.
|
|
771
|
+
const r = spawnSync(process.execPath, ['forge-update.mjs', '--apply'], { cwd: kbDir, stdio: 'inherit' });
|
|
772
|
+
if (r.error) {
|
|
773
|
+
console.error(`\n${c.red('โ update failed to launch:')} ${r.error.message}`);
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
process.exit(r.status === null ? 1 : r.status); // exit with the updater's own verdict
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function enableNightly() {
|
|
780
|
+
printBanner('enable nightly updates');
|
|
781
|
+
const kbDir = resolvedKbDir();
|
|
782
|
+
|
|
783
|
+
if (process.platform !== 'darwin') {
|
|
784
|
+
info('The LaunchAgent scheduler is macOS-only (for now).');
|
|
785
|
+
info("On this platform, schedule the bundle's self-updater with cron โ the pattern");
|
|
786
|
+
info('forge-update.mjs itself documents:');
|
|
787
|
+
console.log(`\n ${c.bold(cronExample(kbDir))}\n`);
|
|
788
|
+
info(`(${c.bold('crontab -e')}, paste the line, save. Remove the line to disable.)`);
|
|
789
|
+
return; // exit 0 โ the user got the working recipe
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
info(`brain dir: ${c.bold(kbDir)}`);
|
|
793
|
+
if (!fs.existsSync(path.join(kbDir, 'forge-update.mjs'))) {
|
|
794
|
+
// Refuse to schedule a job that is guaranteed to fail every night โ fail loud NOW instead.
|
|
795
|
+
missingUpdaterHelp(kbDir);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Template the plist to THIS user's kb dir + node binary. Quotes guard paths with spaces;
|
|
800
|
+
// xmlEscape guards the XML (>> and && must survive as shell operators after plist parsing).
|
|
801
|
+
const logPath = path.join(kbDir, 'update.log');
|
|
802
|
+
const shellCmd = `cd "${kbDir}" && "${process.execPath}" forge-update.mjs --apply >> "${logPath}" 2>&1`;
|
|
803
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
804
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
805
|
+
<plist version="1.0">
|
|
806
|
+
<dict>
|
|
807
|
+
<key>Label</key>
|
|
808
|
+
<string>${NIGHTLY_LABEL}</string>
|
|
809
|
+
<key>ProgramArguments</key>
|
|
810
|
+
<array>
|
|
811
|
+
<string>/bin/sh</string>
|
|
812
|
+
<string>-c</string>
|
|
813
|
+
<string>${xmlEscape(shellCmd)}</string>
|
|
814
|
+
</array>
|
|
815
|
+
<key>StartCalendarInterval</key>
|
|
816
|
+
<dict>
|
|
817
|
+
<key>Hour</key>
|
|
818
|
+
<integer>3</integer>
|
|
819
|
+
<key>Minute</key>
|
|
820
|
+
<integer>47</integer>
|
|
821
|
+
</dict>
|
|
822
|
+
<key>RunAtLoad</key>
|
|
823
|
+
<false/>
|
|
824
|
+
</dict>
|
|
825
|
+
</plist>
|
|
826
|
+
`;
|
|
827
|
+
const plistPath = nightlyPlistPath();
|
|
828
|
+
try {
|
|
829
|
+
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
830
|
+
fs.writeFileSync(plistPath, plist);
|
|
831
|
+
} catch (e) {
|
|
832
|
+
console.error(`\n${c.red('โ couldn\'t write the LaunchAgent:')} ${e.message}`);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
ok(`wrote ${c.bold(plistPath)}`);
|
|
836
|
+
info(c.dim('runs nightly at 03:47 โ an off-hour minute, so it never lands on the :00 rush'));
|
|
837
|
+
|
|
838
|
+
if (TEST_MODE) {
|
|
839
|
+
warn('RUVNET_BRAIN_TEST=1 โ skipping launchctl bootout/bootstrap (plist written only)');
|
|
840
|
+
} else {
|
|
841
|
+
const uid = process.getuid();
|
|
842
|
+
// bootout first so re-running replaces the loaded job cleanly; failure just means "wasn't loaded".
|
|
843
|
+
spawnSync('launchctl', ['bootout', `gui/${uid}/${NIGHTLY_LABEL}`], { stdio: 'ignore' });
|
|
844
|
+
const boot = spawnSync('launchctl', ['bootstrap', `gui/${uid}`, plistPath], { encoding: 'utf8' });
|
|
845
|
+
if (boot.status === 0) ok('LaunchAgent loaded โ your brain now updates while you sleep');
|
|
846
|
+
else {
|
|
847
|
+
warn(`launchctl bootstrap failed (${(boot.stderr || '').trim() || `exit ${boot.status}`}) โ the plist is in place;`);
|
|
848
|
+
info(`load it yourself: ${c.bold(`launchctl bootstrap gui/${uid} ${plistPath}`)}`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
console.log(`\n ${c.bold('Verify it:')} launchctl list | grep ${NIGHTLY_LABEL}`);
|
|
853
|
+
console.log(` ${c.bold('Watch it:')} tail ${logPath} ${c.dim('(appears after the first nightly run)')}`);
|
|
854
|
+
console.log(` ${c.bold('Disable it:')} npx ruvnet-brain --disable-nightly`);
|
|
855
|
+
console.log(`\n ${c.dim('It only ever PULLS the published Release bundle (backup + re-verify built in) โ it never publishes,')}`);
|
|
856
|
+
console.log(` ${c.dim('and a night with no new Release is a clean no-op.')}\n`);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function disableNightly() {
|
|
860
|
+
printBanner('disable nightly updates');
|
|
861
|
+
if (process.platform !== 'darwin') {
|
|
862
|
+
info('The LaunchAgent nightly is macOS-only, so nothing was scheduled here by this tool.');
|
|
863
|
+
info(`If you added the cron line yourself, remove it with: ${c.bold('crontab -e')}`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const plistPath = nightlyPlistPath();
|
|
867
|
+
const existed = fs.existsSync(plistPath);
|
|
868
|
+
if (TEST_MODE) {
|
|
869
|
+
warn('RUVNET_BRAIN_TEST=1 โ skipping launchctl bootout (plist removal only)');
|
|
870
|
+
} else {
|
|
871
|
+
// Ignore failure: "not loaded" is exactly the state we want anyway.
|
|
872
|
+
spawnSync('launchctl', ['bootout', `gui/${process.getuid()}/${NIGHTLY_LABEL}`], { stdio: 'ignore' });
|
|
873
|
+
}
|
|
874
|
+
if (existed) {
|
|
875
|
+
try {
|
|
876
|
+
fs.rmSync(plistPath);
|
|
877
|
+
} catch (e) {
|
|
878
|
+
console.error(`\n${c.red('โ couldn\'t remove the LaunchAgent:')} ${e.message}`);
|
|
879
|
+
console.error(` Remove it yourself: rm ${plistPath}`);
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
ok(`nightly updates disabled โ removed ${plistPath}`);
|
|
883
|
+
} else {
|
|
884
|
+
ok('nightly updates were already off โ nothing to remove (safe to run any time)');
|
|
885
|
+
}
|
|
886
|
+
info(`re-enable any time: ${c.bold('npx ruvnet-brain --enable-nightly')}`);
|
|
887
|
+
}
|
|
888
|
+
|
|
728
889
|
// โโ tiny interactive yes/no โ SAFE in non-TTY (returns the default; never blocks a piped install) โโ
|
|
729
890
|
function ask(question, def = false) {
|
|
730
891
|
if (FLAG_YES) return Promise.resolve(true);
|
|
@@ -949,9 +1110,14 @@ function success({ cacheDir, isCustom, plugin, env }) {
|
|
|
949
1110
|
โข Want to see it answer, live, right now? ${c.bold('npx ruvnet-brain --demo')} โ 2 real questions, real cited answers.`);
|
|
950
1111
|
|
|
951
1112
|
console.log(`\n ${c.bold('Set it up your way:')} this default is ${c.bold('global')} โ live in every VS Code project automatically,`);
|
|
952
|
-
console.log(` which is what most people want. Want it different (project-only, moved, with the build stack
|
|
953
|
-
console.log(`
|
|
954
|
-
|
|
1113
|
+
console.log(` which is what most people want. Want it different (project-only, moved, with the build stack`);
|
|
1114
|
+
console.log(` added)? ${c.bold('Just tell Claude')} once it's on. You never have to learn its internals.`);
|
|
1115
|
+
|
|
1116
|
+
console.log(`\n ${c.bold('Staying current:')} nightly updates are ${c.bold('OFF by default')} โ nothing runs on your machine unasked.`);
|
|
1117
|
+
console.log(` โข one-shot check + update now: ${c.bold('npx ruvnet-brain --update')}`);
|
|
1118
|
+
console.log(` โข nightly at 03:47 (macOS): ${c.bold('npx ruvnet-brain --enable-nightly')} ${c.dim('ยท off again: --disable-nightly')}`);
|
|
1119
|
+
console.log(` โข Linux/Windows: the cron line documented in the brain's own ${c.bold('forge-update.mjs')}`);
|
|
1120
|
+
console.log(` ${c.dim('Either way, your copy only advances when a new Release is actually published.')}`);
|
|
955
1121
|
|
|
956
1122
|
console.log(`\n ${c.dim('You can\'t break anything โ the plugin is disable-able and only acts on RuvNet-shaped work.')}`);
|
|
957
1123
|
console.log('');
|
|
@@ -969,6 +1135,11 @@ Usage:
|
|
|
969
1135
|
npx github:stuinfla/ruvnet-brain Same, but from the bleeding-edge GitHub commit
|
|
970
1136
|
npx ruvnet-brain --doctor Health-check an existing install (green/red per part)
|
|
971
1137
|
npx ruvnet-brain --demo Guided walkthrough โ 2 real questions, real cited answers
|
|
1138
|
+
npx ruvnet-brain --update One-shot: pull the latest Release bundle into your installed brain
|
|
1139
|
+
(runs the bundle's own forge-update.mjs --apply: backup + re-verify)
|
|
1140
|
+
npx ruvnet-brain --enable-nightly Schedule that update nightly at 03:47 โ macOS LaunchAgent;
|
|
1141
|
+
other platforms get the documented cron line. OFF by default.
|
|
1142
|
+
npx ruvnet-brain --disable-nightly Remove the nightly schedule (safe to run any time)
|
|
972
1143
|
node bin/install.mjs --version <tag> Install a specific Release tag (e.g. --version v0.4.0-dev)
|
|
973
1144
|
node bin/install.mjs --pin Skip the latest-check; use the bundled known-good version
|
|
974
1145
|
node bin/install.mjs --local Install from a repo clone's dist/ruvnet-brain.zip
|
|
@@ -992,6 +1163,9 @@ It is safe to re-run at any time. After installing, restart Claude Code so the g
|
|
|
992
1163
|
if (FLAG_HELP) return showHelp();
|
|
993
1164
|
if (FLAG_DOCTOR) return await doctor();
|
|
994
1165
|
if (FLAG_DEMO) return runDemo();
|
|
1166
|
+
if (FLAG_UPDATE) return runUpdate();
|
|
1167
|
+
if (FLAG_ENABLE_NIGHTLY) return enableNightly();
|
|
1168
|
+
if (FLAG_DISABLE_NIGHTLY) return disableNightly();
|
|
995
1169
|
|
|
996
1170
|
printBanner('installer');
|
|
997
1171
|
console.log(c.dim("I'll set up the brain and the Claude Code plugin, explaining each step as I go.\n"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0-dev",
|
|
4
4
|
"description": "One-command installer for RuvNet Brain โ a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,16 +9,21 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"test": "node plugin/test/run-tests.mjs",
|
|
11
11
|
"version:check": "node scripts/sync-version.mjs --check",
|
|
12
|
+
"claims:verify": "node scripts/claims-verify.mjs",
|
|
12
13
|
"version:sync": "node scripts/sync-version.mjs",
|
|
13
|
-
"test:unit": "vitest run",
|
|
14
|
-
"test:cov": "vitest run --coverage",
|
|
14
|
+
"test:unit": "vitest run tests/unit",
|
|
15
|
+
"test:cov": "vitest run tests/unit --coverage",
|
|
16
|
+
"test:all": "npm run test:unit && npm run test:integration && npm test",
|
|
15
17
|
"metaharness:fix": "node scripts/fix-metaharness-memretrieve.mjs --apply",
|
|
16
18
|
"metaharness:check": "node scripts/fix-metaharness-memretrieve.mjs --check",
|
|
17
19
|
"eval": "node scripts/eval-brain.mjs",
|
|
18
20
|
"eval:gate": "node scripts/eval-brain.mjs --gate",
|
|
19
21
|
"eval:record": "node scripts/eval-brain.mjs --record",
|
|
20
22
|
"embed:verifier": "node scripts/embed-verifier.mjs",
|
|
21
|
-
"embed:check": "node scripts/embed-verifier.mjs --check"
|
|
23
|
+
"embed:check": "node scripts/embed-verifier.mjs --check",
|
|
24
|
+
"gists:index": "node scripts/ingest-gists.mjs --index-only",
|
|
25
|
+
"gists:sync": "node scripts/ingest-gists.mjs && node kb/forge-big.mjs both --dir kb --name ruv-gists",
|
|
26
|
+
"test:integration": "vitest run tests/integration"
|
|
22
27
|
},
|
|
23
28
|
"files": [
|
|
24
29
|
"bin/install.mjs",
|
|
@@ -50,6 +55,7 @@
|
|
|
50
55
|
"bugs": "https://github.com/stuinfla/ruvnet-brain/issues",
|
|
51
56
|
"author": "Stuart Kerr",
|
|
52
57
|
"devDependencies": {
|
|
58
|
+
"@metaharness/darwin": "~0.8.0",
|
|
53
59
|
"@vitest/coverage-v8": "^4.1.10",
|
|
54
60
|
"vitest": "^4.1.10"
|
|
55
61
|
}
|