pitroom 0.6.9 → 0.7.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -1
- package/CHANGELOG.md +36 -0
- package/README.md +9 -6
- package/bin/pitroom.cjs +68 -0
- package/dist/lib.mjs +59 -12
- package/dist/pitroom.mjs +599 -263
- package/dist/ui/app.css +1 -1
- package/dist/ui/app.js +11 -11
- package/hooks/codex-hooks.json +28 -0
- package/package.json +3 -2
- package/skills/pitroom-crew/SKILL.md +1 -1
- package/skills/using-pitroom/SKILL.md +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pitroom",
|
|
3
3
|
"description": "A free pit crew for your expensive coding agent: the superpowers development workflow, run by cheap workers. Delegate research, fixes, tests, reviews and whole plans from Claude Code, Codex or any agent to OpenCode, Codex or Claude Code workers, with receipts that prove the savings.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TahaAtas"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pitroom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A free pit crew for your expensive coding agent: delegate research, fixes, tests, reviews and parallel investigations to cheap worker agents running your own models, with verified answers, exact diffs and cost receipts.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TahaAtas"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"workflow"
|
|
20
20
|
],
|
|
21
21
|
"skills": "./skills/",
|
|
22
|
+
"hooks": "./hooks/codex-hooks.json",
|
|
22
23
|
"interface": {
|
|
23
24
|
"displayName": "Pitroom",
|
|
24
25
|
"shortDescription": "Cheap workers for your agent",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Codex
|
|
6
|
+
- The Codex plugin registers hooks (`hooks/codex-hooks.json`, named in `.codex-plugin/plugin.json`): Pitroom is introduced at session start, so the agent knows it even when Codex drops skill descriptions because many skills are installed, and a job card follows each `pitroom` command. Codex asks you to trust the plugin's hooks once. The hooks go through `pitroom` on PATH (`pitroom hook-start` is new) and stay silent when it is missing. Checked against Codex 0.159.
|
|
7
|
+
|
|
8
|
+
### CLI
|
|
9
|
+
- `pitroom init` proposes a starter config from the worker CLIs and OpenCode models you have: a fallback chain from your free models, tiers when more than one CLI is installed. It never picks your model, and writes only with `--yes` (`--force` to replace a file, which is kept as `.bak`).
|
|
10
|
+
- `pitroom doctor` groups its checks (Setup, Worker chain, one section per worker CLI, Skills and agents), ends with one verdict line and a Next list of the commands the findings point to, and warns when the first `node` on PATH is too old. Colour only on a terminal (`NO_COLOR` and `FORCE_COLOR` are honoured); `ls`, `status`, `history` and run reports colour the state too.
|
|
11
|
+
- The `pitroom` command is now a small launcher that finds a Node 22.13+ (`PITROOM_NODE`, nvm, Homebrew, `/usr/local`) when the shell's own `node` is old, instead of crashing with a syntax error; without one it says how to fix it.
|
|
12
|
+
- A timed-out run's report says what to do: raise the limit with `-t` (or `PITROOM_TIMEOUT`) for a long job, or `--continue`.
|
|
13
|
+
|
|
14
|
+
### One figure for savings
|
|
15
|
+
- The Stats tab (and `pitroom history stats`) takes its saved figures, total, per worker and per day, from the ledger, the same ones the Live tab, `pitroom savings` and the status line add up. They used to be calculated twice and could differ.
|
|
16
|
+
- A run that finished while another process was marking it as crashed no longer loses its usage and savings.
|
|
17
|
+
|
|
18
|
+
### Dashboard
|
|
19
|
+
- Cards name the model the worker really ran (what the CLI reported), not just `opencode`; terminal cards do too.
|
|
20
|
+
- The close button closes the card (its click used to bubble into the card's open handler and re-open it). The step list scrolls by hand while a worker runs and follows new steps only while you are at the bottom.
|
|
21
|
+
- A `#run-id` link opens that run as the same expandable card the lists use, pinned above the page, and clears the link when closed; `/api/run/:id` carries the card. A page opened in a background tab fetched nothing until shown: fixed.
|
|
22
|
+
- The state icons explain themselves on hover and focus (a clock is a timeout).
|
|
23
|
+
- No `NaN%` for a worker the history has no run for.
|
|
24
|
+
|
|
25
|
+
## 0.6.10
|
|
26
|
+
|
|
27
|
+
### Verified answers
|
|
28
|
+
- A reference written as a bare file name or a partial path (`ipc.ts:218`, `src/main/ipc.ts:218`) is now looked up in the project, so it no longer counts as "file not found" just because the worker left out the directory. If several files match, one that holds up is enough.
|
|
29
|
+
- A call written like a reference (`Schema.parse:432`, `orchestrator.start:638`) is not a file reference and is no longer counted. On a real run this turned "7/18 verified" into "13/13"; the references that really did not match are still reported.
|
|
30
|
+
|
|
31
|
+
### Safety
|
|
32
|
+
- A run warns when the directory its worker runs in holds `.env` files, private keys (`*.pem`, `id_rsa`…) or `credentials.json`: the worker is told not to read them, but nothing stops it. The warning is on the report and when a run starts with `--bg`. For `-i` it names only the files the isolated copy would contain (a git-ignored `.env` is not copied). `PITROOM_NO_SECRET_WARNING=1` silences it.
|
|
33
|
+
|
|
34
|
+
### Smaller
|
|
35
|
+
- OpenCode with no pinned model no longer warns about `--effort` on every run: there is nothing to attach the level to, so it is left out quietly.
|
|
36
|
+
- A detached dashboard stops after an hour without a request (it was four hours).
|
|
37
|
+
- Every run card shows its worker's logo, and its animated mascot while it runs; the skills tell the agent to open the dashboard in the app's own browser pane (not with `--open`, which launches the system browser).
|
|
38
|
+
|
|
3
39
|
## 0.6.9
|
|
4
40
|
|
|
5
41
|
### Dashboard
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Model-agnostic · Verified answers · Receipts, not vibes · OpenCode / Codex /
|
|
|
11
11
|
<br />
|
|
12
12
|
|
|
13
13
|
[](https://github.com/ATASTECH/pitroom/releases/latest)
|
|
14
|
-
[](https://www.npmjs.com/package/pitroom)
|
|
15
15
|
[](https://www.npmjs.com/package/pitroom)
|
|
16
16
|
[](https://github.com/ATASTECH/pitroom/stargazers)
|
|
17
17
|
[](https://github.com/ATASTECH/pitroom/actions/workflows/ci.yml)
|
|
@@ -82,7 +82,7 @@ One group can mix them, and a fallback chain can cross them ([how adapters work]
|
|
|
82
82
|
|
|
83
83
|
### Verified answers
|
|
84
84
|
|
|
85
|
-
Every `path:line` a worker cites is checked on disk.
|
|
85
|
+
Every `path:line` a worker cites is checked on disk. A cite without its directory (`ipc.ts:218`, `src/main/ipc.ts:218`) is found in the project, and a call such as `Schema.parse:432` is not mistaken for a file.
|
|
86
86
|
|
|
87
87
|
Your agent trusts what checks out and skips re-reading it.
|
|
88
88
|
|
|
@@ -270,7 +270,7 @@ pitroom install
|
|
|
270
270
|
| Path | You get |
|
|
271
271
|
|---|---|
|
|
272
272
|
| Claude Code plugin | The 14 skills, the session-start hook that introduces Pitroom, and a card after each `pitroom` command. |
|
|
273
|
-
| Codex plugin | The 14 skills
|
|
273
|
+
| Codex plugin | The 14 skills, a session-start hook that introduces Pitroom (so it is known even when Codex drops skill descriptions because many are installed), and a card after each `pitroom` command. Codex asks you to trust the plugin's hooks once. The hooks call `pitroom`, which comes from npm (`npm i -g pitroom`). |
|
|
274
274
|
| npm + `pitroom install` | The CLI, and the skills linked into `~/.agents/skills` and `~/.claude/skills`. |
|
|
275
275
|
|
|
276
276
|
Pick one path, not several: `doctor` warns if the skills load twice. The plugins alone do not put `pitroom` on your PATH, so install it from npm too if you want to run it yourself (`watch`, `models`, `savings`, the status line).
|
|
@@ -442,7 +442,7 @@ pitroom ls [--running] [-g NAME] · pitroom clean [--days 14] [--yes]
|
|
|
442
442
|
pitroom savings [--since 7d|30d|all] [--models] [--card file.svg] [--badge]
|
|
443
443
|
pitroom models [worker] [--all] [--json] models, effort levels, your costs, your usage
|
|
444
444
|
pitroom statusline [--then CMD] · pitroom hook-card
|
|
445
|
-
pitroom doctor [--probe] · pitroom config · pitroom install [--copy] [--force] · pitroom uninstall
|
|
445
|
+
pitroom doctor [--probe] · pitroom config · pitroom init [--model ID] [--fallback A,B] [--yes] [--force] · pitroom install [--copy] [--force] · pitroom uninstall
|
|
446
446
|
```
|
|
447
447
|
|
|
448
448
|
Exit codes: `0` ok · `1` worker failed · `2` usage · `3` refused/setup · `4` timeout · `5` read-only violation · `6` verify failed · `75` still running.
|
|
@@ -476,7 +476,7 @@ Two settings make every delegation visible, whether or not the agent mentions it
|
|
|
476
476
|
|
|
477
477
|
The Claude Code and Codex **apps** show neither hook messages nor a status line. For them there are two things that need no setup:
|
|
478
478
|
|
|
479
|
-
- `pitroom dash --detach` prints the address of a live dashboard with three tabs: **Live** (every running and recent run as an animated card; click one for the task, what the worker did step by step, its result, the diff and the details), **History** (search and filter everything Pitroom ever ran; every run opens as the same card) and **Stats** (success rate, time, tokens and savings per worker and model). It is read-only, listens on `127.0.0.1` only and stops itself after
|
|
479
|
+
- `pitroom dash --detach` prints the address of a live dashboard with three tabs: **Live** (every running and recent run as an animated card; click one for the task, what the worker did step by step, its result, the diff and the details), **History** (search and filter everything Pitroom ever ran; every run opens as the same card) and **Stats** (success rate, time, tokens and savings per worker and model). It is read-only, listens on `127.0.0.1` only and stops itself after an hour without a request (`pitroom dash --stop` ends it sooner). Open it in a browser or in the app's own browser pane. The skills tell the agent to start it and give you the address when it runs workers in the background.
|
|
480
480
|
- `pitroom watch -g NAME --brief` prints one card line when a worker starts and one when it ends. In Claude Code, the agent runs it through the Monitor tool and the lines appear in the app; in Codex the command's output block fills as it goes.
|
|
481
481
|
|
|
482
482
|
---
|
|
@@ -546,11 +546,12 @@ Fallbacks cross backends (e.g. `"fallback": ["codex:#low", "opencode"]`): a work
|
|
|
546
546
|
| `PITROOM_PRIMARY` | `sonnet` | Pricing preset for the savings estimate: `sonnet`, `opus`, `haiku`, `gpt-5` |
|
|
547
547
|
| `PITROOM_PRICE` | | Custom primary price, USD per 1M tokens: `"in,out[,cachedIn]"` |
|
|
548
548
|
| `PITROOM_HOME` | `~/.local/state/pitroom` | Where run records and the ledger live |
|
|
549
|
+
| `PITROOM_NO_SECRET_WARNING` | | Set to `1` to stop the warning about `.env` files and private keys in the worker's directory |
|
|
549
550
|
| `PITROOM_<WORKER>_BIN` | on PATH | Path to a worker CLI, e.g. `PITROOM_OPENCODE_BIN` |
|
|
550
551
|
|
|
551
552
|
</details>
|
|
552
553
|
|
|
553
|
-
Or put defaults in `~/.config/pitroom/config.json` (flags and env still win); `pitroom config` shows every effective value and where it came from. `models` gives each worker a default model for targets that name none (`-W codex`, a `"codex"` fallback); a model in the target or `-m` still wins. `tiers` names workers for `--tier` and for plan tasks' `**Worker:**` lines:
|
|
554
|
+
Or put defaults in `~/.config/pitroom/config.json` (flags and env still win); `pitroom config` shows every effective value and where it came from. `pitroom init` proposes a starter config from the worker CLIs and OpenCode models you have (a fallback chain of your free models, tiers when more than one CLI is installed) and writes it only with `--yes`; it never picks your model for you. `models` gives each worker a default model for targets that name none (`-W codex`, a `"codex"` fallback); a model in the target or `-m` still wins. `tiers` names workers for `--tier` and for plan tasks' `**Worker:**` lines:
|
|
554
555
|
|
|
555
556
|
```json
|
|
556
557
|
{
|
|
@@ -581,6 +582,8 @@ Or put defaults in `~/.config/pitroom/config.json` (flags and env still win); `p
|
|
|
581
582
|
|
|
582
583
|
**Can the worker break my repo?** It can't touch git history, refs or your index (permission profile + git guard), can't run bulk deletes, and write-mode edits are revertible with one command. The guard is not an OS sandbox: git called by absolute path from inside a script, or non-git tools, are outside its reach, which is why `--isolate` exists: nothing reaches your tree until you apply it.
|
|
583
584
|
|
|
585
|
+
**Can the worker read my `.env`?** In the directory it runs in, yes: its prompt says not to, and nothing stops it, and a free model may be hosted by a third party. Pitroom warns when it sees `.env` files, private keys or `credentials.json` there (`PITROOM_NO_SECRET_WARNING=1` silences it). An isolated copy (`-i`) leaves out git-ignored files, so a git-ignored `.env` is not in it; a clean checkout is the safest place for a first try.
|
|
586
|
+
|
|
584
587
|
**What if the worker fails?** Pitroom exits non-zero with the real cause (for example a default model that no longer exists) and your agent simply continues on its own. `pitroom doctor` diagnoses setup problems.
|
|
585
588
|
|
|
586
589
|
---
|
package/bin/pitroom.cjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// The `pitroom` command. Plain ES5 so that even an old Node can run it: it finds a Node.js of 22.13 or newer
|
|
4
|
+
// and runs dist/pitroom.mjs with it. Agent apps often run commands in a shell whose first `node` is an old one
|
|
5
|
+
// (an installer's Node 14 in /usr/local/bin, say), where the real CLI would only crash with a syntax error.
|
|
6
|
+
var cp = require('child_process');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
|
|
10
|
+
var MIN = [22, 13];
|
|
11
|
+
var cli = path.join(__dirname, '..', 'dist', 'pitroom.mjs');
|
|
12
|
+
|
|
13
|
+
function ok(v) {
|
|
14
|
+
return v[0] > MIN[0] || (v[0] === MIN[0] && v[1] >= MIN[1]);
|
|
15
|
+
}
|
|
16
|
+
function parse(text) {
|
|
17
|
+
var m = /(\d+)\.(\d+)\.(\d+)/.exec(String(text));
|
|
18
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
19
|
+
}
|
|
20
|
+
function versionOf(bin) {
|
|
21
|
+
var r = cp.spawnSync(bin, ['-p', 'process.versions.node'], { encoding: 'utf8', timeout: 5000 });
|
|
22
|
+
return r.status === 0 ? parse(r.stdout) : null;
|
|
23
|
+
}
|
|
24
|
+
function newer(a, b) {
|
|
25
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// PITROOM_TEST_NODE_VERSION only lets the tests pretend this Node is old.
|
|
29
|
+
var current = parse(process.env.PITROOM_TEST_NODE_VERSION || process.versions.node) || [0, 0, 0];
|
|
30
|
+
var node = process.execPath;
|
|
31
|
+
|
|
32
|
+
if (!ok(current)) {
|
|
33
|
+
node = null;
|
|
34
|
+
var candidates = [];
|
|
35
|
+
if (process.env.PITROOM_NODE) candidates.push(process.env.PITROOM_NODE);
|
|
36
|
+
// PITROOM_NODE_SEARCH (a path-delimited list) replaces the places searched below; the tests use it.
|
|
37
|
+
var nvm = path.join(process.env.NVM_DIR || path.join(process.env.HOME || '', '.nvm'), 'versions', 'node');
|
|
38
|
+
try {
|
|
39
|
+
candidates = candidates.concat(
|
|
40
|
+
fs.readdirSync(nvm).filter(function (d) { return /^v\d+\.\d+\.\d+$/.test(d); })
|
|
41
|
+
.sort(function (a, b) { return newer(parse(b), parse(a)); })
|
|
42
|
+
.map(function (d) { return path.join(nvm, d, 'bin', 'node'); }),
|
|
43
|
+
);
|
|
44
|
+
} catch (e) { /* no nvm */ }
|
|
45
|
+
candidates.push('/opt/homebrew/bin/node', '/usr/local/bin/node', '/usr/bin/node');
|
|
46
|
+
if (process.env.PITROOM_NODE_SEARCH !== undefined) candidates = process.env.PITROOM_NODE_SEARCH.split(path.delimiter).filter(Boolean);
|
|
47
|
+
for (var i = 0; i < candidates.length && !node; i++) {
|
|
48
|
+
if (!fs.existsSync(candidates[i])) continue;
|
|
49
|
+
var v = versionOf(candidates[i]);
|
|
50
|
+
if (v && ok(v)) node = candidates[i];
|
|
51
|
+
}
|
|
52
|
+
if (!node) {
|
|
53
|
+
process.stderr.write(
|
|
54
|
+
'pitroom needs Node.js ' + MIN.join('.') + ' or newer, but this shell runs Node.js v' + current.join('.') + ' (' + process.execPath + ').\n' +
|
|
55
|
+
'Install a current one and make it the default, for example: nvm install 24 && nvm alias default 24\n' +
|
|
56
|
+
'(or put a newer node first on PATH, or set PITROOM_NODE=/path/to/node)\n',
|
|
57
|
+
);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
var r = cp.spawnSync(node, [cli].concat(process.argv.slice(2)), { stdio: 'inherit' });
|
|
63
|
+
if (r.error) {
|
|
64
|
+
process.stderr.write('pitroom: could not run ' + node + ': ' + r.error.message + '\n');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
if (r.signal) process.kill(process.pid, r.signal);
|
|
68
|
+
process.exit(r.status === null ? 1 : r.status);
|
package/dist/lib.mjs
CHANGED
|
@@ -1040,6 +1040,11 @@ var describeTarget = (t) => t.model ? formatTarget(t) : `${t.backend} (default m
|
|
|
1040
1040
|
// src/core/refs.ts
|
|
1041
1041
|
import fs5 from "node:fs";
|
|
1042
1042
|
import path5 from "node:path";
|
|
1043
|
+
var FILE_EXTENSIONS = new Set(
|
|
1044
|
+
"ts tsx mts cts js jsx mjs cjs json jsonc json5 md mdx txt rst py pyi rb go rs java kt kts swift c h cc cpp cxx hpp hh cs fs php lua r jl sh bash zsh fish ps1 bat yml yaml toml ini cfg conf env html htm css scss sass less vue svelte astro sql graphql gql proto lock xml svg csv tsv gradle tf hcl dart ex exs erl hs ml scala clj vim el mk cmake dockerfile gitignore gitattributes editorconfig npmrc nvmrc".split(" ")
|
|
1045
|
+
);
|
|
1046
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".nuxt", "target", "vendor", ".venv", "venv", "__pycache__", "coverage", ".turbo", ".cache"]);
|
|
1047
|
+
var MAX_INDEXED = 6e4;
|
|
1043
1048
|
var EXTENSIONLESS = "Makefile|Dockerfile|Containerfile|Gemfile|Rakefile|Procfile|Justfile|Vagrantfile|BUILD|WORKSPACE";
|
|
1044
1049
|
var REF = new RegExp(
|
|
1045
1050
|
String.raw`(?<![\w/:.-])(\.{0,2}/?(?:[\w@.+-]+/)*(?:[\w@+-][\w@.+-]*\.[A-Za-z][A-Za-z0-9]{0,7}|${EXTENSIONLESS})):(\d+)(?:[-–](\d+))?`,
|
|
@@ -1097,23 +1102,36 @@ function verifyRefs(refs, dirs) {
|
|
|
1097
1102
|
}
|
|
1098
1103
|
return lineCache.get(file);
|
|
1099
1104
|
};
|
|
1105
|
+
let index;
|
|
1106
|
+
const byName = () => index ??= indexFiles(roots);
|
|
1107
|
+
const candidates = (ref) => {
|
|
1108
|
+
const direct = resolve(ref.file, roots);
|
|
1109
|
+
if (direct) return [direct];
|
|
1110
|
+
if (path5.isAbsolute(ref.file)) return [];
|
|
1111
|
+
const wanted = ref.file.replace(/^(\.{1,2}\/)+/, "");
|
|
1112
|
+
return (byName().get(path5.basename(wanted)) ?? []).filter((f) => f.endsWith(`/${wanted}`) || path5.basename(f) === wanted).slice(0, 20);
|
|
1113
|
+
};
|
|
1114
|
+
const check = (file, ref) => {
|
|
1115
|
+
const lines = load(file);
|
|
1116
|
+
if (!lines) return void 0;
|
|
1117
|
+
if (ref.end > lines.length) return `file has ${lines.length} lines`;
|
|
1118
|
+
if (ref.symbol && !mentions(lines, ref) && !enclosedBy(lines, ref)) return `\`${ref.symbol}\` not near line ${ref.start}`;
|
|
1119
|
+
return void 0;
|
|
1120
|
+
};
|
|
1121
|
+
let total = 0;
|
|
1100
1122
|
for (const ref of refs) {
|
|
1101
|
-
const
|
|
1102
|
-
if (!
|
|
1123
|
+
const files = candidates(ref);
|
|
1124
|
+
if (!files.length) {
|
|
1125
|
+
if (!ref.file.includes("/") && !isFileName(ref.file)) continue;
|
|
1126
|
+
total++;
|
|
1103
1127
|
invalid.push({ ref: ref.text, reason: path5.isAbsolute(ref.file) && !inside(ref.file, roots) ? "outside the project" : "file not found" });
|
|
1104
1128
|
continue;
|
|
1105
1129
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (ref.
|
|
1109
|
-
invalid.push({ ref: ref.text, reason: `file has ${lines.length} lines` });
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
if (ref.symbol && !mentions(lines, ref) && !enclosedBy(lines, ref)) {
|
|
1113
|
-
invalid.push({ ref: ref.text, reason: `\`${ref.symbol}\` not near line ${ref.start}` });
|
|
1114
|
-
}
|
|
1130
|
+
total++;
|
|
1131
|
+
const reasons = files.map((f) => check(f, ref));
|
|
1132
|
+
if (!reasons.some((r) => r === void 0)) invalid.push({ ref: ref.text, reason: files.length > 1 ? `${reasons[0]} (${files.length} files match)` : reasons[0] });
|
|
1115
1133
|
}
|
|
1116
|
-
return { total
|
|
1134
|
+
return { total, valid: total - invalid.length, invalid };
|
|
1117
1135
|
}
|
|
1118
1136
|
function mentions(lines, ref) {
|
|
1119
1137
|
const near = lines.slice(Math.max(0, ref.start - 1 - WINDOW), ref.end + WINDOW).join("\n");
|
|
@@ -1126,6 +1144,35 @@ function enclosedBy(lines, ref) {
|
|
|
1126
1144
|
);
|
|
1127
1145
|
return lines.slice(Math.max(0, ref.start - 1 - DEFINITION_LOOKBACK), ref.start).some((l) => def.test(l));
|
|
1128
1146
|
}
|
|
1147
|
+
var isFileName = (name) => {
|
|
1148
|
+
const base = path5.basename(name);
|
|
1149
|
+
return !base.includes(".") || new RegExp(`^(${EXTENSIONLESS})$`).test(base) || FILE_EXTENSIONS.has(base.split(".").pop().toLowerCase());
|
|
1150
|
+
};
|
|
1151
|
+
function indexFiles(roots) {
|
|
1152
|
+
const index = /* @__PURE__ */ new Map();
|
|
1153
|
+
let count = 0;
|
|
1154
|
+
const walk = (dir) => {
|
|
1155
|
+
let entries;
|
|
1156
|
+
try {
|
|
1157
|
+
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1158
|
+
} catch {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
for (const e of entries) {
|
|
1162
|
+
if (count >= MAX_INDEXED) return;
|
|
1163
|
+
if (e.isDirectory()) {
|
|
1164
|
+
if (!SKIP_DIRS.has(e.name)) walk(path5.join(dir, e.name));
|
|
1165
|
+
} else if (e.isFile()) {
|
|
1166
|
+
count++;
|
|
1167
|
+
const list = index.get(e.name);
|
|
1168
|
+
if (list) list.push(path5.join(dir, e.name));
|
|
1169
|
+
else index.set(e.name, [path5.join(dir, e.name)]);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
for (const r of roots) walk(r);
|
|
1174
|
+
return index;
|
|
1175
|
+
}
|
|
1129
1176
|
function resolve(ref, roots) {
|
|
1130
1177
|
const candidates = path5.isAbsolute(ref) ? inside(ref, roots) ? [ref] : [] : roots.map((r) => path5.join(r, ref));
|
|
1131
1178
|
return candidates.find((c) => {
|