@longtable/cli 0.1.25 → 0.1.27
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 +25 -23
- package/dist/cli.js +227 -99
- package/dist/debate.d.ts +0 -1
- package/dist/debate.js +2 -4
- package/dist/persona-router.d.ts +1 -0
- package/dist/persona-router.js +4 -1
- package/dist/project-session.d.ts +1 -1
- package/dist/project-session.js +17 -17
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -20,8 +20,7 @@ npm install -g @longtable/cli
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The npm install only installs the CLI. It does not write Codex skills, MCP
|
|
23
|
-
config, hooks,
|
|
24
|
-
approval.
|
|
23
|
+
config, hooks, or provider runtime files without explicit setup approval.
|
|
25
24
|
|
|
26
25
|
## Primary Flow
|
|
27
26
|
|
|
@@ -91,9 +90,7 @@ longtable roles
|
|
|
91
90
|
longtable ask --cwd "<project-path>" --prompt "..."
|
|
92
91
|
longtable panel --prompt "..."
|
|
93
92
|
longtable sentinel --prompt "Should I define a new measurement construct?"
|
|
94
|
-
longtable hud --watch
|
|
95
93
|
longtable team --prompt "Review this measurement plan." --role editor,measurement_auditor --json
|
|
96
|
-
longtable team --tmux --prompt "Review this measurement plan."
|
|
97
94
|
longtable team --debate --prompt "Review this measurement plan." --role editor,measurement_auditor --json
|
|
98
95
|
longtable codex install-skills
|
|
99
96
|
longtable claude install-skills
|
|
@@ -111,6 +108,16 @@ longtable ask --prompt "lt panel: show the disagreement before I commit" --json
|
|
|
111
108
|
|
|
112
109
|
Natural language should be the default.
|
|
113
110
|
|
|
111
|
+
Codex UI Researcher Checkpoints are a core LongTable feature when enabled:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
longtable setup --provider codex --surfaces skills_mcp --checkpoint-ui strong
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That setup writes the MCP configuration and Codex elicitation approval needed
|
|
118
|
+
for form-style checkpoint prompts. Without it, LongTable keeps the same
|
|
119
|
+
`QuestionRecord` pending and falls back to numbered text.
|
|
120
|
+
|
|
114
121
|
Explicit short forms are available when needed:
|
|
115
122
|
|
|
116
123
|
```text
|
|
@@ -152,10 +159,6 @@ Inside a LongTable project workspace, panel planning also appends an
|
|
|
152
159
|
`InvocationRecord` to `.longtable/state.json`, creates a pending follow-up
|
|
153
160
|
`QuestionRecord`, and refreshes `CURRENT.md`.
|
|
154
161
|
|
|
155
|
-
```bash
|
|
156
|
-
longtable decide --answer evidence --rationale "Need citation support before continuing."
|
|
157
|
-
```
|
|
158
|
-
|
|
159
162
|
Default panel roles include:
|
|
160
163
|
|
|
161
164
|
- `reviewer`
|
|
@@ -165,32 +168,25 @@ Default panel roles include:
|
|
|
165
168
|
|
|
166
169
|
Use `--role` to constrain the panel when the research problem is already clear.
|
|
167
170
|
|
|
168
|
-
## Sentinel
|
|
171
|
+
## Sentinel And Agent Team
|
|
169
172
|
|
|
170
173
|
`longtable sentinel` is an explicit gap/tacit check for prompts that may contain
|
|
171
174
|
measurement, theory, method, evidence, authorship, or tacit-assumption risks.
|
|
172
175
|
Use `--record` inside a LongTable workspace to store the finding as an
|
|
173
176
|
unconfirmed inferred hypothesis.
|
|
174
177
|
|
|
175
|
-
`longtable hud --watch` renders a compact view of the current project goal,
|
|
176
|
-
blocker, pending checkpoints, recent decisions, and invocation counts.
|
|
177
|
-
`longtable hud --tmux` opens that view in a tmux pane.
|
|
178
|
-
|
|
179
178
|
`longtable team` creates a file-backed agent-team review under
|
|
180
179
|
`.longtable/team/<id>/`: independent review, cross-review, and
|
|
181
180
|
synthesis/checkpoint. Use it when roles should inspect each other's concerns
|
|
182
181
|
before LongTable proposes a researcher decision.
|
|
183
182
|
|
|
184
|
-
`longtable team --tmux` opens role-specific panes for live discussion. The panes
|
|
185
|
-
can add logs, but the file-backed team artifact remains the source of truth.
|
|
186
|
-
|
|
187
183
|
`longtable team --debate` creates a fixed five-round debate record under
|
|
188
184
|
`.longtable/team/<id>/`: independent review, cross-review, rebuttal,
|
|
189
|
-
convergence, and synthesis/checkpoint.
|
|
190
|
-
|
|
185
|
+
convergence, and synthesis/checkpoint. The file-backed artifact directory is
|
|
186
|
+
the source of truth.
|
|
191
187
|
|
|
192
188
|
See `docs/AGENT-TEAM-README.md` in the repository for a user-facing guide to
|
|
193
|
-
panel, team,
|
|
189
|
+
panel, team, and debate surfaces.
|
|
194
190
|
|
|
195
191
|
## Evidence And Search Direction
|
|
196
192
|
|
|
@@ -198,14 +194,20 @@ LongTable should not behave like a generic web scraper. Research search should
|
|
|
198
194
|
start from scholarly routes when the user needs literature discovery, citation
|
|
199
195
|
verification, publication metadata, or evidence-backed research decisions.
|
|
200
196
|
|
|
201
|
-
|
|
202
|
-
PubMed/NCBI, ERIC, DOAJ, and Unpaywall
|
|
203
|
-
|
|
204
|
-
reliable
|
|
197
|
+
`longtable search` routes research queries through arXiv, Crossref, OpenAlex,
|
|
198
|
+
Semantic Scholar, PubMed/NCBI, ERIC, DOAJ, and Unpaywall, then normalizes,
|
|
199
|
+
deduplicates, ranks, and labels results as evidence cards. Some sources work
|
|
200
|
+
without keys, some require a contact email, and some need API keys for reliable
|
|
201
|
+
use.
|
|
205
202
|
|
|
206
203
|
Citation support should be checked explicitly. A reference can be useful as
|
|
207
204
|
background while still failing to support the specific claim attached to it.
|
|
208
205
|
|
|
206
|
+
```bash
|
|
207
|
+
longtable search --query "trust calibration measurement" --intent measurement
|
|
208
|
+
longtable search --query "trust calibration citation support" --intent citation --record
|
|
209
|
+
```
|
|
210
|
+
|
|
209
211
|
See:
|
|
210
212
|
|
|
211
213
|
- [Research Search](https://github.com/HosungYou/LongTable/blob/main/docs/RESEARCH-SEARCH.md)
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
5
|
import { emitKeypressEvents } from "node:readline";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
-
import { stdin as input, stdout as output, cwd, exit } from "node:process";
|
|
7
|
+
import { stdin as input, stdout as output, cwd, env, exit } from "node:process";
|
|
8
8
|
import { dirname, join, resolve } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { classifyCheckpointTrigger } from "@longtable/checkpoints";
|
|
11
|
+
import { assessSearchSourceCapabilities, buildResearchSearchIntent, runResearchSearch } from "@longtable/search";
|
|
11
12
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
|
|
12
13
|
import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
|
|
13
14
|
import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
|
|
@@ -15,7 +16,7 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
15
16
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
16
17
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
17
18
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
18
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion,
|
|
19
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
19
20
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
20
21
|
const VALID_MODES = new Set([
|
|
21
22
|
"explore",
|
|
@@ -42,7 +43,7 @@ const ANSI = {
|
|
|
42
43
|
green: "\u001B[32m"
|
|
43
44
|
};
|
|
44
45
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
45
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
46
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.27";
|
|
46
47
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
47
48
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
48
49
|
function style(text, prefix) {
|
|
@@ -88,8 +89,9 @@ function usage() {
|
|
|
88
89
|
" longtable show [--json] [--path <file>]",
|
|
89
90
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
90
91
|
" longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
92
|
+
" 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] [--record] [--cwd <path>] [--json]",
|
|
91
93
|
" longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
|
|
92
|
-
" longtable team --prompt <text> [--role <role[,role]>] [--
|
|
94
|
+
" longtable team --prompt <text> [--role <role[,role]>] [--debate] [--rounds 3|5] [--cwd <path>] [--json]",
|
|
93
95
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
94
96
|
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
95
97
|
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
@@ -125,7 +127,7 @@ function parseArgs(argv) {
|
|
|
125
127
|
const values = {};
|
|
126
128
|
let subcommand = maybeSubcommand;
|
|
127
129
|
const modeCommand = command && VALID_MODES.has(command);
|
|
128
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "sentinel", "team"].includes(command);
|
|
130
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
129
131
|
let startIndex = 1;
|
|
130
132
|
if (modeCommand) {
|
|
131
133
|
subcommand = undefined;
|
|
@@ -622,12 +624,12 @@ function buildPermissionSetupChoices() {
|
|
|
622
624
|
{
|
|
623
625
|
id: "strong",
|
|
624
626
|
label: "Strong Researcher Checkpoint UI",
|
|
625
|
-
description: "Why:
|
|
627
|
+
description: "Why: makes Codex UI checkpoints the default for high-responsibility research decisions. What you get: MCP form checkpoints with a single decision field. Tradeoff: requires Codex MCP elicitation approval."
|
|
626
628
|
},
|
|
627
629
|
{
|
|
628
630
|
id: "interactive",
|
|
629
631
|
label: "Interactive checkpoint UI",
|
|
630
|
-
description: "Why: keeps UI prompts available for required checkpoints. What you get: MCP form checkpoints when supported. Tradeoff: requires Codex MCP elicitation approval."
|
|
632
|
+
description: "Why: keeps Codex UI prompts available for required checkpoints. What you get: MCP form checkpoints when supported. Tradeoff: requires Codex MCP elicitation approval."
|
|
631
633
|
},
|
|
632
634
|
{
|
|
633
635
|
id: "off",
|
|
@@ -768,7 +770,6 @@ async function runSetup(args) {
|
|
|
768
770
|
interventionPosture: effectiveIntervention,
|
|
769
771
|
checkpointUiMode: checkpointUi,
|
|
770
772
|
workspaceCreationPreference: workspacePreference,
|
|
771
|
-
tmuxMode: "standard",
|
|
772
773
|
teamMode: "panel"
|
|
773
774
|
};
|
|
774
775
|
if (surfaces === "skills_mcp_sentinel") {
|
|
@@ -1769,6 +1770,38 @@ function inferModeFromPrompt(prompt) {
|
|
|
1769
1770
|
}
|
|
1770
1771
|
return "explore";
|
|
1771
1772
|
}
|
|
1773
|
+
function inferCollaborationRoute(prompt) {
|
|
1774
|
+
const normalized = prompt.toLowerCase();
|
|
1775
|
+
const explicitDebate = /\bdebate\b|\bdebated\b|\brebuttal\b|\bconvergence\b|\bargue both sides\b/i.test(prompt) ||
|
|
1776
|
+
/토론|논쟁|반박|재반박|수렴/.test(prompt);
|
|
1777
|
+
if (explicitDebate) {
|
|
1778
|
+
return "debate";
|
|
1779
|
+
}
|
|
1780
|
+
const explicitTeam = /\bagent team\b|\bresearch team\b|\bteam review\b|\bteam-style\b|\buse a team\b/i.test(prompt) ||
|
|
1781
|
+
/에이전트\s*팀|연구\s*팀|팀\s*(리뷰|검토)|팀으로/.test(prompt);
|
|
1782
|
+
if (explicitTeam) {
|
|
1783
|
+
return "team";
|
|
1784
|
+
}
|
|
1785
|
+
const panelCue = /\bpanel\b|\bmulti[- ]?role\b|\bmultiple perspectives\b|\brole disagreement\b|\bdisagreement\b|\bconflict\b/i.test(prompt) ||
|
|
1786
|
+
/패널|여러\s*관점|복수\s*관점|역할.*불일치|불일치|충돌/.test(prompt);
|
|
1787
|
+
const multiPerspectiveCue = panelCue ||
|
|
1788
|
+
/\bperspectives?\b|\broles?\b|\breviewer and editor\b|\beditor and reviewer\b|\bmethods and measurement\b/i.test(prompt) ||
|
|
1789
|
+
/관점|역할|리뷰어.*에디터|에디터.*리뷰어|방법.*측정|측정.*방법/.test(prompt);
|
|
1790
|
+
if (!multiPerspectiveCue) {
|
|
1791
|
+
return null;
|
|
1792
|
+
}
|
|
1793
|
+
const trigger = classifyCheckpointTrigger(prompt, {});
|
|
1794
|
+
const highStakes = trigger.signal.artifactStakes === "external_submission" ||
|
|
1795
|
+
trigger.signal.artifactStakes === "study_protocol" ||
|
|
1796
|
+
trigger.requiresQuestionBeforeClosure;
|
|
1797
|
+
if (panelCue && trigger.signal.artifactStakes === "external_submission") {
|
|
1798
|
+
return "debate";
|
|
1799
|
+
}
|
|
1800
|
+
if (highStakes) {
|
|
1801
|
+
return "team";
|
|
1802
|
+
}
|
|
1803
|
+
return "panel";
|
|
1804
|
+
}
|
|
1772
1805
|
function parsePanelVisibility(value) {
|
|
1773
1806
|
if (value === "synthesis_only" ||
|
|
1774
1807
|
value === "show_on_conflict" ||
|
|
@@ -1820,6 +1853,9 @@ async function runModeCommand(mode, args) {
|
|
|
1820
1853
|
}
|
|
1821
1854
|
const setup = await loadOptionalSetup(typeof args.setup === "string" ? args.setup : undefined);
|
|
1822
1855
|
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
1856
|
+
if (projectContext) {
|
|
1857
|
+
await assertWorkspaceNotBlocked(projectContext);
|
|
1858
|
+
}
|
|
1823
1859
|
const projectAware = await buildProjectAwarePrompt(prompt, workingDirectory);
|
|
1824
1860
|
const panelPreference = setup?.profileSeed.panelPreference;
|
|
1825
1861
|
const panelRequested = args.panel === true ||
|
|
@@ -1917,6 +1953,150 @@ async function runPanelCommand(args) {
|
|
|
1917
1953
|
});
|
|
1918
1954
|
exit(exitCode);
|
|
1919
1955
|
}
|
|
1956
|
+
function parseLimit(value) {
|
|
1957
|
+
if (typeof value !== "string") {
|
|
1958
|
+
return undefined;
|
|
1959
|
+
}
|
|
1960
|
+
const parsed = Number(value);
|
|
1961
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1962
|
+
throw new Error(`Invalid search limit: ${value}`);
|
|
1963
|
+
}
|
|
1964
|
+
return parsed;
|
|
1965
|
+
}
|
|
1966
|
+
async function confirmPartialSearch(skippedSources) {
|
|
1967
|
+
if (!input.isTTY || !output.isTTY) {
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
const rl = createInterface({ input, output });
|
|
1971
|
+
try {
|
|
1972
|
+
console.log("Some scholarly sources are unavailable:");
|
|
1973
|
+
for (const source of skippedSources) {
|
|
1974
|
+
console.log(`- ${source.source}: ${source.reason ?? "unavailable"}`);
|
|
1975
|
+
}
|
|
1976
|
+
const answer = await rl.question("Continue with the available sources? [y/N] ");
|
|
1977
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1978
|
+
}
|
|
1979
|
+
finally {
|
|
1980
|
+
rl.close();
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
function renderEvidenceRunSummary(run, recordedPath) {
|
|
1984
|
+
const lines = [
|
|
1985
|
+
"LongTable Search",
|
|
1986
|
+
`- status: ${run.status}`,
|
|
1987
|
+
`- query: ${run.intent.query}`,
|
|
1988
|
+
`- intent: ${run.intent.kind}`,
|
|
1989
|
+
`- sources: ${run.sourceReports.map((report) => `${report.source}:${report.status}`).join(", ")}`
|
|
1990
|
+
];
|
|
1991
|
+
if (run.blockedReason) {
|
|
1992
|
+
lines.push(`- blocked: ${run.blockedReason}`);
|
|
1993
|
+
}
|
|
1994
|
+
if (recordedPath) {
|
|
1995
|
+
lines.push(`- recorded: ${recordedPath}`);
|
|
1996
|
+
}
|
|
1997
|
+
if (run.warnings.length > 0) {
|
|
1998
|
+
lines.push("- warnings:");
|
|
1999
|
+
for (const warning of run.warnings) {
|
|
2000
|
+
lines.push(` - ${warning}`);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
if (run.cards.length === 0) {
|
|
2004
|
+
lines.push("- cards: none");
|
|
2005
|
+
return lines.join("\n");
|
|
2006
|
+
}
|
|
2007
|
+
lines.push("- top evidence cards:");
|
|
2008
|
+
for (const card of run.cards.slice(0, 8)) {
|
|
2009
|
+
const identifiers = [
|
|
2010
|
+
card.doi ? `doi:${card.doi}` : "",
|
|
2011
|
+
card.pmid ? `pmid:${card.pmid}` : "",
|
|
2012
|
+
card.arxivId ? `arxiv:${card.arxivId}` : ""
|
|
2013
|
+
].filter(Boolean).join(", ");
|
|
2014
|
+
lines.push(` - ${card.title}`);
|
|
2015
|
+
lines.push(` score: ${card.relevanceScore}; support: ${card.citationSupportStatus}; depth: ${card.evidenceDepth}; sources: ${card.sourceRoutes.join(", ")}`);
|
|
2016
|
+
if (identifiers) {
|
|
2017
|
+
lines.push(` ids: ${identifiers}`);
|
|
2018
|
+
}
|
|
2019
|
+
if (card.url) {
|
|
2020
|
+
lines.push(` url: ${card.url}`);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
return lines.join("\n");
|
|
2024
|
+
}
|
|
2025
|
+
async function recordEvidenceRun(run, workingDirectory) {
|
|
2026
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2027
|
+
if (!context) {
|
|
2028
|
+
throw new Error("`longtable search --record` requires a LongTable project workspace. Run inside a project or pass --cwd.");
|
|
2029
|
+
}
|
|
2030
|
+
const evidenceDir = join(context.project.projectPath, ".longtable", "evidence");
|
|
2031
|
+
await mkdir(evidenceDir, { recursive: true });
|
|
2032
|
+
const evidencePath = join(evidenceDir, `${run.id}.json`);
|
|
2033
|
+
await writeJsonFile(evidencePath, run);
|
|
2034
|
+
const state = await loadWorkspaceState(context);
|
|
2035
|
+
state.workingState = {
|
|
2036
|
+
...state.workingState,
|
|
2037
|
+
recentEvidenceRun: {
|
|
2038
|
+
id: run.id,
|
|
2039
|
+
query: run.intent.query,
|
|
2040
|
+
intent: run.intent.kind,
|
|
2041
|
+
status: run.status,
|
|
2042
|
+
cardCount: run.cards.length,
|
|
2043
|
+
path: evidencePath
|
|
2044
|
+
}
|
|
2045
|
+
};
|
|
2046
|
+
state.artifactRecords.push({
|
|
2047
|
+
id: `artifact_${run.id}`,
|
|
2048
|
+
timestamp: run.createdAt,
|
|
2049
|
+
artifactType: "evidence_search",
|
|
2050
|
+
stakes: "internal_draft",
|
|
2051
|
+
source: "longtable search",
|
|
2052
|
+
location: evidencePath,
|
|
2053
|
+
provenanceSummary: `Scholar-first search for "${run.intent.query}" using ${run.intent.requestedSources.join(", ")}.`
|
|
2054
|
+
});
|
|
2055
|
+
await writeFile(context.stateFilePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
2056
|
+
await syncCurrentWorkspaceView(context);
|
|
2057
|
+
return evidencePath;
|
|
2058
|
+
}
|
|
2059
|
+
async function runSearch(args) {
|
|
2060
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2061
|
+
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
2062
|
+
const searchInput = {
|
|
2063
|
+
query: typeof args.query === "string" ? args.query : undefined,
|
|
2064
|
+
prompt: typeof args.prompt === "string" ? args.prompt : undefined,
|
|
2065
|
+
projectGoal: projectContext?.session.currentGoal,
|
|
2066
|
+
projectBlocker: projectContext?.session.currentBlocker,
|
|
2067
|
+
intent: typeof args.intent === "string" ? args.intent : undefined,
|
|
2068
|
+
field: typeof args.field === "string" ? args.field : undefined,
|
|
2069
|
+
must: typeof args.must === "string" ? args.must : undefined,
|
|
2070
|
+
exclude: typeof args.exclude === "string" ? args.exclude : undefined,
|
|
2071
|
+
sources: typeof args.source === "string" ? args.source : undefined,
|
|
2072
|
+
limit: parseLimit(args.limit),
|
|
2073
|
+
source: "cli"
|
|
2074
|
+
};
|
|
2075
|
+
const plannedIntent = buildResearchSearchIntent(searchInput);
|
|
2076
|
+
const skippedSources = assessSearchSourceCapabilities(plannedIntent.requestedSources, env)
|
|
2077
|
+
.filter((capability) => !capability.enabled);
|
|
2078
|
+
let allowPartial = args["allow-partial"] === true;
|
|
2079
|
+
if (skippedSources.length > 0 && !allowPartial) {
|
|
2080
|
+
allowPartial = await confirmPartialSearch(skippedSources);
|
|
2081
|
+
}
|
|
2082
|
+
const run = await runResearchSearch({
|
|
2083
|
+
...searchInput,
|
|
2084
|
+
env,
|
|
2085
|
+
allowPartial
|
|
2086
|
+
});
|
|
2087
|
+
let recordedPath;
|
|
2088
|
+
if (args.record === true && run.status !== "blocked") {
|
|
2089
|
+
recordedPath = await recordEvidenceRun(run, workingDirectory);
|
|
2090
|
+
}
|
|
2091
|
+
if (args.json === true) {
|
|
2092
|
+
console.log(JSON.stringify({
|
|
2093
|
+
run,
|
|
2094
|
+
files: recordedPath ? { evidence: recordedPath } : undefined
|
|
2095
|
+
}, null, 2));
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
console.log(renderEvidenceRunSummary(run, recordedPath));
|
|
2099
|
+
}
|
|
1920
2100
|
async function runQuestion(args) {
|
|
1921
2101
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
1922
2102
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
@@ -2019,9 +2199,9 @@ function questionRecordToChoices(record) {
|
|
|
2019
2199
|
: [])
|
|
2020
2200
|
];
|
|
2021
2201
|
}
|
|
2022
|
-
function
|
|
2202
|
+
function renderFollowUpQuestions(questions) {
|
|
2023
2203
|
if (questions.length === 0) {
|
|
2024
|
-
return "No new
|
|
2204
|
+
return "No new follow-up questions are pending for this prompt.";
|
|
2025
2205
|
}
|
|
2026
2206
|
const width = 44;
|
|
2027
2207
|
const boxLine = (text = "") => `│ ${text.padEnd(width, " ")} │`;
|
|
@@ -2072,13 +2252,13 @@ function renderClarificationCard(questions) {
|
|
|
2072
2252
|
lines.push("Answer in a terminal with `longtable clarify --prompt ...`, or record choices with `longtable decide --question <id> --answer <value>`.");
|
|
2073
2253
|
return lines.join("\n");
|
|
2074
2254
|
}
|
|
2075
|
-
async function
|
|
2255
|
+
async function answerFollowUpQuestionsInTerminal(context, questions, provider) {
|
|
2076
2256
|
if (questions.length === 0) {
|
|
2077
2257
|
return;
|
|
2078
2258
|
}
|
|
2079
2259
|
const rl = createInterface({ input, output });
|
|
2080
2260
|
try {
|
|
2081
|
-
console.log(renderBrandBanner("LongTable", "
|
|
2261
|
+
console.log(renderBrandBanner("LongTable", "Follow-up Questions"));
|
|
2082
2262
|
console.log("");
|
|
2083
2263
|
for (let index = 0; index < questions.length; index += 1) {
|
|
2084
2264
|
const question = questions[index];
|
|
@@ -2109,7 +2289,7 @@ async function runClarify(args) {
|
|
|
2109
2289
|
}
|
|
2110
2290
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
2111
2291
|
const required = args.required === true ? true : args.advisory === true ? false : undefined;
|
|
2112
|
-
const result = await
|
|
2292
|
+
const result = await createWorkspaceFollowUpQuestions({
|
|
2113
2293
|
context,
|
|
2114
2294
|
prompt,
|
|
2115
2295
|
provider,
|
|
@@ -2129,17 +2309,17 @@ async function runClarify(args) {
|
|
|
2129
2309
|
return;
|
|
2130
2310
|
}
|
|
2131
2311
|
if (args.print === true || !isInteractiveTerminal()) {
|
|
2132
|
-
console.log(
|
|
2312
|
+
console.log(renderFollowUpQuestions(result.questions));
|
|
2133
2313
|
return;
|
|
2134
2314
|
}
|
|
2135
|
-
await
|
|
2315
|
+
await answerFollowUpQuestionsInTerminal(context, result.questions, provider);
|
|
2136
2316
|
console.log("");
|
|
2137
|
-
console.log("LongTable
|
|
2317
|
+
console.log("LongTable follow-up decisions recorded");
|
|
2138
2318
|
console.log(`- answered: ${result.questions.length}`);
|
|
2139
2319
|
console.log(`- state: ${context.stateFilePath}`);
|
|
2140
2320
|
console.log(`- current: ${context.currentFilePath}`);
|
|
2141
2321
|
}
|
|
2142
|
-
async function
|
|
2322
|
+
async function runAutomaticFollowUpIfNeeded(prompt, args) {
|
|
2143
2323
|
if (args["no-clarify"] === true || args.print === true || args.json === true) {
|
|
2144
2324
|
return false;
|
|
2145
2325
|
}
|
|
@@ -2149,7 +2329,7 @@ async function runAutomaticClarificationIfNeeded(prompt, args) {
|
|
|
2149
2329
|
return false;
|
|
2150
2330
|
}
|
|
2151
2331
|
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
2152
|
-
const result = await
|
|
2332
|
+
const result = await createWorkspaceFollowUpQuestions({
|
|
2153
2333
|
context,
|
|
2154
2334
|
prompt,
|
|
2155
2335
|
provider,
|
|
@@ -2159,17 +2339,22 @@ async function runAutomaticClarificationIfNeeded(prompt, args) {
|
|
|
2159
2339
|
return false;
|
|
2160
2340
|
}
|
|
2161
2341
|
if (!isInteractiveTerminal()) {
|
|
2162
|
-
console.log(
|
|
2342
|
+
console.log(renderFollowUpQuestions(result.questions));
|
|
2163
2343
|
return true;
|
|
2164
2344
|
}
|
|
2165
|
-
await
|
|
2345
|
+
await answerFollowUpQuestionsInTerminal(context, result.questions, provider);
|
|
2166
2346
|
return false;
|
|
2167
2347
|
}
|
|
2168
2348
|
async function runAsk(args) {
|
|
2349
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2169
2350
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
2170
2351
|
if (!prompt) {
|
|
2171
2352
|
throw new Error("A prompt is required.");
|
|
2172
2353
|
}
|
|
2354
|
+
const projectContext = await loadProjectContextFromDirectory(workingDirectory);
|
|
2355
|
+
if (projectContext) {
|
|
2356
|
+
await assertWorkspaceNotBlocked(projectContext);
|
|
2357
|
+
}
|
|
2173
2358
|
const directive = parseInvocationDirective(prompt);
|
|
2174
2359
|
const effectivePrompt = directive.cleanedPrompt;
|
|
2175
2360
|
const inferred = directive.mode ?? inferModeFromPrompt(effectivePrompt);
|
|
@@ -2178,7 +2363,7 @@ async function runAsk(args) {
|
|
|
2178
2363
|
return;
|
|
2179
2364
|
}
|
|
2180
2365
|
const mode = inferred === "panel" ? "review" : inferred;
|
|
2181
|
-
if (await
|
|
2366
|
+
if (await runAutomaticFollowUpIfNeeded(effectivePrompt, args)) {
|
|
2182
2367
|
return;
|
|
2183
2368
|
}
|
|
2184
2369
|
const delegatedArgs = {
|
|
@@ -2188,7 +2373,18 @@ async function runAsk(args) {
|
|
|
2188
2373
|
if (directive.roles.length > 0 && typeof delegatedArgs.role !== "string") {
|
|
2189
2374
|
delegatedArgs.role = directive.roles.join(",");
|
|
2190
2375
|
}
|
|
2191
|
-
|
|
2376
|
+
const collaborationRoute = directive.collaboration ??
|
|
2377
|
+
(directive.panel || delegatedArgs.panel === true
|
|
2378
|
+
? "panel"
|
|
2379
|
+
: inferCollaborationRoute(effectivePrompt) ?? (inferred === "panel" ? "panel" : null));
|
|
2380
|
+
if (collaborationRoute === "team" || collaborationRoute === "debate") {
|
|
2381
|
+
await runTeam({
|
|
2382
|
+
...delegatedArgs,
|
|
2383
|
+
debate: collaborationRoute === "debate"
|
|
2384
|
+
});
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
if (collaborationRoute === "panel") {
|
|
2192
2388
|
await runPanelCommand({
|
|
2193
2389
|
...delegatedArgs,
|
|
2194
2390
|
visibility: "always_visible"
|
|
@@ -2200,9 +2396,6 @@ async function runAsk(args) {
|
|
|
2200
2396
|
function localId(prefix) {
|
|
2201
2397
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2202
2398
|
}
|
|
2203
|
-
function shellEscape(value) {
|
|
2204
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2205
|
-
}
|
|
2206
2399
|
async function writeJsonFile(path, value) {
|
|
2207
2400
|
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
2208
2401
|
}
|
|
@@ -2329,8 +2522,7 @@ async function runTeam(args) {
|
|
|
2329
2522
|
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2330
2523
|
provider: setup?.providerSelection.provider,
|
|
2331
2524
|
visibility: "always_visible",
|
|
2332
|
-
roundCount: rounds
|
|
2333
|
-
tmux: args.tmux === true
|
|
2525
|
+
roundCount: rounds
|
|
2334
2526
|
});
|
|
2335
2527
|
await writeTeamDebateArtifacts(debate, teamDir, prompt);
|
|
2336
2528
|
const canRecordWorkspace = projectAware.projectContextFound && projectContext && existsSync(projectContext.stateFilePath);
|
|
@@ -2354,40 +2546,6 @@ async function runTeam(args) {
|
|
|
2354
2546
|
}, null, 2));
|
|
2355
2547
|
return;
|
|
2356
2548
|
}
|
|
2357
|
-
if (args.tmux === true) {
|
|
2358
|
-
const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
|
|
2359
|
-
const shell = process.env.SHELL || "/bin/sh";
|
|
2360
|
-
const launcher = process.argv[1] ?? "longtable";
|
|
2361
|
-
const leaderCommand = [
|
|
2362
|
-
`echo ${shellEscape(`LongTable debate ${teamId}`)}`,
|
|
2363
|
-
`echo ${shellEscape(`Artifacts: ${teamDir}`)}`,
|
|
2364
|
-
`echo ${shellEscape("Fixed rounds are recorded. Role panes can add live review logs.")}`,
|
|
2365
|
-
`echo ${shellEscape(`Checkpoint: ${debate.questionRecord.id}`)}`,
|
|
2366
|
-
`exec ${shellEscape(shell)}`
|
|
2367
|
-
].join("; ");
|
|
2368
|
-
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2369
|
-
for (const member of debate.plan.members) {
|
|
2370
|
-
const rolePrompt = [
|
|
2371
|
-
`LongTable autonomous debate role: ${member.label} (${member.role}).`,
|
|
2372
|
-
"Use the fixed debate artifacts as the shared record. Add live notes only; do not answer the researcher checkpoint.",
|
|
2373
|
-
`Artifacts: ${teamDir}`,
|
|
2374
|
-
"",
|
|
2375
|
-
projectAware.prompt
|
|
2376
|
-
].join("\n");
|
|
2377
|
-
const logPath = join(teamDir, `${member.role}.debate.log`);
|
|
2378
|
-
const command = [
|
|
2379
|
-
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2380
|
-
`echo ${shellEscape(`Debate role log written to ${logPath}`)}`,
|
|
2381
|
-
`exec ${shellEscape(shell)}`
|
|
2382
|
-
].join("; ");
|
|
2383
|
-
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2384
|
-
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2385
|
-
}
|
|
2386
|
-
console.log(`LongTable tmux debate launched: ${sessionName}`);
|
|
2387
|
-
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2388
|
-
console.log(`Artifacts: ${teamDir}`);
|
|
2389
|
-
return;
|
|
2390
|
-
}
|
|
2391
2549
|
console.log(renderTeamDebateSummary(debate.run));
|
|
2392
2550
|
console.log(`- checkpoint: ${debate.questionRecord.id}`);
|
|
2393
2551
|
return;
|
|
@@ -2399,8 +2557,7 @@ async function runTeam(args) {
|
|
|
2399
2557
|
roleFlag: typeof args.role === "string" ? args.role : undefined,
|
|
2400
2558
|
provider: setup?.providerSelection.provider,
|
|
2401
2559
|
visibility: "always_visible",
|
|
2402
|
-
roundCount: rounds
|
|
2403
|
-
tmux: args.tmux === true
|
|
2560
|
+
roundCount: rounds
|
|
2404
2561
|
});
|
|
2405
2562
|
await writeTeamDebateArtifacts(team, teamDir, prompt);
|
|
2406
2563
|
const canRecordWorkspace = projectAware.projectContextFound && projectContext && existsSync(projectContext.stateFilePath);
|
|
@@ -2425,41 +2582,8 @@ async function runTeam(args) {
|
|
|
2425
2582
|
}, null, 2));
|
|
2426
2583
|
return;
|
|
2427
2584
|
}
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
console.log(`- checkpoint: ${team.questionRecord.id}`);
|
|
2431
|
-
return;
|
|
2432
|
-
}
|
|
2433
|
-
const sessionName = `longtable-${teamId.replaceAll("_", "-")}`;
|
|
2434
|
-
const shell = process.env.SHELL || "/bin/sh";
|
|
2435
|
-
const launcher = process.argv[1] ?? "longtable";
|
|
2436
|
-
const leaderCommand = [
|
|
2437
|
-
`echo ${shellEscape(`LongTable team ${teamId}`)}`,
|
|
2438
|
-
`echo ${shellEscape(`Artifacts: ${teamDir}`)}`,
|
|
2439
|
-
`echo ${shellEscape("Cross-review artifacts are recorded. Role panes can add live review logs.")}`,
|
|
2440
|
-
`echo ${shellEscape(`Checkpoint: ${team.questionRecord.id}`)}`,
|
|
2441
|
-
`exec ${shellEscape(shell)}`
|
|
2442
|
-
].join("; ");
|
|
2443
|
-
execFileSync("tmux", ["new-session", "-d", "-s", sessionName, "-c", workingDirectory, leaderCommand], { stdio: "inherit" });
|
|
2444
|
-
for (const member of team.plan.members) {
|
|
2445
|
-
const rolePrompt = [
|
|
2446
|
-
`LongTable team discussion role: ${member.label} (${member.role}).`,
|
|
2447
|
-
"Give claims, objections, open questions, and evidence needs. Address likely disagreement with other roles.",
|
|
2448
|
-
"",
|
|
2449
|
-
prompt
|
|
2450
|
-
].join("\n");
|
|
2451
|
-
const logPath = join(teamDir, `${member.role}.log`);
|
|
2452
|
-
const command = [
|
|
2453
|
-
`node ${shellEscape(launcher)} review --role ${shellEscape(member.role)} --prompt ${shellEscape(rolePrompt)} --cwd ${shellEscape(workingDirectory)} 2>&1 | tee ${shellEscape(logPath)}`,
|
|
2454
|
-
`echo ${shellEscape(`Role log written to ${logPath}`)}`,
|
|
2455
|
-
`exec ${shellEscape(shell)}`
|
|
2456
|
-
].join("; ");
|
|
2457
|
-
execFileSync("tmux", ["split-window", "-t", sessionName, "-c", workingDirectory, command], { stdio: "inherit" });
|
|
2458
|
-
execFileSync("tmux", ["select-layout", "-t", sessionName, "tiled"], { stdio: "ignore" });
|
|
2459
|
-
}
|
|
2460
|
-
console.log(`LongTable tmux team launched: ${sessionName}`);
|
|
2461
|
-
console.log(`Attach with: tmux attach -t ${sessionName}`);
|
|
2462
|
-
console.log(`Logs: ${teamDir}`);
|
|
2585
|
+
console.log(renderTeamDebateSummary(team.run));
|
|
2586
|
+
console.log(`- checkpoint: ${team.questionRecord.id}`);
|
|
2463
2587
|
}
|
|
2464
2588
|
async function runDecide(args) {
|
|
2465
2589
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
@@ -2782,6 +2906,10 @@ async function main() {
|
|
|
2782
2906
|
await runMcpSubcommand(subcommand, values);
|
|
2783
2907
|
return;
|
|
2784
2908
|
}
|
|
2909
|
+
if (command === "search") {
|
|
2910
|
+
await runSearch(values);
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2785
2913
|
if (command === "ask") {
|
|
2786
2914
|
await runAsk(values);
|
|
2787
2915
|
return;
|
package/dist/debate.d.ts
CHANGED
package/dist/debate.js
CHANGED
|
@@ -322,7 +322,7 @@ function buildTeamBundle(options, kind) {
|
|
|
322
322
|
prompt: options.prompt,
|
|
323
323
|
roles: plan.members,
|
|
324
324
|
status: "completed",
|
|
325
|
-
surface:
|
|
325
|
+
surface: "file_backed_debate",
|
|
326
326
|
interactionDepth: kind === "debate" ? "debated" : "cross_reviewed",
|
|
327
327
|
roundPolicy: kind === "debate" ? "fixed" : "team_cross_review",
|
|
328
328
|
roundCount,
|
|
@@ -371,9 +371,7 @@ function buildTeamBundle(options, kind) {
|
|
|
371
371
|
interactionDepth: run.interactionDepth,
|
|
372
372
|
panelPlan: plan,
|
|
373
373
|
teamDebateRun: run,
|
|
374
|
-
degradationReason:
|
|
375
|
-
? undefined
|
|
376
|
-
: "Tmux was not requested; file-backed debate artifacts are the canonical execution record."
|
|
374
|
+
degradationReason: "File-backed team artifacts are the canonical execution record."
|
|
377
375
|
};
|
|
378
376
|
return {
|
|
379
377
|
plan,
|
package/dist/persona-router.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface LongTableInvocationDirective {
|
|
|
12
12
|
explicit: boolean;
|
|
13
13
|
cleanedPrompt: string;
|
|
14
14
|
mode?: InteractionMode | "panel" | "status";
|
|
15
|
+
collaboration?: "panel" | "team" | "debate";
|
|
15
16
|
roles: CanonicalPersona[];
|
|
16
17
|
panel: boolean;
|
|
17
18
|
showConflicts: boolean;
|
package/dist/persona-router.js
CHANGED
|
@@ -7,7 +7,9 @@ const DIRECTIVE_MAP = [
|
|
|
7
7
|
{ key: "critique", mode: "critique" },
|
|
8
8
|
{ key: "draft", mode: "draft" },
|
|
9
9
|
{ key: "commit", mode: "commit" },
|
|
10
|
-
{ key: "panel", mode: "panel", panel: true, showConflicts: true },
|
|
10
|
+
{ key: "panel", mode: "panel", collaboration: "panel", panel: true, showConflicts: true },
|
|
11
|
+
{ key: "team", mode: "review", collaboration: "team", panel: true, showConflicts: true },
|
|
12
|
+
{ key: "debate", mode: "review", collaboration: "debate", panel: true, showConflicts: true },
|
|
11
13
|
{ key: "status", mode: "status" },
|
|
12
14
|
{ key: "editor", mode: "review", roles: ["editor"] },
|
|
13
15
|
{ key: "reviewer", mode: "review", roles: ["reviewer"] },
|
|
@@ -72,6 +74,7 @@ export function parseInvocationDirective(prompt) {
|
|
|
72
74
|
explicit: true,
|
|
73
75
|
cleanedPrompt: body || prompt,
|
|
74
76
|
mode: directive.mode,
|
|
77
|
+
collaboration: directive.collaboration,
|
|
75
78
|
roles: directive.roles ?? [],
|
|
76
79
|
panel: directive.panel === true,
|
|
77
80
|
showConflicts: directive.showConflicts === true
|
|
@@ -113,7 +113,7 @@ export declare function syncCurrentWorkspaceView(context: LongTableProjectContex
|
|
|
113
113
|
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<ResearchState>;
|
|
114
114
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
115
115
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
116
|
-
export declare function
|
|
116
|
+
export declare function createWorkspaceFollowUpQuestions(options: {
|
|
117
117
|
context: LongTableProjectContext;
|
|
118
118
|
prompt: string;
|
|
119
119
|
provider?: ProviderKind;
|
package/dist/project-session.js
CHANGED
|
@@ -647,10 +647,10 @@ function optionsForCheckpointTrigger(family, checkpointKey) {
|
|
|
647
647
|
function includesAny(prompt, patterns) {
|
|
648
648
|
return patterns.some((pattern) => pattern.test(prompt));
|
|
649
649
|
}
|
|
650
|
-
function
|
|
650
|
+
function followUpQuestionOptions(first, second, third, fourth) {
|
|
651
651
|
return [first, second, third, ...(fourth ? [fourth] : [])];
|
|
652
652
|
}
|
|
653
|
-
function
|
|
653
|
+
function buildFollowUpQuestionSpecs(prompt) {
|
|
654
654
|
const normalized = prompt.toLowerCase();
|
|
655
655
|
const specs = [];
|
|
656
656
|
function push(spec) {
|
|
@@ -664,7 +664,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
664
664
|
title: "Rubric update basis",
|
|
665
665
|
question: "How should LongTable use the available materials to update the rubric?",
|
|
666
666
|
whyNow: "Rubric updates can silently change grading criteria if LongTable guesses the calibration basis.",
|
|
667
|
-
options:
|
|
667
|
+
options: followUpQuestionOptions({ value: "calibrate_to_exemplars", label: "Calibrate criteria to exemplars", description: "Use strong submissions to refine what each criterion means.", recommended: true }, { value: "polish_existing", label: "Polish existing rubric only", description: "Keep criteria stable and improve wording or consistency." }, { value: "rewrite_structure", label: "Restructure the rubric", description: "Change categories or levels where the materials suggest a better structure." })
|
|
668
668
|
});
|
|
669
669
|
}
|
|
670
670
|
if (includesAny(normalized, [/\bexemplar\b/, /\bbest submission\b/, /\bselected submission\b/, /\bTA\b/i, /우수\s*답안|예시|선정|조교/])) {
|
|
@@ -673,7 +673,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
673
673
|
title: "Exemplar use",
|
|
674
674
|
question: "How should LongTable use selected exemplars or TA guidance?",
|
|
675
675
|
whyNow: "Exemplars can either calibrate criteria privately or become visible evidence inside the output.",
|
|
676
|
-
options:
|
|
676
|
+
options: followUpQuestionOptions({ value: "calibrate_only", label: "Use as private calibration", description: "Adjust criteria using exemplars without quoting them.", recommended: true }, { value: "include_deidentified_excerpts", label: "Include de-identified excerpts", description: "Add short anonymized examples where they clarify quality." }, { value: "separate_notes", label: "Keep examples in separate notes", description: "Use exemplars outside the main artifact." })
|
|
677
677
|
});
|
|
678
678
|
}
|
|
679
679
|
if (includesAny(normalized, [/\binstruction/, /\bguidance\b/, /\bsource\b/, /\bfile\b/, /\bdocx?\b/, /지침|가이드|문서|파일|자료/])) {
|
|
@@ -682,7 +682,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
682
682
|
title: "Source authority",
|
|
683
683
|
question: "If sources conflict or leave gaps, which source should LongTable privilege?",
|
|
684
684
|
whyNow: "Without an authority rule, LongTable may resolve conflicts by convenience rather than researcher intent.",
|
|
685
|
-
options:
|
|
685
|
+
options: followUpQuestionOptions({ value: "explicit_user_instruction", label: "Your explicit instruction", description: "Use the researcher's current instruction as the highest authority.", recommended: true }, { value: "project_files", label: "Project files", description: "Treat supplied files or existing artifacts as authoritative." }, { value: "external_guidance", label: "TA or external guidance", description: "Prioritize instructor, TA, venue, or policy guidance." })
|
|
686
686
|
});
|
|
687
687
|
}
|
|
688
688
|
if (includesAny(normalized, [/\bdeliver\b/, /\boutput\b/, /\btracked?[- ]?change/, /\bdocx?\b/, /\bmarkdown\b/, /\btable\b/, /전달|산출물|결과물|수정\s*표시|트랙|형식|포맷/])) {
|
|
@@ -691,7 +691,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
691
691
|
title: "Delivery format",
|
|
692
692
|
question: "How should LongTable deliver the clarified output?",
|
|
693
693
|
whyNow: "Format and change-tracking choices affect whether the result is usable for review or handoff.",
|
|
694
|
-
options:
|
|
694
|
+
options: followUpQuestionOptions({ value: "tracked_changes", label: "Tracked-change artifact", description: "Produce a reviewable changed version where possible.", recommended: true }, { value: "clean_final", label: "Clean final artifact", description: "Deliver the final version without change markup." }, { value: "summary_plus_artifact", label: "Summary plus artifact", description: "Include a concise change summary with the output." })
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
697
|
if (includesAny(normalized, [/\bupdate\b/, /\bchange\b/, /\bedit\b/, /\bfix\b/, /\bimplement\b/, /\bbuild\b/, /\bcreate\b/, /업데이트|수정|변경|구현|만들|고쳐/])) {
|
|
@@ -700,7 +700,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
700
700
|
title: "Autonomy boundary",
|
|
701
701
|
question: "How much should LongTable do before checking back with you?",
|
|
702
702
|
whyNow: "Execution requests can move from advice to authorship or artifact ownership unless the boundary is explicit.",
|
|
703
|
-
options:
|
|
703
|
+
options: followUpQuestionOptions({ value: "ask_then_act", label: "Clarify first, then act", description: "Ask needed questions before changing the artifact.", recommended: true }, { value: "act_with_defaults", label: "Act with visible defaults", description: "Proceed using recommended defaults and record them." }, { value: "recommend_only", label: "Recommend only", description: "Describe changes but do not alter artifacts." })
|
|
704
704
|
});
|
|
705
705
|
}
|
|
706
706
|
if (includesAny(normalized, [/\bperformance\b/, /\btest\b/, /\bevaluate\b/, /\bcheck\b/, /\bbenchmark\b/, /성능|테스트|평가|체크|검증/])) {
|
|
@@ -709,7 +709,7 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
709
709
|
title: "Evaluation target",
|
|
710
710
|
question: "What should LongTable treat as the main performance target?",
|
|
711
711
|
whyNow: "Performance checks can optimize for UX, correctness, trigger sensitivity, or delivery reliability.",
|
|
712
|
-
options:
|
|
712
|
+
options: followUpQuestionOptions({ value: "question_sensitivity", label: "Question sensitivity", description: "Check whether LongTable asks at the right knowledge-gap moments.", recommended: true }, { value: "renderer_convenience", label: "Renderer convenience", description: "Check whether the most convenient question UI is used." }, { value: "state_reliability", label: "State reliability", description: "Check whether questions and answers persist correctly." })
|
|
713
713
|
});
|
|
714
714
|
}
|
|
715
715
|
if (specs.length === 0) {
|
|
@@ -718,19 +718,19 @@ function buildClarificationQuestionSpecs(prompt) {
|
|
|
718
718
|
title: "Missing context",
|
|
719
719
|
question: "What should LongTable clarify before proceeding?",
|
|
720
720
|
whyNow: "The request can be answered in multiple ways, and choosing silently would hide a researcher judgment.",
|
|
721
|
-
options:
|
|
721
|
+
options: followUpQuestionOptions({ value: "scope", label: "Clarify scope first", description: "Ask what is included and excluded before acting.", recommended: true }, { value: "criteria", label: "Clarify success criteria", description: "Ask what would count as a good result." }, { value: "proceed", label: "Proceed with visible assumptions", description: "Continue, but make assumptions explicit." })
|
|
722
722
|
});
|
|
723
723
|
}
|
|
724
724
|
return specs;
|
|
725
725
|
}
|
|
726
|
-
const
|
|
727
|
-
function
|
|
728
|
-
return record.prompt.rationale.includes(`${
|
|
726
|
+
const FOLLOW_UP_PROMPT_PREFIX = "Follow-up prompt:";
|
|
727
|
+
function hasFollowUpPrompt(record, prompt) {
|
|
728
|
+
return record.prompt.rationale.includes(`${FOLLOW_UP_PROMPT_PREFIX} ${prompt}`);
|
|
729
729
|
}
|
|
730
|
-
export async function
|
|
730
|
+
export async function createWorkspaceFollowUpQuestions(options) {
|
|
731
731
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
732
732
|
if (!options.force) {
|
|
733
|
-
const existing = (state.questionLog ?? []).filter((record) =>
|
|
733
|
+
const existing = (state.questionLog ?? []).filter((record) => hasFollowUpPrompt(record, options.prompt));
|
|
734
734
|
const pending = existing.filter((record) => record.status === "pending");
|
|
735
735
|
if (pending.length > 0) {
|
|
736
736
|
return { questions: pending, state, created: false, alreadyAnswered: false };
|
|
@@ -743,14 +743,14 @@ export async function createWorkspaceClarificationCard(options) {
|
|
|
743
743
|
const preferredSurfaces = options.provider === "claude"
|
|
744
744
|
? ["native_structured", "terminal_selector", "numbered"]
|
|
745
745
|
: ["mcp_elicitation", "terminal_selector", "numbered"];
|
|
746
|
-
const questions =
|
|
746
|
+
const questions = buildFollowUpQuestionSpecs(options.prompt).map((spec) => ({
|
|
747
747
|
id: createId("question_record"),
|
|
748
748
|
createdAt,
|
|
749
749
|
updatedAt: createdAt,
|
|
750
750
|
status: "pending",
|
|
751
751
|
prompt: {
|
|
752
752
|
id: createId("question_prompt"),
|
|
753
|
-
checkpointKey: `
|
|
753
|
+
checkpointKey: `follow_up_${spec.key}`,
|
|
754
754
|
title: spec.title,
|
|
755
755
|
question: spec.question,
|
|
756
756
|
type: "single_choice",
|
|
@@ -761,7 +761,7 @@ export async function createWorkspaceClarificationCard(options) {
|
|
|
761
761
|
source: "runtime_guidance",
|
|
762
762
|
rationale: [
|
|
763
763
|
spec.whyNow,
|
|
764
|
-
`${
|
|
764
|
+
`${FOLLOW_UP_PROMPT_PREFIX} ${options.prompt}`
|
|
765
765
|
],
|
|
766
766
|
preferredSurfaces: preferredSurfaces
|
|
767
767
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@longtable/checkpoints": "0.1.
|
|
32
|
-
"@longtable/core": "0.1.
|
|
33
|
-
"@longtable/memory": "0.1.
|
|
34
|
-
"@longtable/provider-claude": "0.1.
|
|
35
|
-
"@longtable/provider-codex": "0.1.
|
|
36
|
-
"@longtable/
|
|
31
|
+
"@longtable/checkpoints": "0.1.27",
|
|
32
|
+
"@longtable/core": "0.1.27",
|
|
33
|
+
"@longtable/memory": "0.1.27",
|
|
34
|
+
"@longtable/provider-claude": "0.1.27",
|
|
35
|
+
"@longtable/provider-codex": "0.1.27",
|
|
36
|
+
"@longtable/search": "0.1.27",
|
|
37
|
+
"@longtable/setup": "0.1.27"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/node": "^22.10.1",
|