@longtable/cli 0.1.8 → 0.1.9

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
@@ -1,17 +1,12 @@
1
1
  # @longtable/cli
2
2
 
3
- Long Table의 researcher-facing CLI입니다.
3
+ Researcher-facing CLI for LongTable.
4
4
 
5
- 패키지의 핵심은 명령을 많이 외우게 하는 것이 아니라, 아래 두 단계를 명확하게 만드는 것입니다.
5
+ LongTable is designed around a simple contract:
6
6
 
7
- 1. `longtable init`
8
- 2. `longtable start`
9
-
10
- 중요한 점:
11
-
12
- - 이 두 명령은 **터미널에서 실행하는 셸 명령**입니다.
13
- - Codex 채팅창에 `longtable start`를 입력하는 것이 아닙니다.
14
- - `longtable start`로 프로젝트 작업공간을 만든 뒤, 그 디렉토리에서 `codex`를 여는 것이 기본 경로입니다.
7
+ 1. seed the researcher profile once
8
+ 2. create a workspace for each project
9
+ 3. continue the research conversation inside that workspace
15
10
 
16
11
  ## Install
17
12
 
@@ -19,47 +14,7 @@ Long Table의 researcher-facing CLI입니다.
19
14
  npm install -g @longtable/cli
20
15
  ```
21
16
 
22
- ## 1. Global setup
23
-
24
- ```bash
25
- longtable init --flow interview
26
- ```
27
-
28
- 여기서는 연구자 프로필과 기본 선호를 묻습니다.
29
-
30
- - 연구 분야
31
- - 연구자 역할
32
- - challenge 강도
33
- - 저자성/서사 관련 기본값
34
- - Long Table이 보통 어디서부터 시작해야 하는지
35
-
36
- ## 2. Project start
37
-
38
- ```bash
39
- longtable start
40
- ```
41
-
42
- 여기서는 실제 작업공간을 만듭니다.
43
-
44
- - 프로젝트 이름
45
- - 프로젝트 디렉토리 위치
46
- - 현재 세션 목표
47
- - 현재 blocker
48
- - 필요한 관점
49
- - disagreement 가시성
50
-
51
- 그리고 Long Table은:
52
-
53
- - 프로젝트 디렉토리 생성
54
- - `.longtable/` 메모리 파일 생성
55
- - 프로젝트용 `AGENTS.md` 생성
56
- - `START-HERE.md` 생성
57
- - `NEXT-STEPS.md` 생성
58
- - `SESSION-SNAPSHOT.md` 생성
59
-
60
- 을 수행합니다.
61
-
62
- ## Recommended flow
17
+ ## Primary Flow
63
18
 
64
19
  ```bash
65
20
  longtable init --flow interview
@@ -68,9 +23,7 @@ cd "<project-path>"
68
23
  codex
69
24
  ```
70
25
 
71
- 이게 현재 가장 신뢰할 수 있는 Long Table 사용 경로입니다.
72
-
73
- 돌아와서 다시 이어갈 때는:
26
+ Return later:
74
27
 
75
28
  ```bash
76
29
  cd "<project-path>"
@@ -78,58 +31,69 @@ longtable resume
78
31
  codex
79
32
  ```
80
33
 
81
- ## After the project starts
82
-
83
- 프로젝트 디렉토리 안에서는 보통 그냥 `codex`를 열고 자연어로 연구를 시작하면 됩니다.
84
-
85
- Codex 안에서 더 명시적으로 부르고 싶다면, 아래처럼 짧은 문법도 사용할 수 있습니다.
34
+ ## What `longtable start` Creates
86
35
 
87
36
  ```text
88
- lt explore: 연구 질문을 어디서부터 좁혀야 할지 모르겠어.
89
- lt review: 이 주장 어디가 약한지 봐줘.
90
- lt panel: 이 방향에 대한 의견 충돌도 같이 보여줘.
91
- lt editor: BJET 편집자 관점에서 봐줘.
92
- lt methods: 방법론적으로 어디가 취약한지 말해줘.
37
+ <project>/
38
+ AGENTS.md
39
+ CURRENT.md
40
+ .longtable/
41
+ project.json
42
+ current-session.json
43
+ state.json
44
+ sessions/
93
45
  ```
94
46
 
95
- CLI를 계속 쓰고 싶다면 아래 명령은 보조 경로입니다.
47
+ ## Artifact Contract
96
48
 
97
- ## Advanced commands
49
+ - `AGENTS.md`: runtime guidance for Codex
50
+ - `CURRENT.md`: human-facing current view regenerated from state
51
+ - `.longtable/project.json`: stable project identity
52
+ - `.longtable/current-session.json`: current session cursor
53
+ - `.longtable/state.json`: layered memory state
54
+ - `.longtable/sessions/`: historical snapshots
98
55
 
99
- ```bash
100
- longtable roles
101
- longtable resume --cwd "<project-path>"
102
- longtable ask --cwd "<project-path>" --prompt "연구 질문을 어디서부터 좁혀야 할지 모르겠어."
103
- longtable review --cwd "<project-path>" --prompt "방법론적으로 어디가 취약한지 말해줘." --role methods_critic
104
- ```
56
+ ## Why This Shape
105
57
 
106
- ## Roles
58
+ The CLI tries to keep the root simple for novice researchers while preserving enough structure for power users and downstream tooling.
107
59
 
108
- ```bash
109
- longtable roles
110
- ```
60
+ The memory model distinguishes:
111
61
 
112
- 예:
62
+ - explicit state
63
+ - working state
64
+ - inferred hypotheses
65
+ - open tensions
66
+ - narrative traces
113
67
 
114
- - `editor`
115
- - `reviewer`
116
- - `methods_critic`
117
- - `voice_keeper`
68
+ This is how LongTable avoids turning tacit knowledge into fake certainty.
118
69
 
119
- ## Codex prompt-file integration
70
+ ## Commands
120
71
 
121
72
  ```bash
