@longtable/cli 0.1.12 → 0.1.13
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 +35 -0
- package/dist/project-session.d.ts +6 -0
- package/dist/project-session.js +165 -10
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -1075,6 +1075,15 @@ function renderDoctorStatus(status) {
|
|
|
1075
1075
|
lines.push(` - ${question.id}: ${question.question} (${question.options.join("/")})`);
|
|
1076
1076
|
}
|
|
1077
1077
|
}
|
|
1078
|
+
if ((workspace.answerWarnings ?? []).length > 0) {
|
|
1079
|
+
lines.push("- answer warnings:");
|
|
1080
|
+
for (const warning of workspace.answerWarnings ?? []) {
|
|
1081
|
+
lines.push(` - ${warning.questionId}: ${warning.issue}`);
|
|
1082
|
+
if (warning.suggestion) {
|
|
1083
|
+
lines.push(` ${warning.suggestion}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1078
1087
|
}
|
|
1079
1088
|
const nextActions = [];
|
|
1080
1089
|
const canFix = status.providers.codex.missingSkills.length > 0 ||
|
|
@@ -1525,6 +1534,32 @@ async function runQuestion(args) {
|
|
|
1525
1534
|
}
|
|
1526
1535
|
return;
|
|
1527
1536
|
}
|
|
1537
|
+
if (isInteractiveTerminal()) {
|
|
1538
|
+
const rl = createInterface({ input, output });
|
|
1539
|
+
try {
|
|
1540
|
+
console.log(renderBrandBanner("LongTable", "Researcher Checkpoint"));
|
|
1541
|
+
console.log("");
|
|
1542
|
+
const answer = await promptChoice(rl, renderQuestionHeader(1, 1, result.question.prompt.title, result.question.prompt.question), questionRecordToChoices(result.question));
|
|
1543
|
+
const decision = await answerWorkspaceQuestion({
|
|
1544
|
+
context,
|
|
1545
|
+
questionId: result.question.id,
|
|
1546
|
+
answer,
|
|
1547
|
+
provider,
|
|
1548
|
+
surface: "terminal_selector"
|
|
1549
|
+
});
|
|
1550
|
+
console.log("");
|
|
1551
|
+
console.log("LongTable checkpoint decision recorded");
|
|
1552
|
+
console.log(`- question: ${decision.question.id}`);
|
|
1553
|
+
console.log(`- decision: ${decision.decision.id}`);
|
|
1554
|
+
console.log(`- answer: ${decision.decision.selectedOption ?? answer}`);
|
|
1555
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
1556
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
finally {
|
|
1560
|
+
rl.close();
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1528
1563
|
const optionValues = [
|
|
1529
1564
|
...result.question.prompt.options.map((option) => option.value),
|
|
1530
1565
|
...(result.question.prompt.allowOther ? ["other"] : [])
|
|
@@ -98,6 +98,12 @@ export interface LongTableWorkspaceInspection {
|
|
|
98
98
|
selectedOption?: string;
|
|
99
99
|
timestamp: string;
|
|
100
100
|
}>;
|
|
101
|
+
answerWarnings?: Array<{
|
|
102
|
+
questionId: string;
|
|
103
|
+
decisionRecordId?: string;
|
|
104
|
+
issue: string;
|
|
105
|
+
suggestion?: string;
|
|
106
|
+
}>;
|
|
101
107
|
}
|
|
102
108
|
export declare function loadWorkspaceState(context: LongTableProjectContext): Promise<ResearchState>;
|
|
103
109
|
export declare function syncCurrentWorkspaceView(context: LongTableProjectContext): Promise<string>;
|
package/dist/project-session.js
CHANGED
|
@@ -280,7 +280,23 @@ function summarizeWorkspaceInspection(context, state) {
|
|
|
280
280
|
summary: record.summary,
|
|
281
281
|
...(record.selectedOption ? { selectedOption: record.selectedOption } : {}),
|
|
282
282
|
timestamp: record.timestamp
|
|
283
|
-
}))
|
|
283
|
+
})),
|
|
284
|
+
answerWarnings: questions
|
|
285
|
+
.filter((record) => record.status === "answered" && record.answer?.selectedValues.includes("other"))
|
|
286
|
+
.flatMap((record) => {
|
|
287
|
+
const raw = record.answer?.otherText ?? record.answer?.selectedLabels[0] ?? "";
|
|
288
|
+
if (!/^\d+$/.test(raw.trim())) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
const index = Number(raw.trim()) - 1;
|
|
292
|
+
const option = record.prompt.options[index];
|
|
293
|
+
return [{
|
|
294
|
+
questionId: record.id,
|
|
295
|
+
...(record.decisionRecordId ? { decisionRecordId: record.decisionRecordId } : {}),
|
|
296
|
+
issue: `Numeric answer "${raw.trim()}" was stored as other text.`,
|
|
297
|
+
...(option ? { suggestion: `Use "${option.value}" (${option.label}) for this checkpoint option.` } : {})
|
|
298
|
+
}];
|
|
299
|
+
})
|
|
284
300
|
};
|
|
285
301
|
}
|
|
286
302
|
function buildProjectAgentsMd(project, session) {
|
|
@@ -457,7 +473,7 @@ function questionTextForCheckpoint(family, prompt) {
|
|
|
457
473
|
return `What should LongTable decide before proceeding with: ${prompt}`;
|
|
458
474
|
}
|
|
459
475
|
}
|
|
460
|
-
function
|
|
476
|
+
function optionsForCheckpointTrigger(family, checkpointKey) {
|
|
461
477
|
if (family === "evidence") {
|
|
462
478
|
return [
|
|
463
479
|
{ value: "verify", label: "Verify evidence first", description: "Check whether the source supports the specific claim." },
|
|
@@ -482,6 +498,72 @@ function optionsForCheckpointFamily(family) {
|
|
|
482
498
|
{ value: "defer", label: "Do not submit yet", description: "Keep the submission decision open." }
|
|
483
499
|
];
|
|
484
500
|
}
|
|
501
|
+
if (family === "authorship") {
|
|
502
|
+
return [
|
|
503
|
+
{ value: "preserve_voice", label: "Preserve the researcher's voice", description: "Keep the current authorship trace visible before rewriting or smoothing." },
|
|
504
|
+
{ value: "revise_with_trace", label: "Revise with an explicit authorship trace", description: "Change the text, but record what came from the researcher." },
|
|
505
|
+
{ value: "ask_researcher", label: "Ask the researcher for wording first", description: "Do not infer the intended voice or narrative stance." },
|
|
506
|
+
{ value: "defer", label: "Keep authorship open", description: "Do not settle the voice or authorship decision yet." }
|
|
507
|
+
];
|
|
508
|
+
}
|
|
509
|
+
if (family === "exploration") {
|
|
510
|
+
return [
|
|
511
|
+
{ value: "surface_tensions", label: "Surface tensions first", description: "Ask what is unresolved before narrowing the project." },
|
|
512
|
+
{ value: "narrow_scope", label: "Narrow the research scope", description: "Move toward a smaller question while keeping the choice visible." },
|
|
513
|
+
{ value: "gather_context", label: "Gather context before narrowing", description: "Check materials, constraints, or evidence before choosing a direction." },
|
|
514
|
+
{ value: "defer", label: "Keep exploration open", description: "Do not collapse the problem space yet." }
|
|
515
|
+
];
|
|
516
|
+
}
|
|
517
|
+
if (family === "review") {
|
|
518
|
+
return [
|
|
519
|
+
{ value: "revise", label: "Revise before accepting the review", description: "Change the claim, design, or draft before treating the critique as resolved." },
|
|
520
|
+
{ value: "evidence", label: "Check evidence for the objection", description: "Verify whether the review concern is actually supported." },
|
|
521
|
+
{ value: "proceed", label: "Proceed while logging the risk", description: "Accept the objection profile and continue with the decision recorded." },
|
|
522
|
+
{ value: "defer", label: "Keep the objection open", description: "Do not convert the review into closure yet." }
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
if (family === "commitment") {
|
|
526
|
+
if (checkpointKey === "research_question_freeze") {
|
|
527
|
+
return [
|
|
528
|
+
{ value: "revise", label: "Revise the research question", description: "Change the framing before treating the question as settled." },
|
|
529
|
+
{ value: "scope", label: "Choose the scope boundary", description: "Commit only the boundary, not the full study design." },
|
|
530
|
+
{ value: "evidence", label: "Gather support before freezing", description: "Check literature, feasibility, or data fit before locking the question." },
|
|
531
|
+
{ value: "defer", label: "Keep the question open", description: "Do not freeze the research question yet." }
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
if (checkpointKey === "theory_selection") {
|
|
535
|
+
return [
|
|
536
|
+
{ value: "revise", label: "Revise the theory anchor", description: "Change the conceptual frame before treating it as settled." },
|
|
537
|
+
{ value: "compare", label: "Compare candidate theories first", description: "Keep alternatives visible before choosing one anchor." },
|
|
538
|
+
{ value: "evidence", label: "Check construct fit first", description: "Verify that the theory supports the constructs and claims." },
|
|
539
|
+
{ value: "defer", label: "Keep theory selection open", description: "Do not commit to a theory anchor yet." }
|
|
540
|
+
];
|
|
541
|
+
}
|
|
542
|
+
if (checkpointKey === "method_design_commitment") {
|
|
543
|
+
return [
|
|
544
|
+
{ value: "revise", label: "Revise the study design", description: "Change method, sample, or design before treating it as settled." },
|
|
545
|
+
{ value: "ethics", label: "Check participant and ethics implications", description: "Pause for consent, representation, or trust concerns." },
|
|
546
|
+
{ value: "evidence", label: "Check feasibility and evidence first", description: "Verify that the method can support the intended claims." },
|
|
547
|
+
{ value: "defer", label: "Keep method design open", description: "Do not commit the design yet." }
|
|
548
|
+
];
|
|
549
|
+
}
|
|
550
|
+
if (checkpointKey === "measurement_validity") {
|
|
551
|
+
return [
|
|
552
|
+
{ value: "revise", label: "Revise the measurement plan", description: "Change scales, constructs, or instruments before treating them as settled." },
|
|
553
|
+
{ value: "evidence", label: "Verify construct validity first", description: "Check whether the instrument supports the construct." },
|
|
554
|
+
{ value: "pilot", label: "Pilot or inspect the measure", description: "Gather local evidence before committing the measurement." },
|
|
555
|
+
{ value: "defer", label: "Keep measurement open", description: "Do not settle the measurement plan yet." }
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
if (checkpointKey === "analysis_plan") {
|
|
559
|
+
return [
|
|
560
|
+
{ value: "revise", label: "Revise the analysis plan", description: "Change model, coding, or inference strategy before committing." },
|
|
561
|
+
{ value: "assumptions", label: "Check assumptions first", description: "Inspect data, model assumptions, or coding validity before closure." },
|
|
562
|
+
{ value: "evidence", label: "Verify analysis fit", description: "Confirm the analysis can answer the research question." },
|
|
563
|
+
{ value: "defer", label: "Keep analysis open", description: "Do not commit the analysis plan yet." }
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
485
567
|
return [
|
|
486
568
|
{ value: "revise", label: "Revise before proceeding", description: "Change the framing, design, or draft before treating this as settled." },
|
|
487
569
|
{ value: "evidence", label: "Gather or verify evidence first", description: "Do not proceed until the relevant evidence is checked." },
|
|
@@ -634,7 +716,7 @@ export async function createWorkspaceQuestion(options) {
|
|
|
634
716
|
title: options.title ?? questionTitleForCheckpoint(trigger.family),
|
|
635
717
|
question: options.question ?? questionTextForCheckpoint(trigger.family, options.prompt),
|
|
636
718
|
type: "single_choice",
|
|
637
|
-
options:
|
|
719
|
+
options: optionsForCheckpointTrigger(trigger.family, trigger.signal.checkpointKey),
|
|
638
720
|
allowOther: true,
|
|
639
721
|
otherLabel: "Other decision",
|
|
640
722
|
required: options.required ?? trigger.requiresQuestionBeforeClosure,
|
|
@@ -672,20 +754,93 @@ function updateInvocationWithDecision(invocation, questionId, decisionId) {
|
|
|
672
754
|
}
|
|
673
755
|
};
|
|
674
756
|
}
|
|
757
|
+
function normalizeAnswerToken(value) {
|
|
758
|
+
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
759
|
+
}
|
|
760
|
+
function optionAnswerCandidates(option) {
|
|
761
|
+
return [
|
|
762
|
+
option.value,
|
|
763
|
+
option.label,
|
|
764
|
+
...(option.description
|
|
765
|
+
? [
|
|
766
|
+
`${option.label} - ${option.description}`,
|
|
767
|
+
`${option.label} — ${option.description}`
|
|
768
|
+
]
|
|
769
|
+
: [])
|
|
770
|
+
].map(normalizeAnswerToken);
|
|
771
|
+
}
|
|
772
|
+
function splitAnswerAndRationale(rawAnswer) {
|
|
773
|
+
const [firstLine = "", ...restLines] = rawAnswer.trim().split(/\r?\n/);
|
|
774
|
+
const rationale = restLines.join("\n").trim();
|
|
775
|
+
return {
|
|
776
|
+
selection: firstLine.trim(),
|
|
777
|
+
...(rationale ? { rationale } : {})
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function normalizeQuestionAnswerSelection(question, rawAnswer) {
|
|
781
|
+
const trimmed = rawAnswer.trim();
|
|
782
|
+
const { selection, rationale } = splitAnswerAndRationale(trimmed);
|
|
783
|
+
const numeric = Number(selection);
|
|
784
|
+
if (/^\d+$/.test(selection) && Number.isInteger(numeric)) {
|
|
785
|
+
const option = question.prompt.options[numeric - 1];
|
|
786
|
+
if (option) {
|
|
787
|
+
return {
|
|
788
|
+
selectedValue: option.value,
|
|
789
|
+
selectedLabel: option.label,
|
|
790
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
if (question.prompt.allowOther && numeric === question.prompt.options.length + 1) {
|
|
794
|
+
return {
|
|
795
|
+
selectedValue: "other",
|
|
796
|
+
selectedLabel: question.prompt.otherLabel ?? "Other",
|
|
797
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
throw new Error(`Answer ${selection} is outside the available LongTable question options.`);
|
|
801
|
+
}
|
|
802
|
+
const normalizedSelection = normalizeAnswerToken(selection);
|
|
803
|
+
const option = question.prompt.options.find((candidate) => optionAnswerCandidates(candidate).includes(normalizedSelection));
|
|
804
|
+
if (option) {
|
|
805
|
+
return {
|
|
806
|
+
selectedValue: option.value,
|
|
807
|
+
selectedLabel: option.label,
|
|
808
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
if (normalizedSelection === "other" && question.prompt.allowOther) {
|
|
812
|
+
return {
|
|
813
|
+
selectedValue: "other",
|
|
814
|
+
selectedLabel: question.prompt.otherLabel ?? "Other",
|
|
815
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (question.prompt.allowOther) {
|
|
819
|
+
return {
|
|
820
|
+
selectedValue: "other",
|
|
821
|
+
selectedLabel: selection,
|
|
822
|
+
otherText: trimmed,
|
|
823
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
throw new Error(`Answer "${selection}" does not match a LongTable question option.`);
|
|
827
|
+
}
|
|
675
828
|
export async function answerWorkspaceQuestion(options) {
|
|
676
829
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
677
830
|
const question = findQuestionForDecision(state, options.questionId);
|
|
678
831
|
if (!question) {
|
|
679
832
|
throw new Error(options.questionId ? `No pending LongTable question found for ${options.questionId}.` : "No pending LongTable question was found.");
|
|
680
833
|
}
|
|
681
|
-
const
|
|
682
|
-
const
|
|
834
|
+
const normalized = normalizeQuestionAnswerSelection(question, options.answer);
|
|
835
|
+
const rationale = [normalized.inlineRationale, options.rationale]
|
|
836
|
+
.filter((entry) => Boolean(entry && entry.trim()))
|
|
837
|
+
.join("\n");
|
|
683
838
|
const answer = {
|
|
684
839
|
promptId: question.prompt.id,
|
|
685
|
-
selectedValues: [
|
|
686
|
-
selectedLabels: [
|
|
687
|
-
...(
|
|
688
|
-
...(
|
|
840
|
+
selectedValues: [normalized.selectedValue],
|
|
841
|
+
selectedLabels: [normalized.selectedLabel],
|
|
842
|
+
...(normalized.otherText ? { otherText: normalized.otherText } : {}),
|
|
843
|
+
...(rationale ? { rationale } : {}),
|
|
689
844
|
...(options.provider ? { provider: options.provider } : {}),
|
|
690
845
|
surface: options.surface ?? (options.provider === "claude" ? "native_structured" : "numbered")
|
|
691
846
|
};
|
|
@@ -698,7 +853,7 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
698
853
|
mode: "commit",
|
|
699
854
|
summary: `Answered ${question.prompt.title}: ${answer.selectedLabels.join(", ")}`,
|
|
700
855
|
selectedOption: answer.selectedValues[0],
|
|
701
|
-
...(
|
|
856
|
+
...(rationale ? { rationale } : {})
|
|
702
857
|
};
|
|
703
858
|
const answeredQuestion = {
|
|
704
859
|
...question,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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.13",
|
|
32
|
+
"@longtable/core": "0.1.13",
|
|
33
|
+
"@longtable/memory": "0.1.13",
|
|
34
|
+
"@longtable/provider-claude": "0.1.13",
|
|
35
|
+
"@longtable/provider-codex": "0.1.13",
|
|
36
|
+
"@longtable/setup": "0.1.13"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.1",
|