@longtable/cli 0.1.15 → 0.1.17
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 +34 -18
- package/dist/cli.js +526 -84
- package/dist/debate.d.ts +21 -0
- package/dist/debate.js +380 -0
- package/dist/panel.d.ts +1 -3
- package/dist/panel.js +4 -10
- package/dist/project-session.d.ts +0 -2
- package/dist/project-session.js +12 -10
- package/dist/prompt-aliases.js +1 -1
- package/package.json +8 -10
- package/scripts/postinstall.mjs +0 -85
package/dist/debate.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { InvocationIntent, InvocationRecord, PanelPlan, PanelVisibility, ProviderKind, QuestionRecord, TeamDebateRun } from "@longtable/core";
|
|
2
|
+
export interface BuildTeamDebateOptions {
|
|
3
|
+
teamId: string;
|
|
4
|
+
teamDir: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
roleFlag?: string;
|
|
7
|
+
provider?: ProviderKind;
|
|
8
|
+
visibility?: PanelVisibility;
|
|
9
|
+
roundCount?: number;
|
|
10
|
+
tmux?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface TeamDebateBundle {
|
|
13
|
+
plan: PanelPlan;
|
|
14
|
+
run: TeamDebateRun;
|
|
15
|
+
intent: InvocationIntent;
|
|
16
|
+
invocationRecord: InvocationRecord;
|
|
17
|
+
questionRecord: QuestionRecord;
|
|
18
|
+
}
|
|
19
|
+
export declare function createTeamDebateQuestionRecord(run: TeamDebateRun, provider?: ProviderKind): QuestionRecord;
|
|
20
|
+
export declare function buildTeamDebate(options: BuildTeamDebateOptions): TeamDebateBundle;
|
|
21
|
+
export declare function renderTeamDebateSummary(run: TeamDebateRun): string;
|
package/dist/debate.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { buildInvocationIntent, buildPanelPlan } from "./panel.js";
|
|
3
|
+
import { getPersonaDefinition, parsePersonaKey } from "./personas.js";
|
|
4
|
+
function nowIso() {
|
|
5
|
+
return new Date().toISOString();
|
|
6
|
+
}
|
|
7
|
+
function createId(prefix) {
|
|
8
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9
|
+
}
|
|
10
|
+
function signalTags(prompt) {
|
|
11
|
+
const normalized = prompt.toLowerCase();
|
|
12
|
+
const tags = [];
|
|
13
|
+
if (/\bmeasure|\bscale|\bvalid|reliab|측정|척도|타당|신뢰/.test(normalized))
|
|
14
|
+
tags.push("measurement");
|
|
15
|
+
if (/\btheor|\bconstruct|\bconcept|이론|개념|구성개념/.test(normalized))
|
|
16
|
+
tags.push("theory");
|
|
17
|
+
if (/\bmethod|\bdesign|\bsample|\banalysis|방법|설계|분석|표본/.test(normalized))
|
|
18
|
+
tags.push("method");
|
|
19
|
+
if (/\bevidence|\bcitation|\breference|근거|인용|문헌/.test(normalized))
|
|
20
|
+
tags.push("evidence");
|
|
21
|
+
if (/\bethic|\birb|consent|윤리|동의/.test(normalized))
|
|
22
|
+
tags.push("ethics");
|
|
23
|
+
if (/\bvoice|\bauthor|narrative|저자|서사|문체/.test(normalized))
|
|
24
|
+
tags.push("authorship");
|
|
25
|
+
return tags.length > 0 ? tags : ["general"];
|
|
26
|
+
}
|
|
27
|
+
function roleFocus(role) {
|
|
28
|
+
const key = parsePersonaKey(role);
|
|
29
|
+
return key ? getPersonaDefinition(key).shortDescription : "Inspect the research decision for hidden risk.";
|
|
30
|
+
}
|
|
31
|
+
function readableSignals(tags) {
|
|
32
|
+
return tags.join(", ");
|
|
33
|
+
}
|
|
34
|
+
function contribution(options) {
|
|
35
|
+
return {
|
|
36
|
+
id: createId("team_contribution"),
|
|
37
|
+
...options
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function independentContribution(roundId, plan, role, label, artifactPath) {
|
|
41
|
+
const tags = signalTags(plan.prompt);
|
|
42
|
+
return contribution({
|
|
43
|
+
roundId,
|
|
44
|
+
role,
|
|
45
|
+
label,
|
|
46
|
+
artifactPath,
|
|
47
|
+
summary: `${label} independently reviews the prompt for ${readableSignals(tags)} risks before any shared synthesis.`,
|
|
48
|
+
claims: [
|
|
49
|
+
`${label} should preserve a separate judgment lane rather than accepting the first synthesis.`,
|
|
50
|
+
`Primary role focus: ${roleFocus(role)}`
|
|
51
|
+
],
|
|
52
|
+
objections: [
|
|
53
|
+
"The prompt may hide a commitment that has not yet been named by the researcher.",
|
|
54
|
+
"A fluent answer could collapse measurement, theory, evidence, and authorship concerns into one premature recommendation."
|
|
55
|
+
],
|
|
56
|
+
openQuestions: [
|
|
57
|
+
"Which decision would become hard to reverse if LongTable proceeded now?",
|
|
58
|
+
"What evidence or researcher preference is still missing?"
|
|
59
|
+
],
|
|
60
|
+
evidenceNeeds: [
|
|
61
|
+
"Local project state, manuscript/data references, or cited literature should be checked before closure.",
|
|
62
|
+
"If the prompt makes an external claim, attach source links or mark the claim as inference."
|
|
63
|
+
],
|
|
64
|
+
tacitAssumptions: [
|
|
65
|
+
"The researcher may know constraints not present in the prompt.",
|
|
66
|
+
"The role's critique may over-weight its own domain unless challenged by other roles."
|
|
67
|
+
],
|
|
68
|
+
checkpointTriggers: tags.map((tag) => `${tag}_commitment`)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function crossReviewContribution(roundId, plan, role, label, targetRole, targetLabel, artifactPath) {
|
|
72
|
+
return contribution({
|
|
73
|
+
roundId,
|
|
74
|
+
role,
|
|
75
|
+
label,
|
|
76
|
+
targetRole,
|
|
77
|
+
artifactPath,
|
|
78
|
+
summary: `${label} challenges ${targetLabel}'s likely blind spot before synthesis.`,
|
|
79
|
+
claims: [
|
|
80
|
+
`${targetLabel}'s concern is useful only if it does not erase ${label}'s domain-specific risk.`,
|
|
81
|
+
"The debate should expose disagreement as a researcher decision point rather than normalize it away."
|
|
82
|
+
],
|
|
83
|
+
objections: [
|
|
84
|
+
`${targetLabel} may be treating its own role priority as the main research problem.`,
|
|
85
|
+
"The prompt still needs a concrete next decision, not only a list of concerns."
|
|
86
|
+
],
|
|
87
|
+
openQuestions: [
|
|
88
|
+
`What would make ${targetLabel}'s objection decisive rather than advisory?`,
|
|
89
|
+
`What must the researcher answer before ${label} can accept ${targetLabel}'s framing?`
|
|
90
|
+
],
|
|
91
|
+
evidenceNeeds: [
|
|
92
|
+
"Compare role objections against project state and available artifacts.",
|
|
93
|
+
"Separate source-backed objections from inferred risk."
|
|
94
|
+
],
|
|
95
|
+
tacitAssumptions: [
|
|
96
|
+
"Roles may disagree because they are protecting different commitments.",
|
|
97
|
+
"The absence of evidence in the prompt is not evidence that the researcher lacks it."
|
|
98
|
+
],
|
|
99
|
+
checkpointTriggers: ["role_disagreement", "unresolved_gap"]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function rebuttalContribution(roundId, role, label, artifactPath) {
|
|
103
|
+
return contribution({
|
|
104
|
+
roundId,
|
|
105
|
+
role,
|
|
106
|
+
label,
|
|
107
|
+
artifactPath,
|
|
108
|
+
summary: `${label} revises its position after cross-review while preserving unresolved risk.`,
|
|
109
|
+
claims: [
|
|
110
|
+
"Some objections should be accepted as constraints, not treated as blockers.",
|
|
111
|
+
"The final synthesis should distinguish actionable next steps from background concern."
|
|
112
|
+
],
|
|
113
|
+
objections: [
|
|
114
|
+
"A role may over-correct after critique and lose the original high-stakes warning.",
|
|
115
|
+
"Convergence without a checkpoint would turn debate into hidden automation."
|
|
116
|
+
],
|
|
117
|
+
openQuestions: [
|
|
118
|
+
"Which objection changes the next action?",
|
|
119
|
+
"Which unresolved disagreement should remain visible to the researcher?"
|
|
120
|
+
],
|
|
121
|
+
evidenceNeeds: [
|
|
122
|
+
"Record which objections were accepted, rejected, or deferred.",
|
|
123
|
+
"Preserve references needed to verify the most consequential claim."
|
|
124
|
+
],
|
|
125
|
+
tacitAssumptions: [
|
|
126
|
+
"A clean synthesis may be less honest than a visible unresolved tension.",
|
|
127
|
+
"The researcher should own the final prioritization."
|
|
128
|
+
],
|
|
129
|
+
checkpointTriggers: ["synthesis_boundary", "researcher_authority"]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function convergenceContribution(roundId, plan, role, label, artifactPath) {
|
|
133
|
+
const otherRoles = plan.members.filter((member) => member.role !== role).map((member) => member.label);
|
|
134
|
+
return contribution({
|
|
135
|
+
roundId,
|
|
136
|
+
role,
|
|
137
|
+
label,
|
|
138
|
+
artifactPath,
|
|
139
|
+
summary: `${label} states what it can accept and what must remain open.`,
|
|
140
|
+
claims: [
|
|
141
|
+
`Can accept synthesis if it preserves ${label}'s domain warning.`,
|
|
142
|
+
`Must still show disagreement with: ${otherRoles.join(", ") || "no other roles"}.`
|
|
143
|
+
],
|
|
144
|
+
objections: [
|
|
145
|
+
"Do not convert unresolved disagreement into a single confident recommendation.",
|
|
146
|
+
"Do not let the coordinator answer the checkpoint on behalf of the researcher."
|
|
147
|
+
],
|
|
148
|
+
openQuestions: [
|
|
149
|
+
"Which path should LongTable recommend as the next researcher decision?",
|
|
150
|
+
"Which issue should be logged as an open tension if not answered now?"
|
|
151
|
+
],
|
|
152
|
+
evidenceNeeds: [
|
|
153
|
+
"Link the synthesis back to role contributions and local artifacts.",
|
|
154
|
+
"Mark any missing sources as evidence gaps."
|
|
155
|
+
],
|
|
156
|
+
tacitAssumptions: [
|
|
157
|
+
"A role's agreement may be conditional.",
|
|
158
|
+
"The researcher may choose to proceed despite unresolved risk."
|
|
159
|
+
],
|
|
160
|
+
checkpointTriggers: ["panel_next_decision"]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function buildSynthesis(plan, artifactPath) {
|
|
164
|
+
const labels = plan.members.map((member) => member.label);
|
|
165
|
+
const highSensitivity = plan.checkpointSensitivity === "high";
|
|
166
|
+
return {
|
|
167
|
+
artifactPath,
|
|
168
|
+
summary: `The debate completed fixed 5-round review across ${labels.join(", ")}. It should slow closure by turning role disagreement into an explicit researcher decision.`,
|
|
169
|
+
consensus: [
|
|
170
|
+
"The researcher should see role disagreement before LongTable drafts, commits, or submits a conclusion.",
|
|
171
|
+
"Evidence gaps and tacit assumptions should remain visible instead of being smoothed into fluent prose."
|
|
172
|
+
],
|
|
173
|
+
disagreements: [
|
|
174
|
+
"Roles may prioritize different first moves: theory framing, method defensibility, measurement validity, evidence verification, or authorship trace.",
|
|
175
|
+
"The coordinator should not decide which role wins without a researcher checkpoint."
|
|
176
|
+
],
|
|
177
|
+
unresolvedGaps: [
|
|
178
|
+
"Which role concern is decisive for the next action?",
|
|
179
|
+
"What source, data, or local project artifact should be checked before closure?"
|
|
180
|
+
],
|
|
181
|
+
researcherDecisionPoints: [
|
|
182
|
+
"Prioritize revision, evidence gathering, proceeding with risk, or keeping the issue open.",
|
|
183
|
+
"Choose whether the debate should affect the current artifact, the research design, or only the decision log."
|
|
184
|
+
],
|
|
185
|
+
recommendedCheckpoint: highSensitivity
|
|
186
|
+
? "The debate surfaced high-sensitivity disagreement. What should LongTable treat as the next human decision before closure?"
|
|
187
|
+
: "The debate surfaced role disagreement. Should LongTable revise, verify evidence, proceed, or keep the tension open?"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function createTeamDebateQuestionRecord(run, provider) {
|
|
191
|
+
const createdAt = nowIso();
|
|
192
|
+
return {
|
|
193
|
+
id: createId("question_record"),
|
|
194
|
+
createdAt,
|
|
195
|
+
updatedAt: createdAt,
|
|
196
|
+
status: "pending",
|
|
197
|
+
prompt: {
|
|
198
|
+
id: createId("question_prompt"),
|
|
199
|
+
checkpointKey: "team_debate_next_decision",
|
|
200
|
+
title: "Team debate follow-up decision",
|
|
201
|
+
question: run.synthesis.recommendedCheckpoint,
|
|
202
|
+
type: "single_choice",
|
|
203
|
+
options: [
|
|
204
|
+
{
|
|
205
|
+
value: "revise",
|
|
206
|
+
label: "Revise before proceeding",
|
|
207
|
+
description: "Use the debate result to revise the claim, design, or draft first."
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
value: "evidence",
|
|
211
|
+
label: "Gather or verify evidence first",
|
|
212
|
+
description: "Check source, data, or local artifact support before proceeding."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
value: "proceed",
|
|
216
|
+
label: "Proceed with current direction",
|
|
217
|
+
description: "Accept the risk profile and continue with the current direction."
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
value: "defer",
|
|
221
|
+
label: "Keep this open",
|
|
222
|
+
description: "Do not commit yet; keep the debate issue visible as an open tension."
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
allowOther: true,
|
|
226
|
+
otherLabel: "Other decision",
|
|
227
|
+
required: run.roles.some((member) => {
|
|
228
|
+
const key = parsePersonaKey(member.role);
|
|
229
|
+
return key ? getPersonaDefinition(key).checkpointSensitivity === "high" : false;
|
|
230
|
+
}),
|
|
231
|
+
source: "runtime_guidance",
|
|
232
|
+
rationale: [
|
|
233
|
+
"Autonomous team debate is a research harness surface, not a substitute for researcher judgment.",
|
|
234
|
+
"The fixed debate rounds created disagreement that should connect to an explicit researcher decision.",
|
|
235
|
+
`Team debate run: ${run.id}.`
|
|
236
|
+
],
|
|
237
|
+
preferredSurfaces: provider === "claude"
|
|
238
|
+
? ["native_structured", "numbered"]
|
|
239
|
+
: ["numbered", "native_structured"]
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export function buildTeamDebate(options) {
|
|
244
|
+
const roundCount = options.roundCount ?? 5;
|
|
245
|
+
if (roundCount !== 5) {
|
|
246
|
+
throw new Error("LongTable debate v1 supports fixed 5-round debate only.");
|
|
247
|
+
}
|
|
248
|
+
const createdAt = nowIso();
|
|
249
|
+
const plan = buildPanelPlan({
|
|
250
|
+
prompt: options.prompt,
|
|
251
|
+
mode: "review",
|
|
252
|
+
roleFlag: options.roleFlag,
|
|
253
|
+
provider: options.provider,
|
|
254
|
+
visibility: options.visibility ?? "always_visible"
|
|
255
|
+
});
|
|
256
|
+
const rounds = [];
|
|
257
|
+
const round1Id = createId("team_round");
|
|
258
|
+
rounds.push({
|
|
259
|
+
id: round1Id,
|
|
260
|
+
index: 1,
|
|
261
|
+
kind: "independent_review",
|
|
262
|
+
title: "Independent review",
|
|
263
|
+
status: "completed",
|
|
264
|
+
artifactDir: join(options.teamDir, "round-1-independent"),
|
|
265
|
+
contributions: plan.members.map((member) => independentContribution(round1Id, plan, member.role, member.label, join("round-1-independent", `${member.role}.json`)))
|
|
266
|
+
});
|
|
267
|
+
const round2Id = createId("team_round");
|
|
268
|
+
const crossContributions = plan.members.flatMap((member) => plan.members
|
|
269
|
+
.filter((target) => target.role !== member.role)
|
|
270
|
+
.map((target) => crossReviewContribution(round2Id, plan, member.role, member.label, target.role, target.label, join("round-2-cross-review", `${member.role}-on-${target.role}.json`))));
|
|
271
|
+
rounds.push({
|
|
272
|
+
id: round2Id,
|
|
273
|
+
index: 2,
|
|
274
|
+
kind: "cross_review",
|
|
275
|
+
title: "Cross-review",
|
|
276
|
+
status: "completed",
|
|
277
|
+
artifactDir: join(options.teamDir, "round-2-cross-review"),
|
|
278
|
+
contributions: crossContributions
|
|
279
|
+
});
|
|
280
|
+
const round3Id = createId("team_round");
|
|
281
|
+
rounds.push({
|
|
282
|
+
id: round3Id,
|
|
283
|
+
index: 3,
|
|
284
|
+
kind: "rebuttal",
|
|
285
|
+
title: "Rebuttal and revision",
|
|
286
|
+
status: "completed",
|
|
287
|
+
artifactDir: join(options.teamDir, "round-3-rebuttal"),
|
|
288
|
+
contributions: plan.members.map((member) => rebuttalContribution(round3Id, member.role, member.label, join("round-3-rebuttal", `${member.role}.json`)))
|
|
289
|
+
});
|
|
290
|
+
const round4Id = createId("team_round");
|
|
291
|
+
rounds.push({
|
|
292
|
+
id: round4Id,
|
|
293
|
+
index: 4,
|
|
294
|
+
kind: "convergence",
|
|
295
|
+
title: "Convergence and unresolved gaps",
|
|
296
|
+
status: "completed",
|
|
297
|
+
artifactDir: join(options.teamDir, "round-4-convergence"),
|
|
298
|
+
contributions: plan.members.map((member) => convergenceContribution(round4Id, plan, member.role, member.label, join("round-4-convergence", `${member.role}.json`)))
|
|
299
|
+
});
|
|
300
|
+
const synthesis = buildSynthesis(plan, "synthesis.json");
|
|
301
|
+
const run = {
|
|
302
|
+
id: createId("team_debate_run"),
|
|
303
|
+
teamId: options.teamId,
|
|
304
|
+
createdAt,
|
|
305
|
+
updatedAt: createdAt,
|
|
306
|
+
prompt: options.prompt,
|
|
307
|
+
roles: plan.members,
|
|
308
|
+
status: "completed",
|
|
309
|
+
surface: options.tmux ? "tmux_console" : "file_backed_debate",
|
|
310
|
+
roundPolicy: "fixed",
|
|
311
|
+
roundCount,
|
|
312
|
+
artifactRoot: options.teamDir,
|
|
313
|
+
rounds: [
|
|
314
|
+
...rounds,
|
|
315
|
+
{
|
|
316
|
+
id: createId("team_round"),
|
|
317
|
+
index: 5,
|
|
318
|
+
kind: "synthesis",
|
|
319
|
+
title: "Coordinator synthesis and checkpoint",
|
|
320
|
+
status: "completed",
|
|
321
|
+
artifactDir: options.teamDir,
|
|
322
|
+
contributions: []
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
synthesis,
|
|
326
|
+
linkedQuestionRecordIds: []
|
|
327
|
+
};
|
|
328
|
+
const questionRecord = createTeamDebateQuestionRecord(run, options.provider);
|
|
329
|
+
run.linkedQuestionRecordIds = [questionRecord.id];
|
|
330
|
+
const intent = buildInvocationIntent({
|
|
331
|
+
prompt: options.prompt,
|
|
332
|
+
mode: "review",
|
|
333
|
+
roles: plan.members.map((member) => member.role),
|
|
334
|
+
provider: options.provider,
|
|
335
|
+
visibility: plan.visibility,
|
|
336
|
+
checkpointSensitivity: plan.checkpointSensitivity,
|
|
337
|
+
rationale: [
|
|
338
|
+
"Autonomous debate requested through LongTable team orchestration.",
|
|
339
|
+
"File-backed fixed rounds keep disagreement inspectable before researcher closure."
|
|
340
|
+
]
|
|
341
|
+
});
|
|
342
|
+
intent.kind = "team_debate";
|
|
343
|
+
intent.requestedSurface = run.surface;
|
|
344
|
+
const invocationRecord = {
|
|
345
|
+
id: createId("invocation_record"),
|
|
346
|
+
createdAt,
|
|
347
|
+
updatedAt: createdAt,
|
|
348
|
+
intent,
|
|
349
|
+
status: "completed",
|
|
350
|
+
provider: options.provider,
|
|
351
|
+
surface: run.surface,
|
|
352
|
+
panelPlan: plan,
|
|
353
|
+
teamDebateRun: run,
|
|
354
|
+
degradationReason: options.tmux
|
|
355
|
+
? undefined
|
|
356
|
+
: "Tmux was not requested; file-backed debate artifacts are the canonical execution record."
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
plan,
|
|
360
|
+
run,
|
|
361
|
+
intent,
|
|
362
|
+
invocationRecord,
|
|
363
|
+
questionRecord
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
export function renderTeamDebateSummary(run) {
|
|
367
|
+
return [
|
|
368
|
+
"LongTable Team Debate",
|
|
369
|
+
`- team: ${run.teamId}`,
|
|
370
|
+
`- surface: ${run.surface}`,
|
|
371
|
+
`- rounds: ${run.roundCount} fixed`,
|
|
372
|
+
`- roles: ${run.roles.map((role) => `${role.label} (${role.role})`).join(", ")}`,
|
|
373
|
+
`- artifacts: ${run.artifactRoot}`,
|
|
374
|
+
"",
|
|
375
|
+
run.synthesis.summary,
|
|
376
|
+
"",
|
|
377
|
+
"Researcher checkpoint:",
|
|
378
|
+
`- ${run.synthesis.recommendedCheckpoint}`
|
|
379
|
+
].join("\n");
|
|
380
|
+
}
|
package/dist/panel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CheckpointSensitivity, InteractionMode, InvocationIntent,
|
|
1
|
+
import type { CheckpointSensitivity, InteractionMode, InvocationIntent, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, RoleKey } from "@longtable/core";
|
|
2
2
|
import { type CanonicalPersona } from "./personas.js";
|
|
3
3
|
export interface BuildPanelPlanOptions {
|
|
4
4
|
prompt: string;
|
|
@@ -7,7 +7,6 @@ export interface BuildPanelPlanOptions {
|
|
|
7
7
|
roles?: CanonicalPersona[];
|
|
8
8
|
provider?: ProviderKind;
|
|
9
9
|
visibility?: PanelVisibility;
|
|
10
|
-
agentTeamPreference?: "native_when_available" | "sequential_panel_only";
|
|
11
10
|
}
|
|
12
11
|
export interface PanelFallback {
|
|
13
12
|
intent: InvocationIntent;
|
|
@@ -23,7 +22,6 @@ export declare function buildInvocationIntent(options: {
|
|
|
23
22
|
mode?: InteractionMode;
|
|
24
23
|
roles: RoleKey[];
|
|
25
24
|
provider?: ProviderKind;
|
|
26
|
-
requestedSurface?: InvocationSurface;
|
|
27
25
|
visibility?: PanelVisibility;
|
|
28
26
|
checkpointSensitivity?: CheckpointSensitivity;
|
|
29
27
|
rationale?: string[];
|
package/dist/panel.js
CHANGED
|
@@ -67,9 +67,6 @@ export function buildPanelPlan(options) {
|
|
|
67
67
|
const routedRoles = routePersonas(options.prompt).consultedRoles;
|
|
68
68
|
const roles = resolvePanelRoles(options);
|
|
69
69
|
const createdAt = nowIso();
|
|
70
|
-
const preferredSurface = options.agentTeamPreference === "native_when_available"
|
|
71
|
-
? "native_parallel"
|
|
72
|
-
: "sequential_fallback";
|
|
73
70
|
return {
|
|
74
71
|
id: createId("panel_plan"),
|
|
75
72
|
createdAt,
|
|
@@ -77,14 +74,12 @@ export function buildPanelPlan(options) {
|
|
|
77
74
|
prompt: options.prompt,
|
|
78
75
|
members: roles.map((role) => memberForRole(role, explicitRoles, routedRoles)),
|
|
79
76
|
visibility: options.visibility ?? "always_visible",
|
|
80
|
-
preferredSurface,
|
|
77
|
+
preferredSurface: "sequential_fallback",
|
|
81
78
|
fallbackSurface: "sequential_fallback",
|
|
82
79
|
checkpointSensitivity: highestSensitivity(roles),
|
|
83
80
|
rationale: [
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
: "Setup prefers LongTable's provider-neutral sequential panel surface.",
|
|
87
|
-
"Sequential fallback remains the stable execution path for both Claude Code and Codex.",
|
|
81
|
+
"Option A uses provider-neutral panel semantics before native provider orchestration.",
|
|
82
|
+
"Sequential fallback is the stable execution path for both Claude Code and Codex.",
|
|
88
83
|
roles.length === explicitRoles.length && explicitRoles.length > 0
|
|
89
84
|
? "The panel is constrained by explicitly requested roles."
|
|
90
85
|
: "The panel combines default research-review roles with prompt-triggered roles."
|
|
@@ -99,7 +94,7 @@ export function buildInvocationIntent(options) {
|
|
|
99
94
|
prompt: options.prompt,
|
|
100
95
|
roles: options.roles,
|
|
101
96
|
provider: options.provider,
|
|
102
|
-
requestedSurface:
|
|
97
|
+
requestedSurface: "sequential_fallback",
|
|
103
98
|
visibility: options.visibility ?? "always_visible",
|
|
104
99
|
checkpointSensitivity: options.checkpointSensitivity ?? "medium",
|
|
105
100
|
rationale: options.rationale ?? ["Panel invocation requested."]
|
|
@@ -235,7 +230,6 @@ export function buildPanelFallback(options) {
|
|
|
235
230
|
mode: plan.mode,
|
|
236
231
|
roles: plan.members.map((member) => member.role),
|
|
237
232
|
provider: options.provider,
|
|
238
|
-
requestedSurface: plan.preferredSurface,
|
|
239
233
|
visibility: plan.visibility,
|
|
240
234
|
checkpointSensitivity: plan.checkpointSensitivity,
|
|
241
235
|
rationale: plan.rationale
|
|
@@ -17,8 +17,6 @@ export interface LongTableProjectRecord {
|
|
|
17
17
|
humanAuthorshipSignal?: string;
|
|
18
18
|
weakestDomain?: string;
|
|
19
19
|
defaultPanelPreference?: ProjectDisagreementPreference;
|
|
20
|
-
agentTeamPreference?: string;
|
|
21
|
-
mcpPreference?: string;
|
|
22
20
|
};
|
|
23
21
|
}
|
|
24
22
|
export interface LongTableSessionRecord {
|
package/dist/project-session.js
CHANGED
|
@@ -893,6 +893,7 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
893
893
|
const project = existsSync(projectFilePath)
|
|
894
894
|
? {
|
|
895
895
|
...JSON.parse(await readFile(projectFilePath, "utf8")),
|
|
896
|
+
projectPath,
|
|
896
897
|
contractVersion: "workspace-v2",
|
|
897
898
|
locale
|
|
898
899
|
}
|
|
@@ -917,12 +918,6 @@ export async function createOrUpdateProjectWorkspace(options) {
|
|
|
917
918
|
: {}),
|
|
918
919
|
...(options.setup.profileSeed.panelPreference
|
|
919
920
|
? { defaultPanelPreference: options.setup.profileSeed.panelPreference }
|
|
920
|
-
: {}),
|
|
921
|
-
...(options.setup.profileSeed.agentTeamPreference
|
|
922
|
-
? { agentTeamPreference: options.setup.profileSeed.agentTeamPreference }
|
|
923
|
-
: {}),
|
|
924
|
-
...(options.setup.profileSeed.mcpPreference
|
|
925
|
-
? { mcpPreference: options.setup.profileSeed.mcpPreference }
|
|
926
921
|
: {})
|
|
927
922
|
}
|
|
928
923
|
};
|
|
@@ -998,8 +993,15 @@ export async function loadProjectContextFromDirectory(startPath) {
|
|
|
998
993
|
const projectFilePath = join(metaDir, "project.json");
|
|
999
994
|
const sessionFilePath = join(metaDir, "current-session.json");
|
|
1000
995
|
if (existsSync(projectFilePath) && existsSync(sessionFilePath)) {
|
|
1001
|
-
const
|
|
1002
|
-
const
|
|
996
|
+
const workspaceRoot = current;
|
|
997
|
+
const project = {
|
|
998
|
+
...JSON.parse(await readFile(projectFilePath, "utf8")),
|
|
999
|
+
projectPath: workspaceRoot
|
|
1000
|
+
};
|
|
1001
|
+
const session = {
|
|
1002
|
+
...JSON.parse(await readFile(sessionFilePath, "utf8")),
|
|
1003
|
+
projectPath: workspaceRoot
|
|
1004
|
+
};
|
|
1003
1005
|
return {
|
|
1004
1006
|
project,
|
|
1005
1007
|
session: {
|
|
@@ -1012,8 +1014,8 @@ export async function loadProjectContextFromDirectory(startPath) {
|
|
|
1012
1014
|
},
|
|
1013
1015
|
projectFilePath,
|
|
1014
1016
|
sessionFilePath,
|
|
1015
|
-
stateFilePath: resolveStateFilePath(
|
|
1016
|
-
currentFilePath: resolveCurrentFilePath(
|
|
1017
|
+
stateFilePath: resolveStateFilePath(workspaceRoot),
|
|
1018
|
+
currentFilePath: resolveCurrentFilePath(workspaceRoot),
|
|
1017
1019
|
metaDir
|
|
1018
1020
|
};
|
|
1019
1021
|
}
|
package/dist/prompt-aliases.js
CHANGED
|
@@ -35,7 +35,7 @@ function promptSpec() {
|
|
|
35
35
|
"Do not move to the next question until the researcher answers the current one.",
|
|
36
36
|
"Quickstart covers: provider, field, career stage, experience level, checkpoint intensity, and human authorship signal.",
|
|
37
37
|
"Interview also covers: preferred entry mode, weakest domain, and panel visibility preference.",
|
|
38
|
-
"After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-skills` command and 2) a strict JSON object with keys provider, flow, field, careerStage, experienceLevel, preferredCheckpointIntensity, and optional humanAuthorshipSignal, preferredEntryMode, weakestDomain, panelPreference
|
|
38
|
+
"After collecting all answers, summarize the proposed setup and then output both: 1) the exact `longtable codex persist-init ... --install-skills` command and 2) a strict JSON object with keys provider, flow, field, careerStage, experienceLevel, preferredCheckpointIntensity, and optional humanAuthorshipSignal, preferredEntryMode, weakestDomain, panelPreference.",
|
|
39
39
|
"If the user prefers paste-based setup, tell them they can pipe the JSON into `longtable codex persist-init --stdin --install-skills`.",
|
|
40
40
|
"If the researcher asks you to stay inside Codex, keep the conversation in numbered form and do not prematurely close.",
|
|
41
41
|
"Frame the setup like a short researcher interview, not a bare config form.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
|
-
"longtable": "
|
|
16
|
+
"longtable": "bin/longtable"
|
|
17
17
|
},
|
|
18
18
|
"directories": {
|
|
19
19
|
"bin": "./bin"
|
|
@@ -21,21 +21,19 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
23
|
"bin",
|
|
24
|
-
"scripts/postinstall.mjs",
|
|
25
24
|
"README.md"
|
|
26
25
|
],
|
|
27
26
|
"scripts": {
|
|
28
|
-
"postinstall": "node ./scripts/postinstall.mjs",
|
|
29
27
|
"build": "tsc -p tsconfig.json",
|
|
30
28
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
31
29
|
},
|
|
32
30
|
"dependencies": {
|
|
33
|
-
"@longtable/checkpoints": "0.1.
|
|
34
|
-
"@longtable/core": "0.1.
|
|
35
|
-
"@longtable/memory": "0.1.
|
|
36
|
-
"@longtable/provider-claude": "0.1.
|
|
37
|
-
"@longtable/provider-codex": "0.1.
|
|
38
|
-
"@longtable/setup": "0.1.
|
|
31
|
+
"@longtable/checkpoints": "0.1.17",
|
|
32
|
+
"@longtable/core": "0.1.17",
|
|
33
|
+
"@longtable/memory": "0.1.17",
|
|
34
|
+
"@longtable/provider-claude": "0.1.17",
|
|
35
|
+
"@longtable/provider-codex": "0.1.17",
|
|
36
|
+
"@longtable/setup": "0.1.17"
|
|
39
37
|
},
|
|
40
38
|
"devDependencies": {
|
|
41
39
|
"@types/node": "^22.10.1",
|
package/scripts/postinstall.mjs
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
function envFlag(name) {
|
|
8
|
-
const value = process.env[name];
|
|
9
|
-
if (!value) return false;
|
|
10
|
-
return !["0", "false", "no", "off"].includes(value.toLowerCase());
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function isGlobalInstall() {
|
|
14
|
-
return process.env.npm_config_global === "true" || process.env.npm_config_location === "global";
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function commandOnPath(command) {
|
|
18
|
-
try {
|
|
19
|
-
execSync(`command -v ${command}`, { stdio: "ignore" });
|
|
20
|
-
return true;
|
|
21
|
-
} catch {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function requestedProviders() {
|
|
27
|
-
const raw = process.env.LONGTABLE_POSTINSTALL_PROVIDERS?.trim().toLowerCase();
|
|
28
|
-
if (!raw || raw === "auto") {
|
|
29
|
-
const providers = ["codex"];
|
|
30
|
-
if (commandOnPath("claude")) providers.push("claude");
|
|
31
|
-
return providers;
|
|
32
|
-
}
|
|
33
|
-
if (raw === "all") return ["codex", "claude"];
|
|
34
|
-
return raw
|
|
35
|
-
.split(",")
|
|
36
|
-
.map((entry) => entry.trim())
|
|
37
|
-
.filter((entry) => entry === "codex" || entry === "claude");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main() {
|
|
41
|
-
if (!isGlobalInstall() || envFlag("LONGTABLE_SKIP_POSTINSTALL")) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const distEntrypoint = fileURLToPath(new URL("../dist/personas.js", import.meta.url));
|
|
46
|
-
if (!existsSync(distEntrypoint)) {
|
|
47
|
-
console.warn("[longtable] Skipped provider skill bootstrap because package build output was not found.");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const providers = requestedProviders();
|
|
52
|
-
if (providers.length === 0) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const { listRoleDefinitions } = await import("../dist/personas.js");
|
|
57
|
-
const roles = listRoleDefinitions();
|
|
58
|
-
const installed = [];
|
|
59
|
-
|
|
60
|
-
if (providers.includes("codex")) {
|
|
61
|
-
const { installCodexSkills, resolveCodexSkillsDir } = await import("@longtable/provider-codex");
|
|
62
|
-
const skills = await installCodexSkills(roles);
|
|
63
|
-
installed.push(`Codex skills: ${skills.length} (${resolveCodexSkillsDir()})`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (providers.includes("claude")) {
|
|
67
|
-
const { installClaudeSkills, resolveClaudeSkillsDir } = await import("@longtable/provider-claude");
|
|
68
|
-
const skills = await installClaudeSkills(roles);
|
|
69
|
-
installed.push(`Claude skills: ${skills.length} (${resolveClaudeSkillsDir()})`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (installed.length > 0) {
|
|
73
|
-
console.log("[longtable] Provider skills installed during global npm install:");
|
|
74
|
-
for (const line of installed) {
|
|
75
|
-
console.log(`[longtable] - ${line}`);
|
|
76
|
-
}
|
|
77
|
-
console.log("[longtable] Run `longtable doctor` to verify setup.");
|
|
78
|
-
console.log("[longtable] MCP config is opt-in: run `longtable mcp install --provider codex --write` when you want provider config files updated.");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
main().catch((error) => {
|
|
83
|
-
console.warn(`[longtable] Postinstall provider skill bootstrap failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
84
|
-
console.warn("[longtable] You can repair this later with `longtable doctor --fix`.");
|
|
85
|
-
});
|