122
- longtable codex install-prompts
123
- longtable codex status
73
+ longtable init
74
+ longtable start
75
+ longtable resume --cwd "<project-path>"
76
+ longtable roles
77
+ longtable ask --cwd "<project-path>" --prompt "..."
124
78
  ```
125
79
 
126
- 경로는 현재 실험적입니다.
127
- 사용자에게 약속하는 주 경로는 아닙니다.
80
+ ## Inside Codex
128
81
 
129
- 기본 경로는 여전히:
82
+ Natural language should be the default.
130
83
 
131
- - `longtable init`
132
- - `longtable start`
133
- - 프로젝트 디렉토리에서 `codex`
84
+ Explicit short forms are available when needed:
134
85
 
135
- 입니다.
86
+ ```text
87
+ lt explore: Where should I narrow the question first?
88
+ lt review: What is weak in this claim?
89
+ lt panel: Show me the disagreement before I commit.
90
+ lt methods: Where is the design vulnerable?
91
+ ```
92
+
93
+ ## Validation
94
+
95
+ ```bash
96
+ npm install
97
+ npm run typecheck
98
+ npm run build
99
+ ```
package/dist/cli.js CHANGED
@@ -6,12 +6,12 @@ import { createInterface } from "node:readline/promises";
6
6
  import { stdin as input, stdout as output, cwd, exit } from "node:process";
7
7
  import { dirname, resolve } from "node:path";
8
8
  import { homedir } from "node:os";
9
- import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
10
- import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
9
+ import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@longtable/setup";
10
+ import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@longtable/provider-codex";
11
11
  import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
12
12
  import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router.js";
13
13
  import { PERSONA_DEFINITIONS } from "./personas.js";
14
- import { createOrUpdateProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary } from "./project-session.js";
14
+ import { createOrUpdateProjectWorkspace, loadProjectContextFromDirectory, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
15
15
  const VALID_MODES = new Set([
16
16
  "explore",
17
17
  "review",
@@ -155,11 +155,11 @@ function buildSetupFlowChoices() {
155
155
  ];
156
156
  }
157
157
  function renderSetupHeader(flow) {
158
- const title = flow === "interview" ? "Long Table Setup Interview" : "Long Table Quickstart";
158
+ const title = flow === "interview" ? "LongTable Setup Interview" : "LongTable Quickstart";
159
159
  const subtitle = flow === "interview"
160
160
  ? "We will ask about your research persona, challenge preferences, and authorship defaults."
161
- : "We will capture the minimum profile needed to start using Long Table.";
162
- return [renderBrandBanner("Long Table", "Research workspace setup"), "", renderSectionCard(title, [subtitle])].join("\n");
161
+ : "We will capture the minimum profile needed to start using LongTable.";
162
+ return [renderBrandBanner("LongTable", "Research workspace setup"), "", renderSectionCard(title, [subtitle])].join("\n");
163
163
  }
164
164
  function renderQuestionHeader(index, total, section, prompt) {
165
165
  return [
@@ -176,7 +176,7 @@ function questionSection(questionId) {
176
176
  return "Interaction style";
177
177
  }
178
178
  if (questionId === "weakestDomain" || questionId === "panelPreference") {
179
- return "How Long Table should challenge you";
179
+ return "How LongTable should challenge you";
180
180
  }
181
181
  return "Authorship and voice";
182
182
  }
@@ -253,14 +253,14 @@ async function verifyWritableWorkspaceParent(projectPath) {
253
253
  catch (error) {
254
254
  const message = error instanceof Error ? error.message : String(error);
255
255
  throw new Error([
256
- `Long Table could not create a project workspace under: ${parentDir}`,
256
+ `LongTable could not create a project workspace under: ${parentDir}`,
257
257
  "Reason: your current shell process cannot write to that parent directory.",
258
258
  "",
259
259
  "What to try next:",
260
260
  `- test it directly: mkdir -p "${resolve(parentDir, "_longtable_write_test")}"`,
261
- "- if that fails too, this is an OS/disk permission problem rather than a Long Table command problem",
261
+ "- if that fails too, this is an OS/disk permission problem rather than a LongTable command problem",
262
262
  "- try a known-writable path such as ~/Research",
263
- "- or choose a different existing parent directory when Long Table asks where the project should live",
263
+ "- or choose a different existing parent directory when LongTable asks where the project should live",
264
264
  "",
265
265
  `Original error: ${message}`
266
266
  ].join("\n"));
@@ -481,7 +481,7 @@ async function collectInteractiveAnswers(initialFlow) {
481
481
  const rl = createInterface({ input, output });
482
482
  try {
483
483
  const flow = initialFlow ??
484
- (await promptChoice(rl, "How would you like to set up Long Table?", buildSetupFlowChoices()));
484
+ (await promptChoice(rl, "How would you like to set up LongTable?", buildSetupFlowChoices()));
485
485
  console.log("");
486
486
  console.log(renderSetupHeader(flow));
487
487
  console.log("");
@@ -559,11 +559,11 @@ async function collectProjectInterview(setup, args) {
559
559
  try {
560
560
  if (needsInteractivePrompts) {
561
561
  console.log("");
562
- console.log(renderBrandBanner("Long Table", "Project workspace interview"));
562
+ console.log(renderBrandBanner("LongTable", "Project workspace interview"));
563
563
  console.log("");
564
- console.log(renderSectionCard("Long Table Project Start", [
564
+ console.log(renderSectionCard("LongTable Project Start", [
565
565
  "We will create a project workspace and a session memory seed for today's work.",
566
- "At the end, Long Table will tell you exactly which directory to open in Codex."
566
+ "At the end, LongTable will tell you exactly which directory to open in Codex."
567
567
  ]));
568
568
  console.log("");
569
569
  }
@@ -575,7 +575,7 @@ async function collectProjectInterview(setup, args) {
575
575
  const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
576
576
  const projectPath = (typeof args.path === "string" && args.path.trim()
577
577
  ? normalizeUserPath(args.path.trim())
578
- : resolveInteractiveProjectPath((await promptText(rl, renderQuestionHeader(2, 6, "Project interview", `Which parent directory should contain this project?\nLong Table will create this folder:\n${suggestedPath}`), true)), projectName));
578
+ : 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));
579
579
  const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
580
580
  (await promptText(rl, renderQuestionHeader(3, 6, "Current session", "What are you trying to accomplish in this session?"), true));
581
581
  const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
@@ -588,7 +588,7 @@ async function collectProjectInterview(setup, args) {
588
588
  {
589
589
  id: "synthesis_only",
590
590
  label: "Synthesis only",
591
- description: "Show one Long Table answer unless I ask for more."
591
+ description: "Show one LongTable answer unless I ask for more."
592
592
  },
593
593
  {
594
594
  id: "show_on_conflict",
@@ -709,7 +709,7 @@ async function runInit(args) {
709
709
  console.log("- Start here: `longtable start`.");
710
710
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
711
711
  console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
712
- console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
712
+ console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
713
713
  }
714
714
  }
715
715
  async function runShow(args) {
@@ -767,7 +767,7 @@ async function runCodexPersistInit(args) {
767
767
  console.log("- Start here: `longtable start`.");
768
768
  console.log("- If you want a direct natural-language entry: `longtable ask --prompt \"...\"`.");
769
769
  console.log("- Codex prompt files are available as an experimental integration, not the primary path.");
770
- console.log("- Suggested next action: create a project workspace and let Long Table interview the current session.");
770
+ console.log("- Suggested next action: create a project workspace and let LongTable interview the current session.");
771
771
  }
772
772
  }
