@longtable/cli 0.1.6 → 0.1.8
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 +11 -0
- package/dist/cli.js +164 -20
- package/dist/persona-router.js +3 -0
- package/dist/project-session.js +59 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ longtable start
|
|
|
54
54
|
- `.longtable/` 메모리 파일 생성
|
|
55
55
|
- 프로젝트용 `AGENTS.md` 생성
|
|
56
56
|
- `START-HERE.md` 생성
|
|
57
|
+
- `NEXT-STEPS.md` 생성
|
|
58
|
+
- `SESSION-SNAPSHOT.md` 생성
|
|
57
59
|
|
|
58
60
|
을 수행합니다.
|
|
59
61
|
|
|
@@ -68,6 +70,14 @@ codex
|
|
|
68
70
|
|
|
69
71
|
이게 현재 가장 신뢰할 수 있는 Long Table 사용 경로입니다.
|
|
70
72
|
|
|
73
|
+
돌아와서 다시 이어갈 때는:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd "<project-path>"
|
|
77
|
+
longtable resume
|
|
78
|
+
codex
|
|
79
|
+
```
|
|
80
|
+
|
|
71
81
|
## After the project starts
|
|
72
82
|
|
|
73
83
|
프로젝트 디렉토리 안에서는 보통 그냥 `codex`를 열고 자연어로 연구를 시작하면 됩니다.
|
|
@@ -88,6 +98,7 @@ CLI를 계속 쓰고 싶다면 아래 명령은 보조 경로입니다.
|
|
|
88
98
|
|
|
89
99
|
```bash
|
|
90
100
|
longtable roles
|
|
101
|
+
longtable resume --cwd "<project-path>"
|
|
91
102
|
longtable ask --cwd "<project-path>" --prompt "연구 질문을 어디서부터 좁혀야 할지 모르겠어."
|
|
92
103
|
longtable review --cwd "<project-path>" --prompt "방법론적으로 어디가 취약한지 말해줘." --role methods_critic
|
|
93
104
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
4
|
import { emitKeypressEvents } from "node:readline";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { stdin as input, stdout as output, cwd, exit } from "node:process";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
7
9
|
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, serializeSetupOutput } from "@diverga/setup";
|
|
8
10
|
import { buildCodexThinWrappedPrompt, runCodexThinWrapper } from "@diverga/provider-codex";
|
|
9
11
|
import { installCodexPromptAliases, listInstalledCodexPromptAliases, removeCodexPromptAliases, resolveCodexPromptsDir } from "./prompt-aliases.js";
|
|
@@ -45,6 +47,22 @@ function renderSectionCard(title, body) {
|
|
|
45
47
|
...body
|
|
46
48
|
].join("\n");
|
|
47
49
|
}
|
|
50
|
+
function renderBrandBanner(title, subtitle) {
|
|
51
|
+
const lines = [
|
|
52
|
+
style("╭──────────────────────────────────────────────╮", ANSI.cyan),
|
|
53
|
+
style(`│ ${title.padEnd(44, " ")}│`, `${ANSI.bold}${ANSI.cyan}`),
|
|
54
|
+
style("╰──────────────────────────────────────────────╯", ANSI.cyan)
|
|
55
|
+
];
|
|
56
|
+
if (subtitle) {
|
|
57
|
+
lines.push(style(subtitle, ANSI.dim));
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function renderProgressBar(current, total) {
|
|
62
|
+
const width = 10;
|
|
63
|
+
const filled = Math.max(1, Math.round((current / total) * width));
|
|
64
|
+
return `${"█".repeat(filled)}${"·".repeat(Math.max(0, width - filled))}`;
|
|
65
|
+
}
|
|
48
66
|
function usage() {
|
|
49
67
|
return [
|
|
50
68
|
"Usage:",
|
|
@@ -53,6 +71,7 @@ function usage() {
|
|
|
53
71
|
"",
|
|
54
72
|
" longtable init [--flow quickstart|interview] [--provider codex|claude] [--field <field>] [--career-stage <stage>] [--experience novice|intermediate|advanced] [--checkpoint low|balanced|high] [--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-prompts]",
|
|
55
73
|
" longtable start [--path <dir>] [--name <project>] [--goal <text>] [--blocker <text>] [--perspectives <role[,role]>] [--disagreement synthesis_only|show_on_conflict|always_visible] [--setup <path>] [--json]",
|
|
74
|
+
" longtable resume [--cwd <path>] [--json]",
|
|
56
75
|
" longtable roles [--json]",
|
|
57
76
|
" longtable show [--json] [--path <file>]",
|
|
58
77
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
@@ -79,7 +98,7 @@ function parseArgs(argv) {
|
|
|
79
98
|
const values = {};
|
|
80
99
|
let subcommand = maybeSubcommand;
|
|
81
100
|
const modeCommand = command && VALID_MODES.has(command);
|
|
82
|
-
const directCommand = command && ["init", "start", "roles", "show", "install", "codex", "ask"].includes(command);
|
|
101
|
+
const directCommand = command && ["init", "start", "resume", "roles", "show", "install", "codex", "ask"].includes(command);
|
|
83
102
|
let startIndex = 1;
|
|
84
103
|
if (modeCommand) {
|
|
85
104
|
subcommand = undefined;
|
|
@@ -140,10 +159,14 @@ function renderSetupHeader(flow) {
|
|
|
140
159
|
const subtitle = flow === "interview"
|
|
141
160
|
? "We will ask about your research persona, challenge preferences, and authorship defaults."
|
|
142
161
|
: "We will capture the minimum profile needed to start using Long Table.";
|
|
143
|
-
return renderSectionCard(title, [subtitle]);
|
|
162
|
+
return [renderBrandBanner("Long Table", "Research workspace setup"), "", renderSectionCard(title, [subtitle])].join("\n");
|
|
144
163
|
}
|
|
145
164
|
function renderQuestionHeader(index, total, section, prompt) {
|
|
146
|
-
return [
|
|
165
|
+
return [
|
|
166
|
+
"",
|
|
167
|
+
style(`[${index}/${total}] ${section} ${renderProgressBar(index, total)}`, `${ANSI.bold}${ANSI.cyan}`),
|
|
168
|
+
prompt
|
|
169
|
+
].join("\n");
|
|
147
170
|
}
|
|
148
171
|
function questionSection(questionId) {
|
|
149
172
|
if (questionId === "field" || questionId === "careerStage" || questionId === "experienceLevel") {
|
|
@@ -174,6 +197,75 @@ function renderArrowMenu(prompt, choices, selectedIndex) {
|
|
|
174
197
|
}
|
|
175
198
|
return lines.join("\n");
|
|
176
199
|
}
|
|
200
|
+
function countRenderedLines(text) {
|
|
201
|
+
return text.split("\n").length;
|
|
202
|
+
}
|
|
203
|
+
function stripWrappingQuotes(value) {
|
|
204
|
+
const trimmed = value.trim();
|
|
205
|
+
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
206
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
207
|
+
return trimmed.slice(1, -1).trim();
|
|
208
|
+
}
|
|
209
|
+
return trimmed;
|
|
210
|
+
}
|
|
211
|
+
function expandHomePath(value) {
|
|
212
|
+
if (value === "~") {
|
|
213
|
+
return homedir();
|
|
214
|
+
}
|
|
215
|
+
if (value.startsWith("~/")) {
|
|
216
|
+
return resolve(homedir(), value.slice(2));
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
function normalizeUserPath(value) {
|
|
221
|
+
return expandHomePath(stripWrappingQuotes(value));
|
|
222
|
+
}
|
|
223
|
+
function projectFolderSlug(projectName) {
|
|
224
|
+
return projectName
|
|
225
|
+
.trim()
|
|
226
|
+
.replace(/[^\w가-힣]+/g, "-")
|
|
227
|
+
.replace(/-+/g, "-")
|
|
228
|
+
.replace(/^-+|-+$/g, "");
|
|
229
|
+
}
|
|
230
|
+
function resolveInteractiveProjectPath(parentOrPath, projectName) {
|
|
231
|
+
const normalized = normalizeUserPath(parentOrPath);
|
|
232
|
+
const folderName = projectFolderSlug(projectName);
|
|
233
|
+
if (!normalized) {
|
|
234
|
+
return resolve(folderName);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
if (existsSync(normalized) && statSync(normalized).isDirectory()) {
|
|
238
|
+
return resolve(normalized, folderName);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return resolve(normalized);
|
|
243
|
+
}
|
|
244
|
+
return resolve(normalized);
|
|
245
|
+
}
|
|
246
|
+
async function verifyWritableWorkspaceParent(projectPath) {
|
|
247
|
+
const parentDir = dirname(resolve(projectPath));
|
|
248
|
+
const probePrefix = resolve(parentDir, ".longtable-permission-check-");
|
|
249
|
+
try {
|
|
250
|
+
const created = await mkdtemp(probePrefix);
|
|
251
|
+
await rm(created, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
255
|
+
throw new Error([
|
|
256
|
+
`Long Table could not create a project workspace under: ${parentDir}`,
|
|
257
|
+
"Reason: your current shell process cannot write to that parent directory.",
|
|
258
|
+
"",
|
|
259
|
+
"What to try next:",
|
|
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",
|
|
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",
|
|
264
|
+
"",
|
|
265
|
+
`Original error: ${message}`
|
|
266
|
+
].join("\n"));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
177
269
|
async function promptChoiceByNumber(rl, prompt, choices) {
|
|
178
270
|
while (true) {
|
|
179
271
|
const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
@@ -213,17 +305,19 @@ async function promptChoiceWithArrows(rl, prompt, choices) {
|
|
|
213
305
|
}
|
|
214
306
|
const previousRawMode = stream.isRaw;
|
|
215
307
|
let selectedIndex = 0;
|
|
216
|
-
|
|
308
|
+
let lastRenderLineCount = 0;
|
|
217
309
|
return await new Promise((resolve, reject) => {
|
|
218
310
|
function draw(first = false) {
|
|
219
|
-
|
|
220
|
-
|
|
311
|
+
const renderedText = renderArrowMenu(prompt, choices, selectedIndex);
|
|
312
|
+
const rendered = renderedText.split("\n");
|
|
313
|
+
if (!first && lastRenderLineCount > 0) {
|
|
314
|
+
output.write(moveCursorUp(lastRenderLineCount));
|
|
221
315
|
}
|
|
222
|
-
const rendered = renderArrowMenu(prompt, choices, selectedIndex).split("\n");
|
|
223
316
|
for (const line of rendered) {
|
|
224
317
|
output.write(clearLine());
|
|
225
318
|
output.write(`${line}\n`);
|
|
226
319
|
}
|
|
320
|
+
lastRenderLineCount = countRenderedLines(renderedText);
|
|
227
321
|
}
|
|
228
322
|
function cleanup() {
|
|
229
323
|
stream.off("keypress", onKeypress);
|
|
@@ -287,12 +381,9 @@ async function promptMultiChoice(rl, prompt, choices) {
|
|
|
287
381
|
const previousRawMode = stream.isRaw;
|
|
288
382
|
let selectedIndex = 0;
|
|
289
383
|
const selected = new Set();
|
|
290
|
-
|
|
384
|
+
let lastRenderLineCount = 0;
|
|
291
385
|
return await new Promise((resolvePromise, reject) => {
|
|
292
386
|
function draw(first = false) {
|
|
293
|
-
if (!first) {
|
|
294
|
-
output.write(moveCursorUp(renderedLineCount));
|
|
295
|
-
}
|
|
296
387
|
const lines = [prompt, "Use ↑/↓, Space to toggle, and Enter to confirm."];
|
|
297
388
|
for (let index = 0; index < choices.length; index += 1) {
|
|
298
389
|
const choice = choices[index];
|
|
@@ -300,10 +391,15 @@ async function promptMultiChoice(rl, prompt, choices) {
|
|
|
300
391
|
const marker = selected.has(choice.id) ? "[x]" : "[ ]";
|
|
301
392
|
lines.push(`${pointer} ${marker} ${choice.label} - ${choice.description}`);
|
|
302
393
|
}
|
|
394
|
+
const renderedText = lines.join("\n");
|
|
395
|
+
if (!first && lastRenderLineCount > 0) {
|
|
396
|
+
output.write(moveCursorUp(lastRenderLineCount));
|
|
397
|
+
}
|
|
303
398
|
for (const line of lines) {
|
|
304
399
|
output.write(clearLine());
|
|
305
400
|
output.write(`${line}\n`);
|
|
306
401
|
}
|
|
402
|
+
lastRenderLineCount = countRenderedLines(renderedText);
|
|
307
403
|
}
|
|
308
404
|
function cleanup() {
|
|
309
405
|
stream.off("keypress", onKeypress);
|
|
@@ -462,6 +558,8 @@ async function collectProjectInterview(setup, args) {
|
|
|
462
558
|
const rl = createInterface({ input, output });
|
|
463
559
|
try {
|
|
464
560
|
if (needsInteractivePrompts) {
|
|
561
|
+
console.log("");
|
|
562
|
+
console.log(renderBrandBanner("Long Table", "Project workspace interview"));
|
|
465
563
|
console.log("");
|
|
466
564
|
console.log(renderSectionCard("Long Table Project Start", [
|
|
467
565
|
"We will create a project workspace and a session memory seed for today's work.",
|
|
@@ -471,11 +569,13 @@ async function collectProjectInterview(setup, args) {
|
|
|
471
569
|
}
|
|
472
570
|
const projectName = (typeof args.name === "string" && args.name.trim()) ||
|
|
473
571
|
(await promptText(rl, renderQuestionHeader(1, 6, "Project interview", "What should this project be called?"), true));
|
|
474
|
-
const
|
|
475
|
-
? args.path.trim()
|
|
476
|
-
:
|
|
477
|
-
const
|
|
478
|
-
|
|
572
|
+
const suggestedParentDir = typeof args.path === "string" && args.path.trim()
|
|
573
|
+
? normalizeUserPath(args.path.trim())
|
|
574
|
+
: homedir();
|
|
575
|
+
const suggestedPath = resolveInteractiveProjectPath(suggestedParentDir, projectName);
|
|
576
|
+
const projectPath = (typeof args.path === "string" && args.path.trim()
|
|
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));
|
|
479
579
|
const currentGoal = (typeof args.goal === "string" && args.goal.trim()) ||
|
|
480
580
|
(await promptText(rl, renderQuestionHeader(3, 6, "Current session", "What are you trying to accomplish in this session?"), true));
|
|
481
581
|
const currentBlocker = (typeof args.blocker === "string" && args.blocker.trim()) ||
|
|
@@ -838,6 +938,7 @@ async function runRoles(args) {
|
|
|
838
938
|
}
|
|
839
939
|
console.log("Long Table roles");
|
|
840
940
|
console.log("These are perspectives Long Table can consult when relevant.");
|
|
941
|
+
console.log("Inside Codex, explicit forms like `lt editor: ...` and `lt methods: ...` are stronger than plain natural language.");
|
|
841
942
|
console.log("");
|
|
842
943
|
for (const persona of payload) {
|
|
843
944
|
console.log(`- ${persona.label} (${persona.key})`);
|
|
@@ -853,6 +954,7 @@ async function runStart(args) {
|
|
|
853
954
|
throw new Error("Long Table global setup is missing. Run `longtable init --flow interview` first.");
|
|
854
955
|
}
|
|
855
956
|
const interview = await collectProjectInterview(existingSetup, args);
|
|
957
|
+
await verifyWritableWorkspaceParent(interview.projectPath);
|
|
856
958
|
const context = await createOrUpdateProjectWorkspace({
|
|
857
959
|
projectName: interview.projectName,
|
|
858
960
|
projectPath: interview.projectPath,
|
|
@@ -877,12 +979,48 @@ async function runStart(args) {
|
|
|
877
979
|
`1. cd "${context.project.projectPath}"`,
|
|
878
980
|
"2. run `codex` in that directory",
|
|
879
981
|
"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`",
|
|
880
983
|
"",
|
|
881
984
|
`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}."`}`,
|
|
882
985
|
"",
|
|
883
986
|
`Optional CLI path: longtable ask --cwd "${context.project.projectPath}" --prompt "${context.session.currentGoal.replaceAll("\"", "\\\"")}"`
|
|
884
987
|
]));
|
|
885
988
|
}
|
|
989
|
+
async function runResume(args) {
|
|
990
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
991
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
992
|
+
if (!context) {
|
|
993
|
+
throw new Error("No Long Table project workspace was found here. Run `longtable start` first or pass --cwd.");
|
|
994
|
+
}
|
|
995
|
+
const payload = {
|
|
996
|
+
project: context.project,
|
|
997
|
+
session: context.session,
|
|
998
|
+
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")
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
if (args.json === true) {
|
|
1005
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
console.log(renderSectionCard("Long Table Resume", [
|
|
1009
|
+
`Project: ${context.project.projectName}`,
|
|
1010
|
+
`Path: ${context.project.projectPath}`,
|
|
1011
|
+
`Current goal: ${context.session.currentGoal}`,
|
|
1012
|
+
...(context.session.currentBlocker ? [`Current blocker: ${context.session.currentBlocker}`] : []),
|
|
1013
|
+
`Requested perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
|
|
1014
|
+
`Disagreement: ${context.session.disagreementPreference}`,
|
|
1015
|
+
"",
|
|
1016
|
+
"Resume files:",
|
|
1017
|
+
`- ${payload.files.startHere}`,
|
|
1018
|
+
`- ${payload.files.nextSteps}`,
|
|
1019
|
+
`- ${payload.files.sessionSnapshot}`,
|
|
1020
|
+
"",
|
|
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}."`}`
|
|
1022
|
+
]));
|
|
1023
|
+
}
|
|
886
1024
|
async function runCodexSubcommand(subcommand, args) {
|
|
887
1025
|
const customDir = typeof args.dir === "string" ? args.dir : undefined;
|
|
888
1026
|
if (subcommand === "install-prompts") {
|
|
@@ -952,6 +1090,10 @@ async function main() {
|
|
|
952
1090
|
await runStart(values);
|
|
953
1091
|
return;
|
|
954
1092
|
}
|
|
1093
|
+
if (command === "resume") {
|
|
1094
|
+
await runResume(values);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
955
1097
|
if (command === "roles") {
|
|
956
1098
|
await runRoles(values);
|
|
957
1099
|
return;
|
|
@@ -984,7 +1126,9 @@ main().catch((error) => {
|
|
|
984
1126
|
if (message === "Setup cancelled.") {
|
|
985
1127
|
exit(1);
|
|
986
1128
|
}
|
|
987
|
-
|
|
988
|
-
|
|
1129
|
+
if (message.startsWith("Unknown command:") || message.startsWith("Invalid ")) {
|
|
1130
|
+
console.error("");
|
|
1131
|
+
console.error(usage());
|
|
1132
|
+
}
|
|
989
1133
|
exit(1);
|
|
990
1134
|
});
|
package/dist/persona-router.js
CHANGED
|
@@ -154,6 +154,9 @@ export function buildPersonaGuidance(options) {
|
|
|
154
154
|
? "Include a short deliberation trace showing why the roles diverged."
|
|
155
155
|
: "Include a short deliberation trace showing why the roles diverged.");
|
|
156
156
|
}
|
|
157
|
+
lines.push(routing.outputLanguage === "ko"
|
|
158
|
+
? "For factual, current, or external claims, attach source links or local file references when possible. If you cannot source a statement, label it as inference or estimate."
|
|
159
|
+
: "For factual, current, or external claims, attach source links or local file references when possible. If you cannot source a statement, label it as inference or estimate.");
|
|
157
160
|
lines.push(routing.outputLanguage === "ko"
|
|
158
161
|
? "Do not show internal file-search logs, tool traces, or process commentary in the researcher-facing answer."
|
|
159
162
|
: "Do not show internal file-search logs, tool traces, or process commentary in the researcher-facing answer.");
|
package/dist/project-session.js
CHANGED
|
@@ -34,6 +34,8 @@ function buildWorkspaceGuide(project, session) {
|
|
|
34
34
|
"- Ask clarifying or tension questions before closing too early.",
|
|
35
35
|
"- If you foreground specific roles, disclose them with `Long Table consulted: ...`.",
|
|
36
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.",
|
|
37
39
|
"- Do not expose internal tool logs or process commentary in researcher-facing answers.",
|
|
38
40
|
"",
|
|
39
41
|
"## Session files",
|
|
@@ -54,6 +56,7 @@ function buildStartHereGuide(project, session) {
|
|
|
54
56
|
"1. Open Codex in this directory.",
|
|
55
57
|
"2. Start with your current goal, not a shell command.",
|
|
56
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.",
|
|
57
60
|
"",
|
|
58
61
|
"## Suggested first message",
|
|
59
62
|
session.currentBlocker
|
|
@@ -78,10 +81,59 @@ function buildStartHereGuide(project, session) {
|
|
|
78
81
|
"## Files",
|
|
79
82
|
"- `AGENTS.md` tells Codex how to behave in this workspace.",
|
|
80
83
|
"- `.longtable/current-session.json` stores the current session goal and blocker.",
|
|
81
|
-
"- `.longtable/project.json` stores the project-level record."
|
|
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."
|
|
82
87
|
];
|
|
83
88
|
return lines.join("\n");
|
|
84
89
|
}
|
|
90
|
+
function buildNextStepsGuide(project, session) {
|
|
91
|
+
const firstQuestion = session.currentBlocker
|
|
92
|
+
? `What would reduce the uncertainty around "${session.currentBlocker}" first?`
|
|
93
|
+
: `What is the first concrete question that would move "${session.currentGoal}" forward?`;
|
|
94
|
+
return [
|
|
95
|
+
"# Next Steps",
|
|
96
|
+
"",
|
|
97
|
+
`Project: ${project.projectName}`,
|
|
98
|
+
"",
|
|
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.",
|
|
103
|
+
"",
|
|
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.",
|
|
112
|
+
"",
|
|
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",
|
|
120
|
+
"",
|
|
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}`,
|
|
126
|
+
"",
|
|
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}."`,
|
|
131
|
+
"",
|
|
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."
|
|
135
|
+
].join("\n");
|
|
136
|
+
}
|
|
85
137
|
function buildProjectAgentsMd(project, session) {
|
|
86
138
|
return [
|
|
87
139
|
"# AGENTS.md",
|
|
@@ -104,6 +156,8 @@ function buildProjectAgentsMd(project, session) {
|
|
|
104
156
|
"- Begin exploratory work with clarifying or tension questions before recommending a direction.",
|
|
105
157
|
"- If you foreground role perspectives, disclose them with `Long Table consulted: ...`.",
|
|
106
158
|
"- 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.",
|
|
107
161
|
...(session.disagreementPreference === "always_visible"
|
|
108
162
|
? ["- In this workspace, panel disagreement should be visible by default rather than hidden behind a single synthesis."]
|
|
109
163
|
: []),
|
|
@@ -210,6 +264,8 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
210
264
|
await writeFile(join(metaDir, "state.json"), buildStateSeed(project, session, options.setup), "utf8");
|
|
211
265
|
await writeFile(join(projectPath, "LONGTABLE.md"), buildWorkspaceGuide(project, session), "utf8");
|
|
212
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");
|
|
213
269
|
await writeFile(join(projectPath, "AGENTS.md"), buildProjectAgentsMd(project, session), "utf8");
|
|
214
270
|
return {
|
|
215
271
|
project,
|
|
@@ -257,6 +313,8 @@ export function renderProjectWorkspaceSummary(context) {
|
|
|
257
313
|
`- ${context.projectFilePath}`,
|
|
258
314
|
`- ${context.sessionFilePath}`,
|
|
259
315
|
`- ${join(context.project.projectPath, "START-HERE.md")}`,
|
|
316
|
+
`- ${join(context.project.projectPath, "NEXT-STEPS.md")}`,
|
|
317
|
+
`- ${join(context.project.projectPath, "SESSION-SNAPSHOT.md")}`,
|
|
260
318
|
`- ${join(context.project.projectPath, "AGENTS.md")}`
|
|
261
319
|
].join("\n");
|
|
262
320
|
}
|