@companion-ai/feynman 0.5.9 → 0.5.11
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
CHANGED
|
@@ -10,176 +10,70 @@
|
|
|
10
10
|
<a href="https://deepwiki.com/Companion-Inc/feynman"><img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" /></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
**macOS / Linux:**
|
|
15
|
+
macOS / Linux:
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
18
|
curl -fsSL https://feynman.is/install | bash
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
Windows (PowerShell):
|
|
24
22
|
|
|
25
23
|
```powershell
|
|
26
24
|
irm https://feynman.is/install.ps1 | iex
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
**npm alternative** (uses your local Node.js `>=22.22.0`):
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm install -g @companion-ai/feynman
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
If you installed the interim `@advaitpaliwal/feynman` package, migrate once:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
npm uninstall -g @advaitpaliwal/feynman
|
|
41
|
-
npm install -g @companion-ai/feynman
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Then run `feynman setup` to sign in to a model provider. To use Feynman in an ACP editor such as Zed, run it through [pi-acp](https://github.com/svkozak/pi-acp): `"agent_servers": { "Feynman": { "command": "npx", "args": ["-y", "pi-acp"], "env": { "PI_ACP_PI_COMMAND": "feynman" } } }`. See the [installation guide](https://feynman.is/docs/getting-started/installation) for uninstalling and the [setup guide](https://feynman.is/docs/getting-started/setup) for local models (LM Studio, LiteLLM, Ollama, vLLM) and Amazon Bedrock.
|
|
45
|
-
|
|
46
|
-
### Skills Only
|
|
47
|
-
|
|
48
|
-
If you want just the research skills without the full terminal app:
|
|
27
|
+
Or with npm and Node.js 22.22 or newer: `npm install -g @companion-ai/feynman`.
|
|
49
28
|
|
|
50
|
-
|
|
29
|
+
Then sign in to a model provider:
|
|
51
30
|
|
|
52
31
|
```bash
|
|
53
|
-
|
|
32
|
+
feynman setup
|
|
54
33
|
```
|
|
55
34
|
|
|
56
|
-
|
|
35
|
+
The [installation guide](https://feynman.is/docs/getting-started/installation) covers pinned versions, uninstalling, and the skills-only bundle for Codex and OpenCode. The [setup guide](https://feynman.is/docs/getting-started/setup) covers local models and editors such as Zed.
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
irm https://feynman.is/install-skills.ps1 | iex
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
That installs the skill library into `~/.codex/skills/feynman` for Codex. For other targets, pass a scope:
|
|
37
|
+
## Use
|
|
63
38
|
|
|
64
39
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
```powershell
|
|
71
|
-
& ([scriptblock]::Create((irm https://feynman.is/install-skills.ps1))) -Scope Codex # or -Scope Repo, -Scope OpenCode
|
|
40
|
+
feynman "what do we know about scaling laws"
|
|
41
|
+
feynman lit "RLHF alternatives"
|
|
42
|
+
feynman deepresearch "mechanistic interpretability"
|
|
72
43
|
```
|
|
73
44
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
### What you type → what happens
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
$ feynman "what do we know about scaling laws"
|
|
82
|
-
→ Searches papers and the web, answers with cited sources
|
|
83
|
-
|
|
84
|
-
$ feynman deepresearch "mechanistic interpretability"
|
|
85
|
-
→ Plan-first investigation with parallel researchers, synthesis, and citation verification
|
|
86
|
-
|
|
87
|
-
$ feynman lit "RLHF alternatives"
|
|
88
|
-
→ Literature review with consensus, disagreements, open questions, and lab/PI corpus mode when the input names a research group
|
|
89
|
-
|
|
90
|
-
$ feynman audit 2401.12345
|
|
91
|
-
→ Compares paper claims against the public codebase
|
|
92
|
-
|
|
93
|
-
$ feynman replicate "chain-of-thought improves math"
|
|
94
|
-
→ Plans replication checks and runs them only after an explicit environment choice
|
|
95
|
-
|
|
96
|
-
$ feynman recipe "fine-tune a small model for math reasoning"
|
|
97
|
-
→ Finds ranked, implementable ML training recipes from papers, datasets, docs, and code
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
### Workflows
|
|
103
|
-
|
|
104
|
-
Ask naturally or use slash commands as shortcuts.
|
|
105
|
-
|
|
106
|
-
| Command | What it does |
|
|
45
|
+
| Workflow | What it does |
|
|
107
46
|
| --- | --- |
|
|
108
|
-
| `/deepresearch <topic>` |
|
|
109
|
-
| `/lit <topic
|
|
110
|
-
| `/review <
|
|
111
|
-
| `/audit <
|
|
112
|
-
| `/replicate <paper>` |
|
|
113
|
-
| `/recipe <task
|
|
47
|
+
| `/deepresearch <topic>` | Planned investigation with parallel researchers and citation checks |
|
|
48
|
+
| `/lit <topic or lab>` | Literature review: consensus, disagreements, open questions |
|
|
49
|
+
| `/review <paper or draft>` | Critique with severity levels and a revision plan |
|
|
50
|
+
| `/audit <paper>` | Paper claims compared with its public code |
|
|
51
|
+
| `/replicate <paper>` | Replication plan; runs only after you choose an environment |
|
|
52
|
+
| `/recipe <task>` | Ranked ML training recipes with datasets and code |
|
|
114
53
|
| `/compare <topic>` | Source comparison matrix |
|
|
115
54
|
| `/draft <topic>` | Paper-style draft from research findings |
|
|
116
|
-
| `/autoresearch <idea>` | Bounded experiment loop
|
|
117
|
-
| `/summarize <source>` |
|
|
118
|
-
| `/btw <question>` | Side conversation while the main research agent is busy, with optional handoff back into the main thread |
|
|
119
|
-
| `/outputs` | Browse all research artifacts |
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
### Agents
|
|
124
|
-
|
|
125
|
-
Four bundled research agents, invoked by workflow prompts when decomposition helps.
|
|
126
|
-
|
|
127
|
-
- **Researcher** — gather evidence across papers, web, repos, docs
|
|
128
|
-
- **Reviewer** — internal research critique with severity-graded feedback
|
|
129
|
-
- **Writer** — structured drafts from research notes
|
|
130
|
-
- **Verifier** — inline citations, source URL verification, dead link cleanup
|
|
55
|
+
| `/autoresearch <idea>` | Bounded experiment loop against a benchmark |
|
|
56
|
+
| `/summarize <source>` | Summary of a paper, repository, or PDF |
|
|
131
57
|
|
|
132
|
-
|
|
58
|
+
Results are saved to `outputs/` (drafts to `papers/`). `/lit`, `/deepresearch`, and `/recipe` also write a provenance file listing the sources used and what was verified.
|
|
133
59
|
|
|
134
|
-
|
|
60
|
+
## Sources
|
|
135
61
|
|
|
136
|
-
|
|
137
|
-
- **Literature databases** — read-only Semantic Scholar, OpenAlex, arXiv ID lookup, PubMed, Europe PMC full text, bioRxiv/medRxiv, and Crossref, with stable identifiers. Set the free `OPENALEX_API_KEY` ([create one](https://openalex.org/settings/api)) and optionally `SEMANTIC_SCHOLAR_API_KEY` ([request one](https://www.semanticscholar.org/product/api#api-key-form)) to use your own rate limits
|
|
138
|
-
- **[Hugging Face Hub](https://huggingface.co/docs/hub/api)** — dataset metadata, split/schema inspection, and small file reads from model, dataset, and Space repos
|
|
139
|
-
- **Web research** — search, page fetching, and PDF extraction through [pi-web-access](https://github.com/nicobailon/pi-web-access); Exa works without a key, and `feynman search set` configures Perplexity, Exa, or Gemini
|
|
140
|
-
- **Documents** — local PDF and office-document parsing through [pi-docparser](https://github.com/maxedapps/pi-docparser)
|
|
141
|
-
- **Compute** — Docker, plus Modal or RunPod when their CLIs are installed, for replication and experiment runs you explicitly approve
|
|
62
|
+
Feynman searches alphaXiv, Semantic Scholar, OpenAlex, arXiv, PubMed, Europe PMC, bioRxiv, medRxiv, Crossref, the web, and Hugging Face, and reads local PDFs and documents. Free `OPENALEX_API_KEY` and `SEMANTIC_SCHOLAR_API_KEY` keys avoid the shared rate limits. It runs on stock [Pi](https://github.com/earendil-works/pi) with four research agents: researcher, verifier, reviewer, and writer.
|
|
142
63
|
|
|
143
|
-
|
|
64
|
+
## Telemetry
|
|
144
65
|
|
|
145
|
-
|
|
66
|
+
Feynman sends anonymous usage data to PostHog: commands, workflows, tools, models, token counts, and errors with their messages and stack traces (your home folder shown as `~`). It never sends prompts, model output, paper content, or tool arguments. Turn it off with `FEYNMAN_TELEMETRY=off`. [What is sent](https://feynman.is/docs/getting-started/configuration#telemetry)
|
|
146
67
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
### Telemetry
|
|
152
|
-
|
|
153
|
-
Feynman sends anonymous usage telemetry to PostHog by default and prints a one-time notice on first run. It sends commands, workflow names and outcomes, tool names, model and provider names, token counts, latency, and error messages with stack traces (home folder shown as `~`) under a random install ID. It never sends prompts, model output, paper content, or tool arguments. Set `FEYNMAN_TELEMETRY=off` (or `DO_NOT_TRACK=1`) to disable it; `feynman status` shows the current setting. The full event list is in the [configuration docs](https://feynman.is/docs/getting-started/configuration#telemetry).
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
### Star History
|
|
158
|
-
|
|
159
|
-
<a href="https://www.star-history.com/?repos=Companion-Inc%2Ffeynman&type=date&legend=top-left">
|
|
160
|
-
<picture>
|
|
161
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&theme=dark&legend=top-left" />
|
|
162
|
-
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&legend=top-left" />
|
|
163
|
-
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Companion-Inc/feynman&type=date&legend=top-left" />
|
|
164
|
-
</picture>
|
|
165
|
-
</a>
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
The bundled Pi packages are pinned and update with Feynman, not through `feynman update`. See the [package stack](https://feynman.is/docs/reference/package-stack) and [release notes](https://feynman.is/docs/reference/releases).
|
|
170
|
-
|
|
171
|
-
### Contributing
|
|
172
|
-
|
|
173
|
-
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full contributor guide.
|
|
68
|
+
## Contributing
|
|
174
69
|
|
|
175
70
|
```bash
|
|
176
71
|
git clone https://github.com/Companion-Inc/feynman.git
|
|
177
72
|
cd feynman
|
|
178
|
-
nvm use || nvm install
|
|
179
73
|
npm install
|
|
180
74
|
npm test
|
|
181
|
-
npm run typecheck
|
|
182
|
-
npm run build
|
|
183
75
|
```
|
|
184
76
|
|
|
185
|
-
[
|
|
77
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
78
|
+
|
|
79
|
+
[Docs](https://feynman.is/docs) · [Release notes](RELEASES.md) · [MIT License](LICENSE)
|
package/RELEASES.md
CHANGED
|
@@ -6,6 +6,17 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## v0.5.11 - 2026-09-27
|
|
10
|
+
|
|
11
|
+
- **Crossref lookups no longer fail in bursts.** Crossref's public pool allows one request at a time, one per second, and parallel DOI checks (the verifier cross-checks every citation) got `429 Too Many Requests` on most calls. Crossref requests now run one after another at the pool's pace. Set `CROSSREF_MAILTO` to your email for Crossref's faster polite pool; a 429 now says so instead of a generic "Science database request failed".
|
|
12
|
+
- Telemetry's "wrote output" flag no longer misses a report written in the first second of a run on filesystems that round file times (HFS+, FAT, ext3).
|
|
13
|
+
|
|
14
|
+
## v0.5.10 - 2026-09-27
|
|
15
|
+
|
|
16
|
+
- **Semantic Scholar understands the documented query syntax.** A `semantic:` prefix and `year_from=`/`year_to=` flags, which the model reuses from the OpenAlex examples, were sent to Semantic Scholar as search text and returned unrelated papers or nothing. They now become Semantic Scholar's own year filter.
|
|
17
|
+
- **The startup header lists only Feynman's workflows.** It also listed prompt commands that pi-subagents ships, such as `/parallel-review` and `/review-loop`.
|
|
18
|
+
- **Cleaner README and landing page** with a current screenshot, and a shorter configuration page.
|
|
19
|
+
|
|
9
20
|
## v0.5.9 - 2026-09-27
|
|
10
21
|
|
|
11
22
|
- **Subagents keep their search tools in the foreground.** A `researcher` or `verifier` launched in the foreground failed at once because foreground children never load the parent's extensions, so `web_search`, `fetch_content`, and `feynman_science_database_search` were missing. Feynman now points pi-subagents' `defaultSubagentOnlyExtensions` setting at its research tools and pi-web-access, so every child has them, in the foreground or background.
|
|
@@ -6,6 +6,8 @@ import { resolve as resolvePath } from "node:path";
|
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
8
8
|
|
|
9
|
+
import { readPromptSpecs } from "../../metadata/commands.mjs";
|
|
10
|
+
|
|
9
11
|
import {
|
|
10
12
|
APP_ROOT,
|
|
11
13
|
FEYNMAN_AGENT_LOGO,
|
|
@@ -161,9 +163,11 @@ function detectSystemResources(): SystemResources {
|
|
|
161
163
|
|
|
162
164
|
type WorkflowInfo = { name: string; description: string };
|
|
163
165
|
|
|
164
|
-
|
|
166
|
+
// Only Feynman's own workflows; other packages (pi-subagents) also ship prompt commands.
|
|
167
|
+
export function getResearchWorkflows(pi: Pick<ExtensionAPI, "getCommands">, appRoot = APP_ROOT): WorkflowInfo[] {
|
|
168
|
+
const feynmanWorkflows = new Set(readPromptSpecs(appRoot).map((spec) => spec.name));
|
|
165
169
|
return pi.getCommands()
|
|
166
|
-
.filter((cmd) => cmd.source === "prompt")
|
|
170
|
+
.filter((cmd) => cmd.source === "prompt" && feynmanWorkflows.has(cmd.name))
|
|
167
171
|
.map((cmd) => ({ name: `/${cmd.name}`, description: cmd.description ?? "" }))
|
|
168
172
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
169
173
|
}
|
|
@@ -137,6 +137,25 @@ function crossrefAuthors(value: unknown): string[] {
|
|
|
137
137
|
.slice(0, 8);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// Crossref's public pool allows one request at a time, one per second
|
|
141
|
+
// (x-concurrency-limit: 1); the polite pool, used when a mailto is sent, allows
|
|
142
|
+
// three per second. Parallel DOI checks got 429 on most calls, so run Crossref
|
|
143
|
+
// requests one after another at the pool's pace.
|
|
144
|
+
let crossrefQueue: Promise<unknown> = Promise.resolve();
|
|
145
|
+
let crossrefLastStartedAt = Number.NEGATIVE_INFINITY;
|
|
146
|
+
|
|
147
|
+
export function withCrossrefPacing<T>(polite: boolean, run: () => Promise<T>): Promise<T> {
|
|
148
|
+
const minGapMs = polite ? 350 : 1000;
|
|
149
|
+
const next = crossrefQueue.then(async () => {
|
|
150
|
+
const wait = crossrefLastStartedAt + minGapMs - Date.now();
|
|
151
|
+
if (wait > 0) await new Promise((resolve) => setTimeout(resolve, wait));
|
|
152
|
+
crossrefLastStartedAt = Date.now();
|
|
153
|
+
return run();
|
|
154
|
+
});
|
|
155
|
+
crossrefQueue = next.catch(() => {});
|
|
156
|
+
return next;
|
|
157
|
+
}
|
|
158
|
+
|
|
140
159
|
async function searchCrossref(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
|
|
141
160
|
const query = cleanQuery(params.query);
|
|
142
161
|
const limit = safeLimit(params.limit);
|
|
@@ -148,7 +167,17 @@ async function searchCrossref(params: ScienceDatabaseSearchParams): Promise<Reco
|
|
|
148
167
|
select: "DOI,title,published-print,published-online,issued,container-title,author,is-referenced-by-count,URL,type",
|
|
149
168
|
...(mailto ? { mailto } : {}),
|
|
150
169
|
}).toString();
|
|
151
|
-
|
|
170
|
+
let payload: Record<string, unknown>;
|
|
171
|
+
try {
|
|
172
|
+
payload = recordValue(await withCrossrefPacing(Boolean(mailto), () => fetchJson(url)));
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (!(error instanceof ScienceDatabaseRequestError) || error.status !== 429) throw error;
|
|
175
|
+
throw new Error(
|
|
176
|
+
mailto
|
|
177
|
+
? "Crossref rate-limited this request (HTTP 429). Wait a few seconds and retry, or look the DOI up with source openalex."
|
|
178
|
+
: "Crossref's public pool rate-limited this request (HTTP 429). Set CROSSREF_MAILTO to your email for Crossref's faster polite pool, wait and retry, or look the DOI up with source openalex.",
|
|
179
|
+
);
|
|
180
|
+
}
|
|
152
181
|
const message = recordValue(payload.message);
|
|
153
182
|
const results = arrayValue(message.items).flatMap((item) => {
|
|
154
183
|
const record = recordValue(item);
|
|
@@ -257,12 +286,31 @@ function truncateText(value: string | undefined, maxChars: number): string | und
|
|
|
257
286
|
return `${value.slice(0, maxChars).trimEnd()}…`;
|
|
258
287
|
}
|
|
259
288
|
|
|
260
|
-
|
|
289
|
+
// The model reuses the OpenAlex syntax this tool documents (a `semantic:`
|
|
290
|
+
// prefix, year_from=/year_to= flags). Sent as text, it returned junk or zero
|
|
291
|
+
// results; map it to Semantic Scholar's own year filter instead.
|
|
292
|
+
export function parseSemanticScholarQuery(raw: string): { query: string; year?: string } {
|
|
293
|
+
let from = "";
|
|
294
|
+
let to = "";
|
|
295
|
+
const query = raw
|
|
296
|
+
.replace(/^\s*semantic:\s*/i, "")
|
|
297
|
+
.replace(/\byear_(from|to)=(\d{4})\b/gi, (_match, kind: string, year: string) => {
|
|
298
|
+
if (kind.toLowerCase() === "from") from = year;
|
|
299
|
+
else to = year;
|
|
300
|
+
return " ";
|
|
301
|
+
})
|
|
302
|
+
.replace(/\s+/g, " ")
|
|
303
|
+
.trim();
|
|
304
|
+
return from || to ? { query, year: `${from}-${to}` } : { query };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function semanticScholarSearchUrl(query: string, limit: number, sort: string, year?: string): URL {
|
|
261
308
|
const relevance = sort === "relevance";
|
|
262
309
|
const url = new URL(`${SEMANTIC_SCHOLAR_BASE}/paper/search${relevance ? "" : "/bulk"}`);
|
|
263
310
|
url.search = new URLSearchParams({
|
|
264
311
|
query,
|
|
265
312
|
fields: SEMANTIC_SCHOLAR_FIELDS,
|
|
313
|
+
...(year ? { year } : {}),
|
|
266
314
|
...(relevance ? { limit: String(limit) } : { sort }),
|
|
267
315
|
}).toString();
|
|
268
316
|
return url;
|
|
@@ -271,10 +319,10 @@ function semanticScholarSearchUrl(query: string, limit: number, sort: string): U
|
|
|
271
319
|
// Bulk search sorted by citation count surfaces seminal papers that relevance
|
|
272
320
|
// ranking misses; it returns up to 1,000 rows, so only the top `limit` are kept.
|
|
273
321
|
async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promise<Record<string, unknown>> {
|
|
274
|
-
const query = cleanQuery(params.query);
|
|
322
|
+
const { query, year } = parseSemanticScholarQuery(cleanQuery(params.query));
|
|
275
323
|
const limit = safeLimit(params.limit);
|
|
276
324
|
let sort = params.sort === "relevance" ? "relevance" : params.sort === "pub_date" ? "publicationDate:desc" : "citationCount:desc";
|
|
277
|
-
const endpoints = [semanticScholarSearchUrl(query, limit, sort)];
|
|
325
|
+
const endpoints = [semanticScholarSearchUrl(query, limit, sort, year)];
|
|
278
326
|
let note: string | undefined;
|
|
279
327
|
let payload: Record<string, unknown>;
|
|
280
328
|
try {
|
|
@@ -284,7 +332,7 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
|
|
|
284
332
|
const anonymous = !process.env.SEMANTIC_SCHOLAR_API_KEY?.trim();
|
|
285
333
|
if (sort !== "relevance" || !anonymous || !(error instanceof ScienceDatabaseRequestError) || error.status !== 429) throw error;
|
|
286
334
|
sort = "citationCount:desc";
|
|
287
|
-
endpoints.push(semanticScholarSearchUrl(query, limit, sort));
|
|
335
|
+
endpoints.push(semanticScholarSearchUrl(query, limit, sort, year));
|
|
288
336
|
note = `Relevance search was rate-limited (HTTP 429) on Semantic Scholar's shared anonymous pool, so these are citation-sorted bulk results. A free SEMANTIC_SCHOLAR_API_KEY avoids this: ${SEMANTIC_SCHOLAR_KEY_URL}`;
|
|
289
337
|
payload = recordValue(await fetchSemanticScholar(endpoints[1]!));
|
|
290
338
|
}
|
|
@@ -314,6 +362,7 @@ async function searchSemanticScholar(params: ScienceDatabaseSearchParams): Promi
|
|
|
314
362
|
schema: "feynman.scienceDatabaseSearch.v1",
|
|
315
363
|
source: "semanticscholar",
|
|
316
364
|
query,
|
|
365
|
+
...(year ? { year } : {}),
|
|
317
366
|
sort,
|
|
318
367
|
...(note ? { note } : {}),
|
|
319
368
|
totalCount: numberValue(payload.total) ?? results.length,
|
|
@@ -95,6 +95,11 @@ export function workflowName(text: string, workflows: ReadonlySet<string>): stri
|
|
|
95
95
|
return name && workflows.has(name) ? name : "chat";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Filesystems stamp modification times from a coarse clock, and some (HFS+,
|
|
99
|
+
// FAT, ext3) round them to whole seconds, so a file written just after the run
|
|
100
|
+
// started can carry an earlier mtime. Allow for that rounding.
|
|
101
|
+
const MTIME_TOLERANCE_MS = 2000;
|
|
102
|
+
|
|
98
103
|
export function wroteResearchOutput(cwd: string, since: number): boolean {
|
|
99
104
|
let scanned = 0;
|
|
100
105
|
for (const dir of OUTPUT_DIRS) {
|
|
@@ -108,7 +113,7 @@ export function wroteResearchOutput(cwd: string, since: number): boolean {
|
|
|
108
113
|
if (++scanned > OUTPUT_SCAN_LIMIT) return false;
|
|
109
114
|
try {
|
|
110
115
|
const stats = statSync(join(cwd, dir, entry));
|
|
111
|
-
if (stats.isFile() && stats.mtimeMs >= since) return true;
|
|
116
|
+
if (stats.isFile() && stats.mtimeMs >= since - MTIME_TOLERANCE_MS) return true;
|
|
112
117
|
} catch {}
|
|
113
118
|
}
|
|
114
119
|
}
|