@longtable/cli 0.1.58 → 0.1.60
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/dist/cli.js +358 -30
- package/dist/debate.js +19 -31
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/longtable-codex-native-hook.js +36 -8
- package/dist/panel.d.ts +13 -1
- package/dist/panel.js +339 -27
- package/dist/project-session.js +88 -5
- package/dist/search/index.d.ts +1 -6
- package/dist/search/index.js +1 -6
- package/package.json +8 -7
- package/dist/search/publisher-access.d.ts +0 -21
- package/dist/search/publisher-access.js +0 -564
- package/dist/search/query.d.ts +0 -6
- package/dist/search/query.js +0 -179
- package/dist/search/rank.d.ts +0 -2
- package/dist/search/rank.js +0 -173
- package/dist/search/run.d.ts +0 -2
- package/dist/search/run.js +0 -114
- package/dist/search/sources.d.ts +0 -5
- package/dist/search/sources.js +0 -537
- package/dist/search/types.d.ts +0 -181
- package/dist/search/types.js +0 -16
|
@@ -57,12 +57,41 @@ function readCombinedOutput(payload) {
|
|
|
57
57
|
safeString(payload.output)
|
|
58
58
|
].filter(Boolean).join("\n").trim();
|
|
59
59
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
60
|
+
function questionUsesKorean(question) {
|
|
61
|
+
return /[가-힣]/.test([
|
|
62
|
+
question.prompt.title,
|
|
63
|
+
question.prompt.question,
|
|
64
|
+
question.prompt.displayReason ?? "",
|
|
65
|
+
...question.prompt.options.flatMap((option) => [option.label, option.description ?? ""]),
|
|
66
|
+
question.prompt.otherLabel ?? ""
|
|
67
|
+
].join("\n"));
|
|
68
|
+
}
|
|
69
|
+
function formatQuestionChoices(question) {
|
|
70
|
+
const korean = questionUsesKorean(question);
|
|
71
|
+
const options = question.prompt.options.flatMap((option, index) => {
|
|
72
|
+
const recommended = option.recommended ? (korean ? " (추천)" : " (recommended)") : "";
|
|
73
|
+
return [
|
|
74
|
+
`${index + 1}. ${option.label}${recommended}`,
|
|
75
|
+
...(option.description ? [` ${option.description}`] : []),
|
|
76
|
+
` ${korean ? "기록값" : "Record value"}: ${option.value}`
|
|
77
|
+
];
|
|
78
|
+
});
|
|
62
79
|
if (question.prompt.allowOther) {
|
|
63
|
-
options.push("
|
|
80
|
+
options.push(`${question.prompt.options.length + 1}. ${question.prompt.otherLabel ?? (korean ? "직접 입력" : "Other")}`);
|
|
81
|
+
options.push(` ${korean ? "기록값" : "Record value"}: other`);
|
|
64
82
|
}
|
|
65
|
-
return options.join("
|
|
83
|
+
return options.join("\n");
|
|
84
|
+
}
|
|
85
|
+
function buildQuestionDecisionCard(question) {
|
|
86
|
+
const korean = questionUsesKorean(question);
|
|
87
|
+
return [
|
|
88
|
+
korean ? "LongTable 결정 카드" : "LongTable Decision Card",
|
|
89
|
+
`${korean ? "체크포인트" : "Checkpoint"}: ${question.prompt.title}`,
|
|
90
|
+
`${korean ? "무엇이 걸렸나" : "What is blocked"}: ${question.prompt.displayReason ?? question.prompt.rationale[0] ?? question.prompt.title}`,
|
|
91
|
+
`${korean ? "지금 결정할 것" : "Decision needed"}: ${question.prompt.question}`,
|
|
92
|
+
korean ? "선택지:" : "Choices:",
|
|
93
|
+
formatQuestionChoices(question)
|
|
94
|
+
].join("\n");
|
|
66
95
|
}
|
|
67
96
|
function pendingRequiredQuestions(state) {
|
|
68
97
|
return (state.questionLog ?? []).filter((question) => question.status === "pending" && question.prompt.required);
|
|
@@ -271,8 +300,8 @@ function buildWorkspaceSummary(runtime, detail = "compact") {
|
|
|
271
300
|
}
|
|
272
301
|
function buildPendingQuestionContext(question) {
|
|
273
302
|
return [
|
|
274
|
-
|
|
275
|
-
|
|
303
|
+
"Required Researcher Checkpoint is still pending.",
|
|
304
|
+
buildQuestionDecisionCard(question),
|
|
276
305
|
`Record it with longtable decide --question ${question.id} --answer <value> if you are outside MCP elicitation.`,
|
|
277
306
|
"Do not choose or record an answer unless the researcher explicitly provides the selection."
|
|
278
307
|
].join("\n");
|
|
@@ -291,8 +320,7 @@ function buildGeneratedQuestionsContext(questions, created) {
|
|
|
291
320
|
: `LongTable found ${questions.length} pending Researcher Checkpoint${questions.length === 1 ? "" : "s"} for this prompt.`
|
|
292
321
|
];
|
|
293
322
|
for (const question of questions) {
|
|
294
|
-
lines.push(
|
|
295
|
-
lines.push(` Options: ${formatQuestionOptions(question)}`);
|
|
323
|
+
lines.push(buildQuestionDecisionCard(question));
|
|
296
324
|
lines.push(` Record it with longtable decide --question ${question.id} --answer <value> if you are outside MCP elicitation.`);
|
|
297
325
|
}
|
|
298
326
|
lines.push("Do not choose or record answers for these checkpoints unless the researcher explicitly provides the selections.");
|
package/dist/panel.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, InvocationSurface, RoleKey } from "@longtable/core";
|
|
1
|
+
import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionOption, QuestionRecord, ProviderKind, InvocationSurface, RoleKey } from "@longtable/core";
|
|
2
2
|
import { type CanonicalPersona } from "./personas.js";
|
|
3
|
+
import { type OutputLanguage } from "./persona-router.js";
|
|
3
4
|
export interface BuildPanelPlanOptions {
|
|
4
5
|
prompt: string;
|
|
5
6
|
mode?: InteractionMode;
|
|
@@ -18,6 +19,16 @@ export interface PanelFallback {
|
|
|
18
19
|
questionRecord: QuestionRecord;
|
|
19
20
|
prompt: string;
|
|
20
21
|
}
|
|
22
|
+
interface PanelDecisionContext {
|
|
23
|
+
language: OutputLanguage;
|
|
24
|
+
focus: string;
|
|
25
|
+
blockerSummary: string;
|
|
26
|
+
decisionQuestion: string;
|
|
27
|
+
displayReason: string;
|
|
28
|
+
options: QuestionOption[];
|
|
29
|
+
otherLabel: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function buildPanelDecisionContext(prompt: string): PanelDecisionContext;
|
|
21
32
|
export declare function buildPanelPlan(options: BuildPanelPlanOptions): PanelPlan;
|
|
22
33
|
export declare function buildInvocationIntent(options: {
|
|
23
34
|
prompt: string;
|
|
@@ -41,3 +52,4 @@ export declare function renderSequentialFallbackPrompt(plan: PanelPlan): string;
|
|
|
41
52
|
export declare function buildPanelFallback(options: BuildPanelPlanOptions): PanelFallback;
|
|
42
53
|
export declare function renderPanelSummary(plan: PanelPlan): string;
|
|
43
54
|
export declare function listDefaultPanelRoles(): CanonicalPersona[];
|
|
55
|
+
export {};
|
package/dist/panel.js
CHANGED
|
@@ -61,6 +61,336 @@ function memberForRole(role, explicitRoles, routedRoles) {
|
|
|
61
61
|
required: DEFAULT_PANEL_ROLES.includes(role) || explicitRoles.includes(role)
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
+
function firstPromptClause(prompt) {
|
|
65
|
+
const projectContextMatch = prompt.match(/(?:^|\n)LongTable project context\n[\s\S]*?\n\n([\s\S]+)$/);
|
|
66
|
+
const sourcePrompt = projectContextMatch?.[1] ?? prompt;
|
|
67
|
+
const normalized = sourcePrompt.replace(/\s+/g, " ").trim();
|
|
68
|
+
const sentenceEnd = normalized.search(/[.!?]\s/);
|
|
69
|
+
const first = sentenceEnd >= 0 ? normalized.slice(0, sentenceEnd + 1) : normalized;
|
|
70
|
+
return first
|
|
71
|
+
.replace(/^(please\s+)?(run\s+)?(a\s+)?(structured\s+)?(panel\s+)?(review|critique|evaluate|assess|inspect)\s+/i, "")
|
|
72
|
+
.replace(/^(whether|if|of)\s+/i, "")
|
|
73
|
+
.replace(/^the\s+/i, "")
|
|
74
|
+
.trim();
|
|
75
|
+
}
|
|
76
|
+
function conciseFocusFromPrompt(prompt) {
|
|
77
|
+
const focus = firstPromptClause(prompt)
|
|
78
|
+
.replace(/[.:;,\s]+$/g, "")
|
|
79
|
+
.trim();
|
|
80
|
+
if (!focus) {
|
|
81
|
+
return "the reviewed issue";
|
|
82
|
+
}
|
|
83
|
+
return focus.length > 96 ? `${focus.slice(0, 93).trim()}...` : focus;
|
|
84
|
+
}
|
|
85
|
+
function languageText(language, en, ko) {
|
|
86
|
+
return language === "ko" ? ko : en;
|
|
87
|
+
}
|
|
88
|
+
function withFocus(template, focus) {
|
|
89
|
+
return template.replace(/\{focus\}/g, focus);
|
|
90
|
+
}
|
|
91
|
+
function shouldIncludeDeferOption(prompt) {
|
|
92
|
+
return /\b(defer|postpone|not decide|keep open|open tension|later|uncertain|unresolved)\b/i.test(prompt)
|
|
93
|
+
|| /보류|미루|나중|열린\s*쟁점|열어\s*두|불확실|미해결/.test(prompt);
|
|
94
|
+
}
|
|
95
|
+
const PANEL_DECISION_COPY = {
|
|
96
|
+
manuscript_spine: {
|
|
97
|
+
focus: {
|
|
98
|
+
en: "the manuscript table/figure spine",
|
|
99
|
+
ko: "원고의 표/그림 spine"
|
|
100
|
+
},
|
|
101
|
+
blockerSummary: {
|
|
102
|
+
en: "The panel concern is that the current manuscript shell does not yet carry its argument through a useful table/figure spine.",
|
|
103
|
+
ko: "패널이 멈춘 이유는 현재 원고 shell이 논문 주장을 운반할 표/그림 spine을 충분히 보여주지 못했기 때문입니다."
|
|
104
|
+
},
|
|
105
|
+
recommendedValue: "revise",
|
|
106
|
+
labels: {
|
|
107
|
+
revise: { en: "Revise manuscript table/figure spine", ko: "원고 표/그림 spine을 다시 설계한다" },
|
|
108
|
+
evidence: { en: "Verify table/figure evidence", ko: "표/그림 근거를 먼저 확인한다" },
|
|
109
|
+
proceed: { en: "Proceed with current manuscript direction", ko: "현재 원고 방향을 유지하고 표/그림만 보강한다" },
|
|
110
|
+
defer: { en: "Keep manuscript spine concern open", ko: "원고 spine 우려를 열린 쟁점으로 둔다" }
|
|
111
|
+
},
|
|
112
|
+
descriptions: {
|
|
113
|
+
revise: {
|
|
114
|
+
en: "Use the panel result to redesign the table/figure spine before drafting the manuscript.",
|
|
115
|
+
ko: "패널 결과를 반영해 원고를 쓰기 전에 표/그림 spine을 다시 잡습니다."
|
|
116
|
+
},
|
|
117
|
+
evidence: {
|
|
118
|
+
en: "Check which tables, figures, analyses, and citations can actually support the manuscript claim.",
|
|
119
|
+
ko: "어떤 표, 그림, 분석, 인용이 실제로 원고 주장을 지탱할 수 있는지 먼저 확인합니다."
|
|
120
|
+
},
|
|
121
|
+
proceed: {
|
|
122
|
+
en: "Keep the current manuscript direction and only strengthen the missing table/figure pieces.",
|
|
123
|
+
ko: "현재 원고 방향은 유지하고 부족한 표/그림 요소만 보강합니다."
|
|
124
|
+
},
|
|
125
|
+
defer: {
|
|
126
|
+
en: "Do not settle the manuscript spine yet; keep the concern visible as an open issue.",
|
|
127
|
+
ko: "아직 원고 spine을 확정하지 않고 이 우려를 열린 쟁점으로 남깁니다."
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
manuscript_argument: {
|
|
132
|
+
focus: {
|
|
133
|
+
en: "the manuscript or draft argument",
|
|
134
|
+
ko: "원고 또는 초안의 논지"
|
|
135
|
+
},
|
|
136
|
+
blockerSummary: {
|
|
137
|
+
en: "The panel concern is that the manuscript direction could settle before the argument, evidence, and reviewer risk are aligned.",
|
|
138
|
+
ko: "패널이 멈춘 이유는 논지, 근거, 리뷰어 리스크가 정렬되기 전에 원고 방향이 확정될 수 있기 때문입니다."
|
|
139
|
+
},
|
|
140
|
+
recommendedValue: "revise",
|
|
141
|
+
labels: {
|
|
142
|
+
revise: { en: "Revise manuscript argument", ko: "원고 논지를 먼저 수정한다" },
|
|
143
|
+
evidence: { en: "Verify manuscript evidence", ko: "원고 근거를 먼저 확인한다" },
|
|
144
|
+
proceed: { en: "Proceed with current manuscript direction", ko: "현재 원고 방향으로 진행한다" },
|
|
145
|
+
defer: { en: "Keep manuscript concern open", ko: "원고 우려를 열린 쟁점으로 둔다" }
|
|
146
|
+
},
|
|
147
|
+
descriptions: {
|
|
148
|
+
revise: {
|
|
149
|
+
en: "Use the panel result to revise the claim, structure, or draft before proceeding.",
|
|
150
|
+
ko: "패널 결과를 반영해 주장, 구조, 초안을 먼저 수정합니다."
|
|
151
|
+
},
|
|
152
|
+
evidence: {
|
|
153
|
+
en: "Check source, data, artifact, or citation support for the manuscript direction before proceeding.",
|
|
154
|
+
ko: "현재 원고 방향을 지탱할 자료, 데이터, 산출물, 인용 근거를 먼저 확인합니다."
|
|
155
|
+
},
|
|
156
|
+
proceed: {
|
|
157
|
+
en: "Accept the visible risk profile and continue with the current manuscript direction.",
|
|
158
|
+
ko: "드러난 위험을 감수하고 현재 원고 방향으로 계속 진행합니다."
|
|
159
|
+
},
|
|
160
|
+
defer: {
|
|
161
|
+
en: "Do not commit yet; keep the manuscript issue visible as an open tension.",
|
|
162
|
+
ko: "아직 확정하지 않고 원고 쟁점을 열린 긴장으로 남깁니다."
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
measurement: {
|
|
167
|
+
focus: {
|
|
168
|
+
en: "the measurement or coding decision",
|
|
169
|
+
ko: "측정 또는 코딩 결정"
|
|
170
|
+
},
|
|
171
|
+
blockerSummary: {
|
|
172
|
+
en: "The panel concern is that measurement or coding rules decide what will count as evidence later.",
|
|
173
|
+
ko: "패널이 멈춘 이유는 측정 또는 코딩 규칙이 이후 무엇을 근거로 볼지 결정하기 때문입니다."
|
|
174
|
+
},
|
|
175
|
+
recommendedValue: "revise",
|
|
176
|
+
labels: {
|
|
177
|
+
revise: { en: "Revise measurement/coding plan", ko: "측정/코딩 계획을 수정한다" },
|
|
178
|
+
evidence: { en: "Verify measurement validity evidence", ko: "측정 타당도 근거를 확인한다" },
|
|
179
|
+
proceed: { en: "Proceed with current measurement/coding plan", ko: "현재 측정/코딩 계획으로 진행한다" },
|
|
180
|
+
defer: { en: "Keep measurement/coding concern open", ko: "측정/코딩 우려를 열린 쟁점으로 둔다" }
|
|
181
|
+
},
|
|
182
|
+
descriptions: {
|
|
183
|
+
revise: {
|
|
184
|
+
en: "Use the panel result to revise the variables, coding rules, or construct boundary before proceeding.",
|
|
185
|
+
ko: "패널 결과를 반영해 변수, 코딩 규칙, 구성개념 경계를 먼저 수정합니다."
|
|
186
|
+
},
|
|
187
|
+
evidence: {
|
|
188
|
+
en: "Check validity, reliability, source, or coding evidence before treating the plan as settled.",
|
|
189
|
+
ko: "타당도, 신뢰도, 출처, 코딩 근거를 확인한 뒤 계획을 확정합니다."
|
|
190
|
+
},
|
|
191
|
+
proceed: {
|
|
192
|
+
en: "Accept the visible measurement risk and continue with the current plan.",
|
|
193
|
+
ko: "드러난 측정 위험을 감수하고 현재 계획으로 계속 진행합니다."
|
|
194
|
+
},
|
|
195
|
+
defer: {
|
|
196
|
+
en: "Do not commit yet; keep the measurement or coding issue visible as an open tension.",
|
|
197
|
+
ko: "아직 확정하지 않고 측정 또는 코딩 쟁점을 열린 긴장으로 남깁니다."
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
method: {
|
|
202
|
+
focus: {
|
|
203
|
+
en: "the method or analysis design",
|
|
204
|
+
ko: "방법 또는 분석 설계"
|
|
205
|
+
},
|
|
206
|
+
blockerSummary: {
|
|
207
|
+
en: "The panel concern is that the method or analysis choice could become a hard-to-reverse research commitment.",
|
|
208
|
+
ko: "패널이 멈춘 이유는 방법 또는 분석 선택이 되돌리기 어려운 연구 결정이 될 수 있기 때문입니다."
|
|
209
|
+
},
|
|
210
|
+
recommendedValue: "revise",
|
|
211
|
+
labels: {
|
|
212
|
+
revise: { en: "Revise method/analysis plan", ko: "방법/분석 계획을 수정한다" },
|
|
213
|
+
evidence: { en: "Verify method/analysis evidence", ko: "방법/분석 근거를 확인한다" },
|
|
214
|
+
proceed: { en: "Proceed with current method/analysis plan", ko: "현재 방법/분석 계획으로 진행한다" },
|
|
215
|
+
defer: { en: "Keep method/analysis concern open", ko: "방법/분석 우려를 열린 쟁점으로 둔다" }
|
|
216
|
+
},
|
|
217
|
+
descriptions: {
|
|
218
|
+
revise: {
|
|
219
|
+
en: "Use the panel result to revise the design, model, sample, or analysis plan before proceeding.",
|
|
220
|
+
ko: "패널 결과를 반영해 설계, 모형, 표본, 분석 계획을 먼저 수정합니다."
|
|
221
|
+
},
|
|
222
|
+
evidence: {
|
|
223
|
+
en: "Check data, model, method, or artifact support before treating the plan as settled.",
|
|
224
|
+
ko: "데이터, 모형, 방법, 산출물 근거를 확인한 뒤 계획을 확정합니다."
|
|
225
|
+
},
|
|
226
|
+
proceed: {
|
|
227
|
+
en: "Accept the visible method risk and continue with the current plan.",
|
|
228
|
+
ko: "드러난 방법론적 위험을 감수하고 현재 계획으로 계속 진행합니다."
|
|
229
|
+
},
|
|
230
|
+
defer: {
|
|
231
|
+
en: "Do not commit yet; keep the method or analysis issue visible as an open tension.",
|
|
232
|
+
ko: "아직 확정하지 않고 방법 또는 분석 쟁점을 열린 긴장으로 남깁니다."
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
evidence: {
|
|
237
|
+
focus: {
|
|
238
|
+
en: "the evidence or source standard",
|
|
239
|
+
ko: "근거 또는 출처 기준"
|
|
240
|
+
},
|
|
241
|
+
blockerSummary: {
|
|
242
|
+
en: "The panel concern is that LongTable could proceed before the source, artifact, or citation support is explicit.",
|
|
243
|
+
ko: "패널이 멈춘 이유는 출처, 산출물, 인용 근거가 명시되기 전에 LongTable이 진행할 수 있기 때문입니다."
|
|
244
|
+
},
|
|
245
|
+
recommendedValue: "evidence",
|
|
246
|
+
labels: {
|
|
247
|
+
revise: { en: "Revise evidence standard", ko: "근거 기준을 수정한다" },
|
|
248
|
+
evidence: { en: "Verify source/citation support", ko: "출처/인용 근거를 확인한다" },
|
|
249
|
+
proceed: { en: "Proceed with current evidence boundary", ko: "현재 근거 경계로 진행한다" },
|
|
250
|
+
defer: { en: "Keep evidence concern open", ko: "근거 우려를 열린 쟁점으로 둔다" }
|
|
251
|
+
},
|
|
252
|
+
descriptions: {
|
|
253
|
+
revise: {
|
|
254
|
+
en: "Use the panel result to revise what will count as adequate evidence.",
|
|
255
|
+
ko: "패널 결과를 반영해 충분한 근거의 기준을 먼저 수정합니다."
|
|
256
|
+
},
|
|
257
|
+
evidence: {
|
|
258
|
+
en: "Check source, data, artifact, or citation support before proceeding.",
|
|
259
|
+
ko: "진행하기 전에 출처, 데이터, 산출물, 인용 근거를 확인합니다."
|
|
260
|
+
},
|
|
261
|
+
proceed: {
|
|
262
|
+
en: "Accept the visible evidence risk and continue with the current boundary.",
|
|
263
|
+
ko: "드러난 근거 위험을 감수하고 현재 경계로 계속 진행합니다."
|
|
264
|
+
},
|
|
265
|
+
defer: {
|
|
266
|
+
en: "Do not commit yet; keep the evidence issue visible as an open tension.",
|
|
267
|
+
ko: "아직 확정하지 않고 근거 쟁점을 열린 긴장으로 남깁니다."
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
theory: {
|
|
272
|
+
focus: {
|
|
273
|
+
en: "the theory or conceptual frame",
|
|
274
|
+
ko: "이론 또는 개념 프레임"
|
|
275
|
+
},
|
|
276
|
+
blockerSummary: {
|
|
277
|
+
en: "The panel concern is that the conceptual frame could settle before its distinctions and limits are explicit.",
|
|
278
|
+
ko: "패널이 멈춘 이유는 개념 구분과 한계가 명확해지기 전에 이론 프레임이 확정될 수 있기 때문입니다."
|
|
279
|
+
},
|
|
280
|
+
recommendedValue: "revise",
|
|
281
|
+
labels: {
|
|
282
|
+
revise: { en: "Revise theory/conceptual frame", ko: "이론/개념 프레임을 수정한다" },
|
|
283
|
+
evidence: { en: "Verify theory support", ko: "이론 근거를 확인한다" },
|
|
284
|
+
proceed: { en: "Proceed with current theory frame", ko: "현재 이론 프레임으로 진행한다" },
|
|
285
|
+
defer: { en: "Keep theory concern open", ko: "이론 우려를 열린 쟁점으로 둔다" }
|
|
286
|
+
},
|
|
287
|
+
descriptions: {
|
|
288
|
+
revise: {
|
|
289
|
+
en: "Use the panel result to revise the conceptual frame, distinctions, or scope before proceeding.",
|
|
290
|
+
ko: "패널 결과를 반영해 개념 프레임, 구분, 범위를 먼저 수정합니다."
|
|
291
|
+
},
|
|
292
|
+
evidence: {
|
|
293
|
+
en: "Check theory, literature, concept, or citation support before treating the frame as settled.",
|
|
294
|
+
ko: "이론, 문헌, 개념, 인용 근거를 확인한 뒤 프레임을 확정합니다."
|
|
295
|
+
},
|
|
296
|
+
proceed: {
|
|
297
|
+
en: "Accept the visible theory risk and continue with the current frame.",
|
|
298
|
+
ko: "드러난 이론적 위험을 감수하고 현재 프레임으로 계속 진행합니다."
|
|
299
|
+
},
|
|
300
|
+
defer: {
|
|
301
|
+
en: "Do not commit yet; keep the theory issue visible as an open tension.",
|
|
302
|
+
ko: "아직 확정하지 않고 이론 쟁점을 열린 긴장으로 남깁니다."
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
generic: {
|
|
307
|
+
focus: {
|
|
308
|
+
en: "the reviewed issue",
|
|
309
|
+
ko: "검토된 쟁점"
|
|
310
|
+
},
|
|
311
|
+
blockerSummary: {
|
|
312
|
+
en: "The panel concern is that LongTable has reached a decision point that should belong to the researcher.",
|
|
313
|
+
ko: "패널이 멈춘 이유는 LongTable이 연구자가 직접 정해야 할 결정 지점에 도달했기 때문입니다."
|
|
314
|
+
},
|
|
315
|
+
recommendedValue: "revise",
|
|
316
|
+
labels: {
|
|
317
|
+
revise: { en: "Revise reviewed issue", ko: "검토된 쟁점을 수정한다" },
|
|
318
|
+
evidence: { en: "Verify evidence first", ko: "근거를 먼저 확인한다" },
|
|
319
|
+
proceed: { en: "Proceed with current direction", ko: "현재 방향으로 진행한다" },
|
|
320
|
+
defer: { en: "Keep issue open", ko: "쟁점을 열린 상태로 둔다" }
|
|
321
|
+
},
|
|
322
|
+
descriptions: {
|
|
323
|
+
revise: {
|
|
324
|
+
en: "Use the panel result to revise {focus} before proceeding.",
|
|
325
|
+
ko: "패널 결과를 반영해 {focus}을/를 먼저 수정합니다."
|
|
326
|
+
},
|
|
327
|
+
evidence: {
|
|
328
|
+
en: "Check source, data, artifact, or citation support for {focus} before proceeding.",
|
|
329
|
+
ko: "{focus}을/를 지탱할 출처, 데이터, 산출물, 인용 근거를 먼저 확인합니다."
|
|
330
|
+
},
|
|
331
|
+
proceed: {
|
|
332
|
+
en: "Accept the visible risk profile for {focus} and continue.",
|
|
333
|
+
ko: "{focus}에 대해 드러난 위험을 감수하고 계속 진행합니다."
|
|
334
|
+
},
|
|
335
|
+
defer: {
|
|
336
|
+
en: "Do not commit yet; keep {focus} visible as an open tension.",
|
|
337
|
+
ko: "아직 확정하지 않고 {focus}을/를 열린 긴장으로 남깁니다."
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
function decisionDomainFromPrompt(prompt) {
|
|
343
|
+
const normalized = prompt.toLowerCase();
|
|
344
|
+
if (/get[-_ ]?journal|table\/figure|table and figure|figure spine|table spine|manuscript shell|journal manuscript/.test(normalized)) {
|
|
345
|
+
return "manuscript_spine";
|
|
346
|
+
}
|
|
347
|
+
if (/\bmanuscript\b|\bdraft\b|\bpaper\b|\barticle\b|\bsection\b|\bsubmission\b/.test(normalized)) {
|
|
348
|
+
return "manuscript_argument";
|
|
349
|
+
}
|
|
350
|
+
if (/\bmeasurement\b|\bmeasure\b|\bscale\b|\bconstruct\b|\bcoding\b|측정|척도|구성개념|코딩/.test(normalized)) {
|
|
351
|
+
return "measurement";
|
|
352
|
+
}
|
|
353
|
+
if (/\bmethod\b|\bdesign\b|\banalysis\b|\bmodel\b|\bsample\b|\bidentification\b|방법|설계|분석|표본/.test(normalized)) {
|
|
354
|
+
return "method";
|
|
355
|
+
}
|
|
356
|
+
if (/\bevidence\b|\bsource\b|\bcitation\b|\breference\b|\bpdf\b|\bcorpus\b|근거|인용|문헌|자료/.test(normalized)) {
|
|
357
|
+
return "evidence";
|
|
358
|
+
}
|
|
359
|
+
if (/\btheory\b|\bframework\b|\bontology\b|\bconcept\b|이론|개념|온톨로지/.test(normalized)) {
|
|
360
|
+
return "theory";
|
|
361
|
+
}
|
|
362
|
+
return "generic";
|
|
363
|
+
}
|
|
364
|
+
function panelDecisionOptions(copy, language, focus, includeDefer) {
|
|
365
|
+
const values = includeDefer
|
|
366
|
+
? ["revise", "evidence", "defer"]
|
|
367
|
+
: ["revise", "evidence", "proceed"];
|
|
368
|
+
return values
|
|
369
|
+
.sort((left, right) => Number(right === copy.recommendedValue) - Number(left === copy.recommendedValue))
|
|
370
|
+
.map((value) => ({
|
|
371
|
+
value,
|
|
372
|
+
label: copy.labels[value][language],
|
|
373
|
+
description: withFocus(copy.descriptions[value][language], focus),
|
|
374
|
+
...(value === copy.recommendedValue ? { recommended: true } : {})
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
export function buildPanelDecisionContext(prompt) {
|
|
378
|
+
const language = detectOutputLanguage(prompt);
|
|
379
|
+
const domain = decisionDomainFromPrompt(prompt);
|
|
380
|
+
const copy = PANEL_DECISION_COPY[domain];
|
|
381
|
+
const domainFocus = copy.focus[language];
|
|
382
|
+
const focus = domain === "generic" ? conciseFocusFromPrompt(prompt) || domainFocus : domainFocus;
|
|
383
|
+
const blockerSummary = copy.blockerSummary[language];
|
|
384
|
+
return {
|
|
385
|
+
language,
|
|
386
|
+
focus,
|
|
387
|
+
blockerSummary,
|
|
388
|
+
decisionQuestion: languageText(language, `What should LongTable treat as the next human decision for ${focus} after this panel review?`, `이 패널 리뷰 이후 LongTable이 ${focus}에 대해 다음 인간 결정으로 기록해야 할 것은 무엇인가요?`),
|
|
389
|
+
displayReason: languageText(language, `${blockerSummary} This should be resolved by the researcher before LongTable treats the direction as settled.`, `${blockerSummary} LongTable이 방향을 확정하기 전에 연구자가 직접 선택해야 합니다.`),
|
|
390
|
+
options: panelDecisionOptions(copy, language, focus, shouldIncludeDeferOption(prompt)),
|
|
391
|
+
otherLabel: languageText(language, "Other decision", "직접 입력")
|
|
392
|
+
};
|
|
393
|
+
}
|
|
64
394
|
export function buildPanelPlan(options) {
|
|
65
395
|
const mode = options.mode ?? "review";
|
|
66
396
|
const explicitRoles = unique([...(options.roles ?? []), ...parseRoleFlag(options.roleFlag)]);
|
|
@@ -112,6 +442,7 @@ export function buildInvocationIntent(options) {
|
|
|
112
442
|
}
|
|
113
443
|
export function createPlannedPanelQuestionRecord(plan, provider) {
|
|
114
444
|
const createdAt = nowIso();
|
|
445
|
+
const decisionContext = buildPanelDecisionContext(plan.prompt);
|
|
115
446
|
return {
|
|
116
447
|
id: createId("question_record"),
|
|
117
448
|
createdAt,
|
|
@@ -120,43 +451,24 @@ export function createPlannedPanelQuestionRecord(plan, provider) {
|
|
|
120
451
|
prompt: {
|
|
121
452
|
id: createId("question_prompt"),
|
|
122
453
|
checkpointKey: "panel_next_decision",
|
|
123
|
-
title: "Panel follow-up decision",
|
|
124
|
-
question:
|
|
454
|
+
title: languageText(decisionContext.language, "Panel follow-up decision", "패널 후속 결정"),
|
|
455
|
+
question: decisionContext.decisionQuestion,
|
|
125
456
|
type: "single_choice",
|
|
126
|
-
options:
|
|
127
|
-
{
|
|
128
|
-
value: "revise",
|
|
129
|
-
label: "Revise before proceeding",
|
|
130
|
-
description: "Use the panel result to revise the claim, design, or draft first."
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
value: "evidence",
|
|
134
|
-
label: "Gather or verify evidence first",
|
|
135
|
-
description: "Do not proceed until the relevant evidence or citation support is checked."
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
value: "proceed",
|
|
139
|
-
label: "Proceed with current direction",
|
|
140
|
-
description: "Accept the risk profile and continue with the current direction."
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
value: "defer",
|
|
144
|
-
label: "Keep this open",
|
|
145
|
-
description: "Do not commit yet; keep the panel issue visible as an open tension."
|
|
146
|
-
}
|
|
147
|
-
],
|
|
457
|
+
options: decisionContext.options,
|
|
148
458
|
allowOther: true,
|
|
149
|
-
otherLabel:
|
|
459
|
+
otherLabel: decisionContext.otherLabel,
|
|
150
460
|
required: plan.checkpointSensitivity === "high",
|
|
151
461
|
source: "runtime_guidance",
|
|
152
462
|
rationale: [
|
|
153
463
|
"Panel review creates disagreement or risk visibility that should connect to an explicit researcher decision.",
|
|
464
|
+
`Panel decision focus: ${decisionContext.focus}.`,
|
|
465
|
+
"Panel follow-up choices are compact by default; unlisted decisions should use Other.",
|
|
154
466
|
`Panel checkpoint sensitivity: ${plan.checkpointSensitivity}.`
|
|
155
467
|
],
|
|
156
|
-
displayReason:
|
|
468
|
+
displayReason: decisionContext.displayReason,
|
|
157
469
|
preferredSurfaces: provider === "claude"
|
|
158
470
|
? ["native_structured", "numbered"]
|
|
159
|
-
: ["mcp_elicitation", "numbered"]
|
|
471
|
+
: ["tmux_popup", "mcp_elicitation", "numbered"]
|
|
160
472
|
}
|
|
161
473
|
};
|
|
162
474
|
}
|
package/dist/project-session.js
CHANGED
|
@@ -497,6 +497,14 @@ function formatQuestionMetadata(record) {
|
|
|
497
497
|
].filter(Boolean);
|
|
498
498
|
return parts.length > 0 ? ` [${parts.join("; ")}]` : "";
|
|
499
499
|
}
|
|
500
|
+
const QUESTION_SURFACES = new Set([
|
|
501
|
+
"native_structured",
|
|
502
|
+
"tmux_popup",
|
|
503
|
+
"mcp_elicitation",
|
|
504
|
+
"numbered",
|
|
505
|
+
"terminal_selector",
|
|
506
|
+
"web_form"
|
|
507
|
+
]);
|
|
500
508
|
function compactLine(value, limit = 160) {
|
|
501
509
|
const compacted = value.replace(/\s+/g, " ").trim();
|
|
502
510
|
return compacted.length > limit ? `${compacted.slice(0, limit - 1)}…` : compacted;
|
|
@@ -506,6 +514,15 @@ function asRecord(value) {
|
|
|
506
514
|
? value
|
|
507
515
|
: null;
|
|
508
516
|
}
|
|
517
|
+
function asStringArray(value) {
|
|
518
|
+
if (Array.isArray(value)) {
|
|
519
|
+
return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
520
|
+
}
|
|
521
|
+
return typeof value === "string" && value.trim().length > 0 ? [value] : [];
|
|
522
|
+
}
|
|
523
|
+
function isQuestionSurfaceValue(value) {
|
|
524
|
+
return typeof value === "string" && QUESTION_SURFACES.has(value);
|
|
525
|
+
}
|
|
509
526
|
const SPEC_DIFF_IGNORED_PATHS = new Set([
|
|
510
527
|
"createdAt",
|
|
511
528
|
"updatedAt",
|
|
@@ -602,10 +619,10 @@ function buildResearchSpecificationGapQuestion(gaps, timestamp, sourceEvidenceId
|
|
|
602
619
|
"Research Specification is the required durable interview artifact.",
|
|
603
620
|
"Missing required sections can make later resume, screening, coding, or evidence decisions stale."
|
|
604
621
|
],
|
|
605
|
-
preferredSurfaces: ["mcp_elicitation", "numbered"]
|
|
622
|
+
preferredSurfaces: ["tmux_popup", "mcp_elicitation", "numbered"]
|
|
606
623
|
},
|
|
607
624
|
transportStatus: {
|
|
608
|
-
surface: "
|
|
625
|
+
surface: "tmux_popup",
|
|
609
626
|
status: "not_attempted",
|
|
610
627
|
updatedAt: timestamp,
|
|
611
628
|
...(sourceEvidenceIds.length > 0 ? { message: `Source evidence: ${sourceEvidenceIds.join(", ")}` } : {})
|
|
@@ -2682,7 +2699,7 @@ export async function createWorkspaceFollowUpQuestions(options) {
|
|
|
2682
2699
|
const createdAt = nowIso();
|
|
2683
2700
|
const preferredSurfaces = options.provider === "claude"
|
|
2684
2701
|
? ["native_structured", "terminal_selector", "numbered"]
|
|
2685
|
-
: ["mcp_elicitation", "terminal_selector", "numbered"];
|
|
2702
|
+
: ["tmux_popup", "mcp_elicitation", "terminal_selector", "numbered"];
|
|
2686
2703
|
const specs = buildQuestionOpportunitySpecs(options.prompt, {
|
|
2687
2704
|
includeFallback: options.force === true ? true : options.auto !== true,
|
|
2688
2705
|
autoOnly: options.auto === true,
|
|
@@ -2795,7 +2812,7 @@ export async function createWorkspaceQuestion(options) {
|
|
|
2795
2812
|
rationale,
|
|
2796
2813
|
preferredSurfaces: options.provider === "claude"
|
|
2797
2814
|
? ["native_structured", "numbered"]
|
|
2798
|
-
: ["mcp_elicitation", "numbered"]
|
|
2815
|
+
: ["tmux_popup", "mcp_elicitation", "numbered"]
|
|
2799
2816
|
}
|
|
2800
2817
|
};
|
|
2801
2818
|
const updated = appendQuestionRecords(state, [question]);
|
|
@@ -2998,6 +3015,15 @@ function normalizeQuestionAnswerSelection(question, rawAnswer) {
|
|
|
2998
3015
|
...(inlineRationale ? { inlineRationale } : {})
|
|
2999
3016
|
};
|
|
3000
3017
|
}
|
|
3018
|
+
function selectedLabelsForValues(question, selectedValues) {
|
|
3019
|
+
return selectedValues.map((value) => {
|
|
3020
|
+
if (value === "other") {
|
|
3021
|
+
return question.prompt.otherLabel ?? "Other";
|
|
3022
|
+
}
|
|
3023
|
+
const option = question.prompt.options.find((candidate) => candidate.value === value);
|
|
3024
|
+
return option?.label ?? value;
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3001
3027
|
export async function answerWorkspaceQuestion(options) {
|
|
3002
3028
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
3003
3029
|
const question = findQuestionForDecision(state, options.questionId);
|
|
@@ -3036,7 +3062,12 @@ export async function answerWorkspaceQuestion(options) {
|
|
|
3036
3062
|
updatedAt: timestamp,
|
|
3037
3063
|
status: "answered",
|
|
3038
3064
|
answer,
|
|
3039
|
-
decisionRecordId: decision.id
|
|
3065
|
+
decisionRecordId: decision.id,
|
|
3066
|
+
transportStatus: {
|
|
3067
|
+
surface: answer.surface,
|
|
3068
|
+
status: "accepted",
|
|
3069
|
+
updatedAt: timestamp
|
|
3070
|
+
}
|
|
3040
3071
|
};
|
|
3041
3072
|
const withQuestion = {
|
|
3042
3073
|
...state,
|
|
@@ -3242,6 +3273,58 @@ export async function repairWorkspaceStateConsistency(options) {
|
|
|
3242
3273
|
})
|
|
3243
3274
|
};
|
|
3244
3275
|
}
|
|
3276
|
+
const repairTimestamp = nowIso();
|
|
3277
|
+
const repairedQuestionLog = (updated.questionLog ?? []).map((record) => {
|
|
3278
|
+
const answerRecord = asRecord(record.answer);
|
|
3279
|
+
if (!answerRecord) {
|
|
3280
|
+
return record;
|
|
3281
|
+
}
|
|
3282
|
+
const selectedValues = [
|
|
3283
|
+
...asStringArray(answerRecord.selectedValues),
|
|
3284
|
+
...asStringArray(answerRecord.selectedValue),
|
|
3285
|
+
...asStringArray(answerRecord.selectedOptions),
|
|
3286
|
+
...asStringArray(answerRecord.value)
|
|
3287
|
+
];
|
|
3288
|
+
if (selectedValues.length === 0) {
|
|
3289
|
+
return record;
|
|
3290
|
+
}
|
|
3291
|
+
const selectedLabels = asStringArray(answerRecord.selectedLabels);
|
|
3292
|
+
const needsRepair = typeof answerRecord.promptId !== "string" ||
|
|
3293
|
+
selectedLabels.length === 0 ||
|
|
3294
|
+
!isQuestionSurfaceValue(answerRecord.surface);
|
|
3295
|
+
if (!needsRepair) {
|
|
3296
|
+
return record;
|
|
3297
|
+
}
|
|
3298
|
+
repaired.push(`normalized legacy answer shape for question ${record.id}`);
|
|
3299
|
+
const normalizedAnswer = {
|
|
3300
|
+
promptId: typeof answerRecord.promptId === "string" ? answerRecord.promptId : record.prompt.id,
|
|
3301
|
+
selectedValues: uniqueStrings(selectedValues),
|
|
3302
|
+
selectedLabels: selectedLabels.length > 0
|
|
3303
|
+
? selectedLabels
|
|
3304
|
+
: selectedLabelsForValues(record, uniqueStrings(selectedValues)),
|
|
3305
|
+
...(typeof answerRecord.otherText === "string" && answerRecord.otherText.trim()
|
|
3306
|
+
? { otherText: answerRecord.otherText }
|
|
3307
|
+
: {}),
|
|
3308
|
+
...(typeof answerRecord.rationale === "string" && answerRecord.rationale.trim()
|
|
3309
|
+
? { rationale: answerRecord.rationale }
|
|
3310
|
+
: {}),
|
|
3311
|
+
...(answerRecord.provider === "codex" || answerRecord.provider === "claude"
|
|
3312
|
+
? { provider: answerRecord.provider }
|
|
3313
|
+
: {}),
|
|
3314
|
+
surface: isQuestionSurfaceValue(answerRecord.surface) ? answerRecord.surface : "numbered"
|
|
3315
|
+
};
|
|
3316
|
+
return {
|
|
3317
|
+
...record,
|
|
3318
|
+
updatedAt: repairTimestamp,
|
|
3319
|
+
answer: normalizedAnswer
|
|
3320
|
+
};
|
|
3321
|
+
});
|
|
3322
|
+
if (repairedQuestionLog.some((record, index) => record !== (updated.questionLog ?? [])[index])) {
|
|
3323
|
+
updated = {
|
|
3324
|
+
...updated,
|
|
3325
|
+
questionLog: repairedQuestionLog
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3245
3328
|
if (repaired.length > 0) {
|
|
3246
3329
|
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
3247
3330
|
await syncCurrentWorkspaceView(options.context);
|
package/dist/search/index.d.ts
CHANGED
package/dist/search/index.js
CHANGED