@longtable/cli 0.1.38 → 0.1.40

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/README.md CHANGED
@@ -156,16 +156,21 @@ longtable codex install-skills
156
156
  longtable claude install-skills
157
157
  ```
158
158
 
159
- Codex skills include `longtable`, `longtable-panel`, and generated role-specific
160
- skills such as `longtable-methods-critic`. If your Codex build exposes explicit
161
- skill shortcuts, `$longtable-interview` is the research-start entry and
162
- `$longtable` is the general router. Do not depend on `/prompts`; current Codex
163
- builds may reject it.
164
-
165
- Claude Code skills include `longtable`, `longtable-panel`, and generated
166
- role-specific skills such as `longtable-methods-critic`. They also include
167
- `longtable-interview` for the First Research Shape workflow. They are adapter
168
- files generated from the LongTable role registry.
159
+ By default, provider skills use the compact surface: `longtable`,
160
+ `longtable-interview`, and five short role shortcuts: `longtable-methods`,
161
+ `longtable-measure`, `longtable-theory`, `longtable-reviewer`, and
162
+ `longtable-voice`. `$longtable` remains the general router and can still invoke
163
+ editor, ethics, venue, panel, explore, or review behavior when the request calls
164
+ for it.
165
+
166
+ Power users can install the legacy full surface explicitly:
167
+
168
+ ```bash
169
+ longtable codex install-skills --surface full
170
+ longtable claude install-skills --surface full
171
+ ```
172
+
173
+ Do not depend on `/prompts`; current Codex builds may reject it.
169
174
 
170
175
  ## Panel Orchestration
171
176
 
package/dist/cli.js CHANGED
@@ -17,7 +17,7 @@ import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router
17
17
  import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
18
18
  import { buildPanelFallback, renderPanelSummary } from "./panel.js";
19
19
  import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, mergeManagedCodexHooksConfig, removeManagedCodexHooks } from "./codex-hooks.js";
20
- import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
20
+ import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, pruneWorkspaceQuestions, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
21
21
  import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
22
22
  import { createPromptRenderer } from "./prompt-renderer.js";
23
23
  const VALID_MODES = new Set([
@@ -45,7 +45,7 @@ const ANSI = {
45
45
  green: "\u001B[32m"
46
46
  };
47
47
  const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
48
- const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.38";
48
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.39";
49
49
  const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
50
50
  const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
51
51
  function style(text, prefix) {
@@ -120,16 +120,17 @@ function usage() {
120
120
  " longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
121
121
  " longtable explore|review|critique|draft|commit|submit [--prompt <text>] [--role <role[,role]>] [--panel] [--show-conflicts] [--show-deliberation] [--print] [--json] [--stage <stage>] [--setup <path>] [--cwd <path>]",
122
122
  " longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
123
- " longtable codex install-skills [--dir <path>]",
123
+ " longtable codex install-skills [--surface compact|full] [--dir <path>]",
124
124
  " longtable codex remove-skills [--dir <path>]",
125
125
  " longtable codex install-prompts [--dir <path>]",
126
126
  " longtable codex remove-prompts [--dir <path>]",
127
127
  " longtable codex install-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
128
128
  " longtable codex remove-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
129
- " longtable codex status [--dir <path>] [--codex-config <path>] [--hooks-path <path>] [--json]",
130
- " longtable claude install-skills [--dir <path>]",
129
+ " longtable codex status [--surface compact|full] [--dir <path>] [--codex-config <path>] [--hooks-path <path>] [--json]",
130
+ " longtable claude install-skills [--surface compact|full] [--dir <path>]",
131
131
  " longtable claude remove-skills [--dir <path>]",
132
- " longtable claude status [--dir <path>] [--json]",
132
+ " longtable claude status [--surface compact|full] [--dir <path>] [--json]",
133
+ " longtable prune-questions [--cwd <path>] [--dry-run] [--json]",
133
134
  " longtable mcp install --provider all",
134
135
  "",
135
136
  "Examples:",
@@ -150,7 +151,7 @@ function parseArgs(argv) {
150
151
  const values = {};
151
152
  let subcommand = maybeSubcommand;
152
153
  const modeCommand = command && VALID_MODES.has(command);
153
- const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "panel", "decide", "sentinel", "team", "search"].includes(command);
154
+ const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "decide", "sentinel", "team", "search"].includes(command);
154
155
  let startIndex = 1;
155
156
  if (modeCommand) {
156
157
  subcommand = undefined;
@@ -238,6 +239,16 @@ function questionSection(questionId) {
238
239
  function formatModeLabel(mode) {
239
240
  return `${mode[0].toUpperCase()}${mode.slice(1)}`;
240
241
  }
242
+ function parseSkillSurface(args) {
243
+ const value = args.surface;
244
+ if (value === undefined || value === true) {
245
+ return "compact";
246
+ }
247
+ if (value === "compact" || value === "full") {
248
+ return value;
249
+ }
250
+ throw new Error("Invalid --surface value. Use compact or full.");
251
+ }
241
252
  function stripWrappingQuotes(value) {
242
253
  const trimmed = value.trim();
243
254
  if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
@@ -1416,6 +1427,7 @@ function setupForProvider(setup, provider) {
1416
1427
  }
1417
1428
  async function collectDoctorStatus(args) {
1418
1429
  const roles = listRoleDefinitions();
1430
+ const skillSurface = parseSkillSurface(args);
1419
1431
  const setupOverride = typeof args.setup === "string"
1420
1432
  ? args.setup
1421
1433
  : typeof args.path === "string"
@@ -1458,11 +1470,11 @@ async function collectDoctorStatus(args) {
1458
1470
  const missingManagedHookEvents = codexHooksContent
1459
1471
  ? (getMissingManagedCodexHookEvents(codexHooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
1460
1472
  : [...LONGTABLE_MANAGED_HOOK_EVENTS];
1461
- const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
1462
- const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
1473
+ const expectedCodexSkills = buildCodexSkillSpecs(roles, skillSurface).map((skill) => skill.name);
1474
+ const expectedClaudeSkills = buildClaudeSkillSpecs(roles, skillSurface).map((skill) => skill.name);
1463
1475
  const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
1464
- listInstalledCodexSkills(roles, codexDir),
1465
- listInstalledClaudeSkills(roles, claudeDir),
1476
+ listInstalledCodexSkills(roles, codexDir, skillSurface),
1477
+ listInstalledClaudeSkills(roles, claudeDir, skillSurface),
1466
1478
  listInstalledCodexPromptAliases(codexPromptsDir),
1467
1479
  inspectProjectWorkspace(typeof args.cwd === "string" ? args.cwd : cwd())
1468
1480
  ]);
@@ -1646,6 +1658,7 @@ function renderRepairSummary(repair) {
1646
1658
  }
1647
1659
  async function repairDoctorStatus(args, status) {
1648
1660
  const roles = listRoleDefinitions();
1661
+ const skillSurface = parseSkillSurface(args);
1649
1662
  const codexDir = typeof args["codex-dir"] === "string"
1650
1663
  ? args["codex-dir"]
1651
1664
  : typeof args.dir === "string"
@@ -1684,10 +1697,10 @@ async function repairDoctorStatus(args, status) {
1684
1697
  skipped: []
1685
1698
  };
1686
1699
  if (status.providers.codex.missingSkills.length > 0) {
1687
- repair.installedCodexSkills = (await installCodexSkills(roles, codexDir)).map((skill) => skill.name);
1700
+ repair.installedCodexSkills = (await installCodexSkills(roles, codexDir, skillSurface)).map((skill) => skill.name);
1688
1701
  }
1689
1702
  if (status.providers.claude.missingSkills.length > 0) {
1690
- repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir)).map((skill) => skill.name);
1703
+ repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir, skillSurface)).map((skill) => skill.name);
1691
1704
  }
1692
1705
  if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
1693
1706
  repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
@@ -1851,10 +1864,10 @@ function runRoleAudit() {
1851
1864
  "longtable-review"
1852
1865
  ]);
1853
1866
  const roles = [
1854
- ...buildCodexSkillSpecs(listRoleDefinitions())
1867
+ ...buildCodexSkillSpecs(listRoleDefinitions(), "full")
1855
1868
  .filter((spec) => !baseSkillNames.has(spec.name))
1856
1869
  .map((spec) => buildRoleAuditEntry("codex", spec)),
1857
- ...buildClaudeSkillSpecs(listRoleDefinitions())
1870
+ ...buildClaudeSkillSpecs(listRoleDefinitions(), "full")
1858
1871
  .filter((spec) => !baseSkillNames.has(spec.name))
1859
1872
  .map((spec) => buildRoleAuditEntry("claude", spec))
1860
1873
  ];
@@ -2671,6 +2684,41 @@ async function runClearQuestion(args) {
2671
2684
  console.log(`- state: ${context.stateFilePath}`);
2672
2685
  console.log(`- current: ${context.currentFilePath}`);
2673
2686
  }
2687
+ async function runPruneQuestions(args) {
2688
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2689
+ const context = await loadProjectContextFromDirectory(workingDirectory);
2690
+ if (!context) {
2691
+ throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
2692
+ }
2693
+ const dryRun = args["dry-run"] === true;
2694
+ const result = await pruneWorkspaceQuestions({
2695
+ context,
2696
+ dryRun
2697
+ });
2698
+ if (args.json === true) {
2699
+ console.log(JSON.stringify({
2700
+ dryRun,
2701
+ removedCount: result.removedQuestions.length,
2702
+ removedQuestions: result.removedQuestions.map((question) => ({
2703
+ id: question.id,
2704
+ title: question.prompt.title,
2705
+ reason: question.clearedReason
2706
+ })),
2707
+ files: {
2708
+ state: context.stateFilePath,
2709
+ current: context.currentFilePath
2710
+ }
2711
+ }, null, 2));
2712
+ return;
2713
+ }
2714
+ console.log(dryRun ? "LongTable question prune preview" : "LongTable questions pruned");
2715
+ console.log(`- removed false-positive cleared questions: ${result.removedQuestions.length}`);
2716
+ for (const question of result.removedQuestions) {
2717
+ console.log(` - ${question.id}: ${question.prompt.title}`);
2718
+ }
2719
+ console.log(`- state: ${context.stateFilePath}`);
2720
+ console.log(`- current: ${context.currentFilePath}`);
2721
+ }
2674
2722
  function isInteractiveTerminal() {
2675
2723
  return Boolean(input.isTTY && output.isTTY);
2676
2724
  }
@@ -2805,10 +2853,24 @@ async function runClarify(args) {
2805
2853
  console.log(`- state: ${context.stateFilePath}`);
2806
2854
  console.log(`- current: ${context.currentFilePath}`);
2807
2855
  }
2856
+ function looksLikeProductOrToolingPrompt(prompt) {
2857
+ return /\b(longlongtable|hook|checkpoint|mcp|agents?|skills?|ux|interface|setup|install|cli|npm|version|global|release|deploy|git|github|readme|docs?|documentation|workflow|package|router|autocomplete|simulation test)\b/i.test(prompt)
2858
+ || /롱테이블|훅|체크포인트|에이전트|스킬|사용성|인터페이스|설치|세팅|글로벌|배포|버전|릴리즈|깃|깃허브|문서화된\s*절차|패키지|라우터|자동완성|시뮬레이션\s*테스트/.test(prompt);
2859
+ }
2860
+ function looksLikeResearchCommitmentPrompt(prompt) {
2861
+ const researchCue = /\b(research|study|paper|manuscript|journal|article|method|methodology|measurement|construct|theory|analysis|model|data|participant|sample|scale|survey|instrument|validity|hypothesis|literature|meta[- ]?analysis|gold standard|coding|trust|reliance|calibration)\b/i.test(prompt)
2862
+ || /연구|논문|원고|저널|방법론|방법|연구\s*설계|측정|구성개념|개념|이론|분석|모형|모델|데이터|참가자|표본|샘플|척도|설문|도구|타당도|가설|문헌|메타\s*분석|골드\s*스탠더드|코딩|신뢰|의존|캘리브레이션|교정|보정/.test(prompt);
2863
+ const closureCue = /\b(final|finalize|commit|ship|submit|publish|freeze|settle|decide|lock|record|apply|incorporate)\b/i.test(prompt)
2864
+ || /최종|확정|커밋|제출|투고|고정|결정|기록|반영/.test(prompt);
2865
+ return researchCue && closureCue;
2866
+ }
2808
2867
  async function runAutomaticFollowUpIfNeeded(prompt, args) {
2809
2868
  if (args["no-clarify"] === true || args.print === true || args.json === true) {
2810
2869
  return false;
2811
2870
  }
2871
+ if (looksLikeProductOrToolingPrompt(prompt) || !looksLikeResearchCommitmentPrompt(prompt)) {
2872
+ return false;
2873
+ }
2812
2874
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2813
2875
  const context = await loadProjectContextFromDirectory(workingDirectory);
2814
2876
  if (!context) {
@@ -2820,7 +2882,8 @@ async function runAutomaticFollowUpIfNeeded(prompt, args) {
2820
2882
  prompt,
2821
2883
  provider,
2822
2884
  required: true,
2823
- auto: true
2885
+ auto: true,
2886
+ requiredOnly: true
2824
2887
  });
2825
2888
  if (result.questions.length === 0) {
2826
2889
  return false;
@@ -3241,11 +3304,12 @@ async function runResume(args) {
3241
3304
  async function runCodexSubcommand(subcommand, args) {
3242
3305
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
3243
3306
  const roles = listRoleDefinitions();
3307
+ const skillSurface = parseSkillSurface(args);
3244
3308
  if (subcommand === "install-skills") {
3245
- const installed = await installCodexSkills(roles, customDir);
3246
- console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)}`);
3309
+ const installed = await installCodexSkills(roles, customDir, skillSurface);
3310
+ console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)} (${skillSurface} surface)`);
3247
3311
  console.log("Use them inside Codex with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
3248
- console.log("If you want an explicit trigger, use `$longtable` when your Codex build exposes skills that way.");
3312
+ console.log("Use `$longtable` as the general router; compact installs expose only the most common role shortcuts.");
3249
3313
  for (const skill of installed) {
3250
3314
  console.log(`- ${skill.name}`);
3251
3315
  }
@@ -3295,7 +3359,7 @@ async function runCodexSubcommand(subcommand, args) {
3295
3359
  }
3296
3360
  if (subcommand === "status") {
3297
3361
  const aliases = await listInstalledCodexPromptAliases(customDir);
3298
- const skills = await listInstalledCodexSkills(roles, customDir);
3362
+ const skills = await listInstalledCodexSkills(roles, customDir, skillSurface);
3299
3363
  const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
3300
3364
  const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
3301
3365
  const configPath = resolveCodexMcpConfigPath(args);
@@ -3308,6 +3372,7 @@ async function runCodexSubcommand(subcommand, args) {
3308
3372
  runtimePath,
3309
3373
  runtimeExists: existsSync(runtimePath),
3310
3374
  skillsDir: resolveCodexSkillsDir(customDir),
3375
+ skillSurface,
3311
3376
  skillsInstalled: skills.map((skill) => skill.name),
3312
3377
  promptsDir: resolveCodexPromptsDir(customDir),
3313
3378
  legacyPromptFilesInstalled: aliases.map((alias) => alias.name),
@@ -3327,6 +3392,7 @@ async function runCodexSubcommand(subcommand, args) {
3327
3392
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
3328
3393
  console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
3329
3394
  console.log(`- skills dir: ${status.skillsDir}`);
3395
+ console.log(`- skill surface: ${status.skillSurface}`);
3330
3396
  if (skills.length === 0) {
3331
3397
  console.log("- skills: none");
3332
3398
  }
@@ -3358,9 +3424,10 @@ async function runCodexSubcommand(subcommand, args) {
3358
3424
  async function runClaudeSubcommand(subcommand, args) {
3359
3425
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
3360
3426
  const roles = listRoleDefinitions();
3427
+ const skillSurface = parseSkillSurface(args);
3361
3428
  if (subcommand === "install-skills") {
3362
- const installed = await installClaudeSkills(roles, customDir);
3363
- console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)}`);
3429
+ const installed = await installClaudeSkills(roles, customDir, skillSurface);
3430
+ console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)} (${skillSurface} surface)`);
3364
3431
  console.log("Use them inside Claude Code with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
3365
3432
  for (const skill of installed) {
3366
3433
  console.log(`- ${skill.name}`);
@@ -3373,7 +3440,7 @@ async function runClaudeSubcommand(subcommand, args) {
3373
3440
  return;
3374
3441
  }
3375
3442
  if (subcommand === "status") {
3376
- const skills = await listInstalledClaudeSkills(roles, customDir);
3443
+ const skills = await listInstalledClaudeSkills(roles, customDir, skillSurface);
3377
3444
  const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
3378
3445
  const runtimePath = resolveDefaultRuntimeConfigPath("claude", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
3379
3446
  const status = {
@@ -3382,6 +3449,7 @@ async function runClaudeSubcommand(subcommand, args) {
3382
3449
  runtimePath,
3383
3450
  runtimeExists: existsSync(runtimePath),
3384
3451
  skillsDir: resolveClaudeSkillsDir(customDir),
3452
+ skillSurface,
3385
3453
  skillsInstalled: skills.map((skill) => skill.name)
3386
3454
  };
3387
3455
  if (args.json === true) {
@@ -3392,6 +3460,7 @@ async function runClaudeSubcommand(subcommand, args) {
3392
3460
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
3393
3461
  console.log(`- claude runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
