@longtable/cli 0.1.49 → 0.1.50
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/project-session.d.ts +12 -2
- package/dist/project-session.js +133 -25
- package/package.json +7 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationRevision, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionPromptType, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationRevision, 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 type StartInterviewSignal = "phenomenon" | "audience" | "artifact" | "evidence" | "assumption" | "decision_risk" | "voice";
|
|
@@ -393,8 +393,11 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
393
393
|
prompt: string;
|
|
394
394
|
title?: string;
|
|
395
395
|
question?: string;
|
|
396
|
+
type?: QuestionPromptType;
|
|
396
397
|
checkpointKey?: string;
|
|
397
398
|
questionOptions?: QuestionOption[];
|
|
399
|
+
allowOther?: boolean;
|
|
400
|
+
otherLabel?: string;
|
|
398
401
|
displayReason?: string;
|
|
399
402
|
provider?: ProviderKind;
|
|
400
403
|
required?: boolean;
|
|
@@ -404,10 +407,17 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
404
407
|
question: QuestionRecord;
|
|
405
408
|
state: ResearchState;
|
|
406
409
|
}>;
|
|
410
|
+
type WorkspaceQuestionAnswerInput = string | string[] | {
|
|
411
|
+
answer?: string | string[];
|
|
412
|
+
selectedValue?: string;
|
|
413
|
+
selectedValues?: string[];
|
|
414
|
+
otherText?: string;
|
|
415
|
+
rationale?: string;
|
|
416
|
+
};
|
|
407
417
|
export declare function answerWorkspaceQuestion(options: {
|
|
408
418
|
context: LongTableProjectContext;
|
|
409
419
|
questionId?: string;
|
|
410
|
-
answer:
|
|
420
|
+
answer: WorkspaceQuestionAnswerInput;
|
|
411
421
|
rationale?: string;
|
|
412
422
|
provider?: "codex" | "claude";
|
|
413
423
|
surface?: QuestionSurface;
|
package/dist/project-session.js
CHANGED
|
@@ -2397,6 +2397,7 @@ export async function createWorkspaceQuestion(options) {
|
|
|
2397
2397
|
studyContract: state.studyContract
|
|
2398
2398
|
});
|
|
2399
2399
|
const checkpointKey = options.checkpointKey ?? trigger.signal.checkpointKey;
|
|
2400
|
+
const promptType = options.type ?? "single_choice";
|
|
2400
2401
|
const createdAt = nowIso();
|
|
2401
2402
|
const title = options.title ?? questionTitleForCheckpoint(trigger.family, checkpointKey);
|
|
2402
2403
|
const questionText = options.question ?? questionTextForCheckpoint(trigger.family, options.prompt, checkpointKey);
|
|
@@ -2427,10 +2428,10 @@ export async function createWorkspaceQuestion(options) {
|
|
|
2427
2428
|
checkpointKey,
|
|
2428
2429
|
title,
|
|
2429
2430
|
question: questionText,
|
|
2430
|
-
type:
|
|
2431
|
+
type: promptType,
|
|
2431
2432
|
options: options.questionOptions ?? optionsForCheckpointTrigger(trigger.family, checkpointKey),
|
|
2432
|
-
allowOther:
|
|
2433
|
-
otherLabel: "Other decision",
|
|
2433
|
+
allowOther: options.allowOther ?? promptType !== "free_text",
|
|
2434
|
+
otherLabel: options.otherLabel ?? "Other decision",
|
|
2434
2435
|
required: options.required ?? trigger.requiresQuestionBeforeClosure,
|
|
2435
2436
|
source: "checkpoint",
|
|
2436
2437
|
displayReason: options.displayReason ?? trigger.rationale[0],
|
|
@@ -2486,53 +2487,159 @@ function splitAnswerAndRationale(rawAnswer) {
|
|
|
2486
2487
|
...(rationale ? { rationale } : {})
|
|
2487
2488
|
};
|
|
2488
2489
|
}
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (
|
|
2490
|
+
function uniqueStrings(values) {
|
|
2491
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
|
|
2492
|
+
}
|
|
2493
|
+
function answerInputParts(input) {
|
|
2494
|
+
if (typeof input === "string") {
|
|
2495
|
+
return { values: [input], rawText: input };
|
|
2496
|
+
}
|
|
2497
|
+
if (Array.isArray(input)) {
|
|
2498
|
+
return { values: input };
|
|
2499
|
+
}
|
|
2500
|
+
let values = input.selectedValues ?? [];
|
|
2501
|
+
if (values.length === 0 && input.selectedValue) {
|
|
2502
|
+
values = [input.selectedValue];
|
|
2503
|
+
}
|
|
2504
|
+
if (values.length === 0 && Array.isArray(input.answer)) {
|
|
2505
|
+
values = input.answer;
|
|
2506
|
+
}
|
|
2507
|
+
if (values.length === 0 && typeof input.answer === "string") {
|
|
2508
|
+
values = [input.answer];
|
|
2509
|
+
}
|
|
2510
|
+
const otherText = input.otherText?.trim();
|
|
2511
|
+
const rationale = input.rationale?.trim();
|
|
2512
|
+
return {
|
|
2513
|
+
values,
|
|
2514
|
+
...(otherText ? { otherText } : {}),
|
|
2515
|
+
...(rationale ? { inlineRationale: rationale } : {})
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
function splitSelectionTokens(selection, type) {
|
|
2519
|
+
if (type !== "multi_choice") {
|
|
2520
|
+
return [selection];
|
|
2521
|
+
}
|
|
2522
|
+
return selection.split(/[;,]/).map((token) => token.trim()).filter(Boolean);
|
|
2523
|
+
}
|
|
2524
|
+
function normalizeFreeTextAnswer(input) {
|
|
2525
|
+
const parts = answerInputParts(input);
|
|
2526
|
+
const selectedText = parts.values.join("\n").trim();
|
|
2527
|
+
const text = parts.rawText ?? (selectedText || parts.otherText || "");
|
|
2528
|
+
const trimmed = text.trim();
|
|
2529
|
+
if (!trimmed) {
|
|
2530
|
+
throw new Error("Free-text LongTable question answers cannot be empty.");
|
|
2531
|
+
}
|
|
2532
|
+
return {
|
|
2533
|
+
selectedValues: [trimmed],
|
|
2534
|
+
selectedLabels: [trimmed],
|
|
2535
|
+
otherText: trimmed,
|
|
2536
|
+
...(parts.inlineRationale ? { inlineRationale: parts.inlineRationale } : {})
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function resolveQuestionAnswerToken(question, token) {
|
|
2540
|
+
const trimmed = token.trim();
|
|
2541
|
+
if (!trimmed) {
|
|
2542
|
+
throw new Error("LongTable question answers cannot contain empty selections.");
|
|
2543
|
+
}
|
|
2544
|
+
const numeric = Number(trimmed);
|
|
2545
|
+
if (/^\d+$/.test(trimmed) && Number.isInteger(numeric)) {
|
|
2494
2546
|
const option = question.prompt.options[numeric - 1];
|
|
2495
2547
|
if (option) {
|
|
2496
2548
|
return {
|
|
2497
2549
|
selectedValue: option.value,
|
|
2498
|
-
selectedLabel: option.label
|
|
2499
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2550
|
+
selectedLabel: option.label
|
|
2500
2551
|
};
|
|
2501
2552
|
}
|
|
2502
2553
|
if (question.prompt.allowOther && numeric === question.prompt.options.length + 1) {
|
|
2503
2554
|
return {
|
|
2504
2555
|
selectedValue: "other",
|
|
2505
|
-
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2506
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2556
|
+
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2507
2557
|
};
|
|
2508
2558
|
}
|
|
2509
|
-
throw new Error(`Answer ${
|
|
2559
|
+
throw new Error(`Answer ${trimmed} is outside the available LongTable question options.`);
|
|
2510
2560
|
}
|
|
2511
|
-
const normalizedSelection = normalizeAnswerToken(
|
|
2561
|
+
const normalizedSelection = normalizeAnswerToken(trimmed);
|
|
2512
2562
|
const option = question.prompt.options.find((candidate) => optionAnswerCandidates(candidate).includes(normalizedSelection));
|
|
2513
2563
|
if (option) {
|
|
2514
2564
|
return {
|
|
2515
2565
|
selectedValue: option.value,
|
|
2516
|
-
selectedLabel: option.label
|
|
2517
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2566
|
+
selectedLabel: option.label
|
|
2518
2567
|
};
|
|
2519
2568
|
}
|
|
2520
2569
|
if (normalizedSelection === "other" && question.prompt.allowOther) {
|
|
2521
2570
|
return {
|
|
2522
2571
|
selectedValue: "other",
|
|
2523
|
-
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2524
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2572
|
+
selectedLabel: question.prompt.otherLabel ?? "Other"
|
|
2525
2573
|
};
|
|
2526
2574
|
}
|
|
2527
2575
|
if (question.prompt.allowOther) {
|
|
2528
2576
|
return {
|
|
2529
|
-
selectedValue:
|
|
2530
|
-
selectedLabel:
|
|
2531
|
-
otherText: trimmed
|
|
2532
|
-
...(rationale ? { inlineRationale: rationale } : {})
|
|
2577
|
+
selectedValue: trimmed,
|
|
2578
|
+
selectedLabel: question.prompt.otherLabel ?? "Other",
|
|
2579
|
+
otherText: trimmed
|
|
2533
2580
|
};
|
|
2534
2581
|
}
|
|
2535
|
-
throw new Error(`Answer "${
|
|
2582
|
+
throw new Error(`Answer "${trimmed}" does not match a LongTable question option.`);
|
|
2583
|
+
}
|
|
2584
|
+
function isOtherAnswerToken(question, token) {
|
|
2585
|
+
if (!question.prompt.allowOther) {
|
|
2586
|
+
return false;
|
|
2587
|
+
}
|
|
2588
|
+
const trimmed = token.trim();
|
|
2589
|
+
const normalized = normalizeAnswerToken(trimmed);
|
|
2590
|
+
const otherLabel = normalizeAnswerToken(question.prompt.otherLabel ?? "Other");
|
|
2591
|
+
if (normalized === "other" || normalized === otherLabel) {
|
|
2592
|
+
return true;
|
|
2593
|
+
}
|
|
2594
|
+
const numeric = Number(trimmed);
|
|
2595
|
+
return /^\d+$/.test(trimmed) && Number.isInteger(numeric) && numeric === question.prompt.options.length + 1;
|
|
2596
|
+
}
|
|
2597
|
+
function normalizeQuestionAnswerSelection(question, rawAnswer) {
|
|
2598
|
+
if (question.prompt.type === "free_text") {
|
|
2599
|
+
return normalizeFreeTextAnswer(rawAnswer);
|
|
2600
|
+
}
|
|
2601
|
+
const parts = answerInputParts(rawAnswer);
|
|
2602
|
+
const parsedValues = parts.values.flatMap((value) => {
|
|
2603
|
+
if (typeof rawAnswer === "string") {
|
|
2604
|
+
const { selection } = splitAnswerAndRationale(value.trim());
|
|
2605
|
+
return splitSelectionTokens(selection, question.prompt.type);
|
|
2606
|
+
}
|
|
2607
|
+
return splitSelectionTokens(value, question.prompt.type);
|
|
2608
|
+
});
|
|
2609
|
+
const inlineRationale = typeof rawAnswer === "string"
|
|
2610
|
+
? splitAnswerAndRationale(rawAnswer.trim()).rationale
|
|
2611
|
+
: parts.inlineRationale;
|
|
2612
|
+
const hasOtherText = Boolean(parts.otherText);
|
|
2613
|
+
const hasOtherToken = parsedValues.some((value) => isOtherAnswerToken(question, value));
|
|
2614
|
+
if (hasOtherText && !hasOtherToken) {
|
|
2615
|
+
throw new Error("Other text requires selecting the LongTable question Other option.");
|
|
2616
|
+
}
|
|
2617
|
+
const selectionTokens = parts.otherText
|
|
2618
|
+
? parsedValues.filter((value) => !isOtherAnswerToken(question, value))
|
|
2619
|
+
: parsedValues;
|
|
2620
|
+
const tokens = uniqueStrings([
|
|
2621
|
+
...selectionTokens,
|
|
2622
|
+
...(parts.otherText ? [parts.otherText] : [])
|
|
2623
|
+
]);
|
|
2624
|
+
if (tokens.length === 0) {
|
|
2625
|
+
throw new Error("LongTable question answers cannot be empty.");
|
|
2626
|
+
}
|
|
2627
|
+
if (question.prompt.type !== "multi_choice" && tokens.length > 1) {
|
|
2628
|
+
throw new Error("Single-choice LongTable questions accept exactly one answer.");
|
|
2629
|
+
}
|
|
2630
|
+
const resolved = tokens.map((token) => resolveQuestionAnswerToken(question, token));
|
|
2631
|
+
const selectedValues = uniqueStrings(resolved.map((entry) => entry.selectedValue));
|
|
2632
|
+
const selectedLabels = selectedValues.map((value) => {
|
|
2633
|
+
const entry = resolved.find((candidate) => candidate.selectedValue === value);
|
|
2634
|
+
return entry?.selectedLabel ?? value;
|
|
2635
|
+
});
|
|
2636
|
+
const otherText = parts.otherText ?? resolved.map((entry) => entry.otherText).find(Boolean);
|
|
2637
|
+
return {
|
|
2638
|
+
selectedValues,
|
|
2639
|
+
selectedLabels,
|
|
2640
|
+
...(otherText ? { otherText } : {}),
|
|
2641
|
+
...(inlineRationale ? { inlineRationale } : {})
|
|
2642
|
+
};
|
|
2536
2643
|
}
|
|
2537
2644
|
export async function answerWorkspaceQuestion(options) {
|
|
2538
2645
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
@@ -2546,8 +2653,8 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
2546
2653
|
.join("\n");
|
|
2547
2654
|
const answer = {
|
|
2548
2655
|
promptId: question.prompt.id,
|
|
2549
|
-
selectedValues:
|
|
2550
|
-
selectedLabels:
|
|
2656
|
+
selectedValues: normalized.selectedValues,
|
|
2657
|
+
selectedLabels: normalized.selectedLabels,
|
|
2551
2658
|
...(normalized.otherText ? { otherText: normalized.otherText } : {}),
|
|
2552
2659
|
...(rationale ? { rationale } : {}),
|
|
2553
2660
|
...(options.provider ? { provider: options.provider } : {}),
|
|
@@ -2564,6 +2671,7 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
2564
2671
|
...(question.commitmentFamily ? { commitmentFamily: question.commitmentFamily } : {}),
|
|
2565
2672
|
...(question.epistemicBasis ? { epistemicBasis: question.epistemicBasis } : {}),
|
|
2566
2673
|
selectedOption: answer.selectedValues[0],
|
|
2674
|
+
selectedOptions: answer.selectedValues,
|
|
2567
2675
|
...(rationale ? { rationale } : {})
|
|
2568
2676
|
};
|
|
2569
2677
|
const answeredQuestion = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@longtable/checkpoints": "0.1.
|
|
33
|
-
"@longtable/core": "0.1.
|
|
34
|
-
"@longtable/memory": "0.1.
|
|
35
|
-
"@longtable/provider-claude": "0.1.
|
|
36
|
-
"@longtable/provider-codex": "0.1.
|
|
37
|
-
"@longtable/setup": "0.1.
|
|
32
|
+
"@longtable/checkpoints": "0.1.50",
|
|
33
|
+
"@longtable/core": "0.1.50",
|
|
34
|
+
"@longtable/memory": "0.1.50",
|
|
35
|
+
"@longtable/provider-claude": "0.1.50",
|
|
36
|
+
"@longtable/provider-codex": "0.1.50",
|
|
37
|
+
"@longtable/setup": "0.1.50"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|