@longtable/cli 0.1.11 → 0.1.12
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/dist/cli.js +176 -2
- package/dist/project-session.d.ts +14 -1
- package/dist/project-session.js +128 -1
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodex
|
|
|
14
14
|
import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
|
|
15
15
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
16
16
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
17
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
17
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, createWorkspaceClarificationCard, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
18
18
|
const VALID_MODES = new Set([
|
|
19
19
|
"explore",
|
|
20
20
|
"review",
|
|
@@ -86,6 +86,7 @@ function usage() {
|
|
|
86
86
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
87
87
|
" longtable mcp install [--provider codex|claude|all] [--write] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
|
|
88
88
|
" longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
89
|
+
" longtable clarify --prompt <task-context> [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json] [--force]",
|
|
89
90
|
" longtable question --prompt <decision-context> [--title <text>] [--text <question>] [--provider codex|claude] [--required|--advisory] [--print] [--cwd <path>] [--json]",
|
|
90
91
|
" longtable panel [--prompt <text>] [--role <role[,role]>] [--mode review|critique|draft|commit] [--visibility synthesis_only|show_on_conflict|always_visible] [--print] [--json] [--setup <path>] [--cwd <path>]",
|
|
91
92
|
" longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
|
|
@@ -119,7 +120,7 @@ function parseArgs(argv) {
|
|
|
119
120
|
const values = {};
|
|
120
121
|
let subcommand = maybeSubcommand;
|
|
121
122
|
const modeCommand = command && VALID_MODES.has(command);
|
|
122
|
-
const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "question", "panel", "decide"].includes(command);
|
|
123
|
+
const directCommand = command && ["init", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide"].includes(command);
|
|
123
124
|
let startIndex = 1;
|
|
124
125
|
if (modeCommand) {
|
|
125
126
|
subcommand = undefined;
|
|
@@ -1536,6 +1537,172 @@ async function runQuestion(args) {
|
|
|
1536
1537
|
console.log(`- answer: longtable decide --question ${result.question.id} --answer <value>`);
|
|
1537
1538
|
console.log(`- current: ${context.currentFilePath}`);
|
|
1538
1539
|
}
|
|
1540
|
+
function isInteractiveTerminal() {
|
|
1541
|
+
return Boolean(input.isTTY && output.isTTY);
|
|
1542
|
+
}
|
|
1543
|
+
function questionRecordToChoices(record) {
|
|
1544
|
+
return [
|
|
1545
|
+
...record.prompt.options.map((option) => ({
|
|
1546
|
+
id: option.value,
|
|
1547
|
+
label: option.recommended ? `${option.label} (Recommended)` : option.label,
|
|
1548
|
+
description: option.description ?? "Select this option."
|
|
1549
|
+
})),
|
|
1550
|
+
...(record.prompt.allowOther
|
|
1551
|
+
? [{
|
|
1552
|
+
id: "other",
|
|
1553
|
+
label: record.prompt.otherLabel ?? "Other",
|
|
1554
|
+
description: "Type a custom answer.",
|
|
1555
|
+
fallbackToText: true
|
|
1556
|
+
}]
|
|
1557
|
+
: [])
|
|
1558
|
+
];
|
|
1559
|
+
}
|
|
1560
|
+
function renderClarificationCard(questions) {
|
|
1561
|
+
if (questions.length === 0) {
|
|
1562
|
+
return "No new clarification questions are pending for this prompt.";
|
|
1563
|
+
}
|
|
1564
|
+
const width = 44;
|
|
1565
|
+
const boxLine = (text = "") => `│ ${text.padEnd(width, " ")} │`;
|
|
1566
|
+
const wrap = (text) => {
|
|
1567
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
1568
|
+
const wrapped = [];
|
|
1569
|
+
let line = "";
|
|
1570
|
+
for (const word of words) {
|
|
1571
|
+
if (!line) {
|
|
1572
|
+
line = word;
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
if (`${line} ${word}`.length > width) {
|
|
1576
|
+
wrapped.push(line);
|
|
1577
|
+
line = word;
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
line = `${line} ${word}`;
|
|
1581
|
+
}
|
|
1582
|
+
if (line) {
|
|
1583
|
+
wrapped.push(line);
|
|
1584
|
+
}
|
|
1585
|
+
return wrapped.length > 0 ? wrapped : [""];
|
|
1586
|
+
};
|
|
1587
|
+
const lines = [
|
|
1588
|
+
"I want to make sure I handle this in the way you actually want, so here are the choices LongTable should not infer silently:",
|
|
1589
|
+
"",
|
|
1590
|
+
"┌──────────────────────────────────────────────┐"
|
|
1591
|
+
];
|
|
1592
|
+
for (const question of questions) {
|
|
1593
|
+
lines.push(boxLine(question.prompt.title));
|
|
1594
|
+
for (const line of wrap(question.prompt.question)) {
|
|
1595
|
+
lines.push(boxLine(line));
|
|
1596
|
+
}
|
|
1597
|
+
for (const option of question.prompt.options) {
|
|
1598
|
+
const suffix = option.recommended ? " (Recommended)" : "";
|
|
1599
|
+
for (const line of wrap(`- ${option.label}${suffix}`)) {
|
|
1600
|
+
lines.push(boxLine(line));
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (question.prompt.allowOther) {
|
|
1604
|
+
lines.push(boxLine(`- ${question.prompt.otherLabel ?? "Other"}`));
|
|
1605
|
+
}
|
|
1606
|
+
lines.push(boxLine());
|
|
1607
|
+
}
|
|
1608
|
+
lines.push("└──────────────────────────────────────────────┘");
|
|
1609
|
+
lines.push("");
|
|
1610
|
+
lines.push("Answer in a terminal with `longtable clarify --prompt ...`, or record choices with `longtable decide --question <id> --answer <value>`.");
|
|
1611
|
+
return lines.join("\n");
|
|
1612
|
+
}
|
|
1613
|
+
async function answerClarificationCardInTerminal(context, questions, provider) {
|
|
1614
|
+
if (questions.length === 0) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
const rl = createInterface({ input, output });
|
|
1618
|
+
try {
|
|
1619
|
+
console.log(renderBrandBanner("LongTable", "Clarification Card"));
|
|
1620
|
+
console.log("");
|
|
1621
|
+
for (let index = 0; index < questions.length; index += 1) {
|
|
1622
|
+
const question = questions[index];
|
|
1623
|
+
const prompt = renderQuestionHeader(index + 1, questions.length, question.prompt.title, question.prompt.question);
|
|
1624
|
+
const answer = await promptChoice(rl, prompt, questionRecordToChoices(question));
|
|
1625
|
+
await answerWorkspaceQuestion({
|
|
1626
|
+
context,
|
|
1627
|
+
questionId: question.id,
|
|
1628
|
+
answer,
|
|
1629
|
+
provider,
|
|
1630
|
+
surface: "terminal_selector"
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
finally {
|
|
1635
|
+
rl.close();
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
async function runClarify(args) {
|
|
1639
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
1640
|
+
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
1641
|
+
if (!prompt) {
|
|
1642
|
+
throw new Error("A task context is required. Pass --prompt <text>.");
|
|
1643
|
+
}
|
|
1644
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
1645
|
+
if (!context) {
|
|
1646
|
+
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
1647
|
+
}
|
|
1648
|
+
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
1649
|
+
const required = args.required === true ? true : args.advisory === true ? false : undefined;
|
|
1650
|
+
const result = await createWorkspaceClarificationCard({
|
|
1651
|
+
context,
|
|
1652
|
+
prompt,
|
|
1653
|
+
provider,
|
|
1654
|
+
required,
|
|
1655
|
+
force: args.force === true
|
|
1656
|
+
});
|
|
1657
|
+
if (args.json === true) {
|
|
1658
|
+
console.log(JSON.stringify({
|
|
1659
|
+
questions: result.questions,
|
|
1660
|
+
created: result.created,
|
|
1661
|
+
alreadyAnswered: result.alreadyAnswered,
|
|
1662
|
+
files: {
|
|
1663
|
+
state: context.stateFilePath,
|
|
1664
|
+
current: context.currentFilePath
|
|
1665
|
+
}
|
|
1666
|
+
}, null, 2));
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (args.print === true || !isInteractiveTerminal()) {
|
|
1670
|
+
console.log(renderClarificationCard(result.questions));
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
await answerClarificationCardInTerminal(context, result.questions, provider);
|
|
1674
|
+
console.log("");
|
|
1675
|
+
console.log("LongTable clarification decisions recorded");
|
|
1676
|
+
console.log(`- answered: ${result.questions.length}`);
|
|
1677
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
1678
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
1679
|
+
}
|
|
1680
|
+
async function runAutomaticClarificationIfNeeded(prompt, args) {
|
|
1681
|
+
if (args["no-clarify"] === true || args.print === true || args.json === true) {
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
1685
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
1686
|
+
if (!context) {
|
|
1687
|
+
return false;
|
|
1688
|
+
}
|
|
1689
|
+
const provider = args.provider === "claude" ? "claude" : args.provider === "codex" ? "codex" : undefined;
|
|
1690
|
+
const result = await createWorkspaceClarificationCard({
|
|
1691
|
+
context,
|
|
1692
|
+
prompt,
|
|
1693
|
+
provider,
|
|
1694
|
+
required: true
|
|
1695
|
+
});
|
|
1696
|
+
if (result.questions.length === 0) {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
if (!isInteractiveTerminal()) {
|
|
1700
|
+
console.log(renderClarificationCard(result.questions));
|
|
1701
|
+
return true;
|
|
1702
|
+
}
|
|
1703
|
+
await answerClarificationCardInTerminal(context, result.questions, provider);
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1539
1706
|
async function runAsk(args) {
|
|
1540
1707
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
1541
1708
|
if (!prompt) {
|
|
@@ -1549,6 +1716,9 @@ async function runAsk(args) {
|
|
|
1549
1716
|
return;
|
|
1550
1717
|
}
|
|
1551
1718
|
const mode = inferred === "panel" ? "review" : inferred;
|
|
1719
|
+
if (await runAutomaticClarificationIfNeeded(effectivePrompt, args)) {
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1552
1722
|
const delegatedArgs = {
|
|
1553
1723
|
...args,
|
|
1554
1724
|
prompt: effectivePrompt
|
|
@@ -1883,6 +2053,10 @@ async function main() {
|
|
|
1883
2053
|
await runAsk(values);
|
|
1884
2054
|
return;
|
|
1885
2055
|
}
|
|
2056
|
+
if (command === "clarify") {
|
|
2057
|
+
await runClarify(values);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
1886
2060
|
if (command === "question") {
|
|
1887
2061
|
await runQuestion(values);
|
|
1888
2062
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, InvocationRecord, ProviderKind, QuestionRecord, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, InvocationRecord, ProviderKind, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
2
2
|
import type { SetupPersistedOutput } from "@longtable/setup";
|
|
3
3
|
export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
4
4
|
export interface LongTableProjectRecord {
|
|
@@ -104,6 +104,18 @@ export declare function syncCurrentWorkspaceView(context: LongTableProjectContex
|
|
|
104
104
|
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<ResearchState>;
|
|
105
105
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
106
106
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
107
|
+
export declare function createWorkspaceClarificationCard(options: {
|
|
108
|
+
context: LongTableProjectContext;
|
|
109
|
+
prompt: string;
|
|
110
|
+
provider?: ProviderKind;
|
|
111
|
+
required?: boolean;
|
|
112
|
+
force?: boolean;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
questions: QuestionRecord[];
|
|
115
|
+
state: ResearchState;
|
|
116
|
+
created: boolean;
|
|
117
|
+
alreadyAnswered: boolean;
|
|
118
|
+
}>;
|
|
107
119
|
export declare function createWorkspaceQuestion(options: {
|
|
108
120
|
context: LongTableProjectContext;
|
|
109
121
|
prompt: string;
|
|
@@ -121,6 +133,7 @@ export declare function answerWorkspaceQuestion(options: {
|
|
|
121
133
|
answer: string;
|
|
122
134
|
rationale?: string;
|
|
123
135
|
provider?: "codex" | "claude";
|
|
136
|
+
surface?: QuestionSurface;
|
|
124
137
|
}): Promise<{
|
|
125
138
|
question: QuestionRecord;
|
|
126
139
|
decision: DecisionRecord;
|
package/dist/project-session.js
CHANGED
|
@@ -489,6 +489,133 @@ function optionsForCheckpointFamily(family) {
|
|
|
489
489
|
{ value: "defer", label: "Keep this open", description: "Do not commit yet; keep the issue visible as an open tension." }
|
|
490
490
|
];
|
|
491
491
|
}
|
|
492
|
+
function includesAny(prompt, patterns) {
|
|
493
|
+
return patterns.some((pattern) => pattern.test(prompt));
|
|
494
|
+
}
|
|
495
|
+
function clarificationOptions(first, second, third, fourth) {
|
|
496
|
+
return [first, second, third, ...(fourth ? [fourth] : [])];
|
|
497
|
+
}
|
|
498
|
+
function buildClarificationQuestionSpecs(prompt) {
|
|
499
|
+
const normalized = prompt.toLowerCase();
|
|
500
|
+
const specs = [];
|
|
501
|
+
function push(spec) {
|
|
502
|
+
if (!specs.some((candidate) => candidate.key === spec.key)) {
|
|
503
|
+
specs.push(spec);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (includesAny(normalized, [/\brubrics?\b/, /루브릭|채점기준/])) {
|
|
507
|
+
push({
|
|
508
|
+
key: "rubric_update_basis",
|
|
509
|
+
title: "Rubric update basis",
|
|
510
|
+
question: "How should LongTable use the available materials to update the rubric?",
|
|
511
|
+
whyNow: "Rubric updates can silently change grading criteria if LongTable guesses the calibration basis.",
|
|
512
|
+
options: clarificationOptions({ 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." })
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (includesAny(normalized, [/\bexemplar\b/, /\bbest submission\b/, /\bselected submission\b/, /\bTA\b/i, /우수\s*답안|예시|선정|조교/])) {
|
|
516
|
+
push({
|
|
517
|
+
key: "exemplar_use",
|
|
518
|
+
title: "Exemplar use",
|
|
519
|
+
question: "How should LongTable use selected exemplars or TA guidance?",
|
|
520
|
+
whyNow: "Exemplars can either calibrate criteria privately or become visible evidence inside the output.",
|
|
521
|
+
options: clarificationOptions({ 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." })
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
if (includesAny(normalized, [/\binstruction/, /\bguidance\b/, /\bsource\b/, /\bfile\b/, /\bdocx?\b/, /지침|가이드|문서|파일|자료/])) {
|
|
525
|
+
push({
|
|
526
|
+
key: "source_authority",
|
|
527
|
+
title: "Source authority",
|
|
528
|
+
question: "If sources conflict or leave gaps, which source should LongTable privilege?",
|
|
529
|
+
whyNow: "Without an authority rule, LongTable may resolve conflicts by convenience rather than researcher intent.",
|
|
530
|
+
options: clarificationOptions({ 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." })
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (includesAny(normalized, [/\bdeliver\b/, /\boutput\b/, /\btracked?[- ]?change/, /\bdocx?\b/, /\bmarkdown\b/, /\btable\b/, /전달|산출물|결과물|수정\s*표시|트랙|형식|포맷/])) {
|
|
534
|
+
push({
|
|
535
|
+
key: "delivery_format",
|
|
536
|
+
title: "Delivery format",
|
|
537
|
+
question: "How should LongTable deliver the clarified output?",
|
|
538
|
+
whyNow: "Format and change-tracking choices affect whether the result is usable for review or handoff.",
|
|
539
|
+
options: clarificationOptions({ 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." })
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
if (includesAny(normalized, [/\bupdate\b/, /\bchange\b/, /\bedit\b/, /\bfix\b/, /\bimplement\b/, /\bbuild\b/, /\bcreate\b/, /업데이트|수정|변경|구현|만들|고쳐/])) {
|
|
543
|
+
push({
|
|
544
|
+
key: "autonomy_boundary",
|
|
545
|
+
title: "Autonomy boundary",
|
|
546
|
+
question: "How much should LongTable do before checking back with you?",
|
|
547
|
+
whyNow: "Execution requests can move from advice to authorship or artifact ownership unless the boundary is explicit.",
|
|
548
|
+
options: clarificationOptions({ 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." })
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (includesAny(normalized, [/\bperformance\b/, /\btest\b/, /\bevaluate\b/, /\bcheck\b/, /\bbenchmark\b/, /성능|테스트|평가|체크|검증/])) {
|
|
552
|
+
push({
|
|
553
|
+
key: "evaluation_target",
|
|
554
|
+
title: "Evaluation target",
|
|
555
|
+
question: "What should LongTable treat as the main performance target?",
|
|
556
|
+
whyNow: "Performance checks can optimize for UX, correctness, trigger sensitivity, or delivery reliability.",
|
|
557
|
+
options: clarificationOptions({ 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." })
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
if (specs.length === 0) {
|
|
561
|
+
push({
|
|
562
|
+
key: "general_missing_context",
|
|
563
|
+
title: "Missing context",
|
|
564
|
+
question: "What should LongTable clarify before proceeding?",
|
|
565
|
+
whyNow: "The request can be answered in multiple ways, and choosing silently would hide a researcher judgment.",
|
|
566
|
+
options: clarificationOptions({ 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." })
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return specs;
|
|
570
|
+
}
|
|
571
|
+
const CLARIFICATION_PROMPT_PREFIX = "Clarification prompt:";
|
|
572
|
+
function hasClarificationPrompt(record, prompt) {
|
|
573
|
+
return record.prompt.rationale.includes(`${CLARIFICATION_PROMPT_PREFIX} ${prompt}`);
|
|
574
|
+
}
|
|
575
|
+
export async function createWorkspaceClarificationCard(options) {
|
|
576
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
577
|
+
if (!options.force) {
|
|
578
|
+
const existing = (state.questionLog ?? []).filter((record) => hasClarificationPrompt(record, options.prompt));
|
|
579
|
+
const pending = existing.filter((record) => record.status === "pending");
|
|
580
|
+
if (pending.length > 0) {
|
|
581
|
+
return { questions: pending, state, created: false, alreadyAnswered: false };
|
|
582
|
+
}
|
|
583
|
+
if (existing.some((record) => record.status === "answered")) {
|
|
584
|
+
return { questions: [], state, created: false, alreadyAnswered: true };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const createdAt = nowIso();
|
|
588
|
+
const preferredSurfaces = options.provider === "claude"
|
|
589
|
+
? ["native_structured", "terminal_selector", "numbered"]
|
|
590
|
+
: ["terminal_selector", "numbered", "native_structured"];
|
|
591
|
+
const questions = buildClarificationQuestionSpecs(options.prompt).map((spec) => ({
|
|
592
|
+
id: createId("question_record"),
|
|
593
|
+
createdAt,
|
|
594
|
+
updatedAt: createdAt,
|
|
595
|
+
status: "pending",
|
|
596
|
+
prompt: {
|
|
597
|
+
id: createId("question_prompt"),
|
|
598
|
+
checkpointKey: `clarification_${spec.key}`,
|
|
599
|
+
title: spec.title,
|
|
600
|
+
question: spec.question,
|
|
601
|
+
type: "single_choice",
|
|
602
|
+
options: spec.options,
|
|
603
|
+
allowOther: true,
|
|
604
|
+
otherLabel: "Other",
|
|
605
|
+
required: options.required ?? true,
|
|
606
|
+
source: "runtime_guidance",
|
|
607
|
+
rationale: [
|
|
608
|
+
spec.whyNow,
|
|
609
|
+
`${CLARIFICATION_PROMPT_PREFIX} ${options.prompt}`
|
|
610
|
+
],
|
|
611
|
+
preferredSurfaces: preferredSurfaces
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
const updated = appendQuestionRecords(state, questions);
|
|
615
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
616
|
+
await syncCurrentWorkspaceView(options.context);
|
|
617
|
+
return { questions, state: updated, created: true, alreadyAnswered: false };
|
|
618
|
+
}
|
|
492
619
|
export async function createWorkspaceQuestion(options) {
|
|
493
620
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
494
621
|
const trigger = classifyCheckpointTrigger(options.prompt, {
|
|
@@ -560,7 +687,7 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
560
687
|
...(option || explicitOther ? {} : { otherText: options.answer }),
|
|
561
688
|
...(options.rationale ? { rationale: options.rationale } : {}),
|
|
562
689
|
...(options.provider ? { provider: options.provider } : {}),
|
|
563
|
-
surface: options.provider === "claude" ? "native_structured" : "numbered"
|
|
690
|
+
surface: options.surface ?? (options.provider === "claude" ? "native_structured" : "numbered")
|
|
564
691
|
};
|
|
565
692
|
const timestamp = nowIso();
|
|
566
693
|
const decision = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,12 @@
|
|
|
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/setup": "0.1.
|
|
31
|
+
"@longtable/checkpoints": "0.1.12",
|
|
32
|
+
"@longtable/core": "0.1.12",
|
|
33
|
+
"@longtable/memory": "0.1.12",
|
|
34
|
+
"@longtable/provider-claude": "0.1.12",
|
|
35
|
+
"@longtable/provider-codex": "0.1.12",
|
|
36
|
+
"@longtable/setup": "0.1.12"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.1",
|