create-academic-research 0.1.6 → 0.1.8

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
@@ -53,6 +53,9 @@ By default, the wizard:
53
53
  - writes `docs/agent/capability-profile.md`;
54
54
  - writes `docs/agent/mcp-setup.md`;
55
55
  - writes `docs/agent/generated/mcp.json` unless an explicit agent target is set;
56
+ - includes `scripts/README.md` for repeatable command entrypoints;
57
+ - includes reusable `wiki/templates/` pages for sources, claims, experiments,
58
+ decisions, reviewer concerns, and research questions;
56
59
  - appends the onboarding event to `wiki/log.md`;
57
60
  - does not install external MCP tools unless explicitly requested.
58
61
 
@@ -84,11 +87,13 @@ Inside a generated project:
84
87
 
85
88
  ```bash
86
89
  npx academic-research doctor
90
+ npx academic-research setup
87
91
  npx academic-research rename --title "New Title" --slug new-title --package new_title
88
92
  npx academic-research agents list
89
93
  npx academic-research skills presets
90
94
  npx academic-research skills install --preset default
91
95
  npx academic-research skills install --preset enhanced
96
+ npx academic-research skills install source-ingestion sota-literature-review
92
97
  npx academic-research skills list
93
98
  npx academic-research skills status
94
99
  npx academic-research skills remove source-ingestion
@@ -99,21 +104,26 @@ npx academic-research mcp enabled
99
104
  npx academic-research mcp available
100
105
  npx academic-research mcp commands arxiv
101
106
  npx academic-research mcp env openalex semantic-scholar zotero
102
- npx academic-research mcp enable arxiv openalex
107
+ npx academic-research mcp enable arxiv dblp
103
108
  npx academic-research mcp disable arxiv
104
109
  npx academic-research mcp install arxiv
105
110
  npx academic-research mcp uninstall arxiv
111
+ npx academic-research mcp smoke
106
112
  npx academic-research mcp doctor
107
113
  ```
108
114
 
109
115
  ## Command Model
110
116
 
117
+ `academic-research setup` is a non-destructive onboarding status command. It
118
+ prints the active preset, agent, skill counts, enabled MCP records, and next
119
+ commands without changing files.
120
+
111
121
  Skills are project-local by default.
112
122
 
113
123
  | Command | Meaning |
114
124
  |---|---|
115
125
  | `skills presets` | List available capability presets. |
116
- | `skills install` | Install project-local skills for the selected preset and project agent. This does not change MCP records. |
126
+ | `skills install` | Install project-local skills by preset, or selected skill ids such as `source-ingestion`. This does not change MCP records. |
117
127
  | `skills list` | List skills found in project-local skill loader directories. |
118
128
  | `skills status` | Show configured project preset, agent, scope, skill roots, unique skill ids, and installed copies. |
119
129
  | `skills remove` / `skills uninstall` | Remove selected project-local skills. |
@@ -132,6 +142,7 @@ MCP commands are split by side-effect:
132
142
  | `mcp disable` | Remove an MCP server from project records and generated snippets. |
133
143
  | `mcp install` | Run finite external tool install commands for selected MCP servers. It must not launch stdio MCP servers. |
134
144
  | `mcp uninstall` | Run the external uninstall command when one exists. |
145
+ | `mcp smoke` | Print non-launching readiness diagnostics for enabled or selected MCP servers. |
135
146
  | `mcp doctor` | Validate enabled MCP records, generated snippets, required env vars, and documented manual prerequisites. |
136
147
 
137
148
  ## Companion Skills
@@ -185,6 +196,9 @@ live tools by themselves. Your MCP client must load the generated snippet, and
185
196
  the referenced commands must be available on your machine or runnable through
186
197
  `uvx`/`npx`. `mcp install` only runs finite setup commands such as the arXiv
187
198
  tool install; it deliberately does not launch stdio MCP servers.
199
+ Use `mcp smoke` for a non-launching readiness pass before wiring a client: it
200
+ checks required env vars, manual/local-service status, and whether runtime
201
+ commands are visible on `PATH`.
188
202
 
189
203
  ## Validate This Package
190
204
 
@@ -202,8 +216,8 @@ Releases are tag-driven. Update `package.json` and `package-lock.json`, commit
202
216
  the change, create `vX.Y.Z`, and push the tag:
203
217
 
204
218
  ```bash
205
- git tag -a v0.1.6 -m "v0.1.6"
206
- git push origin main v0.1.6
219
+ git tag -a v0.1.8 -m "v0.1.8"
220
+ git push origin main v0.1.8
207
221
  ```
208
222
 
209
223
  Once the GitHub repository is public, the release workflow validates the tag
