@longtable/cli 0.1.30 → 0.1.32
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 +16 -13
- package/dist/cli.js +464 -493
- package/dist/project-session.d.ts +104 -2
- package/dist/project-session.js +293 -11
- package/dist/prompt-aliases.js +5 -5
- package/dist/prompt-renderer.d.ts +11 -0
- package/dist/prompt-renderer.js +130 -0
- package/dist/search/publisher-access.js +1 -1
- package/dist/search/sources.js +2 -2
- package/package.json +8 -7
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
import type { DecisionRecord, InvocationRecord, ProviderKind, QuestionOption, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
2
2
|
import type { SetupPersistedOutput } from "@longtable/setup";
|
|
3
3
|
export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
4
|
+
export type StartInterviewSignal = "phenomenon" | "audience" | "artifact" | "evidence" | "assumption" | "decision_risk" | "voice";
|
|
5
|
+
export interface StartInterviewTurn {
|
|
6
|
+
index: number;
|
|
7
|
+
question: string;
|
|
8
|
+
answer: string;
|
|
9
|
+
signal: StartInterviewSignal;
|
|
10
|
+
purpose: string;
|
|
11
|
+
}
|
|
12
|
+
export interface StartInterviewSession {
|
|
13
|
+
mode: "adaptive";
|
|
14
|
+
openingStyle: "scene_problem";
|
|
15
|
+
createdAt: string;
|
|
16
|
+
completedAt: string;
|
|
17
|
+
turnCount: number;
|
|
18
|
+
turns: StartInterviewTurn[];
|
|
19
|
+
inferredSignals: StartInterviewSignal[];
|
|
20
|
+
summary: string;
|
|
21
|
+
}
|
|
22
|
+
export type InterviewTurnQuality = "thin" | "usable" | "rich";
|
|
23
|
+
export type InterviewDepth = "gathering_context" | "forming_first_handle" | "ready_to_summarize";
|
|
24
|
+
export interface FirstResearchShape {
|
|
25
|
+
handle: string;
|
|
26
|
+
currentGoal: string;
|
|
27
|
+
currentBlocker?: string;
|
|
28
|
+
researchObject?: string;
|
|
29
|
+
gapRisk?: string;
|
|
30
|
+
protectedDecision?: string;
|
|
31
|
+
openQuestions: string[];
|
|
32
|
+
nextAction: string;
|
|
33
|
+
confidence: "low" | "medium" | "high";
|
|
34
|
+
sourceHookId?: string;
|
|
35
|
+
confirmedAt?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface LongTableInterviewTurn {
|
|
38
|
+
id: string;
|
|
39
|
+
index: number;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
question: string;
|
|
42
|
+
answer: string;
|
|
43
|
+
reflection?: string;
|
|
44
|
+
quality: InterviewTurnQuality;
|
|
45
|
+
needsFollowUp: boolean;
|
|
46
|
+
followUpQuestion?: string;
|
|
47
|
+
rationale?: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface LongTableHookRun {
|
|
50
|
+
id: string;
|
|
51
|
+
kind: "longtable_interview" | "quality_probe" | "checkpoint" | "panel_decision";
|
|
52
|
+
status: "pending" | "active" | "ready_to_confirm" | "confirmed" | "deferred" | "cancelled";
|
|
53
|
+
createdAt: string;
|
|
54
|
+
updatedAt: string;
|
|
55
|
+
targetOutcome?: "first_research_handle" | string;
|
|
56
|
+
depth?: InterviewDepth;
|
|
57
|
+
provider?: ProviderKind;
|
|
58
|
+
turns?: LongTableInterviewTurn[];
|
|
59
|
+
firstResearchShape?: FirstResearchShape;
|
|
60
|
+
qualityNotes?: string[];
|
|
61
|
+
rationale?: string[];
|
|
62
|
+
linkedQuestionRecordIds?: string[];
|
|
63
|
+
linkedDecisionRecordIds?: string[];
|
|
64
|
+
}
|
|
65
|
+
export type LongTableWorkspaceState = ResearchState & {
|
|
66
|
+
hooks?: LongTableHookRun[];
|
|
67
|
+
firstResearchShape?: FirstResearchShape;
|
|
68
|
+
};
|
|
4
69
|
export interface LongTableProjectRecord {
|
|
5
70
|
schemaVersion: 1;
|
|
6
71
|
product: "LongTable";
|
|
@@ -33,6 +98,8 @@ export interface LongTableSessionRecord {
|
|
|
33
98
|
protectedDecision?: string;
|
|
34
99
|
nextAction?: string;
|
|
35
100
|
openQuestions?: string[];
|
|
101
|
+
startInterview?: StartInterviewSession;
|
|
102
|
+
firstResearchShape?: FirstResearchShape;
|
|
36
103
|
requestedPerspectives: string[];
|
|
37
104
|
disagreementPreference: ProjectDisagreementPreference;
|
|
38
105
|
activeModes?: string[];
|
|
@@ -108,9 +175,43 @@ export interface LongTableWorkspaceInspection {
|
|
|
108
175
|
suggestion?: string;
|
|
109
176
|
}>;
|
|
110
177
|
}
|
|
111
|
-
export declare function loadWorkspaceState(context: LongTableProjectContext): Promise<
|
|
178
|
+
export declare function loadWorkspaceState(context: LongTableProjectContext): Promise<LongTableWorkspaceState>;
|
|
112
179
|
export declare function syncCurrentWorkspaceView(context: LongTableProjectContext): Promise<string>;
|
|
113
|
-
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<
|
|
180
|
+
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<LongTableWorkspaceState>;
|
|
181
|
+
export declare function beginLongTableInterview(options: {
|
|
182
|
+
context: LongTableProjectContext;
|
|
183
|
+
provider?: ProviderKind;
|
|
184
|
+
openingQuestion?: string;
|
|
185
|
+
seedAnswer?: string;
|
|
186
|
+
}): Promise<{
|
|
187
|
+
hook: LongTableHookRun;
|
|
188
|
+
state: LongTableWorkspaceState;
|
|
189
|
+
}>;
|
|
190
|
+
export declare function appendLongTableInterviewTurn(options: {
|
|
191
|
+
context: LongTableProjectContext;
|
|
192
|
+
hookId?: string;
|
|
193
|
+
question: string;
|
|
194
|
+
answer: string;
|
|
195
|
+
reflection?: string;
|
|
196
|
+
quality?: InterviewTurnQuality;
|
|
197
|
+
needsFollowUp?: boolean;
|
|
198
|
+
followUpQuestion?: string;
|
|
199
|
+
rationale?: string[];
|
|
200
|
+
}): Promise<{
|
|
201
|
+
hook: LongTableHookRun;
|
|
202
|
+
turn: NonNullable<LongTableHookRun["turns"]>[number];
|
|
203
|
+
state: LongTableWorkspaceState;
|
|
204
|
+
}>;
|
|
205
|
+
export declare function summarizeLongTableInterview(options: {
|
|
206
|
+
context: LongTableProjectContext;
|
|
207
|
+
hookId?: string;
|
|
208
|
+
shape: FirstResearchShape;
|
|
209
|
+
}): Promise<{
|
|
210
|
+
hook: LongTableHookRun;
|
|
211
|
+
shape: FirstResearchShape;
|
|
212
|
+
state: LongTableWorkspaceState;
|
|
213
|
+
session: LongTableSessionRecord;
|
|
214
|
+
}>;
|
|
114
215
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
115
216
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
116
217
|
export declare function createWorkspaceFollowUpQuestions(options: {
|
|
@@ -159,6 +260,7 @@ export declare function createOrUpdateProjectWorkspace(options: {
|
|
|
159
260
|
researchObject?: string;
|
|
160
261
|
gapRisk?: string;
|
|
161
262
|
protectedDecision?: string;
|
|
263
|
+
startInterview?: StartInterviewSession;
|
|
162
264
|
requestedPerspectives: string[];
|
|
163
265
|
disagreementPreference: ProjectDisagreementPreference;
|
|
164
266
|
setup: SetupPersistedOutput;
|
package/dist/project-session.js
CHANGED
|
@@ -54,28 +54,53 @@ function resolveUserLocale() {
|
|
|
54
54
|
return normalizeLocale(Intl.DateTimeFormat().resolvedOptions().locale);
|
|
55
55
|
}
|
|
56
56
|
function buildFirstQuestion(session) {
|
|
57
|
+
if (session.firstResearchShape?.openQuestions?.[0]) {
|
|
58
|
+
return session.firstResearchShape.openQuestions[0];
|
|
59
|
+
}
|
|
57
60
|
return session.currentBlocker
|
|
58
|
-
? `
|
|
59
|
-
: `What
|
|
61
|
+
? `Where does "${session.currentBlocker}" show up most concretely in the scene, material, or evidence?`
|
|
62
|
+
: `What scene, case, text, data, or draft would make "${session.currentGoal}" easiest to inspect first?`;
|
|
60
63
|
}
|
|
61
64
|
function buildOpenQuestions(session) {
|
|
62
65
|
const firstQuestion = buildFirstQuestion(session);
|
|
66
|
+
if (session.startInterview) {
|
|
67
|
+
return [
|
|
68
|
+
firstQuestion,
|
|
69
|
+
`What still feels hardest to name or make concrete in "${session.currentGoal}"?`
|
|
70
|
+
];
|
|
71
|
+
}
|
|
63
72
|
return session.currentBlocker
|
|
64
73
|
? [
|
|
65
74
|
firstQuestion,
|
|
66
|
-
`What
|
|
75
|
+
`What would give "${session.currentBlocker}" a usable first research handle without forcing a final research question yet?`
|
|
67
76
|
]
|
|
68
77
|
: [
|
|
69
78
|
firstQuestion,
|
|
70
|
-
`What would
|
|
79
|
+
`What would give this project a usable first research handle without pretending the question is settled?`
|
|
71
80
|
];
|
|
72
81
|
}
|
|
73
82
|
function buildNextAction(session) {
|
|
83
|
+
if (session.firstResearchShape) {
|
|
84
|
+
return session.firstResearchShape.nextAction;
|
|
85
|
+
}
|
|
86
|
+
if (session.startInterview) {
|
|
87
|
+
return session.currentBlocker
|
|
88
|
+
? `Begin from the start-interview brief, then make "${session.currentBlocker}" concrete with one scene, source, case, or dataset.`
|
|
89
|
+
: "Begin from the start-interview brief, then choose one concrete scene, source, case, or dataset to inspect first.";
|
|
90
|
+
}
|
|
74
91
|
return session.currentBlocker
|
|
75
|
-
? `Open with the blocker, then
|
|
92
|
+
? `Open with the blocker, then make "${session.currentBlocker}" concrete with one scene, source, case, or dataset.`
|
|
76
93
|
: "Open with your current goal in one sentence, then ask LongTable for the first concrete research move.";
|
|
77
94
|
}
|
|
78
95
|
function buildResumeHint(session) {
|
|
96
|
+
if (session.firstResearchShape) {
|
|
97
|
+
return `I want to continue from the First Research Shape: ${session.firstResearchShape.handle}.`;
|
|
98
|
+
}
|
|
99
|
+
if (session.startInterview) {
|
|
100
|
+
return session.currentBlocker
|
|
101
|
+
? `I want to continue from the LongTable start interview. The first unresolved issue is ${session.currentBlocker}.`
|
|
102
|
+
: `I want to continue from the LongTable start interview for ${session.currentGoal}.`;
|
|
103
|
+
}
|
|
79
104
|
return session.currentBlocker
|
|
80
105
|
? `I want to continue ${session.currentGoal}. The unresolved blocker is ${session.currentBlocker}.`
|
|
81
106
|
: `I want to continue ${session.currentGoal}.`;
|
|
@@ -102,6 +127,8 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
102
127
|
...(session.researchObject ? [`- 연구 객체: ${session.researchObject}`] : []),
|
|
103
128
|
...(session.gapRisk ? [`- 공백/암묵지 위험: ${session.gapRisk}`] : []),
|
|
104
129
|
...(session.protectedDecision ? [`- 보호할 결정: ${session.protectedDecision}`] : []),
|
|
130
|
+
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
131
|
+
...(session.startInterview ? [`- start interview: ${session.startInterview.summary}`] : []),
|
|
105
132
|
`- 다음 액션: ${nextAction}`,
|
|
106
133
|
`- 관점: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
107
134
|
`- disagreement: ${session.disagreementPreference}`,
|
|
@@ -132,10 +159,19 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
132
159
|
"",
|
|
133
160
|
"## 다시 시작 문장",
|
|
134
161
|
`- "${resumeHint}"`,
|
|
162
|
+
...(session.firstResearchShape
|
|
163
|
+
? [
|
|
164
|
+
"",
|
|
165
|
+
"## First Research Shape",
|
|
166
|
+
`- Handle: ${session.firstResearchShape.handle}`,
|
|
167
|
+
`- Confidence: ${session.firstResearchShape.confidence}`,
|
|
168
|
+
...session.firstResearchShape.openQuestions.map((question) => `- Open question: ${question}`)
|
|
169
|
+
]
|
|
170
|
+
: []),
|
|
135
171
|
"",
|
|
136
172
|
"## 빠른 시작",
|
|
137
173
|
"- 이 디렉토리에서 `codex`를 엽니다.",
|
|
138
|
-
`- 첫 메시지는 보통 \`${suggestedPrompt}\` 정도면 충분합니다.`,
|
|
174
|
+
`- 첫 메시지는 보통 \`${session.firstResearchShape ? suggestedPrompt : "$longtable-interview"}\` 정도면 충분합니다.`,
|
|
139
175
|
"",
|
|
140
176
|
"## 증거 규칙",
|
|
141
177
|
"- 외부 사실이나 현재 정보는 source를 붙이거나 inference로 낮춥니다."
|
|
@@ -154,6 +190,8 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
154
190
|
...(session.researchObject ? [`- Research object: ${session.researchObject}`] : []),
|
|
155
191
|
...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
|
|
156
192
|
...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
|
|
193
|
+
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
194
|
+
...(session.startInterview ? [`- Start interview: ${session.startInterview.summary}`] : []),
|
|
157
195
|
`- Next action: ${nextAction}`,
|
|
158
196
|
`- Perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
159
197
|
`- Disagreement: ${session.disagreementPreference}`,
|
|
@@ -184,10 +222,19 @@ function buildCurrentGuide(project, session, recentInvocations = [], pendingQues
|
|
|
184
222
|
"",
|
|
185
223
|
"## Restart Prompt",
|
|
186
224
|
`- "${resumeHint}"`,
|
|
225
|
+
...(session.firstResearchShape
|
|
226
|
+
? [
|
|
227
|
+
"",
|
|
228
|
+
"## First Research Shape",
|
|
229
|
+
`- Handle: ${session.firstResearchShape.handle}`,
|
|
230
|
+
`- Confidence: ${session.firstResearchShape.confidence}`,
|
|
231
|
+
...session.firstResearchShape.openQuestions.map((question) => `- Open question: ${question}`)
|
|
232
|
+
]
|
|
233
|
+
: []),
|
|
187
234
|
"",
|
|
188
235
|
"## Quick Start",
|
|
189
236
|
"- Open `codex` in this directory.",
|
|
190
|
-
`- A good first message is usually \`${suggestedPrompt}\`.`,
|
|
237
|
+
`- A good first message is usually \`${session.firstResearchShape ? suggestedPrompt : "$longtable-interview"}\`.`,
|
|
191
238
|
"",
|
|
192
239
|
"## Evidence Rule",
|
|
193
240
|
"- External or current claims should carry a source link or be labeled as inference."
|
|
@@ -202,6 +249,8 @@ async function loadResearchState(stateFilePath) {
|
|
|
202
249
|
...parsed,
|
|
203
250
|
explicitState: parsed.explicitState ?? {},
|
|
204
251
|
workingState: parsed.workingState ?? {},
|
|
252
|
+
hooks: parsed.hooks ?? [],
|
|
253
|
+
...(parsed.firstResearchShape ? { firstResearchShape: parsed.firstResearchShape } : {}),
|
|
205
254
|
inferredHypotheses: parsed.inferredHypotheses ?? [],
|
|
206
255
|
openTensions: parsed.openTensions ?? [],
|
|
207
256
|
decisionLog: parsed.decisionLog ?? [],
|
|
@@ -321,13 +370,16 @@ function buildProjectAgentsMd(project, session) {
|
|
|
321
370
|
"- Treat `AGENTS.md` as runtime guidance, not as the researcher-facing resume artifact.",
|
|
322
371
|
"",
|
|
323
372
|
"## Invocation Rules",
|
|
373
|
+
"- If the user message starts with `$longtable-interview`, run the LongTable interview flow before generic research advice.",
|
|
324
374
|
"- If the user message starts with `lt `, `longtable `, `long table `, or `롱테이블 ` followed by a directive and `:`, treat it as an explicit LongTable invocation.",
|
|
325
|
-
"- Supported explicit directives are: explore, review, critique, draft, commit, panel, status, editor, reviewer, methods, theory, measurement, ethics, voice, venue.",
|
|
375
|
+
"- Supported explicit directives are: interview, explore, review, critique, draft, commit, panel, status, editor, reviewer, methods, theory, measurement, ethics, voice, venue.",
|
|
326
376
|
"- For explicit LongTable invocations, do not begin by scanning the workspace. Use the current session files first and answer as LongTable immediately.",
|
|
327
377
|
"- For general research requests in this workspace, prefer LongTable behavior before generic coding behavior.",
|
|
328
378
|
"",
|
|
329
379
|
"## Research Behavior",
|
|
330
380
|
"- Begin exploratory work with clarifying or tension questions before recommending a direction.",
|
|
381
|
+
"- For `$longtable-interview`, ask one natural-language question at a time, reflect with `LongTable hears: ...`, and avoid early reader/reviewer or theory/method/measurement classification.",
|
|
382
|
+
"- Use structured options only at the final First Research Shape confirmation or at true checkpoint boundaries.",
|
|
331
383
|
"- If you foreground role perspectives, disclose them with `LongTable consulted: ...`.",
|
|
332
384
|
"- Keep one accountable synthesis, but do not hide meaningful disagreement.",
|
|
333
385
|
...(session.disagreementPreference === "always_visible"
|
|
@@ -344,6 +396,8 @@ function buildProjectAgentsMd(project, session) {
|
|
|
344
396
|
...(session.researchObject ? [`- Research object: ${session.researchObject}`] : []),
|
|
345
397
|
...(session.gapRisk ? [`- Gap/tacit risk: ${session.gapRisk}`] : []),
|
|
346
398
|
...(session.protectedDecision ? [`- Protected decision: ${session.protectedDecision}`] : []),
|
|
399
|
+
...(session.firstResearchShape ? [`- First Research Shape: ${session.firstResearchShape.handle}`] : []),
|
|
400
|
+
...(session.startInterview ? [`- Start interview summary: ${session.startInterview.summary}`] : []),
|
|
347
401
|
`- Requested perspectives: ${session.requestedPerspectives.length > 0 ? session.requestedPerspectives.join(", ") : "auto"}`,
|
|
348
402
|
`- Disagreement visibility: ${session.disagreementPreference}`,
|
|
349
403
|
"- These instructions apply to this directory and its children."
|
|
@@ -368,8 +422,13 @@ function buildStateSeed(project, session, setup) {
|
|
|
368
422
|
...(session.nextAction ? { nextAction: session.nextAction } : {}),
|
|
369
423
|
openQuestions: session.openQuestions ?? [],
|
|
370
424
|
activeModes: session.activeModes ?? [],
|
|
425
|
+
...(session.startInterview ? { startInterview: session.startInterview } : {}),
|
|
371
426
|
...(session.resumeHint ? { resumeHint: session.resumeHint } : {})
|
|
372
427
|
};
|
|
428
|
+
if (session.firstResearchShape) {
|
|
429
|
+
state.firstResearchShape = session.firstResearchShape;
|
|
430
|
+
state.workingState.firstResearchShape = session.firstResearchShape;
|
|
431
|
+
}
|
|
373
432
|
if (session.currentBlocker) {
|
|
374
433
|
state.openTensions.push(session.currentBlocker);
|
|
375
434
|
}
|
|
@@ -414,6 +473,25 @@ function buildStateSeed(project, session, setup) {
|
|
|
414
473
|
importance: "high"
|
|
415
474
|
});
|
|
416
475
|
}
|
|
476
|
+
if (session.startInterview) {
|
|
477
|
+
state.narrativeTraces.push({
|
|
478
|
+
id: "project-session-start-interview",
|
|
479
|
+
timestamp: nowIso(),
|
|
480
|
+
source: "longtable-start",
|
|
481
|
+
traceType: "experience",
|
|
482
|
+
summary: session.startInterview.summary,
|
|
483
|
+
visibility: "explicit",
|
|
484
|
+
importance: "high"
|
|
485
|
+
});
|
|
486
|
+
if (session.startInterview.inferredSignals.length > 0) {
|
|
487
|
+
state.inferredHypotheses.push({
|
|
488
|
+
hypothesis: `Start interview suggests these early lenses: ${session.startInterview.inferredSignals.join(", ")}.`,
|
|
489
|
+
confidence: 0.65,
|
|
490
|
+
evidence: session.startInterview.turns.map((turn) => turn.answer).filter(Boolean),
|
|
491
|
+
status: "unconfirmed"
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
417
495
|
return JSON.stringify(state, null, 2);
|
|
418
496
|
}
|
|
419
497
|
async function removeLegacyRootFiles(projectPath) {
|
|
@@ -438,6 +516,200 @@ export async function appendInvocationRecordToWorkspace(context, invocation, que
|
|
|
438
516
|
function createId(prefix) {
|
|
439
517
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
440
518
|
}
|
|
519
|
+
function normalizeInterviewQuality(answer, quality) {
|
|
520
|
+
if (quality) {
|
|
521
|
+
return quality;
|
|
522
|
+
}
|
|
523
|
+
const trimmed = answer.trim();
|
|
524
|
+
const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
|
|
525
|
+
if (trimmed.length < 12 || wordCount < 3) {
|
|
526
|
+
return "thin";
|
|
527
|
+
}
|
|
528
|
+
if (trimmed.length > 80 || wordCount >= 12) {
|
|
529
|
+
return "rich";
|
|
530
|
+
}
|
|
531
|
+
return "usable";
|
|
532
|
+
}
|
|
533
|
+
function defaultFollowUpQuestion(answer) {
|
|
534
|
+
const trimmed = answer.trim();
|
|
535
|
+
if (trimmed.length < 12) {
|
|
536
|
+
return "Say one more sentence about where this problem appears or why it matters before LongTable tries to classify it.";
|
|
537
|
+
}
|
|
538
|
+
return "What concrete scene, case, material, text, dataset, or decision would make that problem easier to inspect first?";
|
|
539
|
+
}
|
|
540
|
+
function depthForInterview(turns = []) {
|
|
541
|
+
const usableTurns = turns.filter((turn) => turn.quality !== "thin").length;
|
|
542
|
+
if (usableTurns >= 3) {
|
|
543
|
+
return "ready_to_summarize";
|
|
544
|
+
}
|
|
545
|
+
if (usableTurns >= 1) {
|
|
546
|
+
return "forming_first_handle";
|
|
547
|
+
}
|
|
548
|
+
return "gathering_context";
|
|
549
|
+
}
|
|
550
|
+
function activeInterviewHook(state, hookId) {
|
|
551
|
+
const hooks = state.hooks ?? [];
|
|
552
|
+
if (hookId) {
|
|
553
|
+
return hooks.find((hook) => hook.id === hookId);
|
|
554
|
+
}
|
|
555
|
+
return [...hooks].reverse().find((hook) => hook.kind === "longtable_interview" &&
|
|
556
|
+
(hook.status === "pending" || hook.status === "active" || hook.status === "ready_to_confirm"));
|
|
557
|
+
}
|
|
558
|
+
function upsertHook(state, hook) {
|
|
559
|
+
const hooks = state.hooks ?? [];
|
|
560
|
+
const existingIndex = hooks.findIndex((candidate) => candidate.id === hook.id);
|
|
561
|
+
const nextHooks = existingIndex >= 0
|
|
562
|
+
? hooks.map((candidate) => candidate.id === hook.id ? hook : candidate)
|
|
563
|
+
: [...hooks, hook];
|
|
564
|
+
return {
|
|
565
|
+
...state,
|
|
566
|
+
hooks: nextHooks
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
export async function beginLongTableInterview(options) {
|
|
570
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
571
|
+
const existing = activeInterviewHook(state);
|
|
572
|
+
if (existing) {
|
|
573
|
+
return { hook: existing, state };
|
|
574
|
+
}
|
|
575
|
+
const timestamp = nowIso();
|
|
576
|
+
const hook = {
|
|
577
|
+
id: createId("hook_interview"),
|
|
578
|
+
kind: "longtable_interview",
|
|
579
|
+
status: "active",
|
|
580
|
+
createdAt: timestamp,
|
|
581
|
+
updatedAt: timestamp,
|
|
582
|
+
targetOutcome: "first_research_handle",
|
|
583
|
+
depth: "gathering_context",
|
|
584
|
+
provider: options.provider,
|
|
585
|
+
turns: [],
|
|
586
|
+
qualityNotes: [],
|
|
587
|
+
rationale: [
|
|
588
|
+
"Official LongTable research start surface is provider-native `$longtable-interview`, not the CLI start questionnaire.",
|
|
589
|
+
"The hook keeps early research ambiguity open until a first research handle can be summarized."
|
|
590
|
+
]
|
|
591
|
+
};
|
|
592
|
+
let updated = upsertHook(state, hook);
|
|
593
|
+
updated.workingState = {
|
|
594
|
+
...updated.workingState,
|
|
595
|
+
activeInterviewHookId: hook.id,
|
|
596
|
+
interviewSurface: "$longtable-interview",
|
|
597
|
+
...(options.openingQuestion ? { interviewOpeningQuestion: options.openingQuestion } : {}),
|
|
598
|
+
...(options.seedAnswer ? { interviewSeedAnswer: options.seedAnswer } : {})
|
|
599
|
+
};
|
|
600
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
601
|
+
await syncCurrentWorkspaceView(options.context);
|
|
602
|
+
return { hook, state: updated };
|
|
603
|
+
}
|
|
604
|
+
export async function appendLongTableInterviewTurn(options) {
|
|
605
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
606
|
+
const existing = activeInterviewHook(state, options.hookId);
|
|
607
|
+
if (!existing) {
|
|
608
|
+
throw new Error("No active LongTable interview hook was found. Run begin_interview first.");
|
|
609
|
+
}
|
|
610
|
+
const quality = normalizeInterviewQuality(options.answer, options.quality);
|
|
611
|
+
const needsFollowUp = options.needsFollowUp ?? quality === "thin";
|
|
612
|
+
const followUpQuestion = needsFollowUp
|
|
613
|
+
? options.followUpQuestion ?? defaultFollowUpQuestion(options.answer)
|
|
614
|
+
: options.followUpQuestion;
|
|
615
|
+
const timestamp = nowIso();
|
|
616
|
+
const turns = existing.turns ?? [];
|
|
617
|
+
const turn = {
|
|
618
|
+
id: createId("interview_turn"),
|
|
619
|
+
index: turns.length + 1,
|
|
620
|
+
createdAt: timestamp,
|
|
621
|
+
question: options.question.trim(),
|
|
622
|
+
answer: options.answer.trim(),
|
|
623
|
+
...(options.reflection?.trim() ? { reflection: options.reflection.trim() } : {}),
|
|
624
|
+
quality,
|
|
625
|
+
needsFollowUp,
|
|
626
|
+
...(followUpQuestion?.trim() ? { followUpQuestion: followUpQuestion.trim() } : {}),
|
|
627
|
+
...(options.rationale && options.rationale.length > 0 ? { rationale: options.rationale } : {})
|
|
628
|
+
};
|
|
629
|
+
const nextTurns = [...turns, turn];
|
|
630
|
+
const depth = depthForInterview(nextTurns);
|
|
631
|
+
const hook = {
|
|
632
|
+
...existing,
|
|
633
|
+
status: depth === "ready_to_summarize" ? "ready_to_confirm" : "active",
|
|
634
|
+
updatedAt: timestamp,
|
|
635
|
+
depth,
|
|
636
|
+
turns: nextTurns,
|
|
637
|
+
qualityNotes: [
|
|
638
|
+
...(existing.qualityNotes ?? []),
|
|
639
|
+
...(needsFollowUp ? [`Turn ${turn.index} needs follow-up: ${followUpQuestion}`] : [])
|
|
640
|
+
]
|
|
641
|
+
};
|
|
642
|
+
const updated = upsertHook(state, hook);
|
|
643
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
644
|
+
await syncCurrentWorkspaceView(options.context);
|
|
645
|
+
return { hook, turn, state: updated };
|
|
646
|
+
}
|
|
647
|
+
export async function summarizeLongTableInterview(options) {
|
|
648
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
649
|
+
const existing = activeInterviewHook(state, options.hookId);
|
|
650
|
+
if (!existing) {
|
|
651
|
+
throw new Error("No active LongTable interview hook was found. Run begin_interview first.");
|
|
652
|
+
}
|
|
653
|
+
const timestamp = nowIso();
|
|
654
|
+
const shape = {
|
|
655
|
+
...options.shape,
|
|
656
|
+
handle: options.shape.handle.trim(),
|
|
657
|
+
currentGoal: options.shape.currentGoal.trim(),
|
|
658
|
+
openQuestions: options.shape.openQuestions.map((question) => question.trim()).filter(Boolean),
|
|
659
|
+
nextAction: options.shape.nextAction.trim(),
|
|
660
|
+
sourceHookId: existing.id
|
|
661
|
+
};
|
|
662
|
+
const hook = {
|
|
663
|
+
...existing,
|
|
664
|
+
status: "ready_to_confirm",
|
|
665
|
+
updatedAt: timestamp,
|
|
666
|
+
depth: "ready_to_summarize",
|
|
667
|
+
firstResearchShape: shape
|
|
668
|
+
};
|
|
669
|
+
const session = {
|
|
670
|
+
...options.context.session,
|
|
671
|
+
lastUpdatedAt: timestamp,
|
|
672
|
+
currentGoal: shape.currentGoal,
|
|
673
|
+
...(shape.currentBlocker ? { currentBlocker: shape.currentBlocker } : {}),
|
|
674
|
+
...(shape.researchObject ? { researchObject: shape.researchObject } : {}),
|
|
675
|
+
...(shape.gapRisk ? { gapRisk: shape.gapRisk } : {}),
|
|
676
|
+
...(shape.protectedDecision ? { protectedDecision: shape.protectedDecision } : {}),
|
|
677
|
+
nextAction: shape.nextAction,
|
|
678
|
+
openQuestions: shape.openQuestions,
|
|
679
|
+
firstResearchShape: shape,
|
|
680
|
+
resumeHint: `I want to continue from the First Research Shape: ${shape.handle}.`
|
|
681
|
+
};
|
|
682
|
+
options.context.session = session;
|
|
683
|
+
let updated = upsertHook(state, hook);
|
|
684
|
+
updated.firstResearchShape = shape;
|
|
685
|
+
updated.workingState = {
|
|
686
|
+
...updated.workingState,
|
|
687
|
+
currentGoal: shape.currentGoal,
|
|
688
|
+
...(shape.currentBlocker ? { currentBlocker: shape.currentBlocker } : {}),
|
|
689
|
+
...(shape.researchObject ? { researchObject: shape.researchObject } : {}),
|
|
690
|
+
...(shape.gapRisk ? { gapRisk: shape.gapRisk } : {}),
|
|
691
|
+
...(shape.protectedDecision ? { protectedDecision: shape.protectedDecision } : {}),
|
|
692
|
+
openQuestions: shape.openQuestions,
|
|
693
|
+
nextAction: shape.nextAction,
|
|
694
|
+
firstResearchShape: shape
|
|
695
|
+
};
|
|
696
|
+
if (shape.currentBlocker && !updated.openTensions.includes(shape.currentBlocker)) {
|
|
697
|
+
updated.openTensions.push(shape.currentBlocker);
|
|
698
|
+
}
|
|
699
|
+
updated.narrativeTraces.push({
|
|
700
|
+
id: createId("narrative_trace"),
|
|
701
|
+
timestamp,
|
|
702
|
+
source: "$longtable-interview",
|
|
703
|
+
traceType: "judgment",
|
|
704
|
+
summary: `First Research Shape: ${shape.handle}.`,
|
|
705
|
+
visibility: "explicit",
|
|
706
|
+
importance: shape.confidence
|
|
707
|
+
});
|
|
708
|
+
await writeFile(options.context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
|
|
709
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
710
|
+
await syncCurrentWorkspaceView(options.context);
|
|
711
|
+
return { hook, shape, state: updated, session };
|
|
712
|
+
}
|
|
441
713
|
function findQuestionForDecision(state, questionId) {
|
|
442
714
|
const pending = (state.questionLog ?? []).filter((record) => record.status === "pending");
|
|
443
715
|
if (questionId) {
|
|
@@ -1008,6 +1280,7 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
1008
1280
|
...(options.researchObject ? { researchObject: options.researchObject } : {}),
|
|
1009
1281
|
...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
|
|
1010
1282
|
...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
|
|
1283
|
+
...(options.startInterview ? { startInterview: options.startInterview } : {}),
|
|
1011
1284
|
nextAction: buildNextAction({
|
|
1012
1285
|
schemaVersion: 1,
|
|
1013
1286
|
id: sessionId,
|
|
@@ -1019,6 +1292,7 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
1019
1292
|
...(options.researchObject ? { researchObject: options.researchObject } : {}),
|
|
1020
1293
|
...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
|
|
1021
1294
|
...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
|
|
1295
|
+
...(options.startInterview ? { startInterview: options.startInterview } : {}),
|
|
1022
1296
|
requestedPerspectives: options.requestedPerspectives,
|
|
1023
1297
|
disagreementPreference: options.disagreementPreference
|
|
1024
1298
|
}),
|
|
@@ -1033,6 +1307,7 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
1033
1307
|
...(options.researchObject ? { researchObject: options.researchObject } : {}),
|
|
1034
1308
|
...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
|
|
1035
1309
|
...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
|
|
1310
|
+
...(options.startInterview ? { startInterview: options.startInterview } : {}),
|
|
1036
1311
|
requestedPerspectives: options.requestedPerspectives,
|
|
1037
1312
|
disagreementPreference: options.disagreementPreference
|
|
1038
1313
|
}),
|
|
@@ -1050,6 +1325,7 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
1050
1325
|
...(options.researchObject ? { researchObject: options.researchObject } : {}),
|
|
1051
1326
|
...(options.gapRisk ? { gapRisk: options.gapRisk } : {}),
|
|
1052
1327
|
...(options.protectedDecision ? { protectedDecision: options.protectedDecision } : {}),
|
|
1328
|
+
...(options.startInterview ? { startInterview: options.startInterview } : {}),
|
|
1053
1329
|
requestedPerspectives: options.requestedPerspectives,
|
|
1054
1330
|
disagreementPreference: options.disagreementPreference
|
|
1055
1331
|
}),
|
|
@@ -1123,13 +1399,19 @@ export async function inspectProjectWorkspace(startPath) {
|
|
|
1123
1399
|
}
|
|
1124
1400
|
export function renderProjectWorkspaceSummary(context) {
|
|
1125
1401
|
return [
|
|
1126
|
-
"
|
|
1127
|
-
"
|
|
1128
|
-
"└──────────────────────────────────────────────┘",
|
|
1402
|
+
"┌─ LongTable Project Workspace ─────────────────────────┐",
|
|
1403
|
+
"└───────────────────────────────────────────────────────┘",
|
|
1129
1404
|
`Project: ${context.project.projectName}`,
|
|
1130
1405
|
`Path: ${context.project.projectPath}`,
|
|
1406
|
+
"",
|
|
1407
|
+
"┌─ Current Research Shape ──────────────────────────────┐",
|
|
1131
1408
|
`Goal: ${context.session.currentGoal}`,
|
|
1132
1409
|
...(context.session.currentBlocker ? [`Blocker: ${context.session.currentBlocker}`] : []),
|
|
1410
|
+
...(context.session.researchObject ? [`Working object: ${context.session.researchObject}`] : []),
|
|
1411
|
+
...(context.session.firstResearchShape ? [`First Research Shape: ${context.session.firstResearchShape.handle}`] : []),
|
|
1412
|
+
...(context.session.startInterview ? [`Start interview: ${context.session.startInterview.summary}`] : []),
|
|
1413
|
+
"└───────────────────────────────────────────────────────┘",
|
|
1414
|
+
"",
|
|
1133
1415
|
`Perspectives: ${context.session.requestedPerspectives.length > 0 ? context.session.requestedPerspectives.join(", ") : "auto"}`,
|
|
1134
1416
|
`Disagreement: ${context.session.disagreementPreference}`,
|
|
1135
1417
|
"",
|
package/dist/prompt-aliases.js
CHANGED
|
@@ -29,18 +29,18 @@ function promptSpec() {
|
|
|
29
29
|
argumentHint: "[project context or current uncertainty]",
|
|
30
30
|
body: [
|
|
31
31
|
"You are LongTable setup guidance inside Codex.",
|
|
32
|
-
"Treat `longtable init` as deprecated. Prefer `longtable setup --provider codex` for runtime permissions, then
|
|
32
|
+
"Treat `longtable init` as deprecated. Prefer `longtable setup --provider codex` for runtime permissions, then `$longtable-interview` inside Codex for the project interview.",
|
|
33
33
|
"Ask exactly one setup question at a time.",
|
|
34
34
|
"Use numbered choices and include a concise Why / What you get / Tradeoff for each option.",
|
|
35
35
|
"Do not move to the next question until the researcher answers the current one.",
|
|
36
|
-
"Runtime setup covers only: provider, install scope, runtime surfaces, intervention strength, and whether to
|
|
36
|
+
"Runtime setup covers only: provider, install scope, runtime surfaces, intervention strength, and whether to show interview launch steps.",
|
|
37
37
|
"Do not ask for field, career stage, experience level, authorship signal, weakest domain, or panel preference during runtime setup.",
|
|
38
|
-
"Project
|
|
38
|
+
"Project interview covers: the first research handle, early uncertainty, first inspectable material, and final structured confirmation.",
|
|
39
39
|
"After collecting runtime answers, summarize the proposed setup and output the exact `longtable setup --provider codex ...` command.",
|
|
40
|
-
"If the researcher is ready to create a project workspace,
|
|
40
|
+
"If the researcher is ready to create a project workspace, tell them to open Codex in the research folder and invoke `$longtable-interview`.",
|
|
41
41
|
"If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
|
|
42
42
|
"Frame setup as permission and intervention calibration, not a researcher profile interview.",
|
|
43
|
-
"Do not pretend setup is the full project-start interview. The project-start interview happens in
|
|
43
|
+
"Do not pretend setup is the full project-start interview. The project-start interview happens in `$longtable-interview`.",
|
|
44
44
|
"Treat any slash-command arguments as context for why setup is being done now."
|
|
45
45
|
]
|
|
46
46
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SetupChoice } from "@longtable/setup";
|
|
2
|
+
export interface PromptRenderer {
|
|
3
|
+
text(prompt: string, options?: {
|
|
4
|
+
required?: boolean;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
}): Promise<string | undefined>;
|
|
7
|
+
select(prompt: string, choices: SetupChoice[]): Promise<string>;
|
|
8
|
+
multiselect(prompt: string, choices: SetupChoice[]): Promise<string[]>;
|
|
9
|
+
note(message: string, title?: string): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function createPromptRenderer(): PromptRenderer;
|