773
773
  async function resolvePrompt(prompt) {
@@ -779,7 +779,7 @@ async function resolvePrompt(prompt) {
779
779
  }
780
780
  const rl = createInterface({ input, output });
781
781
  try {
782
- return (await rl.question("What should Long Table help with?\n> ")).trim();
782
+ return (await rl.question("What should LongTable help with?\n> ")).trim();
783
783
  }
784
784
  finally {
785
785
  rl.close();
@@ -842,7 +842,7 @@ async function buildProjectAwarePrompt(prompt, workingDirectory) {
842
842
  return { prompt, projectContextFound: false };
843
843
  }
844
844
  const lines = [
845
- "Long Table project context",
845
+ "LongTable project context",
846
846
  `project: ${context.project.projectName}`,
847
847
  `current session goal: ${context.session.currentGoal}`,
848
848
  ...(context.session.currentBlocker ? [`current blocker: ${context.session.currentBlocker}`] : []),
@@ -936,8 +936,8 @@ async function runRoles(args) {
936
936
  console.log(JSON.stringify(payload, null, 2));
937
937
  return;
938
938
  }
939
- console.log("Long Table roles");
940
- console.log("These are perspectives Long Table can consult when relevant.");
939
+ console.log("LongTable roles");
940
+ console.log("These are perspectives LongTable can consult when relevant.");
941
941
  console.log("Inside Codex, explicit forms like `lt editor: ...` and `lt methods: ...` are stronger than plain natural language.");
942
942
  console.log("");
943
943
  for (const persona of payload) {
@@ -951,7 +951,7 @@ async function runStart(args) {
951
951
  const setupPath = typeof args.setup === "string" ? args.setup : undefined;
952
952
  const existingSetup = await loadOptionalSetup(setupPath);
953
953
  if (!existingSetup) {
954
- throw new Error("Long Table global setup is missing. Run `longtable init --flow interview` first.");
954
+ throw new Error("LongTable global setup is missing. Run `longtable init --flow interview` first.");
955
955
  }
956
956
  const interview = await collectProjectInterview(existingSetup, args);
957
957
  await verifyWritableWorkspaceParent(interview.projectPath);
@@ -968,8 +968,12 @@ async function runStart(args) {
968
968
  console.log(JSON.stringify({
969
969
  project: context.project,
970
970
  session: context.session,
971
- projectFilePath: context.projectFilePath,
972
- sessionFilePath: context.sessionFilePath
971
+ files: {
972
+ project: context.projectFilePath,
973
+ session: context.sessionFilePath,
974
+ state: context.stateFilePath,
975
+ current: context.currentFilePath
976
+ }
973
977
  }, null, 2));
974
978
  return;
975
979
  }
@@ -979,7 +983,7 @@ async function runStart(args) {
979
983
  `1. cd "${context.project.projectPath}"`,
980
984
  "2. run `codex` in that directory",
981
985
  "3. begin with your current goal in natural language",
982
- "4. if you return later, read `START-HERE.md`, `NEXT-STEPS.md`, or run `longtable resume`",
986
+ "4. if you return later, open `CURRENT.md` or run `longtable resume`",
983
987
  "",
984
988
  `Suggested first message: ${context.session.currentBlocker ? `"I want to work on ${context.session.currentGoal}. My current blocker is ${context.session.currentBlocker}."` : `"I want to work on ${context.session.currentGoal}."`}`,
985
989
  "",
@@ -990,22 +994,24 @@ async function runResume(args) {
990
994
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
991
995
  const context = await loadProjectContextFromDirectory(workingDirectory);
992
996
  if (!context) {
993
- throw new Error("No Long Table project workspace was found here. Run `longtable start` first or pass --cwd.");
997
+ throw new Error("No LongTable project workspace was found here. Run `longtable start` first or pass --cwd.");
994
998
  }
999
+ await syncCurrentWorkspaceView(context);
995
1000
  const payload = {
996
1001
  project: context.project,
997
1002
  session: context.session,
998
1003
  files: {
999
- startHere: resolve(context.project.projectPath, "START-HERE.md"),
1000
- nextSteps: resolve(context.project.projectPath, "NEXT-STEPS.md"),
1001
- sessionSnapshot: resolve(context.project.projectPath, "SESSION-SNAPSHOT.md")
1004
+ current: resolve(context.project.projectPath, "CURRENT.md"),
1005
+ project: context.projectFilePath,
1006
+ session: context.sessionFilePath,
1007
+ state: context.stateFilePath
1002
1008
  }
1003
1009
  };
1004
1010
  if (args.json === true) {
1005
1011
  console.log(JSON.stringify(payload, null, 2));
1006
1012
  return;
1007
1013
  }
1008
- console.log(renderSectionCard("Long Table Resume", [
1014
+ console.log(renderSectionCard("LongTable Resume", [
1009
1015
  `Project: ${context.project.projectName}`,
1010
1016
  `Path: ${context.project.projectPath}`,
1011
1017
  `Current goal: ${context.session.currentGoal}`,
@@ -1013,19 +1019,17 @@ async function runResume(args) {
1013
1019
  `Requested perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
1014
1020
  `Disagreement: ${context.session.disagreementPreference}`,
1015
1021
  "",
1016
- "Resume files:",
1017
- `- ${payload.files.startHere}`,
1018
- `- ${payload.files.nextSteps}`,
1019
- `- ${payload.files.sessionSnapshot}`,
1022
+ "Current file:",
1023
+ `- ${payload.files.current}`,
1020
1024
  "",
1021
- `Suggested restart message: ${context.session.currentBlocker ? `"I want to continue ${context.session.currentGoal}. The unresolved blocker is ${context.session.currentBlocker}."` : `"I want to continue ${context.session.currentGoal}."`}`
1025
+ `Suggested restart message: "${context.session.resumeHint ?? (context.session.currentBlocker ? `I want to continue ${context.session.currentGoal}. The unresolved blocker is ${context.session.currentBlocker}.` : `I want to continue ${context.session.currentGoal}.`)}"`
1022
1026
  ]));
1023
1027
  }
1024
1028
  async function runCodexSubcommand(subcommand, args) {
1025
1029
  const customDir = typeof args.dir === "string" ? args.dir : undefined;
1026
1030
  if (subcommand === "install-prompts") {
1027
1031
  const installed = await installCodexPromptAliases(customDir);
1028
- console.log(`Installed ${installed.length} Long Table prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
1032
+ console.log(`Installed ${installed.length} LongTable prompt aliases in ${resolveCodexPromptsDir(customDir)}`);
1029
1033
  console.log("Note: prompt-file discovery depends on the Codex build. Treat this as an experimental integration.");
1030
1034
  for (const prompt of installed) {
1031
1035
  console.log(`- /prompts:${prompt.name}`);
@@ -1038,7 +1042,7 @@ async function runCodexSubcommand(subcommand, args) {
1038
1042
  }
1039
1043
  if (subcommand === "remove-prompts") {
1040
1044
  const removed = await removeCodexPromptAliases(customDir);
1041
- console.log(`Removed ${removed.length} Long Table prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
1045
+ console.log(`Removed ${removed.length} LongTable prompt aliases from ${resolveCodexPromptsDir(customDir)}`);
1042
1046
  return;
1043
1047
  }
1044
1048
  if (subcommand === "status") {
@@ -1057,7 +1061,7 @@ async function runCodexSubcommand(subcommand, args) {
1057
1061
  console.log(JSON.stringify(status, null, 2));
1058
1062
  return;
1059
1063
  }
1060
- console.log("Long Table Codex status");
1064
+ console.log("LongTable Codex status");
1061
1065
  console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
1062
1066
  console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
1063
1067
  console.log(`- prompt aliases dir: ${status.promptsDir}`);
@@ -1,5 +1,5 @@
1
1
  import { type CanonicalPersona } from "./personas.js";
2
- import type { InteractionMode } from "@diverga/core";
2
+ import type { InteractionMode } from "@longtable/core";
3
3
  export type OutputLanguage = "ko" | "en";
4
4
  export interface PersonaRoutingResult {
5
5
  outputLanguage: OutputLanguage;
@@ -118,8 +118,8 @@ export function renderDisclosure(roles, language) {
118
118
  }
119
119
  const labels = roles.map((role) => getPersonaDefinition(role).label);
120
120
  return language === "ko"
121
- ? `Long Table consulted: ${labels.join(", ")}`
122
- : `Long Table consulted: ${labels.join(", ")}`;
121
+ ? `LongTable consulted: ${labels.join(", ")}`
122
+ : `LongTable consulted: ${labels.join(", ")}`;
123
123
  }
124
124
  export function buildPersonaGuidance(options) {
125
125
  const directive = parseInvocationDirective(options.prompt);
@@ -129,8 +129,8 @@ export function buildPersonaGuidance(options) {
129
129
  const disclosure = renderDisclosure(routing.consultedRoles, routing.outputLanguage);
130
130
  const lines = [];
131
131
  lines.push(routing.outputLanguage === "ko"
132
- ? `Long Table mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`
133
- : `Long Table mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`);
132
+ ? `LongTable mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`
133
+ : `LongTable mode: ${options.mode[0].toUpperCase()}${options.mode.slice(1)}`);
134
134
  if (disclosure) {
135
135
  lines.push(disclosure);
136
136
  }
@@ -141,8 +141,8 @@ export function buildPersonaGuidance(options) {
141
141
  }
142
142
  if (options.panel || directive.panel) {
143
143
  lines.push(routing.outputLanguage === "ko"
144
- ? "Return format: 1) Long Table synthesis 2) panel opinions by role 3) decision prompt to the researcher."
145
- : "Return format: 1) Long Table synthesis 2) panel opinions by role 3) decision prompt to the researcher.");
144
+ ? "Return format: 1) LongTable synthesis 2) panel opinions by role 3) decision prompt to the researcher."
145
+ : "Return format: 1) LongTable synthesis 2) panel opinions by role 3) decision prompt to the researcher.");
146
146
  }
147
147
  if (options.showConflicts || directive.showConflicts) {
148
148
  lines.push(routing.outputLanguage === "ko"
@@ -1,11 +1,13 @@
1
- import type { SetupPersistedOutput } from "@diverga/setup";
1
+ import type { SetupPersistedOutput } from "@longtable/setup";
2
2
  export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
3
3
  export interface LongTableProjectRecord {
4
4
  schemaVersion: 1;
5
- product: "Long Table";
5
+ product: "LongTable";
6
6
  projectName: string;
7
7
  projectPath: string;
8
8
  createdAt: string;
9
+ contractVersion?: "workspace-v2";
10
+ locale?: string;
9
11
  globalSetupSummary: {
10
12
  field: string;
11
13
  careerStage: string;
@@ -20,20 +22,29 @@ export interface LongTableSessionRecord {
20
22
  schemaVersion: 1;
21
23
  id: string;
22
24
  createdAt: string;
25
+ lastUpdatedAt?: string;
23
26
  projectName: string;
24
27
  projectPath: string;
25
28
  currentGoal: string;
26
29
  currentBlocker?: string;
30
+ nextAction?: string;
31
+ openQuestions?: string[];
27
32
  requestedPerspectives: string[];
28
33
  disagreementPreference: ProjectDisagreementPreference;
34
+ activeModes?: string[];
35
+ resumeHint?: string;
36
+ locale?: string;
29
37
  }
30
38
  export interface LongTableProjectContext {
31
39
  project: LongTableProjectRecord;
32
40
  session: LongTableSessionRecord;
33
41
  projectFilePath: string;
34
42
  sessionFilePath: string;
43
+ stateFilePath: string;
44
+ currentFilePath: string;
35
45
  metaDir: string;
36
46
  }
47
+ export declare function syncCurrentWorkspaceView(context: LongTableProjectContext): Promise<string>;
37
48
  export declare function createOrUpdateProjectWorkspace(options: {
38
49
  projectName: string;
39
50
  projectPath: string;
@@ -1,7 +1,10 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
3
4
  import { dirname, join, resolve } from "node:path";
4
- import { createEmptyResearchState } from "@diverga/memory";
5
+ import { createEmptyResearchState } from "@longtable/memory";
6
+ const CURRENT_FILE_NAME = "CURRENT.md";
7
+ const LEGACY_ROOT_FILES = ["LONGTABLE.md", "START-HERE.md", "NEXT-STEPS.md", "SESSION-SNAPSHOT.md"];
5
8
  function nowIso() {
6
9
  return new Date().toISOString();
7
10
  }
@@ -16,160 +19,168 @@ function slugify(input) {
16
19
  function resolveMetaDir(projectPath) {
17
20
  return join(projectPath, ".longtable");
18
21
  }
19
- function buildWorkspaceGuide(project, session) {
20
- const lines = [
21
- "# Long Table Workspace",
22
- "",
23
- "This directory is a Long Table research workspace.",
24
- "",
25
- "## Current project",
26
- `- Project: ${project.projectName}`,
27
- `- Goal right now: ${session.currentGoal}`,
28
- ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
29
- `- Disagreement visibility: ${session.disagreementPreference}`,
30
- `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
31
- "",
32
- "## How Codex should behave here",
33
- "- Treat this as a researcher-facing Long Table session, not a generic coding task.",
34
- "- Ask clarifying or tension questions before closing too early.",
35
- "- If you foreground specific roles, disclose them with `Long Table consulted: ...`.",
36
- "- Keep one accountable synthesis, but keep disagreement visible by default when it matters.",
37
- "- For factual, current, or external claims, attach source links or local file references whenever possible.",
38
- "- If you cannot source a statement, label it as an inference or estimate.",
39
- "- Do not expose internal tool logs or process commentary in researcher-facing answers.",
40
- "",
41
- "## Session files",
42
- "- `.longtable/project.json` contains project-level metadata.",
43
- "- `.longtable/current-session.json` contains the current session goal and blocker."
44
- ];
45
- return lines.join("\n");
22
+ function resolveStateFilePath(projectPath) {
23
+ return join(resolveMetaDir(projectPath), "state.json");
46
24
  }
47
- function buildStartHereGuide(project, session) {
48
- const lines = [
49
- "# Start Here",
50
- "",
51
- `Project: ${project.projectName}`,
52
- "",
53
- "This workspace was created by Long Table for a single research project.",
54
- "",
55
- "## What to do now",
56
- "1. Open Codex in this directory.",
57
- "2. Start with your current goal, not a shell command.",
58
- "3. Let Long Table keep disagreement visible when it matters.",
59
- "4. If you ask for factual or current external information, expect sources or an explicit inference label.",
60
- "",
61
- "## Suggested first message",
62
- session.currentBlocker
63
- ? `"I want to work on ${session.currentGoal}. My current blocker is: ${session.currentBlocker}."`
64
- : `"I want to work on ${session.currentGoal}."`,
65
- "",
66
- "## Explicit Long Table invocation inside Codex",
67
- "- `lt explore: <question>`",
68
- "- `lt review: <claim or plan>`",
69
- "- `lt panel: <claim or plan>`",
70
- "- `lt editor: <draft or positioning question>`",
71
- "- `lt reviewer: <claim or section>`",
72
- "- `lt methods: <design, measure, or analysis plan>`",
73
- "- `lt commit: <decision that needs commitment>`",
74
- "",
75
- "## What Long Table already knows in this directory",
76
- `- Current goal: ${session.currentGoal}`,
77
- ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
78
- `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
79
- `- Disagreement visibility: ${session.disagreementPreference}`,
80
- "",
81
- "## Files",
82
- "- `AGENTS.md` tells Codex how to behave in this workspace.",
83
- "- `.longtable/current-session.json` stores the current session goal and blocker.",
84
- "- `.longtable/project.json` stores the project-level record.",
85
- "- `NEXT-STEPS.md` stores the immediate next actions.",
86
- "- `SESSION-SNAPSHOT.md` stores the short resume snapshot."
87
- ];
88
- return lines.join("\n");
25
+ function resolveCurrentFilePath(projectPath) {
26
+ return join(projectPath, CURRENT_FILE_NAME);
89
27
  }
90
- function buildNextStepsGuide(project, session) {
91
- const firstQuestion = session.currentBlocker
28
+ function normalizeLocale(locale) {
29
+ if ((locale ?? "").toLowerCase().startsWith("ko")) {
30
+ return "ko";
31
+ }
32
+ return "en";
33
+ }
34
+ function resolveUserLocale() {
35
+ const envLocale = process.env.LC_ALL ??
36
+ process.env.LC_MESSAGES ??
37
+ process.env.LANG ??
38
+ "";
39
+ if (envLocale && !["c", "c.utf-8", "posix"].includes(envLocale.toLowerCase())) {
40
+ return normalizeLocale(envLocale);
41
+ }
42
+ if (process.platform === "darwin") {
43
+ try {
44
+ const appleLocale = execSync("defaults read -g AppleLocale", { encoding: "utf8" }).trim();
45
+ if (appleLocale) {
46
+ return normalizeLocale(appleLocale);
47
+ }
48
+ }
49
+ catch {
50
+ // Ignore and fall back to Intl below.
51
+ }
52
+ }
53
+ return normalizeLocale(Intl.DateTimeFormat().resolvedOptions().locale);
54
+ }
55
+ function buildFirstQuestion(session) {
56
+ return session.currentBlocker
92
57
  ? `What would reduce the uncertainty around "${session.currentBlocker}" first?`
93
58
  : `What is the first concrete question that would move "${session.currentGoal}" forward?`;
59
+ }
60
+ function buildOpenQuestions(session) {
61
+ const firstQuestion = buildFirstQuestion(session);
62
+ return session.currentBlocker
63
+ ? [
64
+ firstQuestion,
65
+ `What evidence would let you decide whether "${session.currentBlocker}" is a knowledge gap, a coding rule gap, or a data gap?`
66
+ ]
67
+ : [
68
+ firstQuestion,
69
+ `What would count as a good first outcome for "${session.currentGoal}" in this session?`
70
+ ];
71
+ }
72
+ function buildNextAction(session) {
73
+ return session.currentBlocker
74
+ ? `Open with the blocker, then ask LongTable to surface the first high-leverage uncertainty around "${session.currentBlocker}".`
75
+ : "Open with your current goal in one sentence, then ask LongTable for the first concrete research move.";
76
+ }
77
+ function buildResumeHint(session) {
78
+ return session.currentBlocker
79
+ ? `I want to continue ${session.currentGoal}. The unresolved blocker is ${session.currentBlocker}.`
80
+ : `I want to continue ${session.currentGoal}.`;
81
+ }
82
+ function buildCurrentGuide(project, session) {
83
+ const locale = normalizeLocale(session.locale ?? project.locale);
84
+ const openQuestions = session.openQuestions && session.openQuestions.length > 0
85
+ ? session.openQuestions
86
+ : buildOpenQuestions(session);
87
+ const nextAction = session.nextAction ?? buildNextAction(session);
88
+ const resumeHint = session.resumeHint ?? buildResumeHint(session);
89
+ const suggestedPrompt = `lt explore: ${openQuestions[0]}`;
90
+ if (locale === "ko") {
91
+ return [
92
+ "# CURRENT",
93
+ "",
94
+ `Project: ${project.projectName}`,
95
+ "",
96
+ "이 파일은 `.longtable/current-session.json`과 `.longtable/state.json`에서 재생성되는 현재 작업 뷰입니다.",
97
+ "",
98
+ "## 지금 초점",
99
+ `- 현재 목표: ${session.currentGoal}`,
100
+ ...(session.currentBlocker ? [`- 현재 blocker: ${session.currentBlocker}`] : []),
101
+ `- 다음 액션: ${nextAction}`,
102
+ `- 관점: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
103
+ `- disagreement: ${session.disagreementPreference}`,
104
+ "",
105
+ "## 열린 질문",
106
+ ...openQuestions.map((question) => `- ${question}`),
107
+ "",
108
+ "## 다시 시작 문장",
109
+ `- "${resumeHint}"`,
110
+ "",
111
+ "## 빠른 시작",
112
+ "- 이 디렉토리에서 `codex`를 엽니다.",
113
+ `- 첫 메시지는 보통 \`${suggestedPrompt}\` 정도면 충분합니다.`,
114
+ "",
115
+ "## 증거 규칙",
116
+ "- 외부 사실이나 현재 정보는 source를 붙이거나 inference로 낮춥니다."
117
+ ].join("\n");
118
+ }
94
119
  return [
95
- "# Next Steps",
120
+ "# CURRENT",
96
121
  "",
97
122
  `Project: ${project.projectName}`,
98
123
  "",
99
- "## Immediate actions",
100
- "1. Open Codex in this directory.",
101
- "2. State your current goal in one sentence.",
102
- "3. Ask Long Table to surface disagreement before closing on a plan.",
124
+ "This file is regenerated from `.longtable/current-session.json` and `.longtable/state.json`.",
103
125
  "",
104
- "## Suggested openings",
105
- `- ${session.currentBlocker ? `"I want to work on ${session.currentGoal}. My current blocker is ${session.currentBlocker}."` : `"I want to work on ${session.currentGoal}."`}`,
106
- `- "lt explore: ${firstQuestion}"`,
107
- `- "lt panel: Show me the strongest disagreement before I commit."`,
108
- "",
109
- "## If you come back later",
110
- "- Read `SESSION-SNAPSHOT.md` first.",
111
- "- Continue from the unresolved blocker rather than restating the whole project.",
126
+ "## Focus Now",
127
+ `- Current goal: ${session.currentGoal}`,
128
+ ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
129
+ `- Next action: ${nextAction}`,
130
+ `- Perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
131
+ `- Disagreement: ${session.disagreementPreference}`,
112
132
  "",
113
- "## Evidence reminder",
114
- "- For factual or current external claims, Long Table should provide a source link or label the statement as inference."
115
- ].join("\n");
116
- }
117
- function buildSessionSnapshot(project, session) {
118
- return [
119
- "# Session Snapshot",
133
+ "## Open Questions",
134
+ ...openQuestions.map((question) => `- ${question}`),
120
135
  "",
121
- `Project: ${project.projectName}`,
122
- `Current goal: ${session.currentGoal}`,
123
- ...(session.currentBlocker ? [`Current blocker: ${session.currentBlocker}`] : []),
124
- `Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
125
- `Disagreement visibility: ${session.disagreementPreference}`,
136
+ "## Restart Prompt",
137
+ `- "${resumeHint}"`,
126
138
  "",
127
- "## Resume prompt",
128
- session.currentBlocker
129
- ? `"I want to continue ${session.currentGoal}. The unresolved blocker is ${session.currentBlocker}."`
130
- : `"I want to continue ${session.currentGoal}."`,
139
+ "## Quick Start",
140
+ "- Open `codex` in this directory.",
141
+ `- A good first message is usually \`${suggestedPrompt}\`.`,
131
142
  "",
132
- "## Notes",
133
- "- This is a short orientation artifact, not a full transcript.",
134
- "- If factual or external claims matter, Long Table should cite sources or mark them as inference."
143
+ "## Evidence Rule",
144
+ "- External or current claims should carry a source link or be labeled as inference."
135
145
  ].join("\n");
136
146
  }
137
147
  function buildProjectAgentsMd(project, session) {
138
148
  return [
139
149
  "# AGENTS.md",
140
150
  "",
141
- "This directory is a Long Table research workspace.",
151
+ "This directory is a LongTable research workspace.",
142
152
  "",
143
- "## Purpose",
144
- `- Project name: ${project.projectName}`,
145
- `- Current goal: ${session.currentGoal}`,
146
- ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
147
- `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
148
- `- Disagreement visibility: ${session.disagreementPreference}`,
149
- "",
150
- "## Research-facing behavior",
153
+ "## Runtime Contract",
151
154
  "- Treat researcher interaction as the primary task.",
152
- "- If the user message starts with `lt `, `longtable `, `long table `, or `롱테이블 ` followed by a directive and `:`, treat it as an explicit Long Table invocation.",
155
+ "- Read `.longtable/current-session.json` before giving substantial guidance.",
156
+ "- Use `.longtable/project.json` as stable project context.",
157
+ "- Use `.longtable/state.json` as layered working memory.",
158
+ "- Prefer `currentGoal`, `currentBlocker`, `nextAction`, and `openQuestions` over generic assumptions.",
159
+ "- Treat `AGENTS.md` as runtime guidance, not as the researcher-facing resume artifact.",
160
+ "",
161
+ "## Invocation Rules",
162
+ "- If the user message starts with `lt `, `longtable `, `long table `, or `롱테이블 ` followed by a directive and `:`, treat it as an explicit LongTable invocation.",
153
163
  "- Supported explicit directives are: explore, review, critique, draft, commit, panel, status, editor, reviewer, methods, theory, measurement, ethics, voice, venue.",
154
- "- For explicit Long Table invocations, do not begin by scanning the workspace. Use the current session files first and answer as Long Table immediately.",
155
- "- For general research requests in this workspace, prefer Long Table behavior before generic coding behavior.",
164
+ "- For explicit LongTable invocations, do not begin by scanning the workspace. Use the current session files first and answer as LongTable immediately.",
165
+ "- For general research requests in this workspace, prefer LongTable behavior before generic coding behavior.",
166
+ "",
167
+ "## Research Behavior",
156
168
  "- Begin exploratory work with clarifying or tension questions before recommending a direction.",
157
- "- If you foreground role perspectives, disclose them with `Long Table consulted: ...`.",
169
+ "- If you foreground role perspectives, disclose them with `LongTable consulted: ...`.",
158
170
  "- Keep one accountable synthesis, but do not hide meaningful disagreement.",
159
- "- For factual, current, or external claims, provide source links or file references when possible.",
160
- "- If a statement cannot be sourced, label it as an inference or estimate instead of presenting it as a fact.",
161
171
  ...(session.disagreementPreference === "always_visible"
162
- ? ["- In this workspace, panel disagreement should be visible by default rather than hidden behind a single synthesis."]
172
+ ? ["- Panel disagreement should be visible by default rather than hidden behind a single synthesis."]
163
173
  : []),
174
+ "- For factual, current, or external claims, provide source links or file references when possible.",
175
+ "- If a statement cannot be sourced, label it as an inference or estimate instead of presenting it as a fact.",
164
176
  "- Do not expose internal tool logs, file-search traces, or process commentary in the researcher-facing answer.",
165
177
  "",
166
- "## Session memory",
167
- "- Read `.longtable/current-session.json` before giving substantial guidance.",
168
- "- Use `.longtable/project.json` as the project-level context.",
169
- "- Prefer the current goal and blocker over generic assumptions.",
170
- "- Do not recursively search unrelated files unless the researcher explicitly asks for file analysis or code changes.",
171
- "",
172
178
  "## Scope",
179
+ `- Project: ${project.projectName}`,
180
+ `- Current goal: ${session.currentGoal}`,
181
+ ...(session.currentBlocker ? [`- Current blocker: ${session.currentBlocker}`] : []),
182
+ `- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
183
+ `- Disagreement visibility: ${session.disagreementPreference}`,
173
184
  "- These instructions apply to this directory and its children."
174
185
  ].join("\n");
175
186
  }
@@ -180,12 +191,18 @@ function buildStateSeed(project, session, setup) {
180
191
  careerStage: setup.profileSeed.careerStage,
181
192
  experienceLevel: setup.profileSeed.experienceLevel,
182
193
  projectName: project.projectName,
183
- currentGoal: session.currentGoal,
184
194
  disagreementPreference: session.disagreementPreference,
185
195
  requestedPerspectives: session.requestedPerspectives
186
196
  };
197
+ state.workingState = {
198
+ currentGoal: session.currentGoal,
199
+ ...(session.currentBlocker ? { currentBlocker: session.currentBlocker } : {}),
200
+ ...(session.nextAction ? { nextAction: session.nextAction } : {}),
201
+ openQuestions: session.openQuestions ?? [],
202
+ activeModes: session.activeModes ?? [],
203
+ ...(session.resumeHint ? { resumeHint: session.resumeHint } : {})
204
+ };
187
205
  if (session.currentBlocker) {
188
- state.explicitState.currentBlocker = session.currentBlocker;
189
206
  state.openTensions.push(session.currentBlocker);
190
207
  }
191
208
  if (setup.profileSeed.humanAuthorshipSignal) {
@@ -213,24 +230,42 @@ function buildStateSeed(project, session, setup) {
213
230
  }
214
231
  return JSON.stringify(state, null, 2);
215
232
  }
233
+ async function removeLegacyRootFiles(projectPath) {
234
+ await Promise.all(LEGACY_ROOT_FILES.map((file) => rm(join(projectPath, file), { force: true })));
235
+ }
236
+ export async function syncCurrentWorkspaceView(context) {
237
+ const body = buildCurrentGuide(context.project, context.session);
238
+ await writeFile(context.currentFilePath, body, "utf8");
239
+ return context.currentFilePath;
240
+ }
216
241
  export async function createOrUpdateProjectWorkspace(options) {
217
242
  const projectPath = resolve(options.projectPath);
218
243
  const metaDir = resolveMetaDir(projectPath);
219
244
  const sessionsDir = join(metaDir, "sessions");
220
245
  const projectFilePath = join(metaDir, "project.json");
221
246
  const sessionFilePath = join(metaDir, "current-session.json");
247
+ const stateFilePath = resolveStateFilePath(projectPath);
248
+ const currentFilePath = resolveCurrentFilePath(projectPath);
222
249
  const sessionId = slugify(`${options.projectName}-${Date.now()}`);
250
+ const locale = resolveUserLocale();
251
+ const timestamp = nowIso();
223
252
  await mkdir(projectPath, { recursive: true });
224
253
  await mkdir(metaDir, { recursive: true });
225
254
  await mkdir(sessionsDir, { recursive: true });
226
255
  const project = existsSync(projectFilePath)
227
- ? JSON.parse(await readFile(projectFilePath, "utf8"))
256
+ ? {
257
+ ...JSON.parse(await readFile(projectFilePath, "utf8")),
258
+ contractVersion: "workspace-v2",
259
+ locale
260
+ }
228
261
  : {
229
262
  schemaVersion: 1,
230
- product: "Long Table",
263
+ product: "LongTable",
231
264
  projectName: options.projectName,
232
265
  projectPath,
233
- createdAt: nowIso(),
266
+ createdAt: timestamp,
267
+ contractVersion: "workspace-v2",
268
+ locale,
234
269
  globalSetupSummary: {
235
270
  field: options.setup.profileSeed.field,
236
271
  careerStage: options.setup.profileSeed.careerStage,
@@ -250,30 +285,67 @@ export async function createOrUpdateProjectWorkspace(options) {
250
285
  const session = {
251
286
  schemaVersion: 1,
252
287
  id: sessionId,
253
- createdAt: nowIso(),
288
+ createdAt: timestamp,
289
+ lastUpdatedAt: timestamp,
254
290
  projectName: project.projectName,
255
291
  projectPath,
256
292
  currentGoal: options.currentGoal,
257
293
  ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
294
+ nextAction: buildNextAction({
295
+ schemaVersion: 1,
296
+ id: sessionId,
297
+ createdAt: timestamp,
298
+ projectName: project.projectName,
299
+ projectPath,
300
+ currentGoal: options.currentGoal,
301
+ ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
302
+ requestedPerspectives: options.requestedPerspectives,
303
+ disagreementPreference: options.disagreementPreference
304
+ }),
305
+ openQuestions: buildOpenQuestions({
306
+ schemaVersion: 1,
307
+ id: sessionId,
308
+ createdAt: timestamp,
309
+ projectName: project.projectName,
310
+ projectPath,
311
+ currentGoal: options.currentGoal,
312
+ ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
313
+ requestedPerspectives: options.requestedPerspectives,
314
+ disagreementPreference: options.disagreementPreference
315
+ }),
258
316
  requestedPerspectives: options.requestedPerspectives,
259
- disagreementPreference: options.disagreementPreference
317
+ disagreementPreference: options.disagreementPreference,
318
+ activeModes: ["explore"],
319
+ resumeHint: buildResumeHint({
320
+ schemaVersion: 1,
321
+ id: sessionId,
322
+ createdAt: timestamp,
323
+ projectName: project.projectName,
324
+ projectPath,
325
+ currentGoal: options.currentGoal,
326
+ ...(options.currentBlocker ? { currentBlocker: options.currentBlocker } : {}),
327
+ requestedPerspectives: options.requestedPerspectives,
328
+ disagreementPreference: options.disagreementPreference
329
+ }),
330
+ locale
260
331
  };
261
- await writeFile(projectFilePath, JSON.stringify(project, null, 2), "utf8");
262
- await writeFile(sessionFilePath, JSON.stringify(session, null, 2), "utf8");
263
- await writeFile(join(sessionsDir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf8");
264
- await writeFile(join(metaDir, "state.json"), buildStateSeed(project, session, options.setup), "utf8");
265
- await writeFile(join(projectPath, "LONGTABLE.md"), buildWorkspaceGuide(project, session), "utf8");
266
- await writeFile(join(projectPath, "START-HERE.md"), buildStartHereGuide(project, session), "utf8");
267
- await writeFile(join(projectPath, "NEXT-STEPS.md"), buildNextStepsGuide(project, session), "utf8");
268
- await writeFile(join(projectPath, "SESSION-SNAPSHOT.md"), buildSessionSnapshot(project, session), "utf8");
269
- await writeFile(join(projectPath, "AGENTS.md"), buildProjectAgentsMd(project, session), "utf8");
270
- return {
332
+ const context = {
271
333
  project,
272
334
  session,
273
335
  projectFilePath,
274
336
  sessionFilePath,
337
+ stateFilePath,
338
+ currentFilePath,
275
339
  metaDir
276
340
  };
341
+ await writeFile(projectFilePath, JSON.stringify(project, null, 2), "utf8");
342
+ await writeFile(sessionFilePath, JSON.stringify(session, null, 2), "utf8");
343
+ await writeFile(join(sessionsDir, `${sessionId}.json`), JSON.stringify(session, null, 2), "utf8");
344
+ await writeFile(stateFilePath, buildStateSeed(project, session, options.setup), "utf8");
345
+ await writeFile(join(projectPath, "AGENTS.md"), buildProjectAgentsMd(project, session), "utf8");
346
+ await syncCurrentWorkspaceView(context);
347
+ await removeLegacyRootFiles(projectPath);
348
+ return context;
277
349
  }
278
350
  export async function loadProjectContextFromDirectory(startPath) {
279
351
  let current = resolve(startPath);
@@ -282,11 +354,22 @@ export async function loadProjectContextFromDirectory(startPath) {
282
354
  const projectFilePath = join(metaDir, "project.json");
283
355
  const sessionFilePath = join(metaDir, "current-session.json");
284
356
  if (existsSync(projectFilePath) && existsSync(sessionFilePath)) {
357
+ const project = JSON.parse(await readFile(projectFilePath, "utf8"));
358
+ const session = JSON.parse(await readFile(sessionFilePath, "utf8"));
285
359
  return {
286
- project: JSON.parse(await readFile(projectFilePath, "utf8")),
287
- session: JSON.parse(await readFile(sessionFilePath, "utf8")),
360
+ project,
361
+ session: {
362
+ ...session,
363
+ locale: session.locale ?? project.locale ?? resolveUserLocale(),
364
+ openQuestions: session.openQuestions ?? buildOpenQuestions(session),
365
+ nextAction: session.nextAction ?? buildNextAction(session),
366
+ resumeHint: session.resumeHint ?? buildResumeHint(session),
367
+ activeModes: session.activeModes ?? ["explore"]
368
+ },
288
369
  projectFilePath,
289
370
  sessionFilePath,
371
+ stateFilePath: resolveStateFilePath(project.projectPath),
372
+ currentFilePath: resolveCurrentFilePath(project.projectPath),
290
373
  metaDir
291
374
  };
292
375
  }
@@ -300,7 +383,7 @@ export async function loadProjectContextFromDirectory(startPath) {
300
383
  export function renderProjectWorkspaceSummary(context) {
301
384
  return [
302
385
  "┌──────────────────────────────────────────────┐",
303
- "│ Long Table Project Workspace │",
386
+ "│ LongTable Project Workspace │",
304
387
  "└──────────────────────────────────────────────┘",
305
388
  `Project: ${context.project.projectName}`,
306
389
  `Path: ${context.project.projectPath}`,
@@ -312,9 +395,8 @@ export function renderProjectWorkspaceSummary(context) {
312
395
  "Created files:",
313
396
  `- ${context.projectFilePath}`,
314
397
  `- ${context.sessionFilePath}`,
315
- `- ${join(context.project.projectPath, "START-HERE.md")}`,
316
- `- ${join(context.project.projectPath, "NEXT-STEPS.md")}`,
317
- `- ${join(context.project.projectPath, "SESSION-SNAPSHOT.md")}`,
398
+ `- ${context.stateFilePath}`,
399
+ `- ${context.currentFilePath}`,
318
400
  `- ${join(context.project.projectPath, "AGENTS.md")}`
319
401
  ].join("\n");
320
402
  }
@@ -9,26 +9,26 @@ function promptSpec() {
9
9
  return [
10
10
  {
11
11
  name: "longtable",
12
- description: "Single-entry Long Table router for research conversations",
12
+ description: "Single-entry LongTable router for research conversations",
13
13
  argumentHint: "<natural language research request>",
14
14
  body: [
15
- "You are Long Table.",
15
+ "You are LongTable.",
16
16
  "Classify the user's request into one of these modes: explore, review, critique, draft, commit, panel, or status.",
17
17
  "If the request is ambiguous, ask one short clarifying question before closing.",
18
- "Always begin with `Long Table mode: <Mode>`.",
19
- "Always disclose consulted roles with `Long Table consulted: ...` when any role is foregrounded.",
18
+ "Always begin with `LongTable mode: <Mode>`.",
19
+ "Always disclose consulted roles with `LongTable consulted: ...` when any role is foregrounded.",
20
20
  "In explore mode, ask at least two clarifying or tension questions before any recommendation.",
21
- "In panel mode, return 1) Long Table synthesis 2) visible panel opinions by role 3) conflict summary if needed 4) a decision prompt for the researcher.",
21
+ "In panel mode, return 1) LongTable synthesis 2) visible panel opinions by role 3) conflict summary if needed 4) a decision prompt for the researcher.",
22
22
  "Do not expose internal tool logs, file searches, or process notes in the researcher-facing answer.",
23
23
  "Treat any slash-command arguments as the current research object."
24
24
  ]
25
25
  },
26
26
  {
27
27
  name: "longtable-init",
28
- description: "Run Long Table researcher onboarding inside Codex",
28
+ description: "Run LongTable researcher onboarding inside Codex",
29
29
  argumentHint: "[project context or current uncertainty]",
30
30
  body: [
31
- "You are Long Table onboarding inside Codex.",
31
+ "You are LongTable onboarding inside Codex.",
32
32
  "First ask whether the researcher wants Quickstart or Interview setup.",
33
33
  "Ask exactly one setup question at a time.",
34
34
  "Use numbered choices when possible and include a 'None of the above' option when needed.",
@@ -45,11 +45,11 @@ function promptSpec() {
45
45
  },
46
46
  {
47
47
  name: "longtable-explore",
48
- description: "Long Table explore mode for open research questions",
48
+ description: "LongTable explore mode for open research questions",
49
49
  argumentHint: "<topic or research problem>",
50
50
  body: [
51
- "You are Long Table in explore mode.",
52
- "Always begin with `Long Table mode: Explore`.",
51
+ "You are LongTable in explore mode.",
52
+ "Always begin with `LongTable mode: Explore`.",
53
53
  "Ask at least two clarifying or tension questions before any recommendation.",
54
54
  "Keep unresolved tensions visible.",
55
55
  "Do not rush to synthesis.",
@@ -59,11 +59,11 @@ function promptSpec() {
59
59
  },
60
60
  {
61
61
  name: "longtable-review",
62
- description: "Long Table review mode for critical evaluation",
62
+ description: "LongTable review mode for critical evaluation",
63
63
  argumentHint: "<claim, paragraph, design, or plan>",
64
64
  body: [
65
- "You are Long Table in review mode.",
66
- "Always begin with `Long Table mode: Review`.",
65
+ "You are LongTable in review mode.",
66
+ "Always begin with `LongTable mode: Review`.",
67
67
  "Surface why this may be wrong before synthesis.",
68
68
  "Preserve the researcher's own language where possible.",
69
69
  "Treat any slash-command arguments as the object to review."
@@ -71,12 +71,12 @@ function promptSpec() {
71
71
  },
72
72
  {
73
73
  name: "longtable-panel",
74
- description: "Long Table panel mode with visible role disagreement",
74
+ description: "LongTable panel mode with visible role disagreement",
75
75
  argumentHint: "<claim, plan, or draft for multi-role review>",
76
76
  body: [
77
- "You are Long Table in panel mode.",
78
- "Always begin with `Long Table mode: Panel`.",
79
- "Return 1) a Long Table synthesis 2) visible panel opinions by role 3) a decision prompt for the researcher.",
77
+ "You are LongTable in panel mode.",
78
+ "Always begin with `LongTable mode: Panel`.",
79
+ "Return 1) a LongTable synthesis 2) visible panel opinions by role 3) a decision prompt for the researcher.",
80
80
  "If roles disagree, do not collapse them too early.",
81
81
  "Disclose which roles were consulted.",
82
82
  "Treat any slash-command arguments as the object under discussion."
@@ -84,10 +84,10 @@ function promptSpec() {
84
84
  },
85
85
  {
86
86
  name: "longtable-editor",
87
- description: "Long Table editor view",
87
+ description: "LongTable editor view",
88
88
  argumentHint: "<claim, draft, or paper positioning>",
89
89
  body: [
90
- "You are Long Table with the Journal Editor role foregrounded.",
90
+ "You are LongTable with the Journal Editor role foregrounded.",
91
91
  "Prioritize venue fit, framing clarity, contribution shape, and likely editorial concerns.",
92
92
  "Disclose that the editor role was consulted.",
93
93
  "Treat any slash-command arguments as the editorial object."
@@ -95,10 +95,10 @@ function promptSpec() {
95
95
  },
96
96
  {
97
97
  name: "longtable-reviewer",
98
- description: "Long Table reviewer view",
98
+ description: "LongTable reviewer view",
99
99
  argumentHint: "<claim, method, or manuscript section>",
100
100
  body: [
101
- "You are Long Table with the Reviewer role foregrounded.",
101
+ "You are LongTable with the Reviewer role foregrounded.",
102
102
  "Prioritize likely objections, missing evidence, weak claims, and points needing clarification.",
103
103
  "Disclose that the reviewer role was consulted.",
104
104
  "Treat any slash-command arguments as the review object."
@@ -106,10 +106,10 @@ function promptSpec() {
106
106
  },
107
107
  {
108
108
  name: "longtable-methods",
109
- description: "Long Table methods-critic view",
109
+ description: "LongTable methods-critic view",
110
110
  argumentHint: "<study design, measure, or analysis plan>",
111
111
  body: [
112
- "You are Long Table with the Methods Critic role foregrounded.",
112
+ "You are LongTable with the Methods Critic role foregrounded.",
113
113
  "Prioritize design fit, methodological defensibility, and mismatches between question, measure, and analysis.",
114
114
  "Disclose that the methods critic role was consulted.",
115
115
  "Treat any slash-command arguments as the methodological object."
@@ -117,10 +117,10 @@ function promptSpec() {
117
117
  },
118
118
  {
119
119
  name: "longtable-critique",
120
- description: "Long Table critique mode for stronger counterarguments",
120
+ description: "LongTable critique mode for stronger counterarguments",
121
121
  argumentHint: "<claim or draft to challenge>",
122
122
  body: [
123
- "You are Long Table in critique mode.",
123
+ "You are LongTable in critique mode.",
124
124
  "Prioritize counterarguments, blind spots, and hidden assumptions.",
125
125
  "Do not smooth over uncertainty.",
126
126
  "Treat any slash-command arguments as the object to challenge."
@@ -128,10 +128,10 @@ function promptSpec() {
128
128
  },
129
129
  {
130
130
  name: "longtable-draft",
131
- description: "Long Table draft mode with narrative-trace preservation",
131
+ description: "LongTable draft mode with narrative-trace preservation",
132
132
  argumentHint: "<draft goal or section request>",
133
133
  body: [
134
- "You are Long Table in draft mode.",
134
+ "You are LongTable in draft mode.",
135
135
  "Preserve narrative trace and avoid generic fluency.",
136
136
  "Keep the researcher's voice recognizable.",
137
137
  "Treat any slash-command arguments as the drafting target."
@@ -139,10 +139,10 @@ function promptSpec() {
139
139
  },
140
140
  {
141
141
  name: "longtable-commit",
142
- description: "Long Table commit mode for explicit human decisions",
142
+ description: "LongTable commit mode for explicit human decisions",
143
143
  argumentHint: "<decision or choice that needs commitment>",
144
144
  body: [
145
- "You are Long Table in commit mode.",
145
+ "You are LongTable in commit mode.",
146
146
  "Before making any recommendation, ask for the human commitment that is actually at stake.",
147
147
  "Make the trade-offs explicit.",
148
148
  "Treat any slash-command arguments as the decision under consideration."
@@ -150,11 +150,11 @@ function promptSpec() {
150
150
  },
151
151
  {
152
152
  name: "longtable-status",
153
- description: "Inspect Long Table setup and Codex alias status",
153
+ description: "Inspect LongTable setup and Codex alias status",
154
154
  argumentHint: "[optional concern]",
155
155
  body: [
156
- "You are Long Table status mode.",
157
- "Inspect whether setup and runtime artifacts appear to exist under `~/.diverga/` and whether Long Table prompt aliases appear to be installed under `~/.codex/prompts/`.",
156
+ "You are LongTable status mode.",
157
+ "Inspect whether setup and runtime artifacts appear to exist under `~/.longtable/` and whether LongTable prompt aliases appear to be installed under `~/.codex/prompts/`.",
158
158
  "Summarize what is configured, what is missing, and the next minimal action.",
159
159
  "Treat any slash-command arguments as the user's concern."
160
160
  ]
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
- "description": "Researcher-facing Long Table CLI on top of the legacy Diverga package surface",
5
+ "description": "Researcher-facing LongTable CLI",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -12,6 +12,9 @@
12
12
  "import": "./dist/index.js"
13
13
  }
14
14
  },
15
+ "bin": {
16
+ "longtable": "./bin/longtable"
17
+ },
15
18
  "directories": {
16
19
  "bin": "./bin"
17
20
  },
@@ -25,9 +28,9 @@
25
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
26
29
  },
27
30
  "dependencies": {
28
- "@diverga/memory": "0.1.0",
29
- "@diverga/provider-codex": "0.1.1",
30
- "@diverga/setup": "0.1.4"
31
+ "@longtable/memory": "0.1.9",
32
+ "@longtable/provider-codex": "0.1.9",
33
+ "@longtable/setup": "0.1.9"
31
34
  },
32
35
  "devDependencies": {
33
36
  "@types/node": "^22.10.1",
@@ -44,9 +47,9 @@
44
47
  "license": "MIT",
45
48
  "repository": {
46
49
  "type": "git",
47
- "url": "git+https://github.com/HosungYou/Long-Table-Refactoring.git"
50
+ "url": "git+https://github.com/HosungYou/LongTable.git"
48
51
  },
49
- "homepage": "https://github.com/HosungYou/Long-Table-Refactoring#readme",
52
+ "homepage": "https://github.com/HosungYou/LongTable#readme",
50
53
  "publishConfig": {
51
54
  "access": "public"
52
55
  },