@@ -16,6 +16,7 @@ export interface InitializeCapabilitiesOptions {
16
16
  export interface CapabilityCommandResult {
17
17
  ok: true;
18
18
  count?: number;
19
+ skills?: string[];
19
20
  servers?: string[];
20
21
  }
21
22
  export interface InstalledSkill {
@@ -36,7 +37,9 @@ export declare function readCapabilities(root: string): Promise<CapabilityState>
36
37
  export declare function writeCapabilities(root: string, state: Partial<CapabilityState>): Promise<void>;
37
38
  export declare function initializeCapabilities(root: string, options?: InitializeCapabilitiesOptions): Promise<void>;
38
39
  export declare function buildSkillInstallCommands(root: string, preset?: string, options?: SkillInstallOptions): Promise<string[][]>;
40
+ export declare function buildExplicitSkillInstallCommands(root: string, skills: string[], options?: SkillInstallOptions): Promise<string[][]>;
39
41
  export declare function installSkills(root: string, preset?: string, options?: SkillInstallOptions, runner?: Runner): Promise<CapabilityCommandResult>;
42
+ export declare function installSkillIds(root: string, skills: string[], options?: SkillInstallOptions, runner?: Runner): Promise<CapabilityCommandResult>;
40
43
  export declare function listInstalledSkills(root: string): Promise<InstalledSkill[]>;
41
44
  export declare function removeSkills(root: string, skills: string[], runner?: Runner): Promise<CapabilityCommandResult>;
42
45
  export declare function updateSkills(root: string, runner?: Runner): Promise<CapabilityCommandResult>;
@@ -60,6 +60,21 @@ export async function buildSkillInstallCommands(root, preset = "default", option
60
60
  }
61
61
  return commands;
62
62
  }
63
+ export async function buildExplicitSkillInstallCommands(root, skills, options = {}) {
64
+ const selectedSkills = normalizeSkillIds(skills);
65
+ const state = await readCapabilities(root);
66
+ const agent = assertKnownAgentTarget(options.agent ?? state.agent);
67
+ const skillsBySource = new Map();
68
+ for (const skill of selectedSkills) {
69
+ const source = skillSourceForId(skill);
70
+ if (!source)
71
+ throw new Error(`unknown skill id: ${skill}`);
72
+ const sourceSkills = skillsBySource.get(source) ?? [];
73
+ sourceSkills.push(skill);
74
+ skillsBySource.set(source, sourceSkills);
75
+ }
76
+ return [...skillsBySource.entries()].map(([source, sourceSkills]) => skillAddCommand(source, sourceSkills, agent));
77
+ }
63
78
  export async function installSkills(root, preset = "default", options = {}, runner = defaultRunner) {
64
79
  const state = await readCapabilities(root);
65
80
  const agent = assertKnownAgentTarget(options.agent ?? state.agent);
@@ -76,6 +91,22 @@ export async function installSkills(root, preset = "default", options = {}, runn
76
91
  }
77
92
  return { ok: true, count: commands.length };
78
93
  }
94
+ export async function installSkillIds(root, skills, options = {}, runner = defaultRunner) {
95
+ const state = await readCapabilities(root);
96
+ const agent = assertKnownAgentTarget(options.agent ?? state.agent);
97
+ const selectedSkills = normalizeSkillIds(skills);
98
+ const commands = await buildExplicitSkillInstallCommands(root, selectedSkills, options);
99
+ for (const command of commands) {
100
+ await runner.run(command, { cwd: root });
101
+ }
102
+ if (state.agent !== agent) {
103
+ await writeCapabilities(root, {
104
+ ...state,
105
+ agent
106
+ });
107
+ }
108
+ return { ok: true, count: commands.length, skills: selectedSkills };
109
+ }
79
110
  export async function listInstalledSkills(root) {
80
111
  const roots = await discoverProjectSkillRoots(root);
81
112
  const skills = [];
@@ -287,6 +318,7 @@ async function writeCapabilityProfile(root, state) {
287
318
  "## Skills",
288
319
  "",
289
320
  `- Install with: \`academic-research skills install --preset ${state.preset ?? "default"}\``,
321
+ "- Install selected skills with: `academic-research skills install <skill-id> [...]`",
290
322
  "- List installed with: `academic-research skills list`",
291
323
  "- List presets with: `academic-research skills presets`",
292
324
  "- Remove with: `academic-research skills remove <skill>`",
@@ -355,6 +387,48 @@ function renderSkillCommand(command, agent) {
355
387
  const agentFlag = normalized === AUTO_AGENT ? "" : `--agent '${normalized}'`;
356
388
  return command.replaceAll("{agent_flag}", agentFlag).replaceAll("{agent}", normalized);
357
389
  }
390
+ function skillAddCommand(source, skills, agent) {
391
+ return [
392
+ "npm",
393
+ "exec",
394
+ "--yes",
395
+ "--package",
396
+ "skills",
397
+ "--",
398
+ "skills",
399
+ "add",
400
+ source,
401
+ ...skillAgentArgs(agent),
402
+ "--skill",
403
+ ...skills,
404
+ "--copy",
405
+ "-y"
406
+ ];
407
+ }
408
+ function skillAgentArgs(agent) {
409
+ const normalized = assertKnownAgentTarget(agent);
410
+ return normalized === AUTO_AGENT ? [] : ["--agent", normalized];
411
+ }
412
+ function normalizeSkillIds(skills) {
413
+ if (skills.length === 0)
414
+ throw new Error("no skills selected");
415
+ const result = [];
416
+ for (const skill of skills) {
417
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(skill)) {
418
+ throw new Error(`invalid skill id: ${skill}`);
419
+ }
420
+ if (!result.includes(skill))
421
+ result.push(skill);
422
+ }
423
+ return result;
424
+ }
425
+ function skillSourceForId(skill) {
426
+ for (const source of Object.values(AGENT_STACK.skill_sources)) {
427
+ if (source.skills.includes(skill))
428
+ return source.source;
429
+ }
430
+ return undefined;
431
+ }
358
432
  function appendMcpPrerequisiteLines(lines, requiredEnv, recommendedEnv, localService) {
359
433
  if (requiredEnv.length > 0)
360
434
  lines.push(` - Requires env: ${requiredEnv.map((name) => `\`${name}\``).join(", ")}`);
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
- import { readFileSync } from "node:fs";
2
- import { basename, dirname, join, resolve } from "node:path";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { basename, delimiter, dirname, join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { assertKnownMcpServers, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, installMcpTools, installSkills, listInstalledSkills, mcpToolCommandTexts, readCapabilities, removeSkills, uninstallMcpTools, updateSkills } from "./capabilities.js";
4
+ import { assertKnownMcpServers, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, installMcpTools, installSkillIds, installSkills, listInstalledSkills, mcpToolCommandTexts, readCapabilities, removeSkills, uninstallMcpTools, updateSkills } from "./capabilities.js";
5
5
  import { createProject, doctorProject, renameProject } from "./project.js";
6
6
  import { askCreateOptions } from "./prompts.js";
7
7
  import { AGENT_STACK, presetMcpServers } from "./stack.js";
@@ -9,7 +9,15 @@ import { formatAgentAliasLines, formatAgentTargetList, formatSupportedAgentTarge
9
9
  import { packageify, slugify, titleFromSlug } from "./names.js";
10
10
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
11
11
  const packageVersion = readPackageVersion();
12
- const CREATE_FLAGS = flagSchema(["yes", "help", "version", "install-skills", "no-install-skills", "install-mcp-tools"], ["title", "slug", "package", "preset", "profile", "agent"]);
12
+ const CREATE_FLAGS = flagSchema([
13
+ "yes",
14
+ "help",
15
+ "version",
16
+ "install-skills",
17
+ "no-install-skills",
18
+ "install-mcp-tools",
19
+ "no-install-mcp-tools"
20
+ ], ["title", "slug", "package", "preset", "profile", "agent"]);
13
21
  const ROOT_FLAGS = flagSchema(["help"], ["root"]);
14
22
  const RENAME_FLAGS = flagSchema(["help"], ["root", "title", "slug", "package"]);
15
23
  const SKILLS_FLAGS = flagSchema(["help"], ["root", "preset", "agent"]);
@@ -43,6 +51,9 @@ async function createMain(argv) {
43
51
  if (flagBool(parsed.flags, "install-skills") && flagBool(parsed.flags, "no-install-skills")) {
44
52
  throw new Error("cannot use --install-skills and --no-install-skills together");
45
53
  }
54
+ if (flagBool(parsed.flags, "install-mcp-tools") && flagBool(parsed.flags, "no-install-mcp-tools")) {
55
+ throw new Error("cannot use --install-mcp-tools and --no-install-mcp-tools together");
56
+ }
46
57
  if (parsed.positionals.length > 1) {
47
58
  throw new Error(`unexpected argument: ${parsed.positionals[1]}`);
48
59
  }
@@ -60,7 +71,9 @@ async function createMain(argv) {
60
71
  const installSkillsLock = flagBool(parsed.flags, "install-skills") || flagBool(parsed.flags, "no-install-skills")
61
72
  ? defaults.installSkills
62
73
  : undefined;
63
- const installMcpToolsLock = flagBool(parsed.flags, "install-mcp-tools") ? true : undefined;
74
+ const installMcpToolsLock = flagBool(parsed.flags, "install-mcp-tools") || flagBool(parsed.flags, "no-install-mcp-tools")
75
+ ? defaults.installMcpTools
76
+ : undefined;
64
77
  const answers = interactive
65
78
  ? await askInteractiveCreateOptions(defaults, {
66
79
  installSkills: installSkillsLock,
@@ -100,6 +113,8 @@ async function lifecycleMain(argv) {
100
113
  }
101
114
  if (command === "doctor")
102
115
  return doctorCommand(argv.slice(1));
116
+ if (command === "setup")
117
+ return setupCommand(argv.slice(1));
103
118
  if (command === "rename")
104
119
  return renameCommand(argv.slice(1));
105
120
  if (command === "agents")
@@ -125,6 +140,41 @@ async function doctorCommand(argv) {
125
140
  console.log(`OK: ${root}`);
126
141
  return result.ok ? 0 : 1;
127
142
  }
143
+ async function setupCommand(argv) {
144
+ const parsed = parseFlags(argv, ROOT_FLAGS);
145
+ if (flagBool(parsed.flags, "help")) {
146
+ printSetupHelp();
147
+ return 0;
148
+ }
149
+ assertNoArguments(parsed.positionals, "setup");
150
+ const root = resolve(flagString(parsed.flags, "root") ?? ".");
151
+ const project = await doctorProject(root);
152
+ const state = await readCapabilities(root);
153
+ const skills = await listInstalledSkills(root);
154
+ const skillIds = new Set(skills.map((skill) => skill.name));
155
+ console.log("Project Setup");
156
+ console.log(`root\t${root}`);
157
+ console.log(`doctor\t${project.ok ? "ok" : "error"}`);
158
+ console.log(`agent\t${state.agent}`);
159
+ console.log(`preset\t${state.preset}`);
160
+ console.log(`scope\t${state.scope}`);
161
+ console.log(`installed_skill_ids\t${skillIds.size}`);
162
+ console.log(`installed_skill_copies\t${skills.length}`);
163
+ console.log(`mcp_enabled\t${state.mcp_servers.length > 0 ? state.mcp_servers.join(",") : "none"}`);
164
+ if (!project.ok) {
165
+ for (const error of project.errors)
166
+ console.error(`ERROR: ${error}`);
167
+ }
168
+ console.log("");
169
+ console.log("Next Commands");
170
+ console.log(`academic-research skills install --preset ${state.preset}`);
171
+ console.log("academic-research skills status");
172
+ console.log("academic-research mcp list");
173
+ console.log("academic-research mcp env");
174
+ console.log("academic-research mcp smoke");
175
+ console.log("academic-research doctor");
176
+ return project.ok ? 0 : 1;
177
+ }
128
178
  async function renameCommand(argv) {
129
179
  const parsed = parseFlags(argv, RENAME_FLAGS);
130
180
  if (flagBool(parsed.flags, "help")) {
@@ -203,8 +253,18 @@ async function skillsCommand(argv) {
203
253
  if (subcommand === "install") {
204
254
  assertOnlyOptions(parsed.flags, "skills install", ["root", "preset", "agent"]);
205
255
  const root = resolve(flagString(parsed.flags, "root") ?? ".");
206
- assertNoArguments(parsed.positionals, "skills install");
207
- const preset = flagString(parsed.flags, "preset") ?? "default";
256
+ const explicitSkills = parsed.positionals;
257
+ const explicitPreset = flagString(parsed.flags, "preset");
258
+ if (explicitSkills.length > 0) {
259
+ if (explicitPreset)
260
+ throw new Error("skills install does not accept --preset when skill ids are provided");
261
+ const result = await installSkillIds(root, explicitSkills, {
262
+ agent: flagString(parsed.flags, "agent")
263
+ });
264
+ console.log(`Installed ${result.skills?.length ?? explicitSkills.length} skill(s) with ${result.count ?? 0} command(s).`);
265
+ return 0;
266
+ }
267
+ const preset = explicitPreset ?? "default";
208
268
  const result = await installSkills(root, preset, {
209
269
  agent: flagString(parsed.flags, "agent")
210
270
  });
@@ -317,6 +377,24 @@ async function mcpCommand(argv) {
317
377
  console.log(`Ran ${result.count ?? 0} MCP uninstall command(s).`);
318
378
  return 0;
319
379
  }
380
+ if (subcommand === "smoke") {
381
+ assertOnlyOptions(parsed.flags, "mcp smoke", ["root"]);
382
+ const root = resolve(flagString(parsed.flags, "root") ?? ".");
383
+ const state = await readCapabilities(root);
384
+ const explicitSelection = parsed.positionals.length > 0;
385
+ const selected = explicitSelection ? parsed.positionals : state.mcp_servers;
386
+ assertKnownMcpServers(selected);
387
+ const failed = printMcpSmokeDiagnostics(selected);
388
+ if (!explicitSelection) {
389
+ const result = await doctorMcpServers(root);
390
+ for (const error of result.errors)
391
+ console.error(`ERROR: ${error}`);
392
+ for (const warning of result.warnings)
393
+ console.warn(`WARN: ${warning}`);
394
+ return result.ok && !failed ? 0 : 1;
395
+ }
396
+ return failed ? 1 : 0;
397
+ }
320
398
  if (subcommand === "doctor") {
321
399
  assertOnlyOptions(parsed.flags, "mcp doctor", ["root"]);
322
400
  const root = resolve(flagString(parsed.flags, "root") ?? ".");
@@ -461,6 +539,7 @@ function printCreateHelp() {
461
539
  " --install-skills Install project-local skills without prompting.",
462
540
  " --no-install-skills Skip project-local skill installation.",
463
541
  " --install-mcp-tools Run finite external MCP install commands after creation.",
542
+ " --no-install-mcp-tools Skip finite external MCP install commands.",
464
543
  " -h, --help Show this help.",
465
544
  " -v, --version Show package version."
466
545
  ].join("\n"));
@@ -476,7 +555,7 @@ function printMissingTargetHelp() {
476
555
  }
477
556
  function printLifecycleHelp() {
478
557
  console.log([
479
- "Usage: academic-research <doctor|rename|agents|skills|mcp>",
558
+ "Usage: academic-research <doctor|setup|rename|agents|skills|mcp>",
480
559
  "",
481
560
  "Manage a generated academic research repository after creation.",
482
561
  "",
@@ -485,6 +564,17 @@ function printLifecycleHelp() {
485
564
  " -v, --version Show package version."
486
565
  ].join("\n"));
487
566
  }
567
+ function printSetupHelp() {
568
+ console.log([
569
+ "Usage: academic-research setup [options]",
570
+ "",
571
+ "Print project onboarding status and next commands without changing files.",
572
+ "",
573
+ "Options:",
574
+ " --root <path> Project root. Default: current directory.",
575
+ " -h, --help Show this help."
576
+ ].join("\n"));
577
+ }
488
578
  function printAgentsHelp() {
489
579
  console.log([
490
580
  "Usage: academic-research agents <list>",
@@ -502,22 +592,26 @@ function printAgentsHelp() {
502
592
  }
503
593
  function printSkillsHelp() {
504
594
  console.log([
505
- "Usage: academic-research skills <list|status|presets|install|remove|uninstall|update> [options]",
595
+ "Usage: academic-research skills <list|status|presets|install|remove|uninstall|update> [skill-id...] [options]",
506
596
  "",
507
597
  "Manage project-local skill installs for a generated research repository.",
508
598
  "",
599
+ "Examples:",
600
+ " academic-research skills install --preset default",
601
+ " academic-research skills install source-ingestion sota-literature-review",
602
+ "",
509
603
  "Options:",
510
604
  " --root <path> Project root for list, status, install, remove, uninstall, update.",
511
- " --preset <name> Capability preset for install.",
605
+ " --preset <name> Capability preset for install when no skill ids are provided.",
512
606
  " --agent <id> Agent selector for install. Default: project capability agent.",
513
607
  " -h, --help Show this help."
514
608
  ].join("\n"));
515
609
  }
516
610
  function printMcpHelp() {
517
611
  console.log([
518
- "Usage: academic-research mcp <list|enabled|available|commands|env|enable|disable|install|uninstall|doctor> [servers...]",
612
+ "Usage: academic-research mcp <list|enabled|available|commands|env|enable|disable|install|uninstall|smoke|doctor> [servers...]",
519
613
  "",
520
- "Manage MCP records and finite external MCP tool installs.",
614
+ "Manage MCP records, readiness checks, and finite external MCP tool installs.",
521
615
  "",
522
616
  "Options:",
523
617
  " --root <path> Project root for project-state commands.",
@@ -525,6 +619,32 @@ function printMcpHelp() {
525
619
  " -h, --help Show this help."
526
620
  ].join("\n"));
527
621
  }
622
+ function printMcpSmokeDiagnostics(servers) {
623
+ let failed = false;
624
+ console.log("id\tstatus\truntime\tcheck");
625
+ for (const name of servers) {
626
+ const server = AGENT_STACK.mcp_servers[name];
627
+ const missingRequired = server.required_env.filter((envName) => !process.env[envName]);
628
+ if (missingRequired.length > 0)
629
+ failed = true;
630
+ const runtime = server.command ? [server.command, ...server.args].join(" ") : "manual setup";
631
+ let status = "manual";
632
+ if (missingRequired.length > 0) {
633
+ status = `missing-required-env:${missingRequired.join(",")}`;
634
+ }
635
+ else if (server.command && commandExists(server.command)) {
636
+ status = "runtime-found";
637
+ }
638
+ else if (server.command) {
639
+ status = "runtime-missing";
640
+ }
641
+ else if (server.local_service) {
642
+ status = "manual-local-service";
643
+ }
644
+ console.log(`${name}\t${status}\t${runtime}\t${server.smoke_test}`);
645
+ }
646
+ return failed;
647
+ }
528
648
  function printMcpEnvironment(servers) {
529
649
  for (const name of servers) {
530
650
  const server = AGENT_STACK.mcp_servers[name];
@@ -553,6 +673,25 @@ function printMcpEnvironment(servers) {
553
673
  console.log(`${name}\tnone\t-`);
554
674
  }
555
675
  }
676
+ function commandExists(command) {
677
+ if (!command)
678
+ return false;
679
+ if (command.includes("/") || command.includes("\\"))
680
+ return existsSync(command);
681
+ const pathValue = process.env.PATH ?? "";
682
+ const extensions = process.platform === "win32"
683
+ ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
684
+ : [""];
685
+ for (const directory of pathValue.split(delimiter).filter(Boolean)) {
686
+ for (const extension of extensions) {
687
+ const hasExtension = extension && command.toLowerCase().endsWith(extension.toLowerCase());
688
+ const candidate = join(directory, hasExtension ? command : `${command}${extension}`);
689
+ if (existsSync(candidate))
690
+ return true;
691
+ }
692
+ }
693
+ return false;
694
+ }
556
695
  function readPackageVersion() {
557
696
  try {
558
697
  const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
@@ -138,10 +138,17 @@ export async function doctorProject(root) {
138
138
  "docs/agent/capability-profile.md",
139
139
  "docs/agent/mcp-setup.md",
140
140
  "docs/agent/generated",
141
+ "scripts/README.md",
141
142
  "sources/source-ledger.csv",
142
143
  "sota/literature-matrix.csv",
143
144
  "wiki/index.md",
144
- "wiki/log.md"
145
+ "wiki/log.md",
146
+ "wiki/templates/source-page.md",
147
+ "wiki/templates/claim-page.md",
148
+ "wiki/templates/experiment-page.md",
149
+ "wiki/templates/decision-record.md",
150
+ "wiki/templates/reviewer-concern.md",
151
+ "wiki/templates/research-question.md"
145
152
  ];
146
153
  for (const relative of required) {
147
154
  if (!(await exists(join(target, relative))))
@@ -207,7 +214,7 @@ async function writeGeneratedPackageJson(root, { slug }) {
207
214
  const path = join(root, "package.json");
208
215
  const data = await readJson(path);
209
216
  const existingSpec = data.devDependencies?.["create-academic-research"];
210
- const packageSpec = process.env.CREATE_ACADEMIC_RESEARCH_PACKAGE_SPEC ?? existingSpec ?? "0.1.6";
217
+ const packageSpec = process.env.CREATE_ACADEMIC_RESEARCH_PACKAGE_SPEC ?? existingSpec ?? "0.1.8";
211
218
  data.name = slug;
212
219
  data.devDependencies = {
213
220
  ...(data.devDependencies ?? {}),
@@ -2,6 +2,11 @@ export interface SkillBundle {
2
2
  description: string;
3
3
  commands: string[];
4
4
  }
5
+ export interface SkillSource {
6
+ description: string;
7
+ source: string;
8
+ skills: string[];
9
+ }
5
10
  export interface CapabilityPreset {
6
11
  description: string;
7
12
  skill_bundles: string[];
@@ -30,6 +35,7 @@ export interface AgentStack {
30
35
  version: number;
31
36
  description: string;
32
37
  skill_bundles: Record<string, SkillBundle>;
38
+ skill_sources: Record<string, SkillSource>;
33
39
  presets: Record<string, CapabilityPreset>;
34
40
  mcp_servers: Record<string, McpServer>;
35
41
  }
package/dist/src/stack.js CHANGED
@@ -22,6 +22,76 @@ export const AGENT_STACK = {
22
22
  ]
23
23
  }
24
24
  },
25
+ skill_sources: {
26
+ academic_research: {
27
+ description: "Academic research skills maintained by this project.",
28
+ source: "VincenzoImp/academic-research-skills",
29
+ skills: [
30
+ "academic-mcp-tooling",
31
+ "adversarial-peer-review",
32
+ "artifact-open-science",
33
+ "citation-bibliography-tooling",
34
+ "citation-claim-audit",
35
+ "cs-methodology-evaluation",
36
+ "cs-venue-strategy",
37
+ "document-conversion",
38
+ "ethics-data-governance",
39
+ "experiment-logbook",
40
+ "paper-writing-review",
41
+ "rebuttal-revision-strategy",
42
+ "repo-migration",
43
+ "research-data-analysis",
44
+ "research-design-positioning",
45
+ "research-project-maintenance",
46
+ "research-project-router",
47
+ "research-repo-reproduction",
48
+ "research-ui-prototyping",
49
+ "skill-evaluation",
50
+ "sota-literature-review",
51
+ "source-ingestion",
52
+ "systematic-review-prisma"
53
+ ]
54
+ },
55
+ superpowers: {
56
+ description: "General agent engineering skills from Superpowers.",
57
+ source: "obra/superpowers",
58
+ skills: [
59
+ "brainstorming",
60
+ "dispatching-parallel-agents",
61
+ "executing-plans",
62
+ "finishing-a-development-branch",
63
+ "receiving-code-review",
64
+ "requesting-code-review",
65
+ "subagent-driven-development",
66
+ "systematic-debugging",
67
+ "test-driven-development",
68
+ "using-git-worktrees",
69
+ "using-superpowers",
70
+ "verification-before-completion",
71
+ "writing-plans",
72
+ "writing-skills"
73
+ ]
74
+ },
75
+ anthropics: {
76
+ description: "Document, frontend, testing, MCP, and skill-authoring helpers.",
77
+ source: "anthropics/skills",
78
+ skills: [
79
+ "docx",
80
+ "frontend-design",
81
+ "mcp-builder",
82
+ "pdf",
83
+ "pptx",
84
+ "skill-creator",
85
+ "webapp-testing",
86
+ "xlsx"
87
+ ]
88
+ },
89
+ docling: {
90
+ description: "Document conversion helper from the Beagle collection.",
91
+ source: "existential-birds/beagle",
92
+ skills: ["docling"]
93
+ }
94
+ },
25
95
  presets: {
26
96
  minimal: {
27
97
  description: "Academic research skills only, no MCP records.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-academic-research",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Create and manage agent-ready academic research repositories.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -6,7 +6,8 @@ scholarly record.
6
6
  ## Core Rules
7
7
 
8
8
  - Preserve source originals under `sources/`, `data/raw/`, and `data/external/`.
9
- - Keep reusable logic in `src/`; keep notebooks for exploration and narrative output.
9
+ - Keep reusable logic in `src/`; keep `scripts/` as thin repeatable entrypoints.
10
+ - Keep notebooks for exploration and narrative output.
10
11
  - Tie claims to sources, datasets, experiment records, or decision records.
11
12
  - Update durable records when project knowledge changes.
12
13
  - Keep large data, generated caches, credentials, and private review material out of git.
@@ -35,6 +36,8 @@ scholarly record.
35
36
  - `wiki/synthesis.md` changes only when the project-level interpretation changes.
36
37
  - `wiki/open_questions.md` tracks unresolved questions.
37
38
  - `wiki/contradictions.md` tracks conflicts across sources, data, or runs.
39
+ - `wiki/templates/` contains reusable source, claim, experiment, decision,
40
+ reviewer-concern, and research-question page structures.
38
41
 
39
42
  ## Evidence
40
43
 
@@ -45,3 +48,4 @@ scholarly record.
45
48
  - Citation issues go in `sources/bib/citation-audit.csv`.
46
49
  - SOTA records go in `sota/`.
47
50
  - Curated experiment records go in `experiments/`.
51
+ - Repeatable command entrypoints go in `scripts/`.
@@ -41,6 +41,7 @@ npx academic-research doctor
41
41
  - `docs/agent/`: active agent workflows, capability profile, and MCP setup.
42
42
  - `docs/methodology/`: research design, evaluation plan, and validity threats.
43
43
  - `experiments/`: curated experiment registry and run records.
44
+ - `scripts/`: thin repeatable entrypoints that call reusable code in `src/`.
44
45
  - `notebooks/`: optional exploratory and narrative notebooks.
45
46
  - `outputs/`: final figures, tables, models, and paper-supporting derived assets.
46
47
  - `reports/`: proposal, paper, slides, reviews, and rebuttal material.
@@ -56,13 +57,16 @@ npx academic-research skills presets
56
57
  npx academic-research agents list
57
58
  npx academic-research skills install --preset default
58
59
  npx academic-research skills install --preset enhanced
60
+ npx academic-research skills install source-ingestion sota-literature-review
59
61
  npx academic-research skills list
60
62
  npx academic-research skills status
63
+ npx academic-research setup
61
64
  npx academic-research mcp list
62
65
  npx academic-research mcp env openalex semantic-scholar zotero
63
66
  npx academic-research mcp enable arxiv dblp
64
67
  npx academic-research mcp commands arxiv
65
68
  npx academic-research mcp install arxiv
69
+ npx academic-research mcp smoke
66
70
  npx academic-research mcp doctor
67
71
  ```
68
72
 
@@ -74,6 +78,12 @@ enable optional servers. `mcp install` runs only finite tool installation
74
78
  commands; runtime-only `uvx`/`npx` MCP servers may have no install step and are
75
79
  started later by the MCP client.
76
80
 
81
+ `setup` prints the current project capability state, installed skill counts,
82
+ enabled MCP records, and the next onboarding commands without changing files.
83
+ `mcp smoke` performs a non-launching MCP readiness check: it reports required
84
+ env vars, local/manual setup, and whether client runtime commands such as `uvx`
85
+ or `npx` are available.
86
+
77
87
  `default` installs the companion academic research skill package and keeps the
78
88
  MCP records focused on low-friction arXiv discovery. `literature` and `full`
79
89
  add DBLP for computer science bibliography. Credentialed, local-service, or
@@ -4,15 +4,17 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "doctor": "academic-research doctor",
7
+ "setup": "academic-research setup",
7
8
  "skills:install": "academic-research skills install --preset default",
8
9
  "skills:list": "academic-research skills list",
9
10
  "skills:presets": "academic-research skills presets",
10
11
  "mcp:list": "academic-research mcp list",
11
12
  "mcp:commands": "academic-research mcp commands",
12
13
  "mcp:env": "academic-research mcp env",
14
+ "mcp:smoke": "academic-research mcp smoke",
13
15
  "mcp:doctor": "academic-research mcp doctor"
14
16
  },
15
17
  "devDependencies": {
16
- "create-academic-research": "0.1.6"
18
+ "create-academic-research": "0.1.8"
17
19
  }
18
20
  }
