@longtable/cli 0.1.19 → 0.1.20

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
@@ -27,16 +27,15 @@ approval.
27
27
 
28
28
  ```bash
29
29
  longtable setup --provider codex
30
- longtable init --flow interview
31
30
  longtable start
32
31
  cd "<project-path>"
33
32
  codex
34
33
  ```
35
34
 
36
35
  `longtable setup --provider codex` is the permission-first setup route. It asks
37
- which runtime surfaces LongTable may enable and explains why each choice matters:
38
- CLI only, skills, skills + MCP, skills + MCP + sentinel, intervention posture,
39
- tmux HUD/console, and team discussion mode.
36
+ where LongTable may install support, which runtime surfaces it may enable, how
37
+ strongly it may interrupt research decisions, and whether to create a project
38
+ workspace now. `longtable init` remains only as a deprecated compatibility alias.
40
39
 
41
40
  Return later:
42
41
 
@@ -85,7 +84,7 @@ This is how LongTable avoids turning tacit knowledge into fake certainty.
85
84
  ## Commands
86
85
 
87
86
  ```bash
88
- longtable init
87
+ longtable setup
89
88
  longtable start
90
89
  longtable resume --cwd "<project-path>"
91
90
  longtable roles
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { stdin as input, stdout as output, cwd, exit } from "node:process";
8
8
  import { dirname, join, resolve } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
  import { classifyCheckpointTrigger } from "@longtable/checkpoints";
11
- import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
11
+ import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
12
12
  import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
13
13
  import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
14
14
  import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
@@ -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.19";
45
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.20";
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,9 +78,9 @@ 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 init [--flow quickstart|interview] [--provider codex|claude] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--checkpoint low|balanced|high] [--field <field>] [--authorship-signal <text>] [--entry-mode explore|review|critique|draft|commit] [--weakest-domain theory|methodology|measurement|analysis|writing] [--panel-preference synthesis_only|show_on_conflict|always_visible] [--json] [--no-install] [--install-skills] [--install-prompts]",
82
- " longtable setup [--provider codex|claude] [--json] [--dir <path>] [--skills-dir <path>] [--runtime-path <file>] [--setup-path <file>]",
83
- " longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
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>]",
82
+ " longtable init [deprecated alias for setup; full legacy flags still supported for automation]",
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
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
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>]",
@@ -109,7 +109,7 @@ function usage() {
109
109
  " longtable mcp install --provider all",
110
110
  "",
111
111
  "Examples:",
112
- " longtable init --flow interview --provider codex --install-skills",
112
+ " longtable setup --provider codex",
113
113
  " longtable start",
114
114
  " longtable start --path ~/Research/My-Project --name \"AI Adoption Meta-Analysis\" --goal \"Narrow the review question\"",
115
115
  " cd \"<project-path>\" && codex",
@@ -563,77 +563,72 @@ async function collectInteractiveAnswers(initialFlow) {
563
563
  }
564
564
  function buildPermissionSetupChoices() {
565
565
  return {
566
+ installScope: [
567
+ {
568
+ id: "user",
569
+ label: "User-level provider config",
570
+ description: "Why: available across projects. What you get: writes to ~/.codex or ~/.claude. Tradeoff: broader machine-level change."
571
+ },
572
+ {
573
+ id: "project",
574
+ label: "Current project only",
575
+ description: "Why: keeps LongTable local to this repository. What you get: project-scoped runtime files when supported. Tradeoff: not available elsewhere."
576
+ },
577
+ {
578
+ id: "none",
579
+ label: "Do not install provider files",
580
+ description: "Why: safest permission boundary. What you get: CLI setup record only. Tradeoff: no provider-native skills or MCP config."
581
+ }
582
+ ],
566
583
  surfaces: [
567
584
  {
568
585
  id: "cli_only",
569
586
  label: "CLI only",
570
- description: "Why: least invasive. Tradeoff: no natural in-provider LongTable entrypoints."
587
+ description: "Why: least invasive. What you get: setup record and CLI commands. Tradeoff: no natural in-provider LongTable entrypoints."
571
588
  },
572
589
  {
573
590
  id: "skills",
574
591
  label: "Skills",
575
- description: "Why: enables natural LongTable skill routing. Tradeoff: writes provider skill files."
592
+ description: "Why: enables natural LongTable skill routing. What you get: provider skills. Tradeoff: writes provider skill files."
576
593
  },
577
594
  {
578
595
  id: "skills_mcp",
579
596
  label: "Skills + MCP",
580
- description: "Why: adds structured state access. Tradeoff: writes provider config for MCP transport."
597
+ description: "Why: adds structured state access. What you get: skills and MCP config. Tradeoff: writes provider config for MCP transport."
581
598
  },
582
599
  {
583
600
  id: "skills_mcp_sentinel",
584
601
  label: "Skills + MCP + Sentinel",
585
- description: "Why: prepares advisory gap/tacit monitoring. Tradeoff: LongTable may nudge research turns."
602
+ description: "Why: prepares advisory gap/tacit monitoring. What you get: skills, MCP, and sentinel approval. Tradeoff: LongTable may nudge research turns."
586
603
  }
587
604
  ],
588
605
  intervention: [
589
606
  {
590
607
  id: "advisory",
591
608
  label: "Advisory",
592
- description: "Why: notices gaps without blocking. Tradeoff: you may still miss hard commitments."
609
+ description: "Why: notices gaps without blocking. What you get: light nudges. Tradeoff: you may still miss hard commitments."
593
610
  },
594
611
  {
595
612
  id: "balanced",
596
613
  label: "Balanced",
597
- description: "Why: blocks clear theory, measurement, method, or evidence commitments. Tradeoff: occasional stops."
614
+ description: "Why: blocks clear theory, measurement, method, or evidence commitments. What you get: recommended checkpoints. Tradeoff: occasional stops."
598
615
  },
599
616
  {
600
617
  id: "strong",
601
618
  label: "Strong",
602
- description: "Why: maximizes judgment protection. Tradeoff: more interruption before closure."
619
+ description: "Why: maximizes judgment protection. What you get: stricter checkpoints. Tradeoff: more interruption before closure."
603
620
  }
604
621
  ],
605
- tmux: [
606
- {
607
- id: "standard",
608
- label: "Standard chat",
609
- description: "Why: portable default. Tradeoff: checkpoints and gaps are less persistently visible."
610
- },
622
+ workspace: [
611
623
  {
612
- id: "hud",
613
- label: "Research HUD",
614
- description: "Why: keeps goals, blockers, and pending checkpoints visible. Requires tmux."
624
+ id: "create",
625
+ label: "Yes, create one now",
626
+ description: "Why: durable state needs .longtable/. What you get: decision log and CURRENT.md. Tradeoff: asks project-specific questions now."
615
627
  },
616
628
  {
617
- id: "console",
618
- label: "Research console",
619
- description: "Why: enables a richer tmux layout for HUD and team discussion. Requires tmux."
620
- }
621
- ],
622
- team: [
623
- {
624
- id: "off",
625
- label: "Off",
626
- description: "Why: simplest. Tradeoff: panel disagreement stays inside one LongTable response."
627
- },
628
- {
629
- id: "panel",
630
- label: "Structured panel",
631
- description: "Why: role disagreement is visible without tmux. Tradeoff: not parallel."
632
- },
633
- {
634
- id: "tmux_team",
635
- label: "Tmux team discussion",
636
- description: "Why: opens role panes for parallel debate. Tradeoff: terminal complexity and cleanup."
629
+ id: "later",
630
+ label: "No, prepare runtime only",
631
+ description: "Why: keeps setup short. What you get: runtime support without project state. Tradeoff: no durable research memory until `longtable start`."
637
632
  }
638
633
  ]
639
634
  };
@@ -645,6 +640,65 @@ function checkpointIntensityFromIntervention(choice) {
645
640
  return "low";
646
641
  return "balanced";
647
642
  }
643
+ function shouldInstallSkills(scope, surfaces) {
644
+ return scope !== "none" && surfaces !== "cli_only";
645
+ }
646
+ function shouldInstallMcp(scope, surfaces) {
647
+ return scope !== "none" && (surfaces === "skills_mcp" || surfaces === "skills_mcp_sentinel");
648
+ }
649
+ function setupProjectRoot(args) {
650
+ return resolve(normalizeUserPath(typeof args["project-dir"] === "string" && args["project-dir"].trim()
651
+ ? args["project-dir"].trim()
652
+ : cwd()));
653
+ }
654
+ function setupInstallDir(provider, scope, customDir, projectRoot) {
655
+ if (customDir)
656
+ return customDir;
657
+ if (scope === "project")
658
+ return join(projectRoot, provider === "codex" ? ".codex" : ".claude", "skills");
659
+ return undefined;
660
+ }
661
+ function setupPathForScope(scope, args, projectRoot) {
662
+ if (typeof args["setup-path"] === "string")
663
+ return args["setup-path"];
664
+ if (scope === "project")
665
+ return join(projectRoot, ".longtable", "setup.json");
666
+ return undefined;
667
+ }
668
+ function runtimePathForScope(provider, scope, args, projectRoot) {
669
+ if (typeof args["runtime-path"] === "string")
670
+ return args["runtime-path"];
671
+ if (scope !== "project")
672
+ return undefined;
673
+ return join(projectRoot, ".longtable", provider === "codex" ? "codex-runtime.toml" : "claude-runtime.json");
674
+ }
675
+ function mcpArgsForScope(provider, scope, args, projectRoot) {
676
+ if (scope !== "project")
677
+ return args;
678
+ return {
679
+ ...args,
680
+ ...(provider === "codex" && typeof args["codex-config"] !== "string"
681
+ ? { "codex-config": join(projectRoot, ".codex", "config.toml") }
682
+ : {}),
683
+ ...(provider === "claude" && typeof args["claude-settings"] !== "string"
684
+ ? { "claude-settings": join(projectRoot, ".claude", "settings.json") }
685
+ : {})
686
+ };
687
+ }
688
+ function parseSetupInstallScope(value) {
689
+ return value === "user" || value === "project" || value === "none" ? value : undefined;
690
+ }
691
+ function parseSetupSurface(value) {
692
+ return value === "cli_only" || value === "skills" || value === "skills_mcp" || value === "skills_mcp_sentinel"
693
+ ? value
694
+ : undefined;
695
+ }
696
+ function parseSetupIntervention(value) {
697
+ return value === "advisory" || value === "balanced" || value === "strong" ? value : undefined;
698
+ }
699
+ function parseSetupWorkspace(value) {
700
+ return value === "create" || value === "later" ? value : undefined;
701
+ }
648
702
  async function runSetup(args) {
649
703
  const json = args.json === true;
650
704
  const rl = createInterface({ input, output });
@@ -653,27 +707,30 @@ async function runSetup(args) {
653
707
  ? (args.provider === "claude" ? "claude" : "codex")
654
708
  : await promptChoice(rl, "Which provider should LongTable configure?", buildProviderChoices()));
655
709
  const choices = buildPermissionSetupChoices();
656
- const surfaces = await promptChoice(rl, [
710
+ const installScope = parseSetupInstallScope(args["install-scope"]) ?? await promptChoice(rl, "Where may LongTable install runtime support?", choices.installScope);
711
+ const surfaces = parseSetupSurface(args.surfaces) ?? await promptChoice(rl, [
657
712
  "Which LongTable runtime surfaces should be enabled?",
658
713
  "This is a permission choice because skills, MCP, and sentinel support write provider-facing runtime files."
659
714
  ].join("\n"), choices.surfaces);
660
- const intervention = await promptChoice(rl, "How strongly may LongTable interrupt research decisions?", choices.intervention);
661
- const tmuxMode = await promptChoice(rl, "Should LongTable recommend a tmux-based research interface?", choices.tmux);
662
- const teamMode = await promptChoice(rl, "Should LongTable enable agent/team discussion mode?", choices.team);
715
+ const intervention = parseSetupIntervention(args.intervention) ?? await promptChoice(rl, "How strongly may LongTable interrupt research decisions?", choices.intervention);
716
+ const workspacePreference = parseSetupWorkspace(args.workspace) ?? await promptChoice(rl, "Should LongTable create a project workspace now?", choices.workspace);
717
+ const projectRoot = setupProjectRoot(args);
663
718
  const outputValue = createPersistedSetupOutput({
664
719
  field: "unspecified",
665
720
  careerStage: "unspecified",
666
721
  experienceLevel: "advanced",
667
722
  preferredCheckpointIntensity: checkpointIntensityFromIntervention(intervention),
668
723
  preferredEntryMode: "explore",
669
- panelPreference: teamMode === "off" ? "show_on_conflict" : "always_visible"
724
+ panelPreference: "show_on_conflict"
670
725
  }, provider, "quickstart");
671
726
  outputValue.initialState.explicitState = {
672
727
  ...outputValue.initialState.explicitState,
728
+ installScope,
673
729
  runtimeSurfaces: surfaces,
674
730
  interventionPosture: intervention,
675
- tmuxMode,
676
- teamMode
731
+ workspaceCreationPreference: workspacePreference,
732
+ tmuxMode: "standard",
733
+ teamMode: "panel"
677
734
  };
678
735
  if (surfaces === "skills_mcp_sentinel") {
679
736
  outputValue.initialState.inferredHypotheses.push({
@@ -683,44 +740,64 @@ async function runSetup(args) {
683
740
  status: "confirmed"
684
741
  });
685
742
  }
686
- const result = await saveSetupAndRuntimeConfig(outputValue, {
687
- setupPath: typeof args["setup-path"] === "string" ? args["setup-path"] : undefined,
688
- runtimePath: typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined
689
- });
690
- const installedSkills = surfaces === "cli_only"
743
+ const setupPath = setupPathForScope(installScope, args, projectRoot);
744
+ const runtimePath = runtimePathForScope(provider, installScope, args, projectRoot);
745
+ const result = installScope === "none"
746
+ ? {
747
+ provider,
748
+ setupTarget: await saveSetupOutput(outputValue, setupPath)
749
+ }
750
+ : await saveSetupAndRuntimeConfig(outputValue, {
751
+ setupPath,
752
+ runtimePath
753
+ });
754
+ const scopedInstallDir = setupInstallDir(provider, installScope, typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined, projectRoot);
755
+ const installedSkills = !shouldInstallSkills(installScope, surfaces)
691
756
  ? []
692
757
  : provider === "codex"
693
- ? await installCodexSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined)
694
- : await installClaudeSkills(listRoleDefinitions(), typeof args["skills-dir"] === "string" ? args["skills-dir"] : typeof args.dir === "string" ? args.dir : undefined);
695
- const mcpRequested = surfaces === "skills_mcp" || surfaces === "skills_mcp_sentinel";
696
- if (mcpRequested && !json) {
697
- console.log("");
698
- console.log("MCP setup is approved. To write provider config now, run:");
699
- console.log(`- longtable mcp install --provider ${provider} --write`);
758
+ ? await installCodexSkills(listRoleDefinitions(), scopedInstallDir)
759
+ : await installClaudeSkills(listRoleDefinitions(), scopedInstallDir);
760
+ let mcpInstall;
761
+ if (shouldInstallMcp(installScope, surfaces)) {
762
+ mcpInstall = await installMcpForSetup(provider, mcpArgsForScope(provider, installScope, args, projectRoot));
700
763
  }
701
764
  if (json) {
702
765
  console.log(JSON.stringify({
703
766
  setup: outputValue,
704
767
  runtime: result,
705
768
  installedSkills: installedSkills.map((skill) => skill.name),
706
- mcpRequested,
707
- tmuxMode,
708
- teamMode
769
+ mcpInstall,
770
+ workspacePreference
709
771
  }, null, 2));
710
772
  return;
711
773
  }
712
774
  console.log("");
713
775
  console.log(renderSetupSummary(outputValue));
714
776
  console.log("");
715
- console.log(renderInstallSummary(result));
777
+ if ("runtimeTarget" in result) {
778
+ console.log(renderInstallSummary(result));
779
+ }
780
+ else {
781
+ console.log("LongTable setup summary");
782
+ console.log(`setup path: ${result.setupTarget.path}`);
783
+ console.log("provider files: not installed by researcher choice");
784
+ }
716
785
  console.log(`Installed skills: ${installedSkills.length}`);
717
- if (tmuxMode !== "standard") {
786
+ if (mcpInstall) {
718
787
  console.log("");
719
- console.log("Tmux recommendation:");
720
- console.log("- macOS: brew install tmux");
721
- console.log("- Ubuntu/Debian: sudo apt install tmux");
722
- console.log("- Start HUD in an existing tmux session: longtable hud --tmux");
723
- console.log("- Start a discussion team: longtable team --tmux --prompt \"...\"");
788
+ console.log(renderMcpInstallSummary(mcpInstall));
789
+ }
790
+ if (surfaces === "skills_mcp_sentinel") {
791
+ console.log("");
792
+ console.log("Background sentinel approval recorded.");
793
+ console.log("Hook installation remains opt-in; LongTable will not install hooks without an explicit hook command.");
794
+ }
795
+ if (workspacePreference === "create") {
796
+ console.log("");
797
+ console.log("Project workspace requested. LongTable will now run `longtable start` with research-object and gap-risk prompts.");
798
+ await runStart({
799
+ setup: result.setupTarget.path
800
+ });
724
801
  }
725
802
  }
726
803
  finally {
@@ -734,6 +811,33 @@ function perspectiveChoices() {
734
811
  description: persona.shortDescription
735
812
  }));
736
813
  }
814
+ function researchObjectChoices() {
815
+ return [
816
+ { id: "research_question", label: "Research question", description: "The main question, problem, or contribution boundary." },
817
+ { id: "theory_framework", label: "Theory framework", description: "Constructs, theory choice, or conceptual model." },
818
+ { id: "measurement_instrument", label: "Measurement/instrument", description: "Variables, scales, instruments, or operationalization." },
819
+ { id: "study_design", label: "Study design", description: "Methods, sample, intervention, or data collection design." },
820
+ { id: "analysis_plan", label: "Analysis plan", description: "Analytic strategy, models, coding, or interpretation plan." },
821
+ { id: "manuscript", label: "Manuscript", description: "Drafting, revision, voice, evidence, or submission writing." }
822
+ ];
823
+ }
824
+ function gapRiskChoices() {
825
+ return [
826
+ { id: "known_gap", label: "I know the gap", description: "The blocker is explicit and can be tracked directly." },
827
+ { id: "suspected_tacit_assumptions", label: "I suspect tacit assumptions", description: "There may be hidden commitments or unstated premises." },
828
+ { id: "diagnose", label: "Ask LongTable to diagnose it", description: "Let LongTable classify likely gaps during the session." }
829
+ ];
830
+ }
831
+ function protectedDecisionChoices() {
832
+ return [
833
+ { id: "theory", label: "Theory", description: "Do not let theory or construct choices settle quietly." },
834
+ { id: "measurement", label: "Measurement", description: "Do not let variables, scales, or instruments settle quietly." },
835
+ { id: "method", label: "Method", description: "Do not let design, sampling, or procedure choices settle quietly." },
836
+ { id: "evidence_citation", label: "Evidence/citation", description: "Do not let unsupported source or citation choices settle quietly." },
837
+ { id: "authorship_voice", label: "Authorship/voice", description: "Do not let writing voice or authorial judgment disappear quietly." },
838
+ { id: "submission_public_sharing", label: "Submission/public sharing", description: "Do not let public-facing commitments settle quietly." }
839
+ ];
840
+ }
737
841
  function normalizePerspectiveList(value) {
738
842
  if (!value?.trim()) {
739
843
  return [];
@@ -748,6 +852,9 @@ async function collectProjectInterview(setup, args) {
748
852
  !(typeof args.path === "string" && args.path.trim()) ||
749
853
  !(typeof args.goal === "string" && args.goal.trim()) ||
750
854
  typeof args.blocker !== "string" ||
855
+ !(typeof args["research-object"] === "string" && args["research-object"].trim()) ||
856
+ !(typeof args["gap-risk"] === "string" && args["gap-risk"].trim()) ||
857
+ !(typeof args["protected-decision"] === "string" && args["protected-decision"].trim()) ||
751
858
  normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined).length === 0 ||
752
859
  !(typeof args.disagreement === "string" && args.disagreement.trim());
753
860
  const rl = createInterface({ input, output });
@@ -763,23 +870,29 @@ async function collectProjectInterview(setup, args) {
763
870
  console.log("");
764
871
  }
765
872
  const projectName = (typeof args.name === "string" && args.name.trim()) ||
766
- (await promptText(rl, renderQuestionHeader(1, 6, "Project interview", "What should this project be called?"), true));
873
+ (await promptText(rl, renderQuestionHeader(1, 9, "Project interview", "What should this project be called?"), true));
767
874
  const suggestedParentDir = typeof args.path === "string" && args.path.trim()
768
875
  ? normalizeUserPath(args.path.trim())
769
876
  : homedir();
770
877
  const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
771
878
  const projectPath = (typeof args.path === "string" && args.path.trim()
772
879
  ? normalizeUserPath(args.path.trim())
773
- : resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 6, "Project interview", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
880
+ : resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 9, "Project interview", `Which parent directory should contain this project?\nLongTable will create this folder:\n${suggestedPath}`), true)), projectName));
774
881
  const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
775
- (await promptText(rl, renderQuestionHeader(3, 6, "Current session", "What are you trying to accomplish in this session?"), true));
882
+ (await promptText(rl, renderQuestionHeader(3, 9, "Current session", "What are you trying to accomplish in this session?"), true));
776
883
  const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
777
- (await promptText(rl, renderQuestionHeader(4, 6, "Current session", "What is the main blocker or uncertainty right now?"), false));
884
+ (await promptText(rl, renderQuestionHeader(4, 9, "Current session", "What is the main blocker or uncertainty right now?"), false));
885
+ const researchObject = (typeof args["research-object"] === "string" && args["research-object"].trim()) ||
886
+ await promptChoice(rl, renderQuestionHeader(5, 9, "Research object", "What kind of research object are we protecting right now?"), researchObjectChoices());
887
+ const gapRisk = (typeof args["gap-risk"] === "string" && args["gap-risk"].trim()) ||
888
+ await promptChoice(rl, renderQuestionHeader(6, 9, "Gap/tacit risk", "What is the most likely gap risk at the start of this workspace?"), gapRiskChoices());
889
+ const protectedDecision = (typeof args["protected-decision"] === "string" && args["protected-decision"].trim()) ||
890
+ await promptChoice(rl, renderQuestionHeader(7, 9, "Protected decision", "Which decision should LongTable not let you settle quietly?"), protectedDecisionChoices());
778
891
  const requestedPerspectives = normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined).length > 0
779
892
  ? normalizePerspectiveList(typeof args.perspectives === "string" ? args.perspectives : undefined)
780
- : await promptMultiChoice(rl, renderQuestionHeader(5, 6, "Perspectives", "Which perspectives do you already know you want at the table? Leave everything unchecked for auto."), perspectiveChoices());
893
+ : await promptMultiChoice(rl, renderQuestionHeader(8, 9, "Perspectives", "Which perspectives do you already know you want at the table? Leave everything unchecked for auto."), perspectiveChoices());
781
894
  const disagreementPreference = (typeof args.disagreement === "string" && args.disagreement.trim()) ||
782
- (await promptChoice(rl, renderQuestionHeader(6, 6, "Disagreement", "How visible should disagreement between perspectives be in this project by default?"), [
895
+ (await promptChoice(rl, renderQuestionHeader(9, 9, "Disagreement", "How visible should disagreement between perspectives be in this project by default?"), [
783
896
  {
784
897
  id: "synthesis_only",
785
898
  label: "Synthesis only",
@@ -801,6 +914,9 @@ async function collectProjectInterview(setup, args) {
801
914
  projectPath: projectPath.trim(),
802
915
  currentGoal: currentGoal.trim(),
803
916
  ...(currentBlocker?.trim() ? { currentBlocker: currentBlocker.trim() } : {}),
917
+ researchObject: researchObject.trim(),
918
+ gapRisk: gapRisk.trim(),
919
+ protectedDecision: protectedDecision.trim(),
804
920
  requestedPerspectives,
805
921
  disagreementPreference: disagreementPreference
806
922
  };
@@ -859,6 +975,11 @@ async function readPersistAnswers(args) {
859
975
  throw new Error("persist-init requires either --answers-json, --stdin, or the full set of setup flags.");
860
976
  }
861
977
  async function runInit(args) {
978
+ if (!hasCompleteFlagInput(args)) {
979
+ console.error("`longtable init` is deprecated. Use `longtable setup` for permission-first runtime setup.");
980
+ await runSetup(args);
981
+ return;
982
+ }
862
983
  const json = args.json === true;
863
984
  const installRuntime = args["no-install"] !== true;
864
985
  const installPrompts = args["install-prompts"] === true;
@@ -1056,6 +1177,34 @@ function renderMcpInstallSummary(result) {
1056
1177
  }
1057
1178
  return lines.join("\n").trimEnd();
1058
1179
  }
1180
+ async function installMcpForSetup(provider, args) {
1181
+ const serverName = typeof args.name === "string" && args.name.trim()
1182
+ ? args.name.trim()
1183
+ : LONGTABLE_MCP_SERVER_NAME;
1184
+ const packageSpec = resolveMcpPackageSpec(args);
1185
+ const command = typeof args.command === "string" && args.command.trim() ? args.command.trim() : "npx";
1186
+ const mcpArgs = command === "npx" ? ["-y", packageSpec] : [packageSpec];
1187
+ const targets = [];
1188
+ if (provider === "codex") {
1189
+ const path = resolveCodexMcpConfigPath(args);
1190
+ const block = renderCodexMcpBlock(serverName, command, mcpArgs);
1191
+ const content = await writeCodexMcpConfig(path, block, serverName);
1192
+ targets.push({ provider, path, format: "toml", content });
1193
+ }
1194
+ if (provider === "claude") {
1195
+ const path = resolveClaudeMcpSettingsPath(args);
1196
+ const content = await writeClaudeMcpSettings(path, serverName, command, mcpArgs);
1197
+ targets.push({ provider, path, format: "json", content });
1198
+ }
1199
+ return {
1200
+ serverName,
1201
+ packageSpec,
1202
+ command,
1203
+ args: mcpArgs,
1204
+ write: true,
1205
+ targets
1206
+ };
1207
+ }
1059
1208
  async function runMcpSubcommand(subcommand, args) {
1060
1209
  if (!subcommand || subcommand === "install" || subcommand === "print-config") {
1061
1210
  const serverName = typeof args.name === "string" && args.name.trim()
@@ -1268,7 +1417,7 @@ function renderDoctorStatus(status) {
1268
1417
  nextActions.push("longtable doctor --fix");
1269
1418
  }
1270
1419
  if (!status.setupExists) {
1271
- nextActions.push("longtable init --flow interview --provider codex --install-skills");
1420
+ nextActions.push("longtable setup --provider codex");
1272
1421
  }
1273
1422
  if (!status.workspace.found) {
1274
1423
  nextActions.push("longtable start");
@@ -1360,7 +1509,7 @@ async function repairDoctorStatus(args, status) {
1360
1509
  repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
1361
1510
  }
1362
1511
  if (!status.setupExists) {
1363
- repair.skipped.push("runtime configs require a researcher setup; run `longtable init --flow interview --provider codex` first");
1512
+ repair.skipped.push("runtime configs require setup approval; run `longtable setup --provider codex` first");
1364
1513
  return repair;
1365
1514
  }
1366
1515
  const setup = await loadSetupOutput(setupOverride);
@@ -2304,7 +2453,7 @@ async function runStart(args) {
2304
2453
  const setupPath = typeof args.setup === "string" ? args.setup : undefined;
2305
2454
  const existingSetup = await loadOptionalSetup(setupPath);
2306
2455
  if (!existingSetup) {
2307
- throw new Error("LongTable global setup is missing. Run `longtable init --flow interview` first.");
2456
+ throw new Error("LongTable setup is missing. Run `longtable setup --provider codex` first.");
2308
2457
  }
2309
2458
  const interview = await collectProjectInterview(existingSetup, args);
2310
2459
  await verifyWritableWorkspaceParent(interview.projectPath);
@@ -2313,6 +2462,9 @@ async function runStart(args) {
2313
2462
  projectPath: interview.projectPath,
2314
2463
  currentGoal: interview.currentGoal,
2315
2464
  currentBlocker: interview.currentBlocker,
2465
+ researchObject: interview.researchObject,
2466
+ gapRisk: interview.gapRisk,
2467
+ protectedDecision: interview.protectedDecision,
2316
2468
  requestedPerspectives: interview.requestedPerspectives,
2317
2469
  disagreementPreference: interview.disagreementPreference,
2318
2470
  setup: existingSetup
@@ -28,6 +28,9 @@ export interface LongTableSessionRecord {
28
28
  projectPath: string;
29
29
  currentGoal: string;
30
30
  currentBlocker?: string;
31
+ researchObject?: string;
32
+ gapRisk?: string;
33
+ protectedDecision?: string;
31
34
  nextAction?: string;
32
35
  openQuestions?: string[];
33
36
  requestedPerspectives: string[];
@@ -150,6 +153,9 @@ export declare function createOrUpdateProjectWorkspace(options: {
150
153
  projectPath: string;
151
154
  currentGoal: string;
152
155
  currentBlocker?: string;
156
+ researchObject?: string;
157
+ gapRisk?: string;
158
+ protectedDecision?: string;
153
159
  requestedPerspectives: string[];
154
160
  disagreementPreference: ProjectDisagreementPreference;
155
161
  setup: SetupPersistedOutput;
@@ -99,6 +99,9 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
99
99
  "## 지금 초점",
100
100
  `- 현재 목표: ${session.currentGoal}`,
101
101
  ...(session.currentBlocker ? [`- 현재 blocker: ${session.currentBlocker}`] : []),
102
+ ...(session.researchObject ? [`- 연구 객체: ${session.researchObject}`] : []),
103
+ ...(session.gapRisk ? [`- 공백/암묵지 위험: ${session.gapRisk}`] : []),
104
+ ...(session.protectedDecision ? [`- 보호할 결정: ${session.protectedDecision}`] : []),
102
105
  `- 다음 액션: ${nextAction}`,
103
106
  `- 관점: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
