@longtable/cli 0.1.21 → 0.1.22
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 +126 -76
- package/dist/project-session.js +32 -4
- package/package.json +7 -7
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.
|
|
45
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.22";
|
|
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", "
|
|
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(
|
|
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:
|
|
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,
|
|
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 = { 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
|
|
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
|
|
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;
|
package/dist/project-session.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.1.22",
|
|
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.22",
|
|
32
|
+
"@longtable/core": "0.1.22",
|
|
33
|
+
"@longtable/memory": "0.1.22",
|
|
34
|
+
"@longtable/provider-claude": "0.1.22",
|
|
35
|
+
"@longtable/provider-codex": "0.1.22",
|
|
36
|
+
"@longtable/setup": "0.1.22"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.1",
|