@@ -0,0 +1,10 @@
1
+ # Scripts
2
+
3
+ Use this folder for thin, repeatable command entrypoints.
4
+
5
+ Scripts should orchestrate project code, not contain core scientific logic. Put
6
+ reusable methods, models, data transforms, and analysis functions in `src/`.
7
+
8
+ Each script should make its inputs, outputs, config path, and expected side
9
+ effects clear enough to reproduce later from `docs/reproducibility/` or an
10
+ experiment record.
@@ -8,6 +8,7 @@ def test_core_research_structure_exists() -> None:
8
8
  "configs/default.yaml",
9
9
  "configs/capabilities.yaml",
10
10
  "docs/agent/capability-profile.md",
11
+ "scripts/README.md",
11
12
  "notebooks/README.md",
12
13
  "outputs/figures",
13
14
  "outputs/tables",
@@ -21,5 +22,11 @@ def test_core_research_structure_exists() -> None:
21
22
  "sota/literature-matrix.csv",
22
23
  "wiki/index.md",
23
24
  "wiki/log.md",
25
+ "wiki/templates/source-page.md",
26
+ "wiki/templates/claim-page.md",
27
+ "wiki/templates/experiment-page.md",
28
+ "wiki/templates/decision-record.md",
29
+ "wiki/templates/reviewer-concern.md",
30
+ "wiki/templates/research-question.md",
24
31
  ):
