@longtable/cli 0.1.43 → 0.1.45
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 +35 -6
- package/dist/cli.js +254 -87
- package/dist/longtable-codex-native-hook.js +8 -2
- package/dist/project-session.d.ts +70 -0
- package/dist/project-session.js +227 -5
- package/dist/search/publisher-access.d.ts +1 -3
- package/dist/search/publisher-access.js +0 -13
- package/dist/search/types.d.ts +0 -6
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -86,9 +86,18 @@ summary without starting a provider session.
|
|
|
86
86
|
- `CURRENT.md`: human-facing current view regenerated from state
|
|
87
87
|
- `.longtable/project.json`: stable project identity
|
|
88
88
|
- `.longtable/current-session.json`: current session cursor
|
|
89
|
-
- `.longtable/state.json`: layered memory state
|
|
89
|
+
- `.longtable/state.json`: layered memory state, including First Research
|
|
90
|
+
Shape and Research Specification when the interview has produced them
|
|
90
91
|
- `.longtable/sessions/`: historical snapshots
|
|
91
92
|
|
|
93
|
+
`$longtable-interview` first stabilizes a short First Research Shape. When the
|
|
94
|
+
conversation is substantive enough, it should also preserve a Research
|
|
95
|
+
Specification covering scope, construct ontology, theory framing,
|
|
96
|
+
measurement/coding, method options, evidence/access requirements, epistemic
|
|
97
|
+
alignment, protected decisions, open questions, and next actions. `CURRENT.md`
|
|
98
|
+
renders that specification so later agents do not need to reconstruct the full
|
|
99
|
+
interview from memory.
|
|
100
|
+
|
|
92
101
|
## Why This Shape
|
|
93
102
|
|
|
94
103
|
The CLI tries to keep the root simple for novice researchers while preserving enough structure for power users and downstream tooling.
|
|
@@ -140,6 +149,25 @@ That setup writes the MCP configuration and Codex elicitation approval needed
|
|
|
140
149
|
for form-style checkpoint prompts. Without it, LongTable keeps the same
|
|
141
150
|
`QuestionRecord` pending and falls back to numbered text.
|
|
142
151
|
|
|
152
|
+
## Runtime Boundary
|
|
153
|
+
|
|
154
|
+
LongTable is not a replacement wrapper for Codex. Markdown docs and generated
|
|
155
|
+
skills are soft policy; hooks, MCP elicitation, CLI gates, and `.longtable/`
|
|
156
|
+
state are the enforcement layers.
|
|
157
|
+
|
|
158
|
+
LongTable should ask and stop before acting when the next step would change or
|
|
159
|
+
settle one of four high-risk research commitments:
|
|
160
|
+
|
|
161
|
+
1. Research question or scope
|
|
162
|
+
2. Theory frame or construct map
|
|
163
|
+
3. Measurement, coding, or extraction standard
|
|
164
|
+
4. Method design or analysis strategy
|
|
165
|
+
|
|
166
|
+
Low-risk reversible work should continue with visible assumptions instead of a
|
|
167
|
+
hook interruption. If human knowledge, AI inference, and durable project state
|
|
168
|
+
conflict, LongTable should prefer the most explicit durable state; if that state
|
|
169
|
+
is not explicit enough, it should ask the researcher for clarity.
|
|
170
|
+
|
|
143
171
|
Explicit short forms are available when needed:
|
|
144
172
|
|
|
145
173
|
```text
|
|
@@ -241,16 +269,17 @@ deduplicates, ranks, and labels results as evidence cards. Some sources work
|
|
|
241
269
|
without keys, some require a contact email, and some need API keys for reliable
|
|
242
270
|
use.
|
|
243
271
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
272
|
+
Scholarly access is configured separately through `longtable access setup`.
|
|
273
|
+
It records readiness for metadata, OA full text, institutional access,
|
|
274
|
+
publisher API/TDM credentials, and manual PDFs without storing secrets.
|
|
275
|
+
Publisher probes cover Elsevier, Springer Nature, Wiley, and Taylor & Francis.
|
|
247
276
|
|
|
248
277
|
Citation support should be checked explicitly. A reference can be useful as
|
|
249
278
|
background while still failing to support the specific claim attached to it.
|
|
250
279
|
|
|
251
280
|
```bash
|
|
252
|
-
longtable
|
|
253
|
-
longtable
|
|
281
|
+
longtable access setup
|
|
282
|
+
longtable access probe --doi "10.1016/example" --publisher elsevier
|
|
254
283
|
longtable search --query "trust calibration measurement" --intent measurement
|
|
255
284
|
longtable search --query "trust calibration measurement" --publisher-access --json
|
|
256
285
|
longtable search --query "trust calibration citation support" --intent citation --record
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
12
|
-
import { assessSearchSourceCapabilities, buildResearchSearchIntent,
|
|
12
|
+
import { assessSearchSourceCapabilities, buildResearchSearchIntent, parsePublisherTarget, probePublisherAccess, publisherConfigs, runResearchSearch, SEARCH_SOURCES, summarizeConfiguredPublisherAccess } from "./search/index.js";
|
|
13
13
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
14
14
|
import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
|
|
15
15
|
import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
|
|
@@ -109,10 +109,11 @@ function usage() {
|
|
|
109
109
|
" longtable show [--json] [--path <file>]",
|
|
110
110
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
111
111
|
" longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
112
|
+
" longtable access setup [--doi <doi>] [--json]",
|
|
113
|
+
" longtable access status [--json]",
|
|
114
|
+
" longtable access doctor [--doi <doi>] [--publisher auto|elsevier|springer_nature|wiley|taylor_francis|all] [--json]",
|
|
115
|
+
" longtable access probe --doi <doi> [--publisher auto|elsevier|springer_nature|wiley|taylor_francis] [--json]",
|
|
112
116
|
" longtable search --query <text> [--intent literature|theory|measurement|citation|metadata|venue] [--field <text>] [--source all|crossref,arxiv,openalex,semantic_scholar,pubmed,eric,doaj,unpaywall] [--must <term[,term]>] [--exclude <term[,term]>] [--limit <n>] [--allow-partial] [--publisher-access] [--record] [--cwd <path>] [--json]",
|
|
113
|
-
" longtable search setup [--doi <doi>] [--json]",
|
|
114
|
-
" longtable search doctor [--doi <doi>] [--publisher auto|elsevier|springer_nature|wiley|taylor_francis|all] [--json]",
|
|
115
|
-
" longtable search probe --doi <doi> [--publisher auto|elsevier|springer_nature|wiley|taylor_francis] [--json]",
|
|
116
117
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
117
118
|
" longtable team --prompt <text> [--role <role[,role]>] [--debate] [--rounds 3|5] [--cwd <path>] [--json]",
|
|
118
119
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
@@ -154,7 +155,7 @@ function parseArgs(argv) {
|
|
|
154
155
|
const values = {};
|
|
155
156
|
let subcommand = maybeSubcommand;
|
|
156
157
|
const modeCommand = command && VALID_MODES.has(command);
|
|
157
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
158
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "decide", "sentinel", "team", "access", "search"].includes(command);
|
|
158
159
|
let startIndex = 1;
|
|
159
160
|
if (modeCommand) {
|
|
160
161
|
subcommand = undefined;
|
|
@@ -163,7 +164,7 @@ function parseArgs(argv) {
|
|
|
163
164
|
else if (command === "codex" || command === "claude" || command === "mcp") {
|
|
164
165
|
startIndex = 2;
|
|
165
166
|
}
|
|
166
|
-
else if (command === "search" && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
167
|
+
else if ((command === "access" || command === "search") && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
167
168
|
subcommand = maybeSubcommand;
|
|
168
169
|
startIndex = 2;
|
|
169
170
|
}
|
|
@@ -1789,6 +1790,11 @@ const QUESTION_AUDIT_FIXTURES = [
|
|
|
1789
1790
|
prompt: "Protected decision closure pressure: measurement. User prompt: Implement the plan.",
|
|
1790
1791
|
expectedKinds: ["research_commitment"]
|
|
1791
1792
|
},
|
|
1793
|
+
{
|
|
1794
|
+
id: "scholarly_access_policy",
|
|
1795
|
+
prompt: "메타분석 논문들의 PDF와 full text를 수집해서 원문 기반으로 코딩해줘.",
|
|
1796
|
+
expectedKinds: ["evidence_risk"]
|
|
1797
|
+
},
|
|
1792
1798
|
{
|
|
1793
1799
|
id: "low_stakes_copyedit",
|
|
1794
1800
|
prompt: "문장 끝 공백만 정리해줘.",
|
|
@@ -2347,6 +2353,86 @@ async function recordEvidenceRun(run, workingDirectory) {
|
|
|
2347
2353
|
await syncCurrentWorkspaceView(context);
|
|
2348
2354
|
return evidencePath;
|
|
2349
2355
|
}
|
|
2356
|
+
function nowIso() {
|
|
2357
|
+
return new Date().toISOString();
|
|
2358
|
+
}
|
|
2359
|
+
function uniqueAccessRoutes(routes) {
|
|
2360
|
+
return [...new Set(routes)];
|
|
2361
|
+
}
|
|
2362
|
+
function accessReadinessPath(home = homedir()) {
|
|
2363
|
+
return join(home, ".longtable", "access-readiness.json");
|
|
2364
|
+
}
|
|
2365
|
+
function readAccessReadinessProfile() {
|
|
2366
|
+
const path = accessReadinessPath();
|
|
2367
|
+
if (!existsSync(path)) {
|
|
2368
|
+
return undefined;
|
|
2369
|
+
}
|
|
2370
|
+
try {
|
|
2371
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
2372
|
+
}
|
|
2373
|
+
catch {
|
|
2374
|
+
return undefined;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
function hasPublisherCredentialSignal(records) {
|
|
2378
|
+
return records.some((record) => record.presentEnv.length > 0 || record.credentialStatus !== "missing");
|
|
2379
|
+
}
|
|
2380
|
+
function readinessPublisherRecord(record) {
|
|
2381
|
+
const safeRecord = { ...record };
|
|
2382
|
+
delete safeRecord.evidenceSnippet;
|
|
2383
|
+
return safeRecord;
|
|
2384
|
+
}
|
|
2385
|
+
function inferAccessRoutes(records) {
|
|
2386
|
+
const routes = ["metadata"];
|
|
2387
|
+
if (hasPublisherCredentialSignal(records)) {
|
|
2388
|
+
routes.push("publisher_tdm");
|
|
2389
|
+
}
|
|
2390
|
+
return routes;
|
|
2391
|
+
}
|
|
2392
|
+
function buildAccessReadinessProfile(options) {
|
|
2393
|
+
const publisherRecords = options.publisherRecords ?? summarizeConfiguredPublisherAccess(env);
|
|
2394
|
+
const routes = uniqueAccessRoutes(options.routes?.length ? options.routes : inferAccessRoutes(publisherRecords));
|
|
2395
|
+
const disabled = options.readiness === "disabled";
|
|
2396
|
+
const institutionalMode = options.institutionalAccessMode;
|
|
2397
|
+
const institutionalAccess = routes.includes("institutional")
|
|
2398
|
+
? {
|
|
2399
|
+
available: true,
|
|
2400
|
+
mode: institutionalMode ?? "unknown",
|
|
2401
|
+
verified: false,
|
|
2402
|
+
note: "LongTable records institutional access readiness only. The researcher must complete VPN/proxy/library login directly."
|
|
2403
|
+
}
|
|
2404
|
+
: undefined;
|
|
2405
|
+
return {
|
|
2406
|
+
version: 1,
|
|
2407
|
+
updatedAt: nowIso(),
|
|
2408
|
+
readiness: options.readiness,
|
|
2409
|
+
metadataSources: [...SEARCH_SOURCES],
|
|
2410
|
+
routes: disabled ? [] : routes,
|
|
2411
|
+
...(institutionalAccess ? { institutionalAccess } : {}),
|
|
2412
|
+
publisherTdm: disabled
|
|
2413
|
+
? "deferred"
|
|
2414
|
+
: routes.includes("publisher_tdm")
|
|
2415
|
+
? hasPublisherCredentialSignal(publisherRecords) ? "configured" : "unknown"
|
|
2416
|
+
: "not_configured",
|
|
2417
|
+
oaOnly: !disabled && routes.includes("oa_full_text") && !routes.includes("institutional") && !routes.includes("publisher_tdm"),
|
|
2418
|
+
manualPdfAllowed: !disabled && routes.includes("manual_pdf"),
|
|
2419
|
+
storesSecrets: false,
|
|
2420
|
+
requiresCheckpointBeforeSearch: options.readiness === "deferred",
|
|
2421
|
+
requiresCheckpointBeforeFullText: options.readiness !== "disabled",
|
|
2422
|
+
...(disabled ? {} : {
|
|
2423
|
+
publisherAccess: {
|
|
2424
|
+
contactEmailPresent: Boolean(env.LONGTABLE_CONTACT_EMAIL?.trim()),
|
|
2425
|
+
records: publisherRecords.map(readinessPublisherRecord)
|
|
2426
|
+
}
|
|
2427
|
+
})
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2430
|
+
async function saveAccessReadinessProfile(profile) {
|
|
2431
|
+
const profilePath = accessReadinessPath();
|
|
2432
|
+
await mkdir(dirname(profilePath), { recursive: true });
|
|
2433
|
+
await writeJsonFile(profilePath, profile);
|
|
2434
|
+
return profilePath;
|
|
2435
|
+
}
|
|
2350
2436
|
function renderPublisherAccessRecord(record) {
|
|
2351
2437
|
const envSummary = record.missingEnv.length > 0
|
|
2352
2438
|
? `missing ${record.missingEnv.join(", ")}`
|
|
@@ -2381,12 +2467,6 @@ function renderPublisherAccessRecords(title, records, capabilityPath) {
|
|
|
2381
2467
|
}
|
|
2382
2468
|
return lines.join("\n");
|
|
2383
2469
|
}
|
|
2384
|
-
async function saveSearchCapabilityRecords(records) {
|
|
2385
|
-
const snapshotPath = searchCapabilitySnapshotPath();
|
|
2386
|
-
await mkdir(dirname(snapshotPath), { recursive: true });
|
|
2387
|
-
await writeJsonFile(snapshotPath, buildSearchCapabilitySnapshot(records, env));
|
|
2388
|
-
return snapshotPath;
|
|
2389
|
-
}
|
|
2390
2470
|
async function probeAllPublishers(doi) {
|
|
2391
2471
|
const records = [];
|
|
2392
2472
|
for (const publisher of publisherConfigs()) {
|
|
@@ -2398,9 +2478,48 @@ async function probeAllPublishers(doi) {
|
|
|
2398
2478
|
}
|
|
2399
2479
|
return records;
|
|
2400
2480
|
}
|
|
2401
|
-
|
|
2481
|
+
function renderAccessReadinessProfile(profile, profilePath = accessReadinessPath()) {
|
|
2482
|
+
const lines = [
|
|
2483
|
+
"LongTable Scholarly Access Readiness",
|
|
2484
|
+
`- profile: ${profilePath}`,
|
|
2485
|
+
`- readiness: ${profile.readiness}`,
|
|
2486
|
+
`- routes: ${profile.routes.length > 0 ? profile.routes.join(", ") : "none"}`,
|
|
2487
|
+
`- metadata sources: ${profile.metadataSources.join(", ")}`,
|
|
2488
|
+
`- OA-only: ${profile.oaOnly ? "yes" : "no"}`,
|
|
2489
|
+
`- manual PDF allowed: ${profile.manualPdfAllowed ? "yes" : "no"}`,
|
|
2490
|
+
`- publisher/TDM: ${profile.publisherTdm}`,
|
|
2491
|
+
`- stores secrets: ${profile.storesSecrets ? "yes" : "no"}`,
|
|
2492
|
+
`- checkpoint before search: ${profile.requiresCheckpointBeforeSearch ? "yes" : "no"}`,
|
|
2493
|
+
`- checkpoint before full text: ${profile.requiresCheckpointBeforeFullText ? "yes" : "no"}`
|
|
2494
|
+
];
|
|
2495
|
+
if (profile.institutionalAccess) {
|
|
2496
|
+
lines.push(`- institutional access: ${profile.institutionalAccess.mode}; verified: no`);
|
|
2497
|
+
lines.push(` note: ${profile.institutionalAccess.note}`);
|
|
2498
|
+
}
|
|
2499
|
+
if (profile.publisherAccess) {
|
|
2500
|
+
lines.push(`- contact email: ${profile.publisherAccess.contactEmailPresent ? "present" : "missing"}`);
|
|
2501
|
+
lines.push(`- publisher adapters: ${profile.publisherAccess.records.length}`);
|
|
2502
|
+
}
|
|
2503
|
+
return lines.join("\n");
|
|
2504
|
+
}
|
|
2505
|
+
function renderAccessDoctor(profile, records, profilePath) {
|
|
2506
|
+
const metadataCapabilities = assessSearchSourceCapabilities([...SEARCH_SOURCES], env);
|
|
2507
|
+
const lines = [
|
|
2508
|
+
"LongTable Scholarly Access Doctor",
|
|
2509
|
+
`- readiness profile: ${profile ? "present" : "missing"} (${profilePath})`,
|
|
2510
|
+
`- metadata sources: ${metadataCapabilities.map((capability) => `${capability.source}:${capability.enabled ? "available" : "needs_config"}`).join(", ")}`,
|
|
2511
|
+
"- institutional access: LongTable cannot verify VPN/proxy/SSO until the researcher logs in.",
|
|
2512
|
+
"- secrets: LongTable stores env var names and capability status only, never credential values.",
|
|
2513
|
+
renderPublisherAccessRecords("Publisher API/TDM adapters", records)
|
|
2514
|
+
];
|
|
2515
|
+
if (!profile) {
|
|
2516
|
+
lines.push("- next: run `longtable access setup` before PDF collection or full-text extraction.");
|
|
2517
|
+
}
|
|
2518
|
+
return lines.join("\n");
|
|
2519
|
+
}
|
|
2520
|
+
async function runAccessProbe(args) {
|
|
2402
2521
|
if (typeof args.doi !== "string" || !args.doi.trim()) {
|
|
2403
|
-
throw new Error("`longtable
|
|
2522
|
+
throw new Error("`longtable access probe` requires --doi <doi>.");
|
|
2404
2523
|
}
|
|
2405
2524
|
const publisher = parsePublisherTarget(args.publisher);
|
|
2406
2525
|
const record = await probePublisherAccess({
|
|
@@ -2416,7 +2535,7 @@ async function runSearchProbe(args) {
|
|
|
2416
2535
|
}
|
|
2417
2536
|
return [record];
|
|
2418
2537
|
}
|
|
2419
|
-
async function
|
|
2538
|
+
async function collectAccessDoctorRecords(args) {
|
|
2420
2539
|
let records;
|
|
2421
2540
|
if (typeof args.doi === "string" && args.doi.trim()) {
|
|
2422
2541
|
if (args.publisher === "all") {
|
|
@@ -2434,104 +2553,148 @@ async function runSearchDoctor(args) {
|
|
|
2434
2553
|
else {
|
|
2435
2554
|
records = summarizeConfiguredPublisherAccess(env);
|
|
2436
2555
|
}
|
|
2437
|
-
|
|
2438
|
-
|
|
2556
|
+
return records;
|
|
2557
|
+
}
|
|
2558
|
+
async function runAccessDoctor(args) {
|
|
2559
|
+
const records = await collectAccessDoctorRecords(args);
|
|
2560
|
+
const profilePath = accessReadinessPath();
|
|
2561
|
+
const profile = readAccessReadinessProfile();
|
|
2439
2562
|
if (args.json === true) {
|
|
2440
2563
|
console.log(JSON.stringify({
|
|
2441
|
-
|
|
2442
|
-
|
|
2564
|
+
readinessFile: profilePath,
|
|
2565
|
+
readinessFileExists: Boolean(profile),
|
|
2566
|
+
readiness: profile,
|
|
2567
|
+
metadataSources: assessSearchSourceCapabilities([...SEARCH_SOURCES], env),
|
|
2443
2568
|
records
|
|
2444
2569
|
}, null, 2));
|
|
2445
2570
|
}
|
|
2446
2571
|
else {
|
|
2447
|
-
console.log(
|
|
2448
|
-
if (!snapshotExists) {
|
|
2449
|
-
console.log("- saved capabilities: none yet; run `longtable search setup` to record non-secret capability status.");
|
|
2450
|
-
}
|
|
2572
|
+
console.log(renderAccessDoctor(profile, records, profilePath));
|
|
2451
2573
|
}
|
|
2452
2574
|
return records;
|
|
2453
2575
|
}
|
|
2454
|
-
async function
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
const answer = (await rl.question(prompt)).trim();
|
|
2459
|
-
if (!answer && defaultDoi) {
|
|
2460
|
-
return defaultDoi;
|
|
2461
|
-
}
|
|
2462
|
-
if (!answer || /^skip$/i.test(answer)) {
|
|
2463
|
-
return undefined;
|
|
2576
|
+
async function publisherRecordsForAccessSetup(args, routes) {
|
|
2577
|
+
const defaultDoi = typeof args.doi === "string" ? args.doi : undefined;
|
|
2578
|
+
if (!routes.includes("publisher_tdm")) {
|
|
2579
|
+
return summarizeConfiguredPublisherAccess(env);
|
|
2464
2580
|
}
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
async function runInteractiveSearchSetup(defaultDoi) {
|
|
2468
|
-
const rl = createInterface({ input, output });
|
|
2469
|
-
const records = [];
|
|
2470
|
-
try {
|
|
2471
|
-
console.log("LongTable publisher access setup");
|
|
2472
|
-
console.log("LongTable does not store API keys or TDM tokens. It reads environment variables and records only non-secret capability results.");
|
|
2473
|
-
console.log("");
|
|
2474
|
-
for (const publisher of publisherConfigs()) {
|
|
2475
|
-
console.log(`${publisher.label}`);
|
|
2476
|
-
console.log(` required env: ${publisher.requiredEnv.join(", ")}`);
|
|
2477
|
-
if (publisher.optionalEnv.length > 0) {
|
|
2478
|
-
console.log(` optional env: ${publisher.optionalEnv.join(", ")}`);
|
|
2479
|
-
}
|
|
2480
|
-
console.log(` ${publisher.setupHint}`);
|
|
2481
|
-
const doi = await promptPublisherDoi(rl, publisher.label, defaultDoi);
|
|
2482
|
-
if (doi) {
|
|
2483
|
-
records.push(await probePublisherAccess({
|
|
2484
|
-
doi,
|
|
2485
|
-
publisher: publisher.publisher,
|
|
2486
|
-
env
|
|
2487
|
-
}));
|
|
2488
|
-
}
|
|
2489
|
-
else {
|
|
2490
|
-
const summary = summarizeConfiguredPublisherAccess(env)
|
|
2491
|
-
.find((record) => record.publisher === publisher.publisher);
|
|
2492
|
-
if (summary) {
|
|
2493
|
-
records.push(summary);
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
console.log(renderPublisherAccessRecord(records[records.length - 1]));
|
|
2497
|
-
console.log("");
|
|
2498
|
-
}
|
|
2581
|
+
if (defaultDoi) {
|
|
2582
|
+
return probeAllPublishers(defaultDoi);
|
|
2499
2583
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2584
|
+
if (input.isTTY && output.isTTY && args.json !== true) {
|
|
2585
|
+
const doi = await promptText("Optional DOI for publisher/TDM probing. Leave blank to record env-var readiness only.", false);
|
|
2586
|
+
return doi
|
|
2587
|
+
? await probeAllPublishers(doi)
|
|
2588
|
+
: summarizeConfiguredPublisherAccess(env);
|
|
2502
2589
|
}
|
|
2503
|
-
return
|
|
2590
|
+
return summarizeConfiguredPublisherAccess(env);
|
|
2504
2591
|
}
|
|
2505
|
-
async function
|
|
2506
|
-
const
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
:
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2592
|
+
async function runInteractiveAccessSetup(args) {
|
|
2593
|
+
const readiness = await promptChoice("Scholarly Access Readiness\n\nWill this machine/account use scholarly search or full-text access?", [
|
|
2594
|
+
{ id: "configured", label: "Configure now", description: "Record access capability without storing secrets." },
|
|
2595
|
+
{ id: "deferred", label: "Ask later", description: "Defer setup and require an access checkpoint before search or extraction." },
|
|
2596
|
+
{ id: "disabled", label: "Do not use", description: "This project will use metadata/manual notes without scholarly full-text access." }
|
|
2597
|
+
]);
|
|
2598
|
+
if (readiness !== "configured") {
|
|
2599
|
+
return buildAccessReadinessProfile({ readiness, routes: [] });
|
|
2600
|
+
}
|
|
2601
|
+
const routeSelections = await promptMultiChoice("Select every scholarly access route that is available or intended. Secrets are never stored.", [
|
|
2602
|
+
{ id: "metadata", label: "Open metadata", description: "Crossref, OpenAlex, Semantic Scholar, PubMed, ERIC, DOAJ, Unpaywall." },
|
|
2603
|
+
{ id: "oa_full_text", label: "OA full text", description: "Use open-access PDF/full-text when legally available." },
|
|
2604
|
+
{ id: "institutional", label: "Institutional access", description: "VPN, library proxy, or browser SSO handled by the researcher." },
|
|
2605
|
+
{ id: "publisher_tdm", label: "Publisher API/TDM", description: "Use configured publisher API/TDM environment variables." },
|
|
2606
|
+
{ id: "manual_pdf", label: "Manual PDFs", description: "Researcher supplies PDFs; LongTable organizes/probes allowed extraction." },
|
|
2607
|
+
{ id: "unknown", label: "Unknown", description: "Keep access uncertain and require a checkpoint before full-text work." }
|
|
2608
|
+
]);
|
|
2609
|
+
const routes = uniqueAccessRoutes(routeSelections.length > 0 ? routeSelections : ["metadata"]);
|
|
2610
|
+
const institutionalAccessMode = routes.includes("institutional")
|
|
2611
|
+
? await promptChoice("How will institutional access be completed? The researcher handles login/MFA directly.", [
|
|
2612
|
+
{ id: "vpn", label: "VPN", description: "Researcher connects through school or institutional VPN." },
|
|
2613
|
+
{ id: "library_proxy", label: "Library proxy", description: "Researcher uses proxy links or library resolver." },
|
|
2614
|
+
{ id: "browser_sso", label: "Browser SSO", description: "Researcher logs into library/publisher SSO in the browser." },
|
|
2615
|
+
{ id: "unknown", label: "Unknown", description: "The route exists but is not yet specified." }
|
|
2616
|
+
])
|
|
2617
|
+
: undefined;
|
|
2618
|
+
const publisherRecords = await publisherRecordsForAccessSetup(args, routes);
|
|
2619
|
+
return buildAccessReadinessProfile({
|
|
2620
|
+
readiness,
|
|
2621
|
+
routes,
|
|
2622
|
+
institutionalAccessMode,
|
|
2623
|
+
publisherRecords
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
async function runAccessSetup(args) {
|
|
2627
|
+
const records = typeof args.doi === "string" && args.doi.trim()
|
|
2628
|
+
? await probeAllPublishers(args.doi)
|
|
2629
|
+
: summarizeConfiguredPublisherAccess(env);
|
|
2630
|
+
const profile = input.isTTY && output.isTTY && args.json !== true
|
|
2631
|
+
? await runInteractiveAccessSetup(args)
|
|
2632
|
+
: buildAccessReadinessProfile({
|
|
2633
|
+
readiness: "configured",
|
|
2634
|
+
routes: inferAccessRoutes(records),
|
|
2635
|
+
publisherRecords: records
|
|
2636
|
+
});
|
|
2637
|
+
const profilePath = await saveAccessReadinessProfile(profile);
|
|
2513
2638
|
if (args.json === true) {
|
|
2514
2639
|
console.log(JSON.stringify({
|
|
2515
|
-
|
|
2516
|
-
|
|
2640
|
+
readinessFile: profilePath,
|
|
2641
|
+
profile
|
|
2517
2642
|
}, null, 2));
|
|
2518
2643
|
return;
|
|
2519
2644
|
}
|
|
2520
|
-
console.log(
|
|
2645
|
+
console.log(renderAccessReadinessProfile(profile, profilePath));
|
|
2521
2646
|
}
|
|
2522
|
-
async function
|
|
2523
|
-
|
|
2524
|
-
|
|
2647
|
+
async function runAccessStatus(args) {
|
|
2648
|
+
const profilePath = accessReadinessPath();
|
|
2649
|
+
const profile = readAccessReadinessProfile();
|
|
2650
|
+
if (args.json === true) {
|
|
2651
|
+
console.log(JSON.stringify({
|
|
2652
|
+
readinessFile: profilePath,
|
|
2653
|
+
readinessFileExists: Boolean(profile),
|
|
2654
|
+
readiness: profile
|
|
2655
|
+
}, null, 2));
|
|
2525
2656
|
return;
|
|
2526
2657
|
}
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2658
|
+
if (!profile) {
|
|
2659
|
+
console.log([
|
|
2660
|
+
"LongTable Scholarly Access Readiness",
|
|
2661
|
+
`- profile: missing (${profilePath})`,
|
|
2662
|
+
"- next: run `longtable access setup` before PDF collection or full-text extraction."
|
|
2663
|
+
].join("\n"));
|
|
2529
2664
|
return;
|
|
2530
2665
|
}
|
|
2666
|
+
console.log(renderAccessReadinessProfile(profile, profilePath));
|
|
2667
|
+
}
|
|
2668
|
+
async function runAccess(subcommand, args) {
|
|
2531
2669
|
if (subcommand === "setup") {
|
|
2532
|
-
await
|
|
2670
|
+
await runAccessSetup(args);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
if (subcommand === "status") {
|
|
2674
|
+
await runAccessStatus(args);
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
if (subcommand === "doctor") {
|
|
2678
|
+
await runAccessDoctor(args);
|
|
2533
2679
|
return;
|
|
2534
2680
|
}
|
|
2681
|
+
if (subcommand === "probe") {
|
|
2682
|
+
await runAccessProbe(args);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
if (!subcommand) {
|
|
2686
|
+
await runAccessStatus(args);
|
|
2687
|
+
return;
|
|
2688
|
+
}
|
|
2689
|
+
throw new Error(`Unknown access subcommand: ${subcommand}`);
|
|
2690
|
+
}
|
|
2691
|
+
function movedSearchAccessCommand(subcommand) {
|
|
2692
|
+
return new Error(`\`longtable search ${subcommand}\` has moved. Use \`longtable access ${subcommand}\`.`);
|
|
2693
|
+
}
|
|
2694
|
+
async function runSearch(subcommand, args) {
|
|
2695
|
+
if (subcommand === "setup" || subcommand === "doctor" || subcommand === "status" || subcommand === "probe") {
|
|
2696
|
+
throw movedSearchAccessCommand(subcommand);
|
|
2697
|
+
}
|
|
2535
2698
|
if (subcommand) {
|
|
2536
2699
|
throw new Error(`Unknown search subcommand: ${subcommand}`);
|
|
2537
2700
|
}
|
|
@@ -3524,6 +3687,10 @@ async function main() {
|
|
|
3524
3687
|
await runMcpSubcommand(subcommand, values);
|
|
3525
3688
|
return;
|
|
3526
3689
|
}
|
|
3690
|
+
if (command === "access") {
|
|
3691
|
+
await runAccess(subcommand, values);
|
|
3692
|
+
return;
|
|
3693
|
+
}
|
|
3527
3694
|
if (command === "search") {
|
|
3528
3695
|
await runSearch(subcommand, values);
|
|
3529
3696
|
return;
|
|
@@ -131,6 +131,11 @@ function looksLikeResearchCommitmentPrompt(prompt) {
|
|
|
131
131
|
/\b(change|revise|update|replace|reframe|modify|alter)\b/i.test(prompt) ||
|
|
132
132
|
/바꾸|변경|수정|교체|전환|재설정/.test(prompt));
|
|
133
133
|
}
|
|
134
|
+
function looksLikeAccessSensitiveResearchAction(prompt) {
|
|
135
|
+
const normalized = prompt.trim();
|
|
136
|
+
return /\b(pdf|full[- ]?text|tdm|publisher api|institutional access|library login|vpn|proxy|subscription|paper collection|source collection|corpus|download)\b/i.test(normalized)
|
|
137
|
+
|| /PDF|원문|전문|기관\s*구독|기관구독|구독|VPN|프록시|도서관|라이브러리|TDM|논문\s*수집|문헌\s*수집|코퍼스|다운로드/.test(normalized);
|
|
138
|
+
}
|
|
134
139
|
function looksLikeQuestionGenerationPrompt(prompt) {
|
|
135
140
|
return /\b(needed questions?|necessary questions?|question generation|clarifying questions?|ask questions?)\b/i.test(prompt)
|
|
136
141
|
|| /필요한\s*질문|질문을\s*(모두|많이|생성)|질문\s*생성|물어봐|질문해/.test(prompt);
|
|
@@ -184,11 +189,12 @@ function shouldSurfaceInterviewContext(prompt) {
|
|
|
184
189
|
return looksLikeExplicitInterviewPrompt(prompt) || looksLikeResearchStateConfirmationPrompt(prompt);
|
|
185
190
|
}
|
|
186
191
|
function shouldCreateRequiredQuestionsForPrompt(prompt) {
|
|
187
|
-
return !looksLikeLongTableProductOrToolingPrompt(prompt) &&
|
|
192
|
+
return !looksLikeLongTableProductOrToolingPrompt(prompt) &&
|
|
193
|
+
(looksLikeResearchCommitmentPrompt(prompt) || looksLikeAccessSensitiveResearchAction(prompt));
|
|
188
194
|
}
|
|
189
195
|
function shouldApplyProtectedDecisionClosure(runtime, prompt) {
|
|
190
196
|
return Boolean(runtime.context.session.protectedDecision) &&
|
|
191
|
-
|
|
197
|
+
looksLikeResearchCommitmentPrompt(prompt) &&
|
|
192
198
|
!looksLikeQuestionGenerationPrompt(prompt) &&
|
|
193
199
|
!looksLikeMultiCommitmentChangePrompt(prompt);
|
|
194
200
|
}
|
|
@@ -34,6 +34,58 @@ export interface FirstResearchShape {
|
|
|
34
34
|
sourceHookId?: string;
|
|
35
35
|
confirmedAt?: string;
|
|
36
36
|
}
|
|
37
|
+
export interface ResearchSpecification {
|
|
38
|
+
title: string;
|
|
39
|
+
status?: "draft" | "confirmed" | "deferred";
|
|
40
|
+
createdAt?: string;
|
|
41
|
+
updatedAt?: string;
|
|
42
|
+
sourceHookId?: string;
|
|
43
|
+
researchDirection: {
|
|
44
|
+
question?: string;
|
|
45
|
+
purpose: string;
|
|
46
|
+
scopeBoundary?: string;
|
|
47
|
+
inclusionCriteria?: string[];
|
|
48
|
+
exclusionCriteria?: string[];
|
|
49
|
+
};
|
|
50
|
+
constructOntology: {
|
|
51
|
+
coreConstructs: string[];
|
|
52
|
+
distinctions: string[];
|
|
53
|
+
termsToAvoidCollapsing?: string[];
|
|
54
|
+
};
|
|
55
|
+
theoryAndFraming: {
|
|
56
|
+
anchors: string[];
|
|
57
|
+
alternatives?: string[];
|
|
58
|
+
overreachRisks?: string[];
|
|
59
|
+
};
|
|
60
|
+
measurementCoding: {
|
|
61
|
+
variablesOrConstructs: string[];
|
|
62
|
+
evidenceTypes: string[];
|
|
63
|
+
codingRules: string[];
|
|
64
|
+
openStandards?: string[];
|
|
65
|
+
};
|
|
66
|
+
methodAnalysis: {
|
|
67
|
+
design?: string;
|
|
68
|
+
analysisOptions: string[];
|
|
69
|
+
dataSufficiencyCriteria?: string[];
|
|
70
|
+
unsettledChoices?: string[];
|
|
71
|
+
};
|
|
72
|
+
evidenceAccess: {
|
|
73
|
+
requiredSources?: string[];
|
|
74
|
+
accessRequirements?: string[];
|
|
75
|
+
evidenceStandards?: string[];
|
|
76
|
+
};
|
|
77
|
+
epistemicAlignment: {
|
|
78
|
+
researcherKnowledge?: string[];
|
|
79
|
+
projectStatePriority?: string[];
|
|
80
|
+
aiInferenceLimits?: string[];
|
|
81
|
+
conflictResolutionRule?: string;
|
|
82
|
+
};
|
|
83
|
+
protectedDecisions: string[];
|
|
84
|
+
openQuestions: string[];
|
|
85
|
+
nextActions: string[];
|
|
86
|
+
confidence: "low" | "medium" | "high";
|
|
87
|
+
confirmedAt?: string;
|
|
88
|
+
}
|
|
37
89
|
export interface LongTableInterviewTurn {
|
|
38
90
|
id: string;
|
|
39
91
|
index: number;
|
|
@@ -59,6 +111,7 @@ export interface LongTableHookRun {
|
|
|
59
111
|
provider?: ProviderKind;
|
|
60
112
|
turns?: LongTableInterviewTurn[];
|
|
61
113
|
firstResearchShape?: FirstResearchShape;
|
|
114
|
+
researchSpecification?: ResearchSpecification;
|
|
62
115
|
qualityNotes?: string[];
|
|
63
116
|
rationale?: string[];
|
|
64
117
|
linkedQuestionRecordIds?: string[];
|
|
@@ -67,6 +120,7 @@ export interface LongTableHookRun {
|
|
|
67
120
|
export type LongTableWorkspaceState = ResearchState & {
|
|
68
121
|
hooks?: LongTableHookRun[];
|
|
69
122
|
firstResearchShape?: FirstResearchShape;
|
|
123
|
+
researchSpecification?: ResearchSpecification;
|
|
70
124
|
};
|
|
71
125
|
export interface LongTableProjectRecord {
|
|
72
126
|
schemaVersion: 1;
|
|
@@ -102,6 +156,7 @@ export interface LongTableSessionRecord {
|
|
|
102
156
|
openQuestions?: string[];
|
|
103
157
|
startInterview?: StartInterviewSession;
|
|
104
158
|
firstResearchShape?: FirstResearchShape;
|
|
159
|
+
researchSpecification?: ResearchSpecification;
|
|
105
160
|
requestedPerspectives: string[];
|
|
106
161
|
disagreementPreference: ProjectDisagreementPreference;
|
|
107
162
|
activeModes?: string[];
|
|
@@ -132,6 +187,11 @@ export interface LongTableWorkspaceInspection {
|
|
|
132
187
|
currentBlocker?: string;
|
|
133
188
|
requestedPerspectives: string[];
|
|
134
189
|
disagreementPreference: ProjectDisagreementPreference;
|
|
190
|
+
researchSpecification?: {
|
|
191
|
+
title: string;
|
|
192
|
+
status: "draft" | "confirmed" | "deferred";
|
|
193
|
+
confidence: "low" | "medium" | "high";
|
|
194
|
+
};
|
|
135
195
|
};
|
|
136
196
|
files?: {
|
|
137
197
|
project: string;
|
|
@@ -224,6 +284,16 @@ export declare function summarizeLongTableInterview(options: {
|
|
|
224
284
|
state: LongTableWorkspaceState;
|
|
225
285
|
session: LongTableSessionRecord;
|
|
226
286
|
}>;
|
|
287
|
+
export declare function summarizeLongTableResearchSpecification(options: {
|
|
288
|
+
context: LongTableProjectContext;
|
|
289
|
+
hookId?: string;
|
|
290
|
+
specification: ResearchSpecification;
|
|
291
|
+
}): Promise<{
|
|
292
|
+
hook?: LongTableHookRun;
|
|
293
|
+
specification: ResearchSpecification;
|
|
294
|
+
state: LongTableWorkspaceState;
|
|
295
|
+
session: LongTableSessionRecord;
|
|
296
|
+
}>;
|
|
227
297
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
228
298
|
export declare function listBlockingWorkspaceObligations(context: LongTableProjectContext): Promise<LongTableQuestionObligation[]>;
|
|
229
299
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
package/dist/project-session.js
CHANGED
|
@@ -94,6 +94,9 @@ function buildNextAction(session) {
|
|
|
94
94
|
: "Open with your current goal in one sentence, then ask LongTable for the first concrete research move.";
|
|
95
95
|
}
|
|
96
96
|
function buildResumeHint(session) {
|
|
97
|
+
if (session.researchSpecification) {
|
|
98
|
+
return `I want to continue from the Research Specification: ${session.researchSpecification.title}.`;
|
|
99
|
+
}
|
|
97
100
|
if (session.firstResearchShape) {
|
|
98
101
|
return `I want to continue from the First Research Shape: ${session.firstResearchShape.handle}.`;
|
|
99
102
|
}
|
|
@@ -106,6 +109,60 @@ function buildResumeHint(session) {
|
|
|
106
109
|
? `I want to continue ${session.currentGoal}. The unresolved blocker is ${session.currentBlocker}.`
|
|
107
110
|
: `I want to continue ${session.currentGoal}.`;
|
|
108
111
|
}
|
|
112
|
+
function renderResearchSpecificationSummary(specification, locale) {
|
|
113
|
+
const korean = locale === "ko";
|
|
114
|
+
const lines = [
|
|
115
|
+
"",
|
|
116
|
+
korean ? "## Research Specification" : "## Research Specification",
|
|
117
|
+
`- ${korean ? "제목" : "Title"}: ${specification.title}`,
|
|
118
|
+
`- ${korean ? "상태" : "Status"}: ${specification.confirmedAt ? "confirmed" : specification.status ?? "draft"}`,
|
|
119
|
+
`- ${korean ? "신뢰도" : "Confidence"}: ${specification.confidence}`
|
|
120
|
+
];
|
|
121
|
+
if (specification.researchDirection.question) {
|
|
122
|
+
lines.push(`- ${korean ? "연구 질문" : "Question"}: ${specification.researchDirection.question}`);
|
|
123
|
+
}
|
|
124
|
+
lines.push(`- ${korean ? "목적" : "Purpose"}: ${specification.researchDirection.purpose}`);
|
|
125
|
+
if (specification.researchDirection.scopeBoundary) {
|
|
126
|
+
lines.push(`- ${korean ? "범위 경계" : "Scope boundary"}: ${specification.researchDirection.scopeBoundary}`);
|
|
127
|
+
}
|
|
128
|
+
if (specification.constructOntology.coreConstructs.length > 0) {
|
|
129
|
+
lines.push(`- ${korean ? "핵심 construct" : "Core constructs"}: ${specification.constructOntology.coreConstructs.join("; ")}`);
|
|
130
|
+
}
|
|
131
|
+
if (specification.constructOntology.distinctions.length > 0) {
|
|
132
|
+
lines.push(`- ${korean ? "구분해야 할 차이" : "Key distinctions"}: ${specification.constructOntology.distinctions.join("; ")}`);
|
|
133
|
+
}
|
|
134
|
+
if (specification.theoryAndFraming.anchors.length > 0) {
|
|
135
|
+
lines.push(`- ${korean ? "이론 앵커" : "Theory anchors"}: ${specification.theoryAndFraming.anchors.join("; ")}`);
|
|
136
|
+
}
|
|
137
|
+
if (specification.measurementCoding.codingRules.length > 0) {
|
|
138
|
+
lines.push(`- ${korean ? "코딩 규칙" : "Coding rules"}: ${specification.measurementCoding.codingRules.join("; ")}`);
|
|
139
|
+
}
|
|
140
|
+
if (specification.methodAnalysis.analysisOptions.length > 0) {
|
|
141
|
+
lines.push(`- ${korean ? "분석 옵션" : "Analysis options"}: ${specification.methodAnalysis.analysisOptions.join("; ")}`);
|
|
142
|
+
}
|
|
143
|
+
if (specification.evidenceAccess.requiredSources?.length) {
|
|
144
|
+
lines.push(`- ${korean ? "필요 근거원" : "Required sources"}: ${specification.evidenceAccess.requiredSources.join("; ")}`);
|
|
145
|
+
}
|
|
146
|
+
if (specification.evidenceAccess.accessRequirements?.length) {
|
|
147
|
+
lines.push(`- ${korean ? "Corpus and Access Plan" : "Corpus and Access Plan"}: ${specification.evidenceAccess.accessRequirements.join("; ")}`);
|
|
148
|
+
}
|
|
149
|
+
if (specification.evidenceAccess.evidenceStandards?.length) {
|
|
150
|
+
lines.push(`- ${korean ? "근거 기준" : "Evidence standards"}: ${specification.evidenceAccess.evidenceStandards.join("; ")}`);
|
|
151
|
+
}
|
|
152
|
+
if (specification.epistemicAlignment.conflictResolutionRule) {
|
|
153
|
+
lines.push(`- ${korean ? "충돌 조정 규칙" : "Conflict rule"}: ${specification.epistemicAlignment.conflictResolutionRule}`);
|
|
154
|
+
}
|
|
155
|
+
if (specification.protectedDecisions.length > 0) {
|
|
156
|
+
lines.push(...specification.protectedDecisions.map((decision) => `- ${korean ? "보호할 결정" : "Protected decision"}: ${decision}`));
|
|
157
|
+
}
|
|
158
|
+
if (specification.openQuestions.length > 0) {
|
|
159
|
+
lines.push(...specification.openQuestions.map((question) => `- ${korean ? "열린 질문" : "Open question"}: ${question}`));
|
|
160
|
+
}
|
|
161
|
+
if (specification.nextActions.length > 0) {
|
|
162
|
+
lines.push(...specification.nextActions.map((action) => `- ${korean ? "다음 행동" : "Next action"}: ${action}`));
|
|
163
|
+
}
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
109
166
|
function buildCurrentGuide(project, session, recentInvocations = [], pendingQuestions = [], pendingObligations = []) {
|
|
110
167
|
const locale = normalizeLocale(session.locale ?? project.locale);
|
|
111
168
|
const openQuestions = session.openQuestions && session.openQuestions.length > 0
|
|
@@ -129,6 +186,7 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
129
186
|
...(session.gapRisk ? [`- 공백/암묵지 위험: ${session.gapRisk}`] : []),
|
|
130
187
|
...(session.protectedDecision ? [`- 보호할 결정: ${session.protectedDecision}`] : []),
|
|
131
188
|
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
189
|
+
...(session.researchSpecification ? [`- Research Specification: ${session.researchSpecification.title}`] : []),
|
|
132
190
|
...(session.startInterview ? [`- start interview: ${session.startInterview.summary}`] : []),
|
|
133
191
|
`- 다음 액션: ${nextAction}`,
|
|
134
192
|
`- 관점: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
@@ -179,6 +237,7 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
179
237
|
...session.firstResearchShape.openQuestions.map((question) => `- Open question: ${question}`)
|
|
180
238
|
]
|
|
181
239
|
: []),
|
|
240
|
+
...(session.researchSpecification ? renderResearchSpecificationSummary(session.researchSpecification, locale) : []),
|
|
182
241
|
"",
|
|
183
242
|
"## 빠른 시작",
|
|
184
243
|
"- 이 디렉토리에서 `codex`를 엽니다.",
|
|
@@ -202,6 +261,7 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
202
261
|
...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
|
|
203
262
|
...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
|
|
204
263
|
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
264
|
+
...(session.researchSpecification ? [`- Research Specification: ${session.researchSpecification.title}`] : []),
|
|
205
265
|
...(session.startInterview ? [`- Start interview: ${session.startInterview.summary}`] : []),
|
|
206
266
|
`- Next action: ${nextAction}`,
|
|
207
267
|
`- Perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
@@ -252,6 +312,7 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
252
312
|
...session.firstResearchShape.openQuestions.map((question) => `- Open question: ${question}`)
|
|
253
313
|
]
|
|
254
314
|
: []),
|
|
315
|
+
...(session.researchSpecification ? renderResearchSpecificationSummary(session.researchSpecification, locale) : []),
|
|
255
316
|
"",
|
|
256
317
|
"## Quick Start",
|
|
257
318
|
"- Open `codex` in this directory.",
|
|
@@ -272,6 +333,7 @@ async function loadResearchState(stateFilePath) {
|
|
|
272
333
|
workingState: parsed.workingState ?? {},
|
|
273
334
|
hooks: parsed.hooks ?? [],
|
|
274
335
|
...(parsed.firstResearchShape ? { firstResearchShape: parsed.firstResearchShape } : {}),
|
|
336
|
+
...(parsed.researchSpecification ? { researchSpecification: parsed.researchSpecification } : {}),
|
|
275
337
|
questionObligations: parsed.questionObligations ?? [],
|
|
276
338
|
inferredHypotheses: parsed.inferredHypotheses ?? [],
|
|
277
339
|
openTensions: parsed.openTensions ?? [],
|
|
@@ -332,7 +394,18 @@ function summarizeWorkspaceInspection(context, state) {
|
|
|
332
394
|
currentGoal: context.session.currentGoal,
|
|
333
395
|
...(context.session.currentBlocker ? { currentBlocker: context.session.currentBlocker } : {}),
|
|
334
396
|
requestedPerspectives: context.session.requestedPerspectives,
|
|
335
|
-
disagreementPreference: context.session.disagreementPreference
|
|
397
|
+
disagreementPreference: context.session.disagreementPreference,
|
|
398
|
+
...(context.session.researchSpecification
|
|
399
|
+
? {
|
|
400
|
+
researchSpecification: {
|
|
401
|
+
title: context.session.researchSpecification.title,
|
|
402
|
+
status: context.session.researchSpecification.confirmedAt
|
|
403
|
+
? "confirmed"
|
|
404
|
+
: context.session.researchSpecification.status ?? "draft",
|
|
405
|
+
confidence: context.session.researchSpecification.confidence
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
: {})
|
|
336
409
|
},
|
|
337
410
|
files: {
|
|
338
411
|
project: context.projectFilePath,
|
|
@@ -422,6 +495,7 @@ function buildProjectAgentsMd(project, session) {
|
|
|
422
495
|
"- Begin exploratory work with clarifying or tension questions before recommending a direction.",
|
|
423
496
|
"- For `$longtable-interview`, ask one natural-language question at a time, reflect with `LongTable hears: ...`, record turns when MCP is available, and avoid early reader/reviewer or theory/method/measurement classification.",
|
|
424
497
|
"- Do not summarize `$longtable-interview` because a fixed number of turns has passed; wait for content-based readiness around research object, focal uncertainty, boundary, evidence/material, protected decision, and next action.",
|
|
498
|
+
"- After the First Research Shape, create a Research Specification when the interview has enough detail to preserve scope, construct ontology, theory framing, coding rules, method options, evidence/access requirements, epistemic alignment, protected decisions, open questions, and next actions.",
|
|
425
499
|
"- Do not let unrelated pending Researcher Checkpoints interrupt `$longtable-interview`; mention them only as separate unresolved checkpoints unless the researcher is confirming, saving, or recording a research decision.",
|
|
426
500
|
"- Use structured options only at the final First Research Shape confirmation or at true checkpoint boundaries.",
|
|
427
501
|
"- If you foreground role perspectives, disclose them with `LongTable consulted: ...`.",
|
|
@@ -441,6 +515,7 @@ function buildProjectAgentsMd(project, session) {
|
|
|
441
515
|
...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
|
|
442
516
|
...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
|
|
443
517
|
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
518
|
+
...(session.researchSpecification ? [`- Research Specification: ${session.researchSpecification.title}`] : []),
|
|
444
519
|
...(session.startInterview ? [`- Start interview summary: ${session.startInterview.summary}`] : []),
|
|
445
520
|
`- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
446
521
|
`- Disagreement visibility: ${session.disagreementPreference}`,
|
|
@@ -473,6 +548,10 @@ function buildStateSeed(project, session, setup) {
|
|
|
473
548
|
state.firstResearchShape = session.firstResearchShape;
|
|
474
549
|
state.workingState.firstResearchShape = session.firstResearchShape;
|
|
475
550
|
}
|
|
551
|
+
if (session.researchSpecification) {
|
|
552
|
+
state.researchSpecification = session.researchSpecification;
|
|
553
|
+
state.workingState.researchSpecification = session.researchSpecification;
|
|
554
|
+
}
|
|
476
555
|
if (session.currentBlocker) {
|
|
477
556
|
state.openTensions.push(session.currentBlocker);
|
|
478
557
|
}
|
|
@@ -543,7 +622,16 @@ async function removeLegacyRootFiles(projectPath) {
|
|
|
543
622
|
}
|
|
544
623
|
export async function syncCurrentWorkspaceView(context) {
|
|
545
624
|
const state = await loadResearchState(context.stateFilePath);
|
|
546
|
-
const
|
|
625
|
+
const session = {
|
|
626
|
+
...context.session,
|
|
627
|
+
...(context.session.firstResearchShape ?? state.firstResearchShape
|
|
628
|
+
? { firstResearchShape: context.session.firstResearchShape ?? state.firstResearchShape }
|
|
629
|
+
: {}),
|
|
630
|
+
...(context.session.researchSpecification ?? state.researchSpecification
|
|
631
|
+
? { researchSpecification: context.session.researchSpecification ?? state.researchSpecification }
|
|
632
|
+
: {})
|
|
633
|
+
};
|
|
634
|
+
const body = buildCurrentGuide(context.project, session, recentInvocationRecords(state), recentPendingQuestions(state), recentPendingObligations(state));
|
|
547
635
|
await writeFile(context.currentFilePath, body, "utf8");
|
|
548
636
|
return context.currentFilePath;
|
|
549
637
|
}
|
|
@@ -772,6 +860,122 @@ export async function summarizeLongTableInterview(options) {
|
|
|
772
860
|
await syncCurrentWorkspaceView(options.context);
|
|
773
861
|
return { hook, shape, state: updated, session };
|
|
774
862
|
}
|
|
863
|
+
function normalizeStringArray(values) {
|
|
864
|
+
return (values ?? []).map((value) => value.trim()).filter(Boolean);
|
|
865
|
+
}
|
|
866
|
+
function normalizeOptionalString(value) {
|
|
867
|
+
const trimmed = value?.trim();
|
|
868
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
869
|
+
}
|
|
870
|
+
function normalizeResearchSpecification(input, sourceHookId, timestamp) {
|
|
871
|
+
const title = input.title.trim();
|
|
872
|
+
if (!title) {
|
|
873
|
+
throw new Error("Research Specification title is required.");
|
|
874
|
+
}
|
|
875
|
+
const purpose = input.researchDirection.purpose.trim();
|
|
876
|
+
if (!purpose) {
|
|
877
|
+
throw new Error("Research Specification researchDirection.purpose is required.");
|
|
878
|
+
}
|
|
879
|
+
return {
|
|
880
|
+
title,
|
|
881
|
+
status: input.confirmedAt ? "confirmed" : input.status ?? "draft",
|
|
882
|
+
createdAt: input.createdAt ?? timestamp,
|
|
883
|
+
updatedAt: timestamp,
|
|
884
|
+
...(sourceHookId ? { sourceHookId } : {}),
|
|
885
|
+
researchDirection: {
|
|
886
|
+
...(normalizeOptionalString(input.researchDirection.question) ? { question: normalizeOptionalString(input.researchDirection.question) } : {}),
|
|
887
|
+
purpose,
|
|
888
|
+
...(normalizeOptionalString(input.researchDirection.scopeBoundary) ? { scopeBoundary: normalizeOptionalString(input.researchDirection.scopeBoundary) } : {}),
|
|
889
|
+
inclusionCriteria: normalizeStringArray(input.researchDirection.inclusionCriteria),
|
|
890
|
+
exclusionCriteria: normalizeStringArray(input.researchDirection.exclusionCriteria)
|
|
891
|
+
},
|
|
892
|
+
constructOntology: {
|
|
893
|
+
coreConstructs: normalizeStringArray(input.constructOntology.coreConstructs),
|
|
894
|
+
distinctions: normalizeStringArray(input.constructOntology.distinctions),
|
|
895
|
+
termsToAvoidCollapsing: normalizeStringArray(input.constructOntology.termsToAvoidCollapsing)
|
|
896
|
+
},
|
|
897
|
+
theoryAndFraming: {
|
|
898
|
+
anchors: normalizeStringArray(input.theoryAndFraming.anchors),
|
|
899
|
+
alternatives: normalizeStringArray(input.theoryAndFraming.alternatives),
|
|
900
|
+
overreachRisks: normalizeStringArray(input.theoryAndFraming.overreachRisks)
|
|
901
|
+
},
|
|
902
|
+
measurementCoding: {
|
|
903
|
+
variablesOrConstructs: normalizeStringArray(input.measurementCoding.variablesOrConstructs),
|
|
904
|
+
evidenceTypes: normalizeStringArray(input.measurementCoding.evidenceTypes),
|
|
905
|
+
codingRules: normalizeStringArray(input.measurementCoding.codingRules),
|
|
906
|
+
openStandards: normalizeStringArray(input.measurementCoding.openStandards)
|
|
907
|
+
},
|
|
908
|
+
methodAnalysis: {
|
|
909
|
+
...(normalizeOptionalString(input.methodAnalysis.design) ? { design: normalizeOptionalString(input.methodAnalysis.design) } : {}),
|
|
910
|
+
analysisOptions: normalizeStringArray(input.methodAnalysis.analysisOptions),
|
|
911
|
+
dataSufficiencyCriteria: normalizeStringArray(input.methodAnalysis.dataSufficiencyCriteria),
|
|
912
|
+
unsettledChoices: normalizeStringArray(input.methodAnalysis.unsettledChoices)
|
|
913
|
+
},
|
|
914
|
+
evidenceAccess: {
|
|
915
|
+
requiredSources: normalizeStringArray(input.evidenceAccess.requiredSources),
|
|
916
|
+
accessRequirements: normalizeStringArray(input.evidenceAccess.accessRequirements),
|
|
917
|
+
evidenceStandards: normalizeStringArray(input.evidenceAccess.evidenceStandards)
|
|
918
|
+
},
|
|
919
|
+
epistemicAlignment: {
|
|
920
|
+
researcherKnowledge: normalizeStringArray(input.epistemicAlignment.researcherKnowledge),
|
|
921
|
+
projectStatePriority: normalizeStringArray(input.epistemicAlignment.projectStatePriority),
|
|
922
|
+
aiInferenceLimits: normalizeStringArray(input.epistemicAlignment.aiInferenceLimits),
|
|
923
|
+
...(normalizeOptionalString(input.epistemicAlignment.conflictResolutionRule)
|
|
924
|
+
? { conflictResolutionRule: normalizeOptionalString(input.epistemicAlignment.conflictResolutionRule) }
|
|
925
|
+
: {})
|
|
926
|
+
},
|
|
927
|
+
protectedDecisions: normalizeStringArray(input.protectedDecisions),
|
|
928
|
+
openQuestions: normalizeStringArray(input.openQuestions),
|
|
929
|
+
nextActions: normalizeStringArray(input.nextActions),
|
|
930
|
+
confidence: input.confidence,
|
|
931
|
+
...(input.confirmedAt ? { confirmedAt: input.confirmedAt } : {})
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
export async function summarizeLongTableResearchSpecification(options) {
|
|
935
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
936
|
+
const sourceHookId = options.hookId
|
|
937
|
+
?? options.specification.sourceHookId
|
|
938
|
+
?? state.firstResearchShape?.sourceHookId;
|
|
939
|
+
const existing = sourceHookId
|
|
940
|
+
? (state.hooks ?? []).find((hook) => hook.id === sourceHookId)
|
|
941
|
+
: activeInterviewHook(state);
|
|
942
|
+
const timestamp = nowIso();
|
|
943
|
+
const specification = normalizeResearchSpecification(options.specification, existing?.id ?? sourceHookId, timestamp);
|
|
944
|
+
const hook = existing
|
|
945
|
+
? {
|
|
946
|
+
...existing,
|
|
947
|
+
status: "ready_to_confirm",
|
|
948
|
+
updatedAt: timestamp,
|
|
949
|
+
researchSpecification: specification
|
|
950
|
+
}
|
|
951
|
+
: undefined;
|
|
952
|
+
const session = {
|
|
953
|
+
...options.context.session,
|
|
954
|
+
lastUpdatedAt: timestamp,
|
|
955
|
+
researchSpecification: specification,
|
|
956
|
+
resumeHint: `I want to continue from the Research Specification: ${specification.title}.`
|
|
957
|
+
};
|
|
958
|
+
options.context.session = session;
|
|
959
|
+
let updated = hook ? upsertHook(state, hook) : state;
|
|
960
|
+
updated.researchSpecification = specification;
|
|
961
|
+
updated.workingState = {
|
|
962
|
+
...updated.workingState,
|
|
963
|
+
researchSpecification: specification
|
|
964
|
+
};
|
|
965
|
+
updated.narrativeTraces.push({
|
|
966
|
+
id: createId("narrative_trace"),
|
|
967
|
+
timestamp,
|
|
968
|
+
source: "$longtable-interview",
|
|
969
|
+
traceType: "judgment",
|
|
970
|
+
summary: `Research Specification draft: ${specification.title}.`,
|
|
971
|
+
visibility: "explicit",
|
|
972
|
+
importance: specification.confidence
|
|
973
|
+
});
|
|
974
|
+
await writeFile(options.context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
975
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
976
|
+
await syncCurrentWorkspaceView(options.context);
|
|
977
|
+
return { hook, specification, state: updated, session };
|
|
978
|
+
}
|
|
775
979
|
function findQuestionForDecision(state, questionId) {
|
|
776
980
|
const pending = (state.questionLog ?? []).filter((record) => record.status === "pending");
|
|
777
981
|
if (questionId) {
|
|
@@ -1024,8 +1228,8 @@ function questionPriority(spec) {
|
|
|
1024
1228
|
const requiredWeight = spec.kind === "research_commitment" || spec.required ? 20 : 0;
|
|
1025
1229
|
return (byKey[spec.key] ?? 0) + confidenceWeight + requiredWeight;
|
|
1026
1230
|
}
|
|
1027
|
-
function followUpQuestionOptions(first, second, third,
|
|
1028
|
-
return [first, second, third, ...
|
|
1231
|
+
function followUpQuestionOptions(first, second, third, ...rest) {
|
|
1232
|
+
return [first, second, third, ...rest];
|
|
1029
1233
|
}
|
|
1030
1234
|
export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
1031
1235
|
const normalized = prompt.toLowerCase();
|
|
@@ -1090,8 +1294,26 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
|
1090
1294
|
/\brandom[- ]?effects\b/i,
|
|
1091
1295
|
/분석\s*계획|분석\s*방법|메타\s*분석|분석\s*(?:모형|모델)|통계\s*(?:모형|모델)|구조\s*방정식|경로\s*모형|조절효과|랜덤\s*효과/
|
|
1092
1296
|
]);
|
|
1297
|
+
const accessCue = includesAny(normalized, [
|
|
1298
|
+
/\b(pdf|full[- ]?text|tdm|publisher api|institutional access|library login|vpn|proxy|subscription|paper collection|source collection|corpus|download)\b/i,
|
|
1299
|
+
/PDF|원문|전문|기관\s*구독|기관구독|구독|VPN|프록시|도서관|라이브러리|TDM|논문\s*수집|문헌\s*수집|코퍼스|다운로드/
|
|
1300
|
+
]);
|
|
1093
1301
|
const decisionFamilyCount = [scopeCue, theoryCue, measurementCodingCue, methodCue, analysisCue]
|
|
1094
1302
|
.filter(Boolean).length;
|
|
1303
|
+
if (accessCue) {
|
|
1304
|
+
push({
|
|
1305
|
+
key: "scholarly_access_policy",
|
|
1306
|
+
kind: "evidence_risk",
|
|
1307
|
+
title: "Scholarly access policy",
|
|
1308
|
+
question: "What scholarly access route should LongTable use before collecting PDFs, full text, or subscription-only evidence?",
|
|
1309
|
+
whyNow: "Full-text access decisions can change the corpus, inclusion bias, reproducibility, and TDM permission boundary.",
|
|
1310
|
+
options: followUpQuestionOptions({ value: "oa_only", label: "OA-only", description: "Use only open-access PDF or full text.", recommended: true }, { value: "institutional_access", label: "Institutional access", description: "Include VPN/proxy/library-login access after the researcher completes login." }, { value: "publisher_tdm", label: "Publisher API/TDM", description: "Use configured publisher API/TDM credentials and record entitlement checks." }, { value: "manual_pdf", label: "Manual PDFs", description: "Use PDFs supplied by the researcher and record provenance." }, { value: "metadata_only", label: "Metadata only", description: "Do not collect full text yet." }),
|
|
1311
|
+
confidence: "high",
|
|
1312
|
+
autoEligible: true,
|
|
1313
|
+
required: true,
|
|
1314
|
+
cues: ["scholarly_access", "full_text", "corpus"]
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1095
1317
|
if (decisionActionCue && decisionFamilyCount >= 2) {
|
|
1096
1318
|
push({
|
|
1097
1319
|
key: "research_direction_change_commitment",
|
|
@@ -1424,7 +1646,7 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
|
1424
1646
|
}
|
|
1425
1647
|
let selected = options.autoOnly === true ? specs.filter((spec) => spec.autoEligible) : specs;
|
|
1426
1648
|
if (options.requiredOnly === true) {
|
|
1427
|
-
selected = selected.filter((spec) => spec.kind === "research_commitment");
|
|
1649
|
+
selected = selected.filter((spec) => spec.kind === "research_commitment" || spec.required);
|
|
1428
1650
|
}
|
|
1429
1651
|
if (normalized.includes("protected decision closure pressure")) {
|
|
1430
1652
|
selected = selected.filter((spec) => spec.key === "protected_decision_closure");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CrossrefTdmDiscovery, type EvidenceCard, type Publisher, type PublisherAccessRecord, type PublisherProbeInput, type PublisherProbeTarget, type
|
|
1
|
+
import { type CrossrefTdmDiscovery, type EvidenceCard, type Publisher, type PublisherAccessRecord, type PublisherProbeInput, type PublisherProbeTarget, type SearchFetch } from "./types.js";
|
|
2
2
|
interface PublisherConfig {
|
|
3
3
|
publisher: Publisher;
|
|
4
4
|
label: string;
|
|
@@ -12,8 +12,6 @@ export declare function discoverCrossrefTdm(doi: string, env?: Record<string, st
|
|
|
12
12
|
export declare function publisherConfigs(): PublisherConfig[];
|
|
13
13
|
export declare function probePublisherAccess(input: PublisherProbeInput): Promise<PublisherAccessRecord>;
|
|
14
14
|
export declare function summarizeConfiguredPublisherAccess(env?: Record<string, string | undefined>): PublisherAccessRecord[];
|
|
15
|
-
export declare function buildSearchCapabilitySnapshot(records: PublisherAccessRecord[], env?: Record<string, string | undefined>): SearchCapabilitySnapshot;
|
|
16
|
-
export declare function searchCapabilitySnapshotPath(home?: string): string;
|
|
17
15
|
export declare function enrichCardsWithPublisherAccess(input: {
|
|
18
16
|
cards: EvidenceCard[];
|
|
19
17
|
env?: Record<string, string | undefined>;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
1
|
import { PUBLISHERS } from "./types.js";
|
|
4
2
|
const PUBLISHER_CONFIGS = {
|
|
5
3
|
elsevier: {
|
|
@@ -505,17 +503,6 @@ export function summarizeConfiguredPublisherAccess(env = process.env) {
|
|
|
505
503
|
});
|
|
506
504
|
});
|
|
507
505
|
}
|
|
508
|
-
export function buildSearchCapabilitySnapshot(records, env = process.env) {
|
|
509
|
-
return {
|
|
510
|
-
version: 1,
|
|
511
|
-
updatedAt: now(),
|
|
512
|
-
contactEmailPresent: Boolean(env.LONGTABLE_CONTACT_EMAIL?.trim()),
|
|
513
|
-
records
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
export function searchCapabilitySnapshotPath(home = homedir()) {
|
|
517
|
-
return join(home, ".longtable", "search-capabilities.json");
|
|
518
|
-
}
|
|
519
506
|
function bestAccessStatus(record) {
|
|
520
507
|
if (record.entitlementStatus === "licensed_full_text_available" && record.collectionDepth === "licensed_snippet") {
|
|
521
508
|
return "licensed_full_text_checked";
|
package/dist/search/types.d.ts
CHANGED
|
@@ -173,12 +173,6 @@ export interface PublisherAccessRecord {
|
|
|
173
173
|
evidenceSnippet?: string;
|
|
174
174
|
crossref?: CrossrefTdmDiscovery;
|
|
175
175
|
}
|
|
176
|
-
export interface SearchCapabilitySnapshot {
|
|
177
|
-
version: 1;
|
|
178
|
-
updatedAt: string;
|
|
179
|
-
contactEmailPresent: boolean;
|
|
180
|
-
records: PublisherAccessRecord[];
|
|
181
|
-
}
|
|
182
176
|
export interface PublisherProbeInput {
|
|
183
177
|
doi: string;
|
|
184
178
|
publisher?: PublisherProbeTarget;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.45",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@longtable/checkpoints": "0.1.
|
|
33
|
-
"@longtable/core": "0.1.
|
|
34
|
-
"@longtable/memory": "0.1.
|
|
35
|
-
"@longtable/provider-claude": "0.1.
|
|
36
|
-
"@longtable/provider-codex": "0.1.
|
|
37
|
-
"@longtable/setup": "0.1.
|
|
32
|
+
"@longtable/checkpoints": "0.1.45",
|
|
33
|
+
"@longtable/core": "0.1.45",
|
|
34
|
+
"@longtable/memory": "0.1.45",
|
|
35
|
+
"@longtable/provider-claude": "0.1.45",
|
|
36
|
+
"@longtable/provider-codex": "0.1.45",
|
|
37
|
+
"@longtable/setup": "0.1.45"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|