104
107
  `- disagreement: ${session.disagreementPreference}`,
@@ -148,6 +151,9 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
148
151
  "## Focus Now",
149
152
  `- Current goal: ${session.currentGoal}`,
150
153
  ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
154
+ ...(session.researchObject ? [`- Research object: ${session.researchObject}`] : []),
155
+ ...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
156
+ ...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
151
157
  `- Next action: ${nextAction}`,
152
158
  `- Perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
153
159
  `- Disagreement: ${session.disagreementPreference}`,
@@ -335,6 +341,9 @@ function buildProjectAgentsMd(project, session) {
335
341
  `- Project: ${project.projectName}`,
336
342
  `- Current goal: ${session.currentGoal}`,
337
343
  ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
344
+ ...(session.researchObject ? [`- Research object: ${session.researchObject}`] : []),
345
+ ...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
346
+ ...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
338
347
  `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
339
348
  `- Disagreement visibility: ${session.disagreementPreference}`,
340
349
  "- These instructions apply to this directory and its children."
@@ -344,8 +353,8 @@ function buildStateSeed(project, session, setup) {
344
353
  const state = createEmptyResearchState();
345
354
  state.explicitState = {
346
355
  field: setup.profileSeed.field ?? "unspecified",
347
- careerStage: setup.profileSeed.careerStage,
348
- experienceLevel: setup.profileSeed.experienceLevel,
356
+ careerStage: setup.profileSeed.careerStage ?? "unspecified",
357
+ experienceLevel: setup.profileSeed.experienceLevel ?? "advanced",
349
358
  projectName: project.projectName,
350
359
  disagreementPreference: session.disagreementPreference,
351
360
  requestedPerspectives: session.requestedPerspectives
@@ -353,6 +362,9 @@ function buildStateSeed(project, session, setup) {
353
362
  state.workingState = {
354
363
  currentGoal: session.currentGoal,
355
364
  ...(session.currentBlocker ? { currentBlocker: session.currentBlocker } : {}),
365
+ ...(session.researchObject ? { researchObject: session.researchObject } : {}),
366
+ ...(session.gapRisk ? { gapRisk: session.gapRisk } : {}),
367
+ ...(session.protectedDecision ? { protectedDecision: session.protectedDecision } : {}),
356
368
  ...(session.nextAction ? { nextAction: session.nextAction } : {}),
357
369
  openQuestions: session.openQuestions ?? [],
358
370
  activeModes: session.activeModes ?? [],
@@ -361,6 +373,9 @@ function buildStateSeed(project, session, setup) {
361
373
  if (session.currentBlocker) {
362
374
  state.openTensions.push(session.currentBlocker);
363
375
  }
376
+ if (session.gapRisk) {
377
+ state.openTensions.push(`Gap/tacit risk: ${session.gapRisk}`);
378
+ }
364
379
  if (setup.profileSeed.humanAuthorshipSignal) {
365
380
  state.explicitState.humanAuthorshipSignal = setup.profileSeed.humanAuthorshipSignal;
366
381
  }
@@ -384,6 +399,21 @@ function buildStateSeed(project, session, setup) {
384
399
  importance: "high"
385
400
  });
386
401
  }
402
+ if (session.researchObject || session.gapRisk || session.protectedDecision) {
403
+ state.narrativeTraces.push({
404
+ id: "project-session-risk-profile",
405
+ timestamp: nowIso(),
406
+ source: "longtable-start",
407
+ traceType: "tension",
408
+ summary: [
409
+ session.researchObject ? `Research object: ${session.researchObject}.` : "",
410
+ session.gapRisk ? `Gap/tacit risk: ${session.gapRisk}.` : "",
411
+ session.protectedDecision ? `Protected decision: ${session.protectedDecision}.` : ""
412
+ ].filter(Boolean).join(" "),
413
+ visibility: "explicit",
414
+ importance: "high"
415
+ });
416
+ }
387
417
  return JSON.stringify(state, null, 2);
388
418
  }
389
419
  async function removeLegacyRootFiles(projectPath) {
@@ -908,9 +938,9 @@ export async function createOrUpdateProjectWorkspace(options) {
908
938
  locale,
909
939
  globalSetupSummary: {
910
940
  field: options.setup.profileSeed.field ?? "unspecified",
911
- careerStage: options.setup.profileSeed.careerStage,
912
- experienceLevel: options.setup.profileSeed.experienceLevel,
913
- checkpointIntensity: options.setup.profileSeed.preferredCheckpointIntensity,
941
+ careerStage: options.setup.profileSeed.careerStage ?? "unspecified",
942
+ experienceLevel: options.setup.profileSeed.experienceLevel ?? "advanced",
943
+ checkpointIntensity: options.setup.profileSeed.preferredCheckpointIntensity ?? "balanced",
914
944
  ...(options.setup.profileSeed.humanAuthorshipSignal
915
945
  ? { humanAuthorshipSignal: options.setup.profileSeed.humanAuthorshipSignal }
916
946
  : {}),
@@ -931,6 +961,9 @@ export async function createOrUpdateProjectWorkspace(options) {
931
961
  projectPath,
932
962
  currentGoal: options.currentGoal,
933
963
  ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
964
+ ...(options.researchObject ? { researchObject: options.researchObject } : {}),
965
+ ...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
966
+ ...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
934
967
  nextAction: buildNextAction({
935
968
  schemaVersion: 1,
936
969
  id: sessionId,
@@ -939,6 +972,9 @@ export async function createOrUpdateProjectWorkspace(options) {
939
972
  projectPath,
940
973
  currentGoal: options.currentGoal,
941
974
  ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
975
+ ...(options.researchObject ? { researchObject: options.researchObject } : {}),
976
+ ...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
977
+ ...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
942
978
  requestedPerspectives: options.requestedPerspectives,
943
979
  disagreementPreference: options.disagreementPreference
944
980
  }),
@@ -950,6 +986,9 @@ export async function createOrUpdateProjectWorkspace(options) {
950
986
  projectPath,
951
987
  currentGoal: options.currentGoal,
952
988
  ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
989
+ ...(options.researchObject ? { researchObject: options.researchObject } : {}),
990
+ ...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
991
+ ...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
953
992
  requestedPerspectives: options.requestedPerspectives,
954
993
  disagreementPreference: options.disagreementPreference
955
994
  }),
@@ -964,6 +1003,9 @@ export async function createOrUpdateProjectWorkspace(options) {
964
1003
  projectPath,
965
1004
  currentGoal: options.currentGoal,
966
1005
  ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
1006
+ ...(options.researchObject ? { researchObject: options.researchObject } : {}),
1007
+ ...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
1008
+ ...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
967
1009
  requestedPerspectives: options.requestedPerspectives,
968
1010
  disagreementPreference: options.disagreementPreference
969
1011
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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.19",
32
- "@longtable/core": "0.1.19",
33
- "@longtable/memory": "0.1.19",
34
- "@longtable/provider-claude": "0.1.19",
35
- "@longtable/provider-codex": "0.1.19",
36
- "@longtable/setup": "0.1.19"
31
+ "@longtable/checkpoints": "0.1.20",
32
+ "@longtable/core": "0.1.20",
33
+ "@longtable/memory": "0.1.20",
34
+ "@longtable/provider-claude": "0.1.20",
35
+ "@longtable/provider-codex": "0.1.20",
36
+ "@longtable/setup": "0.1.20"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.1",