25
32
  assert (root / relative).exists()
@@ -7,3 +7,12 @@ This is the content index for the LLM-maintained project wiki.
7
7
  - [[synthesis]]
8
8
  - [[open_questions]]
9
9
  - [[contradictions]]
10
+
11
+ ## Templates
12
+
13
+ - [[templates/source-page]]
14
+ - [[templates/claim-page]]
15
+ - [[templates/experiment-page]]
16
+ - [[templates/decision-record]]
17
+ - [[templates/reviewer-concern]]
18
+ - [[templates/research-question]]
@@ -0,0 +1,24 @@
1
+ # Claim: <claim_id>
2
+
3
+ ## Claim
4
+
5
+ <fill: atomic claim>
6
+
7
+ ## Status
8
+
9
+ - Verdict:
10
+ - Confidence:
11
+ - Last checked:
12
+
13
+ ## Evidence
14
+
15
+ | source_or_run_id | path | support_span | verdict | notes |
16
+ |---|---|---|---|---|
17
+
18
+ ## Safe Wording
19
+
20
+ <fill: wording that current evidence supports>
21
+
22
+ ## Risk
23
+
24
+ <fill: what would weaken, falsify, or narrow this claim>
@@ -0,0 +1,26 @@
1
+ # Decision: <decision_id>
2
+
3
+ ## Context
4
+
5
+ <fill: situation and constraints>
6
+
7
+ ## Decision
8
+
9
+ <fill: chosen option>
10
+
11
+ ## Alternatives Considered
12
+
13
+ | option | reason rejected |
14
+ |---|---|
15
+
16
+ ## Evidence
17
+
18
+ - Sources:
19
+ - Experiments:
20
+ - Reviews:
21
+
22
+ ## Consequences
23
+
24
+ - Expected benefit:
25
+ - Risk:
26
+ - Review date:
@@ -0,0 +1,30 @@
1
+ # Experiment: <run_id>
2
+
3
+ ## Question
4
+
5
+ <fill: research question or hypothesis>
6
+
7
+ ## Run
8
+
9
+ - Date:
10
+ - Git commit:
11
+ - Command:
12
+ - Config:
13
+ - Seed:
14
+ - Dataset:
15
+ - Metric:
16
+ - Output path:
17
+
18
+ ## Result
19
+
20
+ <fill: measured outcome>
21
+
22
+ ## Interpretation
23
+
24
+ <fill: claim status, limitations, and next action>
25
+
26
+ ## Links
27
+
28
+ - Registry row: `experiments/registry.csv`
29
+ - Related claims:
30
+ - Related sources:
@@ -0,0 +1,27 @@
1
+ # Research Question: <rq_id>
2
+
3
+ ## Question
4
+
5
+ <fill: answerable research question>
6
+
7
+ ## Scope
8
+
9
+ - Population/domain:
10
+ - Evidence type:
11
+ - Exclusions:
12
+
13
+ ## Contribution Link
14
+
15
+ - Candidate claim:
16
+ - Closest prior work:
17
+ - Delta:
18
+ - Failure condition:
19
+
20
+ ## Evidence Plan
21
+
22
+ | evidence_needed | path_or_source | status | risk |
23
+ |---|---|---|---|
24
+
25
+ ## Current Status
26
+
27
+ <fill: hypothesis, active, answered, rejected, or out of scope>
@@ -0,0 +1,25 @@
1
+ # Reviewer Concern: <concern_id>
2
+
3
+ ## Concern
4
+
5
+ <fill: reviewer or internal-review concern>
6
+
7
+ ## Classification
8
+
9
+ - Source:
10
+ - Severity:
11
+ - Paper location:
12
+ - Claim or issue:
13
+ - Action: concede | clarify | add-evidence | reframe | defend | defer
14
+
15
+ ## Evidence
16
+
17
+ <fill: source, experiment, or manuscript evidence>
18
+
19
+ ## Response Plan
20
+
21
+ <fill: exact response and manuscript change>
22
+
23
+ ## Status
24
+
25
+ <fill: open, addressed, deferred, rejected with rationale>
@@ -0,0 +1,31 @@
1
+ # Source: <source_id>
2
+
3
+ ## Bibliographic Record
4
+
5
+ - Title:
6
+ - Authors:
7
+ - Year:
8
+ - Venue:
9
+ - Identifiers:
10
+ - Native path:
11
+ - Derived path:
12
+ - Metadata path:
13
+
14
+ ## Summary
15
+
16
+ <fill: concise source summary>
17
+
18
+ ## Evidence Record
19
+
20
+ - Supports:
21
+ - Contradicts:
22
+ - Method / data / metric:
23
+ - Limitations:
24
+ - Project relevance:
25
+ - Claim strength:
26
+ - Allowed wording:
27
+ - Forbidden stronger wording:
28
+
29
+ ## Follow-Up
30
+
31
+ - <fill: unresolved checks>
File without changes