3394
3462
  console.log(`- skills dir: ${status.skillsDir}`);
3463
+ console.log(`- skill surface: ${status.skillSurface}`);
3395
3464
  if (skills.length === 0) {
3396
3465
  console.log("- skills: none");
3397
3466
  }
@@ -3472,6 +3541,10 @@ async function main() {
3472
3541
  await runClearQuestion(values);
3473
3542
  return;
3474
3543
  }
3544
+ if (command === "prune-questions") {
3545
+ await runPruneQuestions(values);
3546
+ return;
3547
+ }
3475
3548
  if (command === "panel") {
3476
3549
  await runPanelCommand(values);
3477
3550
  return;
@@ -1,5 +1,5 @@
1
1
  import { pathToFileURL } from "node:url";
2
- import { createWorkspaceFollowUpQuestions, loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
2
+ import { buildQuestionOpportunitySpecs, createWorkspaceFollowUpQuestions, loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
3
3
  function safeString(value) {
4
4
  return typeof value === "string" ? value : "";
5
5
  }
@@ -107,43 +107,50 @@ function looksLikeClosurePrompt(prompt) {
107
107
  if (!normalized) {
108
108
  return false;
109
109
  }
110
- return /\b(final|finalize|commit|ship|submit|revise|rewrite|draft|publish|implement|fix)\b/i.test(normalized)
111
- || /최종|확정|커밋|제출|수정|초안|구현|진행|고쳐/.test(normalized);
110
+ return /\b(final|finalize|commit|ship|submit|publish|freeze|settle|decide|lock|record|apply|incorporate)\b/i.test(normalized)
111
+ || /최종|확정|커밋|제출|투고|고정|결정|기록|반영/.test(normalized);
112
112
  }
113
- function looksLikeExecutionDirective(prompt) {
113
+ function looksLikeLongTableProductOrToolingPrompt(prompt) {
114
114
  const normalized = prompt.trim();
115
115
  if (!normalized) {
116
116
  return false;
117
117
  }
118
- return /\b(proceed|implement|fix|publish|release|deploy|tag|push|ship)\b/i.test(normalized)
119
- || /진행|구현|수정|고쳐|배포|릴리즈|태그|푸시|출시/.test(normalized);
118
+ return /\b(longlongtable|hook|checkpoint|mcp|agents?|skills?|ux|interface|setup|install|cli|npm|version|global|release|deploy|git|github|readme|docs?|documentation|workflow|package|router|autocomplete|simulation test)\b/i.test(normalized)
119
+ || /롱테이블|훅|체크포인트|에이전트|스킬|사용성|인터페이스|설치|세팅|글로벌|배포|버전|릴리즈|깃|깃허브|문서화된\s*절차|패키지|라우터|자동완성|시뮬레이션\s*테스트/.test(normalized);
120
120
  }
121
- function looksLikeLongTableEngineeringPrompt(prompt) {
121
+ function looksLikeResearchDomainPrompt(prompt) {
122
122
  const normalized = prompt.trim();
123
123
  if (!normalized) {
124
124
  return false;
125
125
  }
126
- return /\b(longtable|hook|checkpoint|mcp|agent|npm|version|global|publish|release|deploy|git)\b/i.test(normalized)
127
- || /롱테이블|훅|체크포인트|에이전트|글로벌|배포|버전|릴리즈|깃|깃허브/.test(normalized);
126
+ return /\b(research|study|paper|manuscript|journal|article|method|methodology|measurement|construct|theory|analysis|model|data|participant|sample|scale|survey|instrument|validity|hypothesis|literature|meta[- ]?analysis|gold standard|coding|trust|reliance|calibration)\b/i.test(normalized)
127
+ || /연구|논문|원고|저널|방법론|방법|연구\s*설계|측정|구성개념|개념|이론|분석|모형|모델|데이터|참가자|표본|샘플|척도|설문|도구|타당도|가설|문헌|메타\s*분석|골드\s*스탠더드|코딩|신뢰|의존|캘리브레이션|교정|보정/.test(normalized);
128
128
  }
129
- function looksLikeDiagnosticOrExplanatoryPrompt(prompt) {
130
- const normalized = prompt.trim();
131
- if (!normalized) {
132
- return false;
133
- }
134
- return /\b(explain|why|how|diagnos(?:e|is)|debug|inspect|check|verify|status|simulate|simulation|test|logs?)\b/i.test(normalized)
135
- || /설명|왜|어떻게|진단|디버그|확인|점검|상태|시뮬레이션|테스트|로그|불필요/.test(normalized);
129
+ function looksLikeResearchCommitmentPrompt(prompt) {
130
+ return looksLikeResearchDomainPrompt(prompt) && looksLikeClosurePrompt(prompt);
136
131
  }
137
- function shouldAutoCreateQuestionsForPrompt(prompt) {
138
- if (!looksLikeLongTableEngineeringPrompt(prompt)) {
139
- return true;
132
+ function buildResponseOnlyAdvisoryQuestions(prompt) {
133
+ if (looksLikeLongTableProductOrToolingPrompt(prompt)) {
134
+ return [];
140
135
  }
141
- return !looksLikeExecutionDirective(prompt) && !looksLikeDiagnosticOrExplanatoryPrompt(prompt);
136
+ const opportunities = buildQuestionOpportunitySpecs(prompt, {
137
+ includeFallback: false,
138
+ autoOnly: true
139
+ });
140
+ if (opportunities.length === 0) {
141
+ return [];
142
+ }
143
+ if (!looksLikeResearchDomainPrompt(prompt) && !/\b(needed questions?|necessary questions?|clarifying questions?|question generation|assumptions?|uncertain|not sure|gap|tension|trade[- ]?off)\b/i.test(prompt) && !/필요한\s*질문|질문\s*생성|물어봐|질문해|전제|가정|불확실|모르겠|공백|긴장|상충|균형/.test(prompt)) {
144
+ return [];
145
+ }
146
+ return opportunities.slice(0, 3);
147
+ }
148
+ function shouldCreateRequiredQuestionsForPrompt(prompt) {
149
+ return !looksLikeLongTableProductOrToolingPrompt(prompt) && looksLikeResearchCommitmentPrompt(prompt);
142
150
  }
143
151
  function shouldApplyProtectedDecisionClosure(runtime, prompt) {
144
152
  return Boolean(runtime.context.session.protectedDecision) &&
145
- looksLikeClosurePrompt(prompt) &&
146
- !looksLikeLongTableEngineeringPrompt(prompt);
153
+ shouldCreateRequiredQuestionsForPrompt(prompt);
147
154
  }
148
155
  function protectedDecisionClosurePrompt(prompt) {
149
156
  return [
@@ -213,6 +220,16 @@ function buildGeneratedQuestionsContext(questions, created) {
213
220
  lines.push("Do not choose or record answers for these checkpoints unless the researcher explicitly provides the selections.");
214
221
  return lines.join("\n");
215
222
  }
223
+ function buildAdvisoryQuestionsContext(questions) {
224
+ const lines = [
225
+ `LongTable surfaced ${questions.length} response-only advisory question${questions.length === 1 ? "" : "s"} for this prompt.`,
226
+ "Use these only if they help the reply. Do not create QuestionRecord entries, call longtable decide, or answer for the researcher unless the prompt explicitly asks to commit a research decision."
227
+ ];
228
+ for (const question of questions) {
229
+ lines.push(`- ${question.title}: ${question.question}`);
230
+ }
231
+ return lines.join("\n");
232
+ }
216
233
  function buildPendingObligationContext(obligation) {
217
234
  return [
218
235
  `Pending LongTable research obligation: ${obligation.prompt}`,
@@ -264,13 +281,14 @@ async function userPromptSubmitContext(runtime, prompt) {
264
281
  }
265
282
  const generatedQuestions = [];
266
283
  let createdQuestions = false;
267
- if (shouldAutoCreateQuestionsForPrompt(prompt)) {
284
+ if (shouldCreateRequiredQuestionsForPrompt(prompt)) {
268
285
  const generated = await createWorkspaceFollowUpQuestions({
269
286
  context: runtime.context,
270
287
  prompt,
271
288
  provider: "codex",
272
289
  required: true,
273
- auto: true
290
+ auto: true,
291
+ requiredOnly: true
274
292
  });
275
293
  generatedQuestions.push(...generated.questions);
276
294
  createdQuestions = createdQuestions || generated.created;
@@ -281,7 +299,8 @@ async function userPromptSubmitContext(runtime, prompt) {
281
299
  prompt: protectedDecisionClosurePrompt(prompt),
282
300
  provider: "codex",
283
301
  required: true,
284
- auto: true
302
+ auto: true,
303
+ requiredOnly: true
285
304
  });
286
305
  generatedQuestions.push(...protectedGenerated.questions.filter((question) => !generatedQuestions.some((existing) => existing.id === question.id)));
287
306
  createdQuestions = createdQuestions || protectedGenerated.created;
@@ -289,11 +308,9 @@ async function userPromptSubmitContext(runtime, prompt) {
289
308
  if (generatedQuestions.length > 0) {
290
309
  return buildGeneratedQuestionsContext(generatedQuestions, createdQuestions);
291
310
  }
292
- if (shouldApplyProtectedDecisionClosure(runtime, prompt)) {
293
- return [
294
- `This workspace marks ${runtime.context.session.protectedDecision} as a protected decision.`,
295
- "Before you settle it through drafting, revision, or closure, surface one researcher-facing checkpoint grounded in the current blocker or open questions."
296
- ].join("\n");
311
+ const advisoryQuestions = buildResponseOnlyAdvisoryQuestions(prompt);
312
+ if (advisoryQuestions.length > 0) {
313
+ return buildAdvisoryQuestionsContext(advisoryQuestions);
297
314
  }
298
315
  return null;
299
316
  }
@@ -227,6 +227,7 @@ type FollowUpQuestionSpec = QuestionOpportunity;
227
227
  interface BuildFollowUpQuestionOptions {
228
228
  includeFallback?: boolean;
229
229
  autoOnly?: boolean;
230
+ requiredOnly?: boolean;
230
231
  }
231
232
  export declare function buildQuestionOpportunitySpecs(prompt: string, options?: BuildFollowUpQuestionOptions): FollowUpQuestionSpec[];
232
233
  export declare function generateQuestionOpportunities(prompt: string, options?: BuildFollowUpQuestionOptions): QuestionGenerationResult;
@@ -237,6 +238,7 @@ export declare function createWorkspaceFollowUpQuestions(options: {
237
238
  required?: boolean;
238
239
  force?: boolean;
239
240
  auto?: boolean;
241
+ requiredOnly?: boolean;
240
242
  }): Promise<{
241
243
  questions: QuestionRecord[];
242
244
  state: ResearchState;
@@ -277,6 +279,13 @@ export declare function clearWorkspaceQuestion(options: {
277
279
  question: QuestionRecord;
278
280
  state: ResearchState;
279
281
  }>;
282
+ export declare function pruneWorkspaceQuestions(options: {
283
+ context: LongTableProjectContext;
284
+ dryRun?: boolean;
285
+ }): Promise<{
286
+ removedQuestions: QuestionRecord[];
287
+ state: ResearchState;
288
+ }>;
280
289
  export declare function repairWorkspaceStateConsistency(options: {
281
290
  context: LongTableProjectContext;
282
291
  }): Promise<{
@@ -990,7 +990,7 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
990
990
  function push(spec) {
991
991
  if (!specs.some((candidate) => candidate.key === spec.key)) {
992
992
  specs.push({
993
- required: true,
993
+ required: false,
994
994
  confidence: "medium",
995
995
  autoEligible: false,
996
996
  cues: [],
@@ -1076,6 +1076,7 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
1076
1076
  options: followUpQuestionOptions({ value: "keep_open", label: "Keep it open", description: "Do not treat the decision as settled; preserve it as an explicit blocker.", recommended: true }, { value: "ask_researcher", label: "Ask the researcher now", description: "Pause execution until the researcher chooses the decision boundary." }, { value: "proceed_with_record", label: "Proceed with record", description: "Continue only after recording the assumption and residual risk." }),
1077
1077
  confidence: "high",
1078
1078
  autoEligible: true,
1079
+ required: true,
1079
1080
  cues: ["protected_decision", "closure_pressure"]
1080
1081
  });
1081
1082
  }
@@ -1226,7 +1227,11 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
1226
1227
  options: followUpQuestionOptions({ value: "scope", label: "Clarify scope first", description: "Ask what is included and excluded before acting.", recommended: true }, { value: "criteria", label: "Clarify success criteria", description: "Ask what would count as a good result." }, { value: "proceed", label: "Proceed with visible assumptions", description: "Continue, but make assumptions explicit." })
1227
1228
  });
1228
1229
  }
1229
- return options.autoOnly === true ? specs.filter((spec) => spec.autoEligible) : specs;
1230
+ let selected = options.autoOnly === true ? specs.filter((spec) => spec.autoEligible) : specs;
1231
+ if (options.requiredOnly === true) {
1232
+ selected = selected.filter((spec) => spec.kind === "research_commitment");
1233
+ }
1234
+ return selected;
1230
1235
  }
1231
1236
  function buildFollowUpQuestionSpecs(prompt) {
1232
1237
  return buildQuestionOpportunitySpecs(prompt);
@@ -1261,7 +1266,8 @@ export async function createWorkspaceFollowUpQuestions(options) {
1261
1266
  : ["mcp_elicitation", "terminal_selector", "numbered"];
1262
1267
  const specs = buildQuestionOpportunitySpecs(options.prompt, {
1263
1268
  includeFallback: options.force === true ? true : options.auto !== true,
1264
- autoOnly: options.auto === true
1269
+ autoOnly: options.auto === true,
1270
+ requiredOnly: options.requiredOnly === true
1265
1271
  });
1266
1272
  if (specs.length === 0) {
1267
1273
  return { questions: [], state, created: false, alreadyAnswered: false };
@@ -1513,6 +1519,53 @@ export async function clearWorkspaceQuestion(options) {
1513
1519
  state: updated
1514
1520
  };
1515
1521
  }
1522
+ function isPrunableFalsePositiveQuestion(record) {
1523
+ if (record.status !== "cleared") {
1524
+ return false;
1525
+ }
1526
+ return /false-positive|duplicated automatic hook/i.test(record.clearedReason ?? "");
1527
+ }
1528
+ export async function pruneWorkspaceQuestions(options) {
1529
+ const state = await loadResearchState(options.context.stateFilePath);
1530
+ const removedQuestions = (state.questionLog ?? []).filter(isPrunableFalsePositiveQuestion);
1531
+ if (removedQuestions.length === 0 || options.dryRun) {
1532
+ return {
1533
+ removedQuestions,
1534
+ state
1535
+ };
1536
+ }
1537
+ const removedIds = new Set(removedQuestions.map((question) => question.id));
1538
+ const updated = {
1539
+ ...state,
1540
+ questionLog: (state.questionLog ?? []).filter((question) => !removedIds.has(question.id)),
1541
+ questionObligations: (state.questionObligations ?? []).filter((obligation) => !obligation.questionId || !removedIds.has(obligation.questionId)),
1542
+ invocationLog: (state.invocationLog ?? []).map((record) => ({
1543
+ ...record,
1544
+ ...(record.panelResult
1545
+ ? {
1546
+ panelResult: {
1547
+ ...record.panelResult,
1548
+ linkedQuestionRecordIds: record.panelResult.linkedQuestionRecordIds.filter((id) => !removedIds.has(id))
1549
+ }
1550
+ }
1551
+ : {}),
1552
+ ...(record.teamDebateRun
1553
+ ? {
1554
+ teamDebateRun: {
1555
+ ...record.teamDebateRun,
1556
+ linkedQuestionRecordIds: record.teamDebateRun.linkedQuestionRecordIds.filter((id) => !removedIds.has(id))
1557
+ }
1558
+ }
1559
+ : {})
1560
+ }))
1561
+ };
1562
+ await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
1563
+ await syncCurrentWorkspaceView(options.context);
1564
+ return {
1565
+ removedQuestions,
1566
+ state: updated
1567
+ };
1568
+ }
1516
1569
  export async function repairWorkspaceStateConsistency(options) {
1517
1570
  const state = await loadResearchState(options.context.stateFilePath);
1518
1571
  const repaired = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
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.38",
33
- "@longtable/core": "0.1.38",
34
- "@longtable/memory": "0.1.38",
35
- "@longtable/provider-claude": "0.1.38",
36
- "@longtable/provider-codex": "0.1.38",
37
- "@longtable/setup": "0.1.38"
32
+ "@longtable/checkpoints": "0.1.40",
33
+ "@longtable/core": "0.1.40",
34
+ "@longtable/memory": "0.1.40",
35
+ "@longtable/provider-claude": "0.1.40",
36
+ "@longtable/provider-codex": "0.1.40",
37
+ "@longtable/setup": "0.1.40"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.1",