@tiagojct/subsub 0.15.0 → 0.16.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/CHANGELOG.md +8 -0
- package/README.md +7 -0
- package/dist/statement.js +35 -0
- package/dist/subsub.js +22 -1
- package/package.json +1 -1
- package/prompts/lit.md +4 -0
- package/src/statement.ts +45 -0
- package/src/subsub.ts +27 -1
- package/web/i18n.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Sub-Sub follows the version numbers of npm: a change in the second number (0.13) adds features or changes how you use it; a change in the third (0.13.1) only fixes.
|
|
4
4
|
|
|
5
|
+
## 0.16.0 (2026-10-08)
|
|
6
|
+
|
|
7
|
+
For clinical researchers:
|
|
8
|
+
|
|
9
|
+
- `/ai-statement [what you used it for]` writes a draft of the statement on the use of AI that journals ask for, with the Sub-Sub version, its DOI, your models and Sub-Sub's checks, in `Research/`. The parts in brackets are for you to complete. It is also a button in the browser view.
|
|
10
|
+
- A `/lit` note now says what it is: an AI-assisted rapid review, with one AI screener, no second reviewer and no formal risk-of-bias assessment, for scoping, a background section or a first search, and not a systematic review.
|
|
11
|
+
- The site has a page for clinical researchers, in English and Portuguese: what Sub-Sub does for them, what it is not, how to get it on a hospital computer, moving from EndNote or Mendeley, data protection, and journals and AI. The Install page has a section for your IT service: what is installed, what runs and which addresses it contacts.
|
|
12
|
+
|
|
5
13
|
## 0.15.0 (2026-10-08)
|
|
6
14
|
|
|
7
15
|
Checks:
|
package/README.md
CHANGED
|
@@ -99,6 +99,7 @@ Commands in Sub-Sub:
|
|
|
99
99
|
| `/profile [name]` | Show or change the profile |
|
|
100
100
|
| `/subsub` | Zotero connection, mode, model and library counts |
|
|
101
101
|
| `/history`, `/undo [id]` | Recent library changes; undo the last one or one by its id |
|
|
102
|
+
| `/ai-statement [uses]` | A draft statement on the use of AI for a manuscript, with the Sub-Sub version, its DOI and your models |
|
|
102
103
|
| `/tag-batch [size]` | Tag items that have no topic, with your tag list |
|
|
103
104
|
| `/clean-tags` | Find tags that are not in your tag list and propose fixes |
|
|
104
105
|
| `/import-queue [path]` | Import the references in the import queue note |
|
|
@@ -177,6 +178,12 @@ End-to-end tests (real pi and Python servers, a fake Zotero, a scripted model):
|
|
|
177
178
|
|
|
178
179
|
See [CHANGELOG.md](CHANGELOG.md).
|
|
179
180
|
|
|
181
|
+
## Cite
|
|
182
|
+
|
|
183
|
+
Jacinto T. Sub-Sub: a research assistant for Zotero [software]. Porto: Faculdade de Medicina da Universidade do Porto; 2026. doi:[10.5281/zenodo.23238602](https://doi.org/10.5281/zenodo.23238602)
|
|
184
|
+
|
|
185
|
+
This DOI always points to the latest version; each version also has its own DOI on [Zenodo](https://doi.org/10.5281/zenodo.23238602). `CITATION.cff` has the same reference for citation managers.
|
|
186
|
+
|
|
180
187
|
## Licence
|
|
181
188
|
|
|
182
189
|
[MIT](LICENSE), Tiago Jacinto. The `/lit`, `/compare` and `/review` prompts adapt text from [Feynman](https://github.com/Companion-Inc/feynman) (MIT); see [THIRD_PARTY_NOTICES](THIRD_PARTY_NOTICES). IBM Plex fonts: SIL Open Font License.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /ai-statement: a draft of the statement on the use of AI that journals ask for (ICMJE and
|
|
3
|
+
* most publishers), with the facts Sub-Sub knows: its version, the models of each mode and their
|
|
4
|
+
* providers, and the checks the program makes. The authors complete the bracketed parts.
|
|
5
|
+
*/
|
|
6
|
+
export const SUBSUB_DOI = "10.5281/zenodo.23238602";
|
|
7
|
+
/** "opencode-go/mimo-v2.6-pro" -> "mimo-v2.6-pro (OpenCode Go)". */
|
|
8
|
+
export function modelLabel(spec) {
|
|
9
|
+
const i = spec.indexOf("/");
|
|
10
|
+
if (i < 0)
|
|
11
|
+
return spec;
|
|
12
|
+
const provider = spec.slice(0, i);
|
|
13
|
+
const names = { "opencode-go": "OpenCode Go", google: "Google", mistral: "Mistral AI", openai: "OpenAI", anthropic: "Anthropic", openrouter: "OpenRouter", deepseek: "DeepSeek" };
|
|
14
|
+
return `${spec.slice(i + 1)} (${names[provider] ?? provider})`;
|
|
15
|
+
}
|
|
16
|
+
export function aiStatement(f) {
|
|
17
|
+
const models = [...new Set(Object.values(f.models).filter(Boolean))].map(modelLabel);
|
|
18
|
+
const modelText = models.length ? `the language model${models.length > 1 ? "s" : ""} ${models.join(" and ")}` : "[the language model and its provider]";
|
|
19
|
+
const uses = f.uses?.trim() || "[the tasks: for example, literature searches, screening of search results, reading notes on included studies, checks of the references]";
|
|
20
|
+
return [
|
|
21
|
+
"## Use of AI tools",
|
|
22
|
+
"",
|
|
23
|
+
`We used Sub-Sub ${f.version} (Jacinto T., doi:${SUBSUB_DOI}), an open-source research assistant that works from a Zotero reference library, with ${modelText}, in [month and year]. We used it for ${uses}.`,
|
|
24
|
+
"",
|
|
25
|
+
"Sub-Sub records in each note whether it was written from the full text, the abstract or the bibliographic record, checks that quotations are in the full text, and makes no change to the reference library without the authors' approval." +
|
|
26
|
+
(f.starbuck ? " The cited works were checked for existence, bibliographic accuracy and retraction with Starbuck." : ""),
|
|
27
|
+
"",
|
|
28
|
+
"[Choose one: No text of this manuscript was written by an AI tool. | Sub-Sub drafted [which parts]; the authors revised and rewrote this text.]",
|
|
29
|
+
"",
|
|
30
|
+
"The authors checked every reference and every statement taken from the sources against the original publications, and take full responsibility for the content of this article.",
|
|
31
|
+
"",
|
|
32
|
+
"<!-- Draft made by Sub-Sub on " + f.date + ". Complete the parts in brackets, delete this line, and follow the journal's instructions on where the statement goes (often the Methods or the Acknowledgements). -->",
|
|
33
|
+
"",
|
|
34
|
+
].join("\n");
|
|
35
|
+
}
|
package/dist/subsub.js
CHANGED
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
* The Python servers start in session_start (not in the factory) and stop in
|
|
16
16
|
* session_shutdown. Tools are registered once and use the current bridge.
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { Bridge } from "./bridge.js";
|
|
22
22
|
import { expand, findUv, FREE_MODELS, isProfile, loadConfig, parseEnvFile, saveConfig, serverCommand, starbuckCommand, starbuckEnabled, uiLanguage, who, } from "./config.js";
|
|
23
23
|
import { profileList, profileSpec, promptBlocked } from "./profiles.js";
|
|
24
24
|
import { coerceArgs, loosenArrays, resolvePathArgs } from "./args.js";
|
|
25
|
+
import { aiStatement } from "./statement.js";
|
|
25
26
|
import { applyEdits, checkLiteratureNote, field, frontMatter, itemKeyOf, missingQuotes, quoteProblem, quotesIn } from "./notes.js";
|
|
26
27
|
import { buildPolicy, isSecret, judgePath, normalizeToolPath, realish, within } from "./paths.js";
|
|
27
28
|
import { describeArgs, formatPreview, isEmptyPreview } from "./preview.js";
|
|
@@ -673,6 +674,26 @@ export async function createSubsub(pi, deps = {}) {
|
|
|
673
674
|
await refreshLibrary();
|
|
674
675
|
},
|
|
675
676
|
});
|
|
677
|
+
pi.registerCommand("ai-statement", {
|
|
678
|
+
description: "Sub-Sub: a draft statement on the use of AI for a manuscript (what journals ask for)",
|
|
679
|
+
handler: async (args, ctx) => {
|
|
680
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
681
|
+
const text = aiStatement({ version: ownVersion(), models: cfg.models, uses: String(args ?? ""), date, starbuck: starbuckEnabled(cfg) });
|
|
682
|
+
const dir = join(cfg.vault ?? ctx.cwd, "Research");
|
|
683
|
+
let file = join(dir, `${date} AI use statement.md`);
|
|
684
|
+
for (let n = 2; existsSync(file); n++)
|
|
685
|
+
file = join(dir, `${date} AI use statement-${n}.md`);
|
|
686
|
+
try {
|
|
687
|
+
mkdirSync(dir, { recursive: true });
|
|
688
|
+
writeFileSync(file, text);
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
ctx.ui.notify(`${text}\n${L("Could not save it", "Não foi possível guardar")}: ${err.message}`, "warning");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
ctx.ui.notify(L(`Sub-Sub: a draft statement on the use of AI is in ${file}. Complete the parts in brackets. To name the tasks, type /ai-statement followed by them, for example: /ai-statement literature searches and reading notes.`, `Sub-Sub: há um rascunho da declaração sobre o uso de IA em ${file}. Complete as partes entre parênteses retos. Para indicar as tarefas, escreva /ai-statement seguido delas, por exemplo: /ai-statement literature searches and reading notes.`), "info");
|
|
695
|
+
},
|
|
696
|
+
});
|
|
676
697
|
pi.registerCommand("history", {
|
|
677
698
|
description: "Sub-Sub: recent library changes",
|
|
678
699
|
handler: async (_args, ctx) => {
|
package/package.json
CHANGED
package/prompts/lit.md
CHANGED
|
@@ -54,6 +54,7 @@ Write one note: `Research/<date> <slug>.md` in the Sub-Sub folder. Do not overwr
|
|
|
54
54
|
title: "<question>"
|
|
55
55
|
date: <YYYY-MM-DD>
|
|
56
56
|
type: literature-review
|
|
57
|
+
review_type: AI-assisted rapid review (not a systematic review)
|
|
57
58
|
question: "<question in one sentence>"
|
|
58
59
|
searches: ["<phrasing 1>", "<phrasing 2>", "<phrasing 3>"]
|
|
59
60
|
sources_found: <number of unique works>
|
|
@@ -78,6 +79,9 @@ reference_check: "<path of the Starbuck report, or none>"
|
|
|
78
79
|
## Methods
|
|
79
80
|
(where you searched, the phrasings, the pre-screen terms, the screening counts, the gap searches, what you included and left out, and why)
|
|
80
81
|
|
|
82
|
+
## What this review is
|
|
83
|
+
(copy this paragraph as it is) An AI-assisted rapid review: one AI screener, no second reviewer, no registered protocol and no formal risk-of-bias assessment (such as RoB 2 or ROBINS-I). Use it to scope a question, to write a background section or as a first search. For a systematic review, use the searches and the screening log as a starting point, then screen in duplicate, assess the risk of bias and report to PRISMA.
|
|
84
|
+
|
|
81
85
|
## Sources
|
|
82
86
|
1. [@citekey] for library items; for other works: Authors, year, title, journal. doi:... (not in library)
|
|
83
87
|
```
|
package/src/statement.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /ai-statement: a draft of the statement on the use of AI that journals ask for (ICMJE and
|
|
3
|
+
* most publishers), with the facts Sub-Sub knows: its version, the models of each mode and their
|
|
4
|
+
* providers, and the checks the program makes. The authors complete the bracketed parts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const SUBSUB_DOI = "10.5281/zenodo.23238602";
|
|
8
|
+
|
|
9
|
+
export interface StatementFacts {
|
|
10
|
+
version: string;
|
|
11
|
+
models: Partial<Record<"librarian" | "researcher", string>>;
|
|
12
|
+
uses?: string;
|
|
13
|
+
date: string;
|
|
14
|
+
starbuck: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** "opencode-go/mimo-v2.6-pro" -> "mimo-v2.6-pro (OpenCode Go)". */
|
|
18
|
+
export function modelLabel(spec: string): string {
|
|
19
|
+
const i = spec.indexOf("/");
|
|
20
|
+
if (i < 0) return spec;
|
|
21
|
+
const provider = spec.slice(0, i);
|
|
22
|
+
const names: Record<string, string> = { "opencode-go": "OpenCode Go", google: "Google", mistral: "Mistral AI", openai: "OpenAI", anthropic: "Anthropic", openrouter: "OpenRouter", deepseek: "DeepSeek" };
|
|
23
|
+
return `${spec.slice(i + 1)} (${names[provider] ?? provider})`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function aiStatement(f: StatementFacts): string {
|
|
27
|
+
const models = [...new Set(Object.values(f.models).filter(Boolean) as string[])].map(modelLabel);
|
|
28
|
+
const modelText = models.length ? `the language model${models.length > 1 ? "s" : ""} ${models.join(" and ")}` : "[the language model and its provider]";
|
|
29
|
+
const uses = f.uses?.trim() || "[the tasks: for example, literature searches, screening of search results, reading notes on included studies, checks of the references]";
|
|
30
|
+
return [
|
|
31
|
+
"## Use of AI tools",
|
|
32
|
+
"",
|
|
33
|
+
`We used Sub-Sub ${f.version} (Jacinto T., doi:${SUBSUB_DOI}), an open-source research assistant that works from a Zotero reference library, with ${modelText}, in [month and year]. We used it for ${uses}.`,
|
|
34
|
+
"",
|
|
35
|
+
"Sub-Sub records in each note whether it was written from the full text, the abstract or the bibliographic record, checks that quotations are in the full text, and makes no change to the reference library without the authors' approval." +
|
|
36
|
+
(f.starbuck ? " The cited works were checked for existence, bibliographic accuracy and retraction with Starbuck." : ""),
|
|
37
|
+
"",
|
|
38
|
+
"[Choose one: No text of this manuscript was written by an AI tool. | Sub-Sub drafted [which parts]; the authors revised and rewrote this text.]",
|
|
39
|
+
"",
|
|
40
|
+
"The authors checked every reference and every statement taken from the sources against the original publications, and take full responsibility for the content of this article.",
|
|
41
|
+
"",
|
|
42
|
+
"<!-- Draft made by Sub-Sub on " + f.date + ". Complete the parts in brackets, delete this line, and follow the journal's instructions on where the statement goes (often the Methods or the Acknowledgements). -->",
|
|
43
|
+
"",
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
package/src/subsub.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
20
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
21
|
import { dirname, join } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { Bridge, type ServerSpec } from "./bridge.ts";
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from "./config.ts";
|
|
41
41
|
import { profileList, profileSpec, promptBlocked } from "./profiles.ts";
|
|
42
42
|
import { coerceArgs, loosenArrays, resolvePathArgs } from "./args.ts";
|
|
43
|
+
import { aiStatement } from "./statement.ts";
|
|
43
44
|
import { applyEdits, checkLiteratureNote, field, frontMatter, itemKeyOf, missingQuotes, quoteProblem, quotesIn } from "./notes.ts";
|
|
44
45
|
import { buildPolicy, isSecret, judgePath, normalizeToolPath, realish, within } from "./paths.ts";
|
|
45
46
|
import { describeArgs, formatPreview, isEmptyPreview } from "./preview.ts";
|
|
@@ -695,6 +696,31 @@ export async function createSubsub(pi: ExtensionAPI, deps: SubsubDeps = {}): Pro
|
|
|
695
696
|
},
|
|
696
697
|
});
|
|
697
698
|
|
|
699
|
+
pi.registerCommand("ai-statement", {
|
|
700
|
+
description: "Sub-Sub: a draft statement on the use of AI for a manuscript (what journals ask for)",
|
|
701
|
+
handler: async (args, ctx) => {
|
|
702
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
703
|
+
const text = aiStatement({ version: ownVersion(), models: cfg.models, uses: String(args ?? ""), date, starbuck: starbuckEnabled(cfg) });
|
|
704
|
+
const dir = join(cfg.vault ?? ctx.cwd, "Research");
|
|
705
|
+
let file = join(dir, `${date} AI use statement.md`);
|
|
706
|
+
for (let n = 2; existsSync(file); n++) file = join(dir, `${date} AI use statement-${n}.md`);
|
|
707
|
+
try {
|
|
708
|
+
mkdirSync(dir, { recursive: true });
|
|
709
|
+
writeFileSync(file, text);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
ctx.ui.notify(`${text}\n${L("Could not save it", "Não foi possível guardar")}: ${(err as Error).message}`, "warning");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
ctx.ui.notify(
|
|
715
|
+
L(
|
|
716
|
+
`Sub-Sub: a draft statement on the use of AI is in ${file}. Complete the parts in brackets. To name the tasks, type /ai-statement followed by them, for example: /ai-statement literature searches and reading notes.`,
|
|
717
|
+
`Sub-Sub: há um rascunho da declaração sobre o uso de IA em ${file}. Complete as partes entre parênteses retos. Para indicar as tarefas, escreva /ai-statement seguido delas, por exemplo: /ai-statement literature searches and reading notes.`,
|
|
718
|
+
),
|
|
719
|
+
"info",
|
|
720
|
+
);
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
|
|
698
724
|
pi.registerCommand("history", {
|
|
699
725
|
description: "Sub-Sub: recent library changes",
|
|
700
726
|
handler: async (_args, ctx) => {
|
package/web/i18n.js
CHANGED
|
@@ -270,6 +270,7 @@ export const ACTIONS = {
|
|
|
270
270
|
{ cmd: "review", fill: true, profiles: ["author", "editor"], en: "Review a manuscript", pt: "Rever um manuscrito" },
|
|
271
271
|
{ cmd: "alert", send: true, en: "New publications", pt: "Novas publicações" },
|
|
272
272
|
{ cmd: "request-copy", fill: true, en: "Ask an author for a copy", pt: "Pedir uma cópia ao autor" },
|
|
273
|
+
{ cmd: "ai-statement", fill: true, en: "Statement on AI use", pt: "Declaração de uso de IA" },
|
|
273
274
|
{ cmd: "digest", send: true, profiles: ["scholar", "author", "editor"], en: "Digest of the week", pt: "Resumo da semana" },
|
|
274
275
|
],
|
|
275
276
|
library: [
|