@longtable/cli 0.1.57 → 0.1.59

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.
@@ -499,96 +499,12 @@ function formatQuestionMetadata(record) {
499
499
  }
500
500
  const QUESTION_SURFACES = new Set([
501
501
  "native_structured",
502
+ "tmux_popup",
502
503
  "mcp_elicitation",
503
504
  "numbered",
504
505
  "terminal_selector",
505
506
  "web_form"
506
507
  ]);
507
- function asStringArray(value) {
508
- if (Array.isArray(value)) {
509
- return value.filter((entry) => typeof entry === "string");
510
- }
511
- return typeof value === "string" ? [value] : [];
512
- }
513
- function legacyQuestionAnswerRecord(record) {
514
- return asRecord(record.answer);
515
- }
516
- function labelForQuestionAnswerValue(record, value) {
517
- const option = record.prompt.options.find((candidate) => candidate.value === value || candidate.label === value);
518
- if (option) {
519
- return option.label;
520
- }
521
- if (value === "other") {
522
- return record.prompt.otherLabel ?? "Other";
523
- }
524
- return value;
525
- }
526
- function selectedValuesForQuestion(record) {
527
- const answer = legacyQuestionAnswerRecord(record);
528
- if (!answer) {
529
- return [];
530
- }
531
- const directValues = asStringArray(answer.selectedValues);
532
- if (directValues.length > 0) {
533
- return directValues;
534
- }
535
- const legacyValues = [
536
- ...asStringArray(answer.selectedValue),
537
- ...asStringArray(answer.selected),
538
- ...asStringArray(answer.selectedOption),
539
- ...asStringArray(answer.answer),
540
- ...asStringArray(answer.value)
541
- ];
542
- return uniqueStrings(legacyValues);
543
- }
544
- function selectedLabelsForQuestion(record, selectedValues) {
545
- const answer = legacyQuestionAnswerRecord(record);
546
- const directLabels = asStringArray(answer?.selectedLabels);
547
- if (directLabels.length > 0) {
548
- return directLabels;
549
- }
550
- return selectedValues.map((value) => labelForQuestionAnswerValue(record, value));
551
- }
552
- function legacyAnswerShapeWarnings(questions) {
553
- return questions.flatMap((record) => {
554
- if (record.status !== "answered" || !legacyQuestionAnswerRecord(record)) {
555
- return [];
556
- }
557
- const selectedValues = asStringArray(legacyQuestionAnswerRecord(record)?.selectedValues);
558
- const selectedLabels = asStringArray(legacyQuestionAnswerRecord(record)?.selectedLabels);
559
- if (selectedValues.length > 0 && selectedLabels.length > 0) {
560
- return [];
561
- }
562
- return [{
563
- questionId: record.id,
564
- ...(record.decisionRecordId ? { decisionRecordId: record.decisionRecordId } : {}),
565
- issue: "Answered question uses a legacy answer shape that is missing selectedValues or selectedLabels.",
566
- suggestion: "Run `longtable repair-state --cwd <project-path>` to normalize the answer without changing the recorded selection."
567
- }];
568
- });
569
- }
570
- function numericOtherAnswerWarnings(questions) {
571
- return questions.flatMap((record) => {
572
- if (record.status !== "answered" || !selectedValuesForQuestion(record).includes("other")) {
573
- return [];
574
- }
575
- const answer = legacyQuestionAnswerRecord(record);
576
- const raw = typeof answer?.otherText === "string"
577
- ? answer.otherText
578
- : selectedLabelsForQuestion(record, selectedValuesForQuestion(record))[0] ?? "";
579
- if (!/^\d+$/.test(raw.trim())) {
580
- return [];
581
- }
582
- const index = Number(raw.trim()) - 1;
583
- const option = record.prompt.options[index];
584
- return [{
585
- questionId: record.id,
586
- ...(record.decisionRecordId ? { decisionRecordId: record.decisionRecordId } : {}),
587
- issue: `Numeric answer "${raw.trim()}" was stored as other text.`,
588
- ...(option ? { suggestion: `Use "${option.value}" (${option.label}) for this checkpoint option.` } : {})
589
- }];
590
- });
591
- }
592
508
  function compactLine(value, limit = 160) {
593
509
  const compacted = value.replace(/\s+/g, " ").trim();
594
510
  return compacted.length > limit ? `${compacted.slice(0, limit - 1)}…` : compacted;
@@ -598,6 +514,15 @@ function asRecord(value) {
598
514
  ? value
599
515
  : null;
600
516
  }
517
+ function asStringArray(value) {
518
+ if (Array.isArray(value)) {
519
+ return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
520
+ }
521
+ return typeof value === "string" && value.trim().length > 0 ? [value] : [];
522
+ }
523
+ function isQuestionSurfaceValue(value) {
524
+ return typeof value === "string" && QUESTION_SURFACES.has(value);
525
+ }
601
526
  const SPEC_DIFF_IGNORED_PATHS = new Set([
602
527
  "createdAt",
603
528
  "updatedAt",
@@ -694,10 +619,10 @@ function buildResearchSpecificationGapQuestion(gaps, timestamp, sourceEvidenceId
694
619
  "Research Specification is the required durable interview artifact.",
695
620
  "Missing required sections can make later resume, screening, coding, or evidence decisions stale."
696
621
  ],
697
- preferredSurfaces: ["mcp_elicitation", "numbered"]
622
+ preferredSurfaces: ["tmux_popup", "mcp_elicitation", "numbered"]
698
623
  },
699
624
  transportStatus: {
700
- surface: "mcp_elicitation",
625
+ surface: "tmux_popup",
701
626
  status: "not_attempted",
702
627
  updatedAt: timestamp,
703
628
  ...(sourceEvidenceIds.length > 0 ? { message: `Source evidence: ${sourceEvidenceIds.join(", ")}` } : {})
@@ -946,10 +871,22 @@ function summarizeWorkspaceInspection(context, state) {
946
871
  ...(record.selectedOption ? { selectedOption: record.selectedOption } : {}),
947
872
  timestamp: record.timestamp
948
873
  })),
949
- answerWarnings: [
950
- ...(legacyAnswerShapeWarnings(questions) ?? []),
951
- ...(numericOtherAnswerWarnings(questions) ?? [])
952
- ]
874
+ answerWarnings: questions
875
+ .filter((record) => record.status === "answered" && record.answer?.selectedValues.includes("other"))
876
+ .flatMap((record) => {
877
+ const raw = record.answer?.otherText ?? record.answer?.selectedLabels[0] ?? "";
878
+ if (!/^\d+$/.test(raw.trim())) {
879
+ return [];
880
+ }
881
+ const index = Number(raw.trim()) - 1;
882
+ const option = record.prompt.options[index];
883
+ return [{
884
+ questionId: record.id,
885
+ ...(record.decisionRecordId ? { decisionRecordId: record.decisionRecordId } : {}),
886
+ issue: `Numeric answer "${raw.trim()}" was stored as other text.`,
887
+ ...(option ? { suggestion: `Use "${option.value}" (${option.label}) for this checkpoint option.` } : {})
888
+ }];
889
+ })
953
890
  };
