@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.
@@ -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, InvocationSurface, InvocationRecord, PanelPlan, PanelResult, PanelVisibility, QuestionRecord, ProviderKind, RoleKey } from "@longtable/core";
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
- preferredSurface === "native_parallel"
85
- ? "Setup prefers native provider team/subagent surfaces when available."
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: options.requestedSurface ?? "sequential_fallback",
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 {
@@ -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 project = JSON.parse(await readFile(projectFilePath, "utf8"));
1002
- const session = JSON.parse(await readFile(sessionFilePath, "utf8"));
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(project.projectPath),
1016
- currentFilePath: resolveCurrentFilePath(project.projectPath),
1017
+ stateFilePath: resolveStateFilePath(workspaceRoot),
1018
+ currentFilePath: resolveCurrentFilePath(workspaceRoot),
1017
1019
  metaDir
1018
1020
  };
1019
1021
  }
@@ -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, agentTeamPreference, mcpPreference.",
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.15",
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": "./bin/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.15",
34
- "@longtable/core": "0.1.15",
35
- "@longtable/memory": "0.1.15",
36
- "@longtable/provider-claude": "0.1.15",
37
- "@longtable/provider-codex": "0.1.15",
38
- "@longtable/setup": "0.1.15"
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",
@@ -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
- });