@longtable/cli 0.1.21 → 0.1.23

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 CHANGED
@@ -42,7 +42,7 @@ const ANSI = {
42
42
  green: "\u001B[32m"
43
43
  };
44
44
  const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
45
- const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.21";
45
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.23";
46
46
  const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
47
47
  const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
48
48
  function style(text, prefix) {
@@ -78,17 +78,16 @@ function usage() {
78
78
  " Run `longtable ...` in your terminal, not inside the Codex chat box.",
79
79
  " After `longtable start`, move into the created project directory and open `codex` there.",
80
80
  "",
81
- " longtable setup [--provider codex|claude] [--install-scope user|project|none] [--surfaces cli_only|skills|skills_mcp|skills_mcp_sentinel] [--intervention advisory|balanced|strong] [--workspace create|later] [--project-dir <path>] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
81
+ " longtable setup [--provider codex|claude] [--install-scope user|project|none] [--surfaces cli_only|skills|skills_mcp|skills_mcp_sentinel] [--intervention advisory|balanced|strong] [--checkpoint-ui off|interactive|strong] [--workspace create|later] [--project-dir <path>] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
82
82
  " longtable init [deprecated alias for setup; full legacy flags still supported for automation]",
83
83
  " longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--research-object research_question|theory_framework|measurement_instrument|study_design|analysis_plan|manuscript] [--gap-risk known_gap|suspected_tacit_assumptions|diagnose] [--protected-decision theory|measurement|method|evidence_citation|authorship_voice|submission_public_sharing] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
84
84
  " longtable resume [--cwd <path>] [--json]",
85
- " longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
86
- " longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
85
+ " longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
86
+ " longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
87
87
  " longtable roles [--json]",
88
88
  " longtable show [--json] [--path <file>]",
89
89
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
90
- " longtable mcp install [--provider codex|claude|all] [--write] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
91
- " longtable hud [--watch] [--tmux] [--preset minimal|full] [--cwd <path>] [--json]",
90
+ " longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
92
91
  " longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
93
92
  " longtable team --prompt <text> [--role <role[,role]>] [--tmux] [--debate] [--rounds 5] [--cwd <path>] [--json]",
94
93
  " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
@@ -126,7 +125,7 @@ function parseArgs(argv) {
126
125
  const values = {};
127
126
  let subcommand = maybeSubcommand;
128
127
  const modeCommand = command && VALID_MODES.has(command);
129
- const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "hud", "sentinel", "team"].includes(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
129
  let startIndex = 1;
131
130
  if (modeCommand) {
132
131
  subcommand = undefined;
@@ -619,6 +618,23 @@ function buildPermissionSetupChoices() {
619
618
  description: "Why: maximizes judgment protection. What you get: stricter checkpoints. Tradeoff: more interruption before closure."
620
619
  }
621
620
  ],
621
+ checkpointUi: [
622
+ {
623
+ id: "strong",
624
+ label: "Strong Researcher Checkpoint UI",
625
+ description: "Why: uses Codex MCP elicitation for high-responsibility research decisions. What you get: clickable checkpoints with other/rationale. Tradeoff: requires Codex MCP elicitation approval."
626
+ },
627
+ {
628
+ id: "interactive",
629
+ 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."
631
+ },
632
+ {
633
+ id: "off",
634
+ label: "Text fallback only",
635
+ description: "Why: safest transport boundary. What you get: numbered checkpoints and terminal selectors only. Tradeoff: no Codex UI form prompts."
636
+ }
637
+ ],
622
638
  workspace: [
623
639
  {
624
640
  id: "create",
@@ -696,9 +712,18 @@ function parseSetupSurface(value) {
696
712
  function parseSetupIntervention(value) {
697
713
  return value === "advisory" || value === "balanced" || value === "strong" ? value : undefined;
698
714
  }
715
+ function parseSetupCheckpointUi(value) {
716
+ return value === "off" || value === "interactive" || value === "strong" ? value : undefined;
717
+ }
699
718
  function parseSetupWorkspace(value) {
700
719
  return value === "create" || value === "later" ? value : undefined;
701
720
  }
721
+ function checkpointUiRequiresMcp(choice) {
722
+ return choice === "interactive" || choice === "strong";
723
+ }
724
+ function checkpointUiIntervention(intervention, checkpointUi) {
725
+ return checkpointUi === "strong" ? "strong" : intervention;
726
+ }
702
727
  async function runSetup(args) {
703
728
  const json = args.json === true;
704
729
  const rl = createInterface({ input, output });
@@ -713,13 +738,26 @@ async function runSetup(args) {
713
738
  "This is a permission choice because skills, MCP, and sentinel support write provider-facing runtime files."
714
739
  ].join("\n"), choices.surfaces);
715
740
  const intervention = parseSetupIntervention(args.intervention) ?? await promptChoice(rl, "How strongly may LongTable interrupt research decisions?", choices.intervention);
741
+ const parsedCheckpointUi = parseSetupCheckpointUi(args["checkpoint-ui"]);
742
+ const checkpointUiEligible = provider === "codex" && installScope !== "none" && shouldInstallMcp(installScope, surfaces);
743
+ if (parsedCheckpointUi && checkpointUiRequiresMcp(parsedCheckpointUi) && !checkpointUiEligible) {
744
+ throw new Error("`--checkpoint-ui interactive|strong` requires Codex with an MCP runtime surface.");
745
+ }
746
+ const checkpointUi = parsedCheckpointUi ?? (checkpointUiEligible
747
+ ? await promptChoice(rl, [
748
+ "Should Codex use UI Researcher Checkpoints when MCP elicitation is available?",
749
+ "This writes Codex MCP elicitation approval only when you choose an interactive mode."
750
+ ].join("\n"), choices.checkpointUi)
751
+ : "off");
716
752
  const workspacePreference = parseSetupWorkspace(args.workspace) ?? await promptChoice(rl, "Should LongTable create a project workspace now?", choices.workspace);
717
753
  const projectRoot = setupProjectRoot(args);
754
+ const effectiveIntervention = checkpointUiIntervention(intervention, checkpointUi);
718
755
  const outputValue = createPersistedSetupOutput({
719
756
  field: "unspecified",
720
757
  careerStage: "unspecified",
721
758
  experienceLevel: "advanced",
722
- preferredCheckpointIntensity: checkpointIntensityFromIntervention(intervention),
759
+ preferredCheckpointIntensity: checkpointIntensityFromIntervention(effectiveIntervention),
760
+ checkpointUiMode: checkpointUi,
723
761
  preferredEntryMode: "explore",
724
762
  panelPreference: "show_on_conflict"
725
763
  }, provider, "quickstart");
@@ -727,7 +765,8 @@ async function runSetup(args) {
727
765
  ...outputValue.initialState.explicitState,
728
766
  installScope,
729
767
  runtimeSurfaces: surfaces,
730
- interventionPosture: intervention,
768
+ interventionPosture: effectiveIntervention,
769
+ checkpointUiMode: checkpointUi,
731
770
  workspaceCreationPreference: workspacePreference,
732
771
  tmuxMode: "standard",
733
772
  teamMode: "panel"
@@ -759,7 +798,12 @@ async function runSetup(args) {
759
798
  : await installClaudeSkills(listRoleDefinitions(), scopedInstallDir);
760
799
  let mcpInstall;
761
800
  if (shouldInstallMcp(installScope, surfaces)) {
762
- mcpInstall = await installMcpForSetup(provider, mcpArgsForScope(provider, installScope, args, projectRoot));
801
+ mcpInstall = await installMcpForSetup(provider, {
802
+ ...mcpArgsForScope(provider, installScope, args, projectRoot),
803
+ ...(provider === "codex" && checkpointUiRequiresMcp(checkpointUi)
804
+ ? { "checkpoint-ui": checkpointUi }
805
+ : {})
806
+ });
763
807
  }
764
808
  if (json) {
765
809
  console.log(JSON.stringify({
@@ -786,6 +830,10 @@ async function runSetup(args) {
786
830
  if (mcpInstall) {
787
831
  console.log("");
788
832
  console.log(renderMcpInstallSummary(mcpInstall));
833
+ if (provider === "codex" && checkpointUiRequiresMcp(checkpointUi)) {
834
+ console.log("");
835
+ console.log("Restart Codex after this config change, then run `longtable doctor` in the project workspace.");
836
+ }
789
837
  }
790
838
  if (surfaces === "skills_mcp_sentinel") {
791
839
  console.log("");
@@ -1112,15 +1160,36 @@ function renderCodexMcpBlock(serverName, command, mcpArgs) {
1112
1160
  LONGTABLE_MCP_MARKER_END
1113
1161
  ].join("\n");
1114
1162
  }
1163
+ function codexElicitationApprovalLine() {
1164
+ return "approval_policy = { granular = { sandbox_approval = false, rules = false, mcp_elicitations = true } }";
1165
+ }
1166
+ function enableCodexMcpElicitations(existing) {
1167
+ const line = codexElicitationApprovalLine();
1168
+ if (/approval_policy\s*=\s*\{[^\n]*mcp_elicitations\s*=\s*true[^\n]*\}/m.test(existing)) {
1169
+ return existing;
1170
+ }
1171
+ if (/^approval_policy\s*=.*$/m.test(existing)) {
1172
+ return existing.replace(/^approval_policy\s*=.*$/m, line);
1173
+ }
1174
+ const trimmed = existing.trimEnd();
1175
+ return trimmed ? `${line}\n${trimmed}\n` : `${line}\n`;
1176
+ }
1177
+ function codexMcpElicitationsAllowed(config) {
1178
+ return /approval_policy\s*=\s*\{[^\n]*mcp_elicitations\s*=\s*true[^\n]*\}/m.test(config);
1179
+ }
1180
+ function codexLongTableMcpConfigured(config) {
1181
+ return new RegExp(`\\[mcp_servers\\.${LONGTABLE_MCP_SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\]`).test(config);
1182
+ }
1115
1183
  function replaceMarkedCodexMcpBlock(existing, block, serverName) {
1116
1184
  const markerPattern = new RegExp(`${LONGTABLE_MCP_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${LONGTABLE_MCP_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "m");
1117
1185
  const serverPattern = new RegExp(`\\n?\\[mcp_servers\\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[|$)`, "m");
1118
1186
  const trimmed = existing.replace(markerPattern, "").replace(serverPattern, "").trimEnd();
1119
1187
  return trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`;
1120
1188
  }
1121
- async function writeCodexMcpConfig(path, block, serverName) {
1189
+ async function writeCodexMcpConfig(path, block, serverName, options = {}) {
1122
1190
  const existing = existsSync(path) ? await readFile(path, "utf8") : "";
1123
- const updated = replaceMarkedCodexMcpBlock(existing, block, serverName);
1191
+ const withMcp = replaceMarkedCodexMcpBlock(existing, block, serverName);
1192
+ const updated = options.enableElicitations ? enableCodexMcpElicitations(withMcp) : withMcp;
1124
1193
  await mkdir(dirname(path), { recursive: true });
1125
1194
  await writeFile(path, updated, "utf8");
1126
1195
  return updated;
@@ -1163,6 +1232,9 @@ function renderMcpInstallSummary(result) {
1163
1232
  `- package: ${result.packageSpec}`,
1164
1233
  `- command: ${result.command} ${result.args.join(" ")}`,
1165
1234
  `- mode: ${result.write ? "wrote provider config" : "printed config only"}`,
1235
+ ...(result.checkpointUi && checkpointUiRequiresMcp(result.checkpointUi)
1236
+ ? [`- Codex checkpoint UI: ${result.checkpointUi} (MCP elicitations allowed)`]
1237
+ : []),
1166
1238
  ""
1167
1239
  ];
1168
1240
  for (const target of result.targets) {
@@ -1175,6 +1247,9 @@ function renderMcpInstallSummary(result) {
1175
1247
  if (!result.write) {
1176
1248
  lines.push("Run again with `--write` to update these provider config files.");
1177
1249
  }
1250
+ if (result.checkpointUi && checkpointUiRequiresMcp(result.checkpointUi)) {
1251
+ lines.push("Restart Codex after changing MCP elicitation approval, then run `longtable doctor`.");
1252
+ }
1178
1253
  return lines.join("\n").trimEnd();
1179
1254
  }
1180
1255
  async function installMcpForSetup(provider, args) {
@@ -1184,11 +1259,14 @@ async function installMcpForSetup(provider, args) {
1184
1259
  const packageSpec = resolveMcpPackageSpec(args);
1185
1260
  const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
1186
1261
  const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
1262
+ const checkpointUi = parseSetupCheckpointUi(args["checkpoint-ui"]) ?? "off";
1187
1263
  const targets = [];
1188
1264
  if (provider === "codex") {
1189
1265
  const path = resolveCodexMcpConfigPath(args);
1190
1266
  const block = renderCodexMcpBlock(serverName, command, mcpArgs);
1191
- const content = await writeCodexMcpConfig(path, block, serverName);
1267
+ const content = await writeCodexMcpConfig(path, block, serverName, {
1268
+ enableElicitations: checkpointUiRequiresMcp(checkpointUi)
1269
+ });
1192
1270
  targets.push({ provider, path, format: "toml", content });
1193
1271
  }
1194
1272
  if (provider === "claude") {
@@ -1202,6 +1280,7 @@ async function installMcpForSetup(provider, args) {
1202
1280
  command,
1203
1281
  args: mcpArgs,
1204
1282
  write: true,
1283
+ checkpointUi,
1205
1284
  targets
1206
1285
  };
1207
1286
  }
@@ -1215,12 +1294,22 @@ async function runMcpSubcommand(subcommand, args) {
1215
1294
  const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
1216
1295
  const providers = resolveMcpProviders(args.provider);
1217
1296
  const write = args.write === true;
1297
+ const checkpointUi = parseSetupCheckpointUi(args["checkpoint-ui"]) ?? "off";
1298
+ if (checkpointUiRequiresMcp(checkpointUi) && !providers.includes("codex")) {
1299
+ throw new Error("`--checkpoint-ui interactive|strong` applies only to the Codex MCP provider.");
1300
+ }
1218
1301
  const targets = [];
1219
1302
  for (const provider of providers) {
1220
1303
  if (provider === "codex") {
1221
1304
  const path = resolveCodexMcpConfigPath(args);
1222
1305
  const block = renderCodexMcpBlock(serverName, command, mcpArgs);
1223
- const content = write ? await writeCodexMcpConfig(path, block, serverName) : block;
1306
+ const content = write
1307
+ ? await writeCodexMcpConfig(path, block, serverName, {
1308
+ enableElicitations: checkpointUiRequiresMcp(checkpointUi)
1309
+ })
1310
+ : checkpointUiRequiresMcp(checkpointUi)
1311
+ ? enableCodexMcpElicitations(block)
1312
+ : block;
1224
1313
  targets.push({ provider, path, format: "toml", content });
1225
1314
  }
1226
1315
  if (provider === "claude") {
@@ -1237,6 +1326,7 @@ async function runMcpSubcommand(subcommand, args) {
1237
1326
  command,
1238
1327
  args: mcpArgs,
1239
1328
  write,
1329
+ checkpointUi,
1240
1330
  targets
1241
1331
  };
1242
1332
  if (args.json === true) {
@@ -1261,6 +1351,13 @@ function missingNames(expected, installed) {
1261
1351
  const installedSet = new Set(installed);
1262
1352
  return expected.filter((name) => !installedSet.has(name));
1263
1353
  }
1354
+ function resolveDoctorCodexMcpConfigPath(args) {
1355
+ if (typeof args["codex-config"] === "string" && args["codex-config"].trim()) {
1356
+ return resolve(normalizeUserPath(args["codex-config"].trim()));
1357
+ }
1358
+ const projectScoped = join(typeof args.cwd === "string" ? resolve(args.cwd) : cwd(), ".codex", "config.toml");
1359
+ return existsSync(projectScoped) ? projectScoped : resolveCodexMcpConfigPath(args);
1360
+ }
1264
1361
  function setupForProvider(setup, provider) {
1265
1362
  return {
1266
1363
  ...setup,
@@ -1310,6 +1407,10 @@ async function collectDoctorStatus(args) {
1310
1407
  : undefined;
1311
1408
  const codexRuntimePath = resolveDefaultRuntimeConfigPath("codex", codexRuntimeOverride).path;
1312
1409
  const claudeRuntimePath = resolveDefaultRuntimeConfigPath("claude", claudeRuntimeOverride).path;
1410
+ const codexMcpConfigPath = resolveDoctorCodexMcpConfigPath(args);
1411
+ const codexMcpConfig = existsSync(codexMcpConfigPath)
1412
+ ? await readFile(codexMcpConfigPath, "utf8")
1413
+ : "";
1313
1414
  const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
1314
1415
  const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
1315
1416
  const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
@@ -1334,7 +1435,11 @@ async function collectDoctorStatus(args) {
1334
1435
  installedSkills: installedCodexSkills,
1335
1436
  missingSkills: missingNames(expectedCodexSkills, installedCodexSkills),
1336
1437
  promptsDir: resolveCodexPromptsDir(codexPromptsDir),
1337
- legacyPromptFilesInstalled: codexAliases.map((alias) => alias.name)
1438
+ legacyPromptFilesInstalled: codexAliases.map((alias) => alias.name),
1439
+ mcpConfigPath: codexMcpConfigPath,
1440
+ mcpConfigExists: existsSync(codexMcpConfigPath),
1441
+ longtableMcpConfigured: codexLongTableMcpConfigured(codexMcpConfig),
1442
+ mcpElicitationsAllowed: codexMcpElicitationsAllowed(codexMcpConfig)
1338
1443
  },
1339
1444
  claude: {
1340
1445
  command: "claude",
@@ -1373,6 +1478,9 @@ function renderDoctorStatus(status) {
1373
1478
  ...(status.providers.codex.legacyPromptFilesInstalled.length > 0
1374
1479
  ? [`- legacy prompt names: ${status.providers.codex.legacyPromptFilesInstalled.join(", ")}`]
1375
1480
  : []),
1481
+ `- MCP config: ${status.providers.codex.mcpConfigExists ? "present" : "missing"} (${status.providers.codex.mcpConfigPath})`,
1482
+ `- LongTable MCP: ${status.providers.codex.longtableMcpConfigured ? "configured" : "missing"}`,
1483
+ `- MCP elicitation approval: ${status.providers.codex.mcpElicitationsAllowed ? "allowed" : "not allowed"}`,
1376
1484
  "",
1377
1485
  ...renderProviderDoctorBlock("Claude", status.providers.claude),
1378
1486
  "",
@@ -2130,6 +2238,9 @@ function sentinelSummary(prompt, workingDirectory) {
2130
2238
  if (/method|design|sample|participant|방법|설계|표본|참여자/.test(normalized)) {
2131
2239
  signals.push("method/design gap");
2132
2240
  }
2241
+ if (/knowledge gap|unknown|uncertain|not sure|don't know|dont know|지식의 공백|지식 공백|모르겠|불확실/.test(normalized)) {
2242
+ signals.push("knowledge gap");
2243
+ }
2133
2244
  if (/citation|reference|source|evidence|doi|문헌|인용|근거|출처/.test(normalized)) {
2134
2245
  signals.push("evidence gap");
2135
2246
  }
@@ -2188,63 +2299,6 @@ async function runSentinel(args) {
2188
2299
  console.log(context ? `- recorded in: ${context.stateFilePath}` : "- record skipped: no LongTable workspace found");
2189
2300
  }
2190
2301
  }
2191
- function renderHudText(inspection, preset) {
2192
- if (!inspection.found) {
2193
- return [
2194
- "LongTable HUD",
2195
- "- workspace: not found",
2196
- "- run `longtable start` for durable research state"
2197
- ].join("\n");
2198
- }
2199
- const lines = [
2200
- "LongTable HUD",
2201
- `- project: ${inspection.project?.name}`,
2202
- `- goal: ${inspection.session?.currentGoal}`,
2203
- ...(inspection.session?.currentBlocker ? [`- blocker: ${inspection.session.currentBlocker}`] : []),
2204
- `- questions: ${inspection.counts?.pendingQuestions ?? 0} pending / ${inspection.counts?.questions ?? 0} total`,
2205
- `- decisions: ${inspection.counts?.decisions ?? 0}`,
2206
- `- invocations: ${inspection.counts?.invocations ?? 0}`
2207
- ];
2208
- if (preset !== "minimal") {
2209
- lines.push("- pending checkpoints:");
2210
- for (const question of inspection.pendingQuestions ?? []) {
2211
- lines.push(` - ${question.required ? "required" : "advisory"}: ${question.question}`);
2212
- }
2213
- lines.push("- recent decisions:");
2214
- for (const decision of inspection.recentDecisions ?? []) {
2215
- lines.push(` - ${decision.summary}`);
2216
- }
2217
- }
2218
- return lines.join("\n");
2219
- }
2220
- async function runHud(args) {
2221
- const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2222
- const preset = typeof args.preset === "string" ? args.preset : "full";
2223
- if (args.tmux === true) {
2224
- if (!process.env.TMUX) {
2225
- throw new Error("`longtable hud --tmux` must be run inside an existing tmux session.");
2226
- }
2227
- const launcher = process.argv[1] ?? "longtable";
2228
- const command = `node ${shellEscape(launcher)} hud --watch --preset ${shellEscape(preset)} --cwd ${shellEscape(workingDirectory)}`;
2229
- execFileSync("tmux", ["split-window", "-v", "-l", "10", command], { stdio: "inherit" });
2230
- return;
2231
- }
2232
- while (true) {
2233
- const inspection = await inspectProjectWorkspace(workingDirectory);
2234
- if (args.json === true) {
2235
- console.log(JSON.stringify(inspection, null, 2));
2236
- return;
2237
- }
2238
- if (args.watch === true) {
2239
- process.stdout.write("\u001Bc");
2240
- }
2241
- console.log(renderHudText(inspection, preset));
2242
- if (args.watch !== true) {
2243
- return;
2244
- }
2245
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 1500));
2246
- }
2247
- }
2248
2302
  async function runTeam(args) {
2249
2303
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2250
2304
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
@@ -2722,10 +2776,6 @@ async function main() {
2722
2776
  await runPanelCommand(values);
2723
2777
  return;
2724
2778
  }
2725
- if (command === "hud") {
2726
- await runHud(values);
2727
- return;
2728
- }
2729
2779
  if (command === "sentinel") {
2730
2780
  await runSentinel(values);
2731
2781
  return;
@@ -466,7 +466,13 @@ export async function assertWorkspaceNotBlocked(context) {
466
466
  `Record an answer with: longtable decide --question ${first.id} --answer <value>`
467
467
  ].join("\n"));
468
468
  }
469
- function questionTitleForCheckpoint(family) {
469
+ function questionTitleForCheckpoint(family, checkpointKey) {
470
+ if (checkpointKey === "knowledge_gap_probe") {
471
+ return "Knowledge-gap checkpoint";
472
+ }
473
+ if (checkpointKey === "tacit_assumption_probe") {
474
+ return "Tacit-assumption checkpoint";
475
+ }
470
476
  switch (family) {
471
477
  case "meta_decision":
472
478
  return "Meta-decision checkpoint";
@@ -486,7 +492,13 @@ function questionTitleForCheckpoint(family) {
486
492
  return "Researcher Checkpoint";
487
493
  }
488
494
  }
489
- function questionTextForCheckpoint(family, prompt) {
495
+ function questionTextForCheckpoint(family, prompt, checkpointKey) {
496
+ if (checkpointKey === "knowledge_gap_probe") {
497
+ return "What knowledge gap should LongTable make explicit before narrowing or recommending a direction?";
498
+ }
499
+ if (checkpointKey === "tacit_assumption_probe") {
500
+ return "What tacit assumption should LongTable surface before treating this direction as acceptable?";
501
+ }
490
502
  switch (family) {
491
503
  case "meta_decision":
492
504
  return "What should LongTable do before treating this platform decision as settled?";
@@ -505,6 +517,22 @@ function questionTextForCheckpoint(family, prompt) {
505
517
  }
506
518
  }
507
519
  function optionsForCheckpointTrigger(family, checkpointKey) {
520
+ if (checkpointKey === "knowledge_gap_probe") {
521
+ return [
522
+ { value: "ask_first", label: "Ask the gap question first", description: "Pause synthesis and make the missing knowledge explicit." },
523
+ { value: "gather_context", label: "Gather context first", description: "Inspect sources, files, or constraints before narrowing." },
524
+ { value: "proceed_tentatively", label: "Proceed tentatively", description: "Continue, but keep the knowledge gap visible as an open tension." },
525
+ { value: "defer", label: "Keep the gap open", description: "Do not convert the uncertainty into a recommendation yet." }
526
+ ];
527
+ }
528
+ if (checkpointKey === "tacit_assumption_probe") {
529
+ return [
530
+ { value: "surface_assumption", label: "Surface the assumption first", description: "Name the implicit premise before accepting the direction." },
531
+ { value: "test_assumption", label: "Test the assumption first", description: "Look for evidence or counterexamples before proceeding." },
532
+ { value: "proceed_with_risk", label: "Proceed while logging the risk", description: "Continue, but record the assumption as unresolved." },
533
+ { value: "defer", label: "Keep the assumption open", description: "Do not treat this framing as settled yet." }
534
+ ];
535
+ }
508
536
  if (family === "evidence") {
509
537
  return [
510
538
  { value: "verify", label: "Verify evidence first", description: "Check whether the source supports the specific claim." },
@@ -744,8 +772,8 @@ export async function createWorkspaceQuestion(options) {
744
772
  prompt: {
745
773
  id: createId("question_prompt"),
746
774
  checkpointKey: trigger.signal.checkpointKey,
747
- title: options.title ?? questionTitleForCheckpoint(trigger.family),
748
- question: options.question ?? questionTextForCheckpoint(trigger.family, options.prompt),
775
+ title: options.title ?? questionTitleForCheckpoint(trigger.family, trigger.signal.checkpointKey),
776
+ question: options.question ?? questionTextForCheckpoint(trigger.family, options.prompt, trigger.signal.checkpointKey),
749
777
  type: "single_choice",
750
778
  options: optionsForCheckpointTrigger(trigger.family, trigger.signal.checkpointKey),
751
779
  allowOther: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
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.21",
32
- "@longtable/core": "0.1.21",
33
- "@longtable/memory": "0.1.21",
34
- "@longtable/provider-claude": "0.1.21",
35
- "@longtable/provider-codex": "0.1.21",
36
- "@longtable/setup": "0.1.21"
31
+ "@longtable/checkpoints": "0.1.23",
32
+ "@longtable/core": "0.1.23",
33
+ "@longtable/memory": "0.1.23",
34
+ "@longtable/provider-claude": "0.1.23",
35
+ "@longtable/provider-codex": "0.1.23",
36
+ "@longtable/setup": "0.1.23"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.1",