954
891
  }
955
892
  function buildProjectAgentsMd(project, session) {
@@ -2762,7 +2699,7 @@ export async function createWorkspaceFollowUpQuestions(options) {
2762
2699
  const createdAt = nowIso();
2763
2700
  const preferredSurfaces = options.provider === "claude"
2764
2701
  ? ["native_structured", "terminal_selector", "numbered"]
2765
- : ["mcp_elicitation", "terminal_selector", "numbered"];
2702
+ : ["tmux_popup", "mcp_elicitation", "terminal_selector", "numbered"];
2766
2703
  const specs = buildQuestionOpportunitySpecs(options.prompt, {
2767
2704
  includeFallback: options.force === true ? true : options.auto !== true,
2768
2705
  autoOnly: options.auto === true,
@@ -2875,7 +2812,7 @@ export async function createWorkspaceQuestion(options) {
2875
2812
  rationale,
2876
2813
  preferredSurfaces: options.provider === "claude"
2877
2814
  ? ["native_structured", "numbered"]
2878
- : ["mcp_elicitation", "numbered"]
2815
+ : ["tmux_popup", "mcp_elicitation", "numbered"]
2879
2816
  }
2880
2817
  };
2881
2818
  const updated = appendQuestionRecords(state, [question]);
@@ -3078,6 +3015,15 @@ function normalizeQuestionAnswerSelection(question, rawAnswer) {
3078
3015
  ...(inlineRationale ? { inlineRationale } : {})
3079
3016
  };
3080
3017
  }
3018
+ function selectedLabelsForValues(question, selectedValues) {
3019
+ return selectedValues.map((value) => {
3020
+ if (value === "other") {
3021
+ return question.prompt.otherLabel ?? "Other";
3022
+ }
3023
+ const option = question.prompt.options.find((candidate) => candidate.value === value);
3024
+ return option?.label ?? value;
3025
+ });
3026
+ }
3081
3027
  export async function answerWorkspaceQuestion(options) {
3082
3028
  const state = await loadResearchState(options.context.stateFilePath);
3083
3029
  const question = findQuestionForDecision(state, options.questionId);
@@ -3116,7 +3062,12 @@ export async function answerWorkspaceQuestion(options) {
3116
3062
  updatedAt: timestamp,
3117
3063
  status: "answered",
3118
3064
  answer,
3119
- decisionRecordId: decision.id
3065
+ decisionRecordId: decision.id,
3066
+ transportStatus: {
3067
+ surface: answer.surface,
3068
+ status: "accepted",
3069
+ updatedAt: timestamp
3070
+ }
3120
3071
  };
3121
3072
  const withQuestion = {
3122
3073
  ...state,
@@ -3322,41 +3273,49 @@ export async function repairWorkspaceStateConsistency(options) {
3322
3273
  })
3323
3274
  };
3324
3275
  }
3325
- const timestamp = nowIso();
3276
+ const repairTimestamp = nowIso();
3326
3277
  const repairedQuestionLog = (updated.questionLog ?? []).map((record) => {
3327
- if (record.status !== "answered") {
3328
- return record;
3329
- }
3330
- const answer = legacyQuestionAnswerRecord(record);
3331
- if (!answer) {
3278
+ const answerRecord = asRecord(record.answer);
3279
+ if (!answerRecord) {
3332
3280
  return record;
3333
3281
  }
3334
- const selectedValues = selectedValuesForQuestion(record);
3282
+ const selectedValues = [
3283
+ ...asStringArray(answerRecord.selectedValues),
3284
+ ...asStringArray(answerRecord.selectedValue),
3285
+ ...asStringArray(answerRecord.selectedOptions),
3286
+ ...asStringArray(answerRecord.value)
3287
+ ];
3335
3288
  if (selectedValues.length === 0) {
3336
3289
  return record;
3337
3290
  }
3338
- const selectedLabels = selectedLabelsForQuestion(record, selectedValues);
3339
- const needsRepair = asStringArray(answer.selectedValues).length === 0 ||
3340
- asStringArray(answer.selectedLabels).length === 0 ||
3341
- typeof answer.promptId !== "string" ||
3342
- !QUESTION_SURFACES.has(answer.surface);
3291
+ const selectedLabels = asStringArray(answerRecord.selectedLabels);
3292
+ const needsRepair = typeof answerRecord.promptId !== "string" ||
3293
+ selectedLabels.length === 0 ||
3294
+ !isQuestionSurfaceValue(answerRecord.surface);
3343
3295
  if (!needsRepair) {
3344
3296
  return record;
3345
3297
  }
3346
3298
  repaired.push(`normalized legacy answer shape for question ${record.id}`);
3347
3299
  const normalizedAnswer = {
3348
- ...answer,
3349
- promptId: typeof answer.promptId === "string" ? answer.promptId : record.prompt.id,
3350
- selectedValues,
3351
- selectedLabels,
3352
- ...(typeof answer.otherText === "string" && answer.otherText.trim() ? { otherText: answer.otherText } : {}),
3353
- ...(typeof answer.rationale === "string" && answer.rationale.trim() ? { rationale: answer.rationale } : {}),
3354
- ...(answer.provider === "codex" || answer.provider === "claude" ? { provider: answer.provider } : {}),
3355
- surface: QUESTION_SURFACES.has(answer.surface) ? answer.surface : "numbered"
3300
+ promptId: typeof answerRecord.promptId === "string" ? answerRecord.promptId : record.prompt.id,
3301
+ selectedValues: uniqueStrings(selectedValues),
3302
+ selectedLabels: selectedLabels.length > 0
3303
+ ? selectedLabels
3304
+ : selectedLabelsForValues(record, uniqueStrings(selectedValues)),
3305
+ ...(typeof answerRecord.otherText === "string" && answerRecord.otherText.trim()
3306
+ ? { otherText: answerRecord.otherText }
3307
+ : {}),
3308
+ ...(typeof answerRecord.rationale === "string" && answerRecord.rationale.trim()
3309
+ ? { rationale: answerRecord.rationale }
3310
+ : {}),
3311
+ ...(answerRecord.provider === "codex" || answerRecord.provider === "claude"
3312
+ ? { provider: answerRecord.provider }
3313
+ : {}),
3314
+ surface: isQuestionSurfaceValue(answerRecord.surface) ? answerRecord.surface : "numbered"
3356
3315
  };
3357
3316
  return {
3358
3317
  ...record,
3359
- updatedAt: timestamp,
3318
+ updatedAt: repairTimestamp,
3360
3319
  answer: normalizedAnswer
3361
3320
  };
3362
3321
  });
@@ -3366,7 +3325,7 @@ export async function repairWorkspaceStateConsistency(options) {
3366
3325
  questionLog: repairedQuestionLog
3367
3326
  };
3368
3327
  }
3369
- if (repaired.length > 0 && !options.dryRun) {
3328
+ if (repaired.length > 0) {
3370
3329
  await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
3371
3330
  await syncCurrentWorkspaceView(options.context);
3372
3331
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
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.57",
33
- "@longtable/core": "0.1.57",
34
- "@longtable/memory": "0.1.57",
35
- "@longtable/provider-claude": "0.1.57",
36
- "@longtable/provider-codex": "0.1.57",
37
- "@longtable/setup": "0.1.57"
32
+ "@longtable/checkpoints": "0.1.59",
33
+ "@longtable/core": "0.1.59",
34
+ "@longtable/memory": "0.1.59",
35
+ "@longtable/provider-claude": "0.1.59",
36
+ "@longtable/provider-codex": "0.1.59",
37
+ "@longtable/setup": "0.1.59"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.1",