@longtable/cli 0.1.55 → 0.1.56
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 +326 -20
- package/dist/panel-runtime.d.ts +20 -0
- package/dist/panel-runtime.js +525 -0
- package/dist/panel.d.ts +4 -1
- package/dist/panel.js +34 -7
- package/dist/project-session.d.ts +35 -1
- package/dist/project-session.js +310 -1
- package/package.json +7 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, HardStopScope, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionPromptType, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationReadiness, ResearchSpecificationRevision, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, EvidenceRecord, InvocationRecord, InvocationStatus, InvocationSurface, LongTableQuestionObligation, PanelMemberResult, ProviderKind, QuestionOption, HardStopScope, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionPromptType, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationReadiness, ResearchSpecificationRevision, ResearchState } from "@longtable/core";
|
|
2
2
|
import type { SetupPersistedOutput } from "@longtable/setup";
|
|
3
3
|
import { type HardStopVerdict } from "@longtable/core";
|
|
4
4
|
export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
@@ -269,6 +269,32 @@ export interface LongTableWorkspaceInspection {
|
|
|
269
269
|
suggestion?: string;
|
|
270
270
|
}>;
|
|
271
271
|
}
|
|
272
|
+
export interface PanelResultRecordInput {
|
|
273
|
+
invocationId?: string;
|
|
274
|
+
status?: InvocationStatus;
|
|
275
|
+
surface?: InvocationSurface;
|
|
276
|
+
synthesis?: string;
|
|
277
|
+
conflictSummary?: string;
|
|
278
|
+
decisionPrompt?: string;
|
|
279
|
+
memberResults?: Array<Partial<PanelMemberResult> & {
|
|
280
|
+
role: string;
|
|
281
|
+
}>;
|
|
282
|
+
}
|
|
283
|
+
export interface PanelResultRecordOutput {
|
|
284
|
+
invocation: InvocationRecord;
|
|
285
|
+
evidenceRecords: EvidenceRecord[];
|
|
286
|
+
state: LongTableWorkspaceState;
|
|
287
|
+
}
|
|
288
|
+
export interface LongTableHandoffOutput {
|
|
289
|
+
id: string;
|
|
290
|
+
createdAt: string;
|
|
291
|
+
path: string;
|
|
292
|
+
content: string;
|
|
293
|
+
sourceEvidenceIds: string[];
|
|
294
|
+
pendingQuestionIds: string[];
|
|
295
|
+
proposedPatchIds: string[];
|
|
296
|
+
latestInvocationId?: string;
|
|
297
|
+
}
|
|
272
298
|
export declare function loadWorkspaceState(context: LongTableProjectContext): Promise<LongTableWorkspaceState>;
|
|
273
299
|
export declare function diffResearchSpecifications(before: ResearchSpecification | undefined, after: ResearchSpecification): ResearchSpecificationChange[];
|
|
274
300
|
export declare function applyResearchSpecificationAuditUpdate(state: LongTableWorkspaceState, options: {
|
|
@@ -291,6 +317,14 @@ export declare function applyResearchSpecificationAuditUpdate(state: LongTableWo
|
|
|
291
317
|
};
|
|
292
318
|
export declare function syncCurrentWorkspaceView(context: LongTableProjectContext): Promise<string>;
|
|
293
319
|
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<LongTableWorkspaceState>;
|
|
320
|
+
export declare function recordPanelResultInWorkspace(options: {
|
|
321
|
+
context: LongTableProjectContext;
|
|
322
|
+
result: PanelResultRecordInput;
|
|
323
|
+
}): Promise<PanelResultRecordOutput>;
|
|
324
|
+
export declare function createWorkspaceHandoff(options: {
|
|
325
|
+
context: LongTableProjectContext;
|
|
326
|
+
outputPath?: string;
|
|
327
|
+
}): Promise<LongTableHandoffOutput>;
|
|
294
328
|
export declare function beginLongTableInterview(options: {
|
|
295
329
|
context: LongTableProjectContext;
|
|
296
330
|
provider?: ProviderKind;
|
package/dist/project-session.js
CHANGED
|
@@ -1070,7 +1070,8 @@ function evidenceRecordsForInvocation(invocation, timestamp) {
|
|
|
1070
1070
|
member.summary ? `Summary: ${member.summary}` : "",
|
|
1071
1071
|
member.claims?.length ? `Claims: ${member.claims.join("; ")}` : "",
|
|
1072
1072
|
member.objections?.length ? `Objections: ${member.objections.join("; ")}` : "",
|
|
1073
|
-
member.openQuestions?.length ? `Open questions: ${member.openQuestions.join("; ")}` : ""
|
|
1073
|
+
member.openQuestions?.length ? `Open questions: ${member.openQuestions.join("; ")}` : "",
|
|
1074
|
+
member.evidenceRefs?.length ? `Evidence refs: ${member.evidenceRefs.join("; ")}` : ""
|
|
1074
1075
|
].filter(Boolean).join("\n"),
|
|
1075
1076
|
linkedInvocationRecordIds: [invocation.id],
|
|
1076
1077
|
linkedQuestionRecordIds: invocation.panelResult.linkedQuestionRecordIds,
|
|
@@ -1125,6 +1126,314 @@ export async function appendInvocationRecordToWorkspace(context, invocation, que
|
|
|
1125
1126
|
await syncCurrentWorkspaceView(context);
|
|
1126
1127
|
return withEvidence;
|
|
1127
1128
|
}
|
|
1129
|
+
function latestPanelInvocation(state) {
|
|
1130
|
+
return (state.invocationLog ?? [])
|
|
1131
|
+
.slice()
|
|
1132
|
+
.reverse()
|
|
1133
|
+
.find((record) => record.intent.kind === "panel" && record.panelResult);
|
|
1134
|
+
}
|
|
1135
|
+
function sanitizeStringArray(value) {
|
|
1136
|
+
if (!Array.isArray(value)) {
|
|
1137
|
+
return undefined;
|
|
1138
|
+
}
|
|
1139
|
+
const normalized = value.filter((entry) => typeof entry === "string");
|
|
1140
|
+
return normalized.length > 0 ? normalized : [];
|
|
1141
|
+
}
|
|
1142
|
+
function isInvocationStatus(value) {
|
|
1143
|
+
return (value === "planned" ||
|
|
1144
|
+
value === "running" ||
|
|
1145
|
+
value === "completed" ||
|
|
1146
|
+
value === "blocked" ||
|
|
1147
|
+
value === "degraded" ||
|
|
1148
|
+
value === "error");
|
|
1149
|
+
}
|
|
1150
|
+
function isInvocationSurface(value) {
|
|
1151
|
+
return (value === "native_parallel" ||
|
|
1152
|
+
value === "native_subagents" ||
|
|
1153
|
+
value === "native_workers" ||
|
|
1154
|
+
value === "generated_skill" ||
|
|
1155
|
+
value === "prompt_alias" ||
|
|
1156
|
+
value === "sequential_fallback" ||
|
|
1157
|
+
value === "file_backed_panel_debate" ||
|
|
1158
|
+
value === "file_backed_debate" ||
|
|
1159
|
+
value === "mcp_transport");
|
|
1160
|
+
}
|
|
1161
|
+
function sanitizePanelMemberResult(member, fallback) {
|
|
1162
|
+
const label = typeof member.label === "string" && member.label.trim()
|
|
1163
|
+
? member.label
|
|
1164
|
+
: fallback?.label ?? member.role;
|
|
1165
|
+
const status = isInvocationStatus(member.status)
|
|
1166
|
+
? member.status
|
|
1167
|
+
: fallback?.status ?? "completed";
|
|
1168
|
+
const claims = sanitizeStringArray(member.claims);
|
|
1169
|
+
const objections = sanitizeStringArray(member.objections);
|
|
1170
|
+
const openQuestions = sanitizeStringArray(member.openQuestions);
|
|
1171
|
+
const evidenceRefs = sanitizeStringArray(member.evidenceRefs);
|
|
1172
|
+
return {
|
|
1173
|
+
role: member.role,
|
|
1174
|
+
label,
|
|
1175
|
+
status,
|
|
1176
|
+
...(typeof member.summary === "string" ? { summary: member.summary } : fallback?.summary ? { summary: fallback.summary } : {}),
|
|
1177
|
+
...(claims !== undefined ? { claims } : fallback?.claims ? { claims: fallback.claims } : {}),
|
|
1178
|
+
...(objections !== undefined ? { objections } : fallback?.objections ? { objections: fallback.objections } : {}),
|
|
1179
|
+
...(openQuestions !== undefined ? { openQuestions } : fallback?.openQuestions ? { openQuestions: fallback.openQuestions } : {}),
|
|
1180
|
+
...(evidenceRefs !== undefined ? { evidenceRefs } : fallback?.evidenceRefs ? { evidenceRefs: fallback.evidenceRefs } : {}),
|
|
1181
|
+
...(typeof member.error === "string" ? { error: member.error } : fallback?.error ? { error: fallback.error } : {})
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
function mergePanelMemberResults(existing, incoming = []) {
|
|
1185
|
+
const incomingByRole = new Map(incoming.map((member) => [member.role, member]));
|
|
1186
|
+
const merged = existing.map((member) => {
|
|
1187
|
+
const update = incomingByRole.get(member.role);
|
|
1188
|
+
if (!update) {
|
|
1189
|
+
return sanitizePanelMemberResult(member);
|
|
1190
|
+
}
|
|
1191
|
+
return sanitizePanelMemberResult(update, member);
|
|
1192
|
+
});
|
|
1193
|
+
const existingRoles = new Set(existing.map((member) => member.role));
|
|
1194
|
+
for (const update of incoming) {
|
|
1195
|
+
if (existingRoles.has(update.role)) {
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
merged.push(sanitizePanelMemberResult(update));
|
|
1199
|
+
}
|
|
1200
|
+
return merged;
|
|
1201
|
+
}
|
|
1202
|
+
function removeEvidenceForInvocation(state, invocationId) {
|
|
1203
|
+
return (state.evidenceRecords ?? []).filter((record) => {
|
|
1204
|
+
const linked = record.linkedInvocationRecordIds ?? [];
|
|
1205
|
+
if (!linked.includes(invocationId)) {
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1208
|
+
return !(record.sourceKind === "panel" || record.sourceId?.startsWith(`${invocationId}:`));
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
export async function recordPanelResultInWorkspace(options) {
|
|
1212
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
1213
|
+
const targetInvocation = options.result.invocationId
|
|
1214
|
+
? (state.invocationLog ?? []).find((record) => record.id === options.result.invocationId)
|
|
1215
|
+
: latestPanelInvocation(state);
|
|
1216
|
+
if (!targetInvocation) {
|
|
1217
|
+
throw new Error(options.result.invocationId
|
|
1218
|
+
? `No panel invocation found for ${options.result.invocationId}.`
|
|
1219
|
+
: "No panel invocation found to record.");
|
|
1220
|
+
}
|
|
1221
|
+
if (!targetInvocation.panelResult) {
|
|
1222
|
+
throw new Error(`Invocation ${targetInvocation.id} does not have a panel result.`);
|
|
1223
|
+
}
|
|
1224
|
+
if (options.result.status !== undefined && !isInvocationStatus(options.result.status)) {
|
|
1225
|
+
throw new Error(`Invalid panel result status: ${String(options.result.status)}.`);
|
|
1226
|
+
}
|
|
1227
|
+
if (options.result.surface !== undefined && !isInvocationSurface(options.result.surface)) {
|
|
1228
|
+
throw new Error(`Invalid panel result surface: ${String(options.result.surface)}.`);
|
|
1229
|
+
}
|
|
1230
|
+
const timestamp = nowIso();
|
|
1231
|
+
const status = options.result.status ?? "completed";
|
|
1232
|
+
const surface = options.result.surface ?? targetInvocation.panelResult.surface;
|
|
1233
|
+
const updatedInvocation = {
|
|
1234
|
+
...targetInvocation,
|
|
1235
|
+
updatedAt: timestamp,
|
|
1236
|
+
status,
|
|
1237
|
+
surface,
|
|
1238
|
+
panelResult: {
|
|
1239
|
+
...targetInvocation.panelResult,
|
|
1240
|
+
updatedAt: timestamp,
|
|
1241
|
+
status,
|
|
1242
|
+
surface,
|
|
1243
|
+
memberResults: mergePanelMemberResults(targetInvocation.panelResult.memberResults, options.result.memberResults),
|
|
1244
|
+
...(normalizeOptionalString(options.result.synthesis) ? { synthesis: normalizeOptionalString(options.result.synthesis) } : {}),
|
|
1245
|
+
...(normalizeOptionalString(options.result.conflictSummary) ? { conflictSummary: normalizeOptionalString(options.result.conflictSummary) } : {}),
|
|
1246
|
+
...(normalizeOptionalString(options.result.decisionPrompt) ? { decisionPrompt: normalizeOptionalString(options.result.decisionPrompt) } : {})
|
|
1247
|
+
},
|
|
1248
|
+
degradationReason: surface === "native_subagents"
|
|
1249
|
+
? "Provider-native subagent execution was recorded as session-dependent; sequential_fallback remains the required fallback."
|
|
1250
|
+
: surface === "native_workers"
|
|
1251
|
+
? "LongTable-native worker outputs were recorded as structured panel evidence; runtime state remains under .longtable/panel-runs and hidden reasoning/raw tmux logs stay out of handoff."
|
|
1252
|
+
: targetInvocation.degradationReason
|
|
1253
|
+
};
|
|
1254
|
+
const evidenceRecords = evidenceRecordsForInvocation(updatedInvocation, timestamp);
|
|
1255
|
+
const updatedState = {
|
|
1256
|
+
...state,
|
|
1257
|
+
invocationLog: (state.invocationLog ?? []).map((record) => record.id === updatedInvocation.id ? updatedInvocation : record),
|
|
1258
|
+
evidenceRecords: [...removeEvidenceForInvocation(state, updatedInvocation.id), ...evidenceRecords]
|
|
1259
|
+
};
|
|
1260
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updatedState, null, 2), "utf8");
|
|
1261
|
+
await syncCurrentWorkspaceView(options.context);
|
|
1262
|
+
return {
|
|
1263
|
+
invocation: updatedInvocation,
|
|
1264
|
+
evidenceRecords,
|
|
1265
|
+
state: updatedState
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function renderBulletList(values, empty) {
|
|
1269
|
+
return values.length > 0 ? values.map((value) => `- ${value}`) : [`- ${empty}`];
|
|
1270
|
+
}
|
|
1271
|
+
function formatInvocationLine(record) {
|
|
1272
|
+
const roles = record.intent.roles.length > 0 ? record.intent.roles.join(", ") : "auto";
|
|
1273
|
+
return `${record.intent.kind}/${record.intent.mode} via ${record.surface} (${record.status}); roles: ${roles}`;
|
|
1274
|
+
}
|
|
1275
|
+
function renderLatestPanelForHandoff(invocation) {
|
|
1276
|
+
if (!invocation?.panelResult) {
|
|
1277
|
+
return [
|
|
1278
|
+
"## Latest Panel Or Discussion",
|
|
1279
|
+
"- No panel invocation is recorded yet.",
|
|
1280
|
+
"- Start with `lt panel: <what needs review>` or `longtable panel --prompt \"...\" --json`."
|
|
1281
|
+
];
|
|
1282
|
+
}
|
|
1283
|
+
const result = invocation.panelResult;
|
|
1284
|
+
const workerResultGuidance = result.surface === "native_workers"
|
|
1285
|
+
? [
|
|
1286
|
+
"- Native worker note: role-worker outputs have been normalized into this `PanelResult`; use `longtable panel status --run <run_id>` for live worker status when a native worker run id is available.",
|
|
1287
|
+
"- Native worker handoff rule: preserve final summaries, claims, objections, open questions, and evidence references; do not paste hidden reasoning, raw tool traces, or tmux logs into the research handoff."
|
|
1288
|
+
]
|
|
1289
|
+
: [];
|
|
1290
|
+
return [
|
|
1291
|
+
"## Latest Panel Or Discussion",
|
|
1292
|
+
`- Invocation: ${invocation.id}`,
|
|
1293
|
+
`- Record: ${formatInvocationLine(invocation)}`,
|
|
1294
|
+
`- Result status: ${result.status}`,
|
|
1295
|
+
`- Execution surface: ${result.surface}`,
|
|
1296
|
+
...workerResultGuidance,
|
|
1297
|
+
...(invocation.degradationReason ? [`- Fallback note: ${invocation.degradationReason}`] : []),
|
|
1298
|
+
...(result.synthesis ? [`- Synthesis: ${result.synthesis}`] : []),
|
|
1299
|
+
...(result.conflictSummary ? [`- Conflict summary: ${result.conflictSummary}`] : []),
|
|
1300
|
+
...(result.decisionPrompt ? [`- Decision prompt: ${result.decisionPrompt}`] : []),
|
|
1301
|
+
"",
|
|
1302
|
+
"### Role Outputs",
|
|
1303
|
+
...result.memberResults.map((member) => {
|
|
1304
|
+
const details = [
|
|
1305
|
+
member.summary,
|
|
1306
|
+
...(member.claims ?? []).map((claim) => `claim: ${claim}`),
|
|
1307
|
+
...(member.objections ?? []).map((objection) => `objection: ${objection}`),
|
|
1308
|
+
...(member.openQuestions ?? []).map((question) => `open question: ${question}`),
|
|
1309
|
+
...(member.evidenceRefs ?? []).map((ref) => `evidence: ${ref}`),
|
|
1310
|
+
...(member.error ? [`error: ${member.error}`] : [])
|
|
1311
|
+
].filter(Boolean);
|
|
1312
|
+
return `- ${member.label} (${member.role}): ${details.length > 0 ? compactLine(details.join("; "), 220) : member.status}`;
|
|
1313
|
+
}),
|
|
1314
|
+
...(result.memberResults.some((member) => (member.evidenceRefs ?? []).length > 0)
|
|
1315
|
+
? [
|
|
1316
|
+
"",
|
|
1317
|
+
"### Role Evidence References",
|
|
1318
|
+
...result.memberResults.flatMap((member) => (member.evidenceRefs ?? []).map((ref) => `- ${member.label} (${member.role}): ${ref}`))
|
|
1319
|
+
]
|
|
1320
|
+
: []),
|
|
1321
|
+
...(result.status === "planned"
|
|
1322
|
+
? [
|
|
1323
|
+
"",
|
|
1324
|
+
"### Missing Persistence Step",
|
|
1325
|
+
"- This panel is only planned. After the provider returns role outputs, record them with:",
|
|
1326
|
+
` \`longtable panel record --invocation ${invocation.id} --result-file panel-result.json\``
|
|
1327
|
+
]
|
|
1328
|
+
: [])
|
|
1329
|
+
];
|
|
1330
|
+
}
|
|
1331
|
+
function renderWorkflowGuidance(context, latestInvocation) {
|
|
1332
|
+
const cwdFlag = `--cwd "${context.project.projectPath.replaceAll("\"", "\\\"")}"`;
|
|
1333
|
+
const recordCommand = latestInvocation?.panelResult
|
|
1334
|
+
? `longtable panel record ${cwdFlag} --invocation ${latestInvocation.id} --result-file panel-result.json`
|
|
1335
|
+
: `longtable panel ${cwdFlag} --prompt "<panel question>" --json`;
|
|
1336
|
+
return [
|
|
1337
|
+
"## Continuation Workflow",
|
|
1338
|
+
"",
|
|
1339
|
+
"### Provider-Neutral Path",
|
|
1340
|
+
"Use this when OMX is not installed or when the researcher wants a plain CLI/native-agent workflow.",
|
|
1341
|
+
"",
|
|
1342
|
+
"1. Open the project in Codex or Claude Code.",
|
|
1343
|
+
"2. Use `$longtable-start` if no usable Research Specification exists; otherwise use `$longtable-interview` or `lt panel: ...` for the next bounded decision.",
|
|
1344
|
+
"3. When a panel or native worker run produces real role outputs, persist the structured result:",
|
|
1345
|
+
` \`${recordCommand}\``,
|
|
1346
|
+
" Native worker outputs should be final role summaries only: summary, claims, objections, open questions, and evidence refs.",
|
|
1347
|
+
"4. Inspect unincorporated evidence:",
|
|
1348
|
+
` \`longtable spec unincorporated ${cwdFlag}\``,
|
|
1349
|
+
"5. Propose a Research Specification patch before applying a changed research direction:",
|
|
1350
|
+
` \`longtable spec propose ${cwdFlag} --spec-file updated-spec.json --rationale \"Panel/discussion handoff\"\``,
|
|
1351
|
+
"6. Apply only after the researcher confirms the decision:",
|
|
1352
|
+
` \`longtable spec apply ${cwdFlag} --patch-id <spec_patch_id>\``,
|
|
1353
|
+
"",
|
|
1354
|
+
"### Optional OMX Path",
|
|
1355
|
+
"Use this only when OMX is installed. The handoff packet can be pasted into `$ralplan` for a plan/test-spec pass, then `$ralph` can execute the approved work until verification. LongTable should remain the research-state source of truth; OMX is only the execution loop.",
|
|
1356
|
+
"",
|
|
1357
|
+
"Suggested OMX prompt:",
|
|
1358
|
+
"```text",
|
|
1359
|
+
"$ralplan: Use the LongTable handoff below as the research-state contract. Produce a PRD/test-spec style execution plan, preserve unresolved panel disagreements, and do not change the Research Specification without a LongTable checkpoint.",
|
|
1360
|
+
"```",
|
|
1361
|
+
"",
|
|
1362
|
+
"Then, after the plan is accepted:",
|
|
1363
|
+
"```text",
|
|
1364
|
+
"$ralph: Execute the approved LongTable handoff plan. Verify artifacts, then record any panel evidence or spec patch through LongTable commands.",
|
|
1365
|
+
"```"
|
|
1366
|
+
];
|
|
1367
|
+
}
|
|
1368
|
+
export async function createWorkspaceHandoff(options) {
|
|
1369
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
1370
|
+
const createdAt = nowIso();
|
|
1371
|
+
const id = createId("handoff");
|
|
1372
|
+
const locale = normalizeLocale(options.context.session.locale ?? options.context.project.locale);
|
|
1373
|
+
const openQuestions = options.context.session.openQuestions && options.context.session.openQuestions.length > 0
|
|
1374
|
+
? options.context.session.openQuestions
|
|
1375
|
+
: buildOpenQuestions(options.context.session);
|
|
1376
|
+
const nextAction = options.context.session.nextAction ?? buildNextAction(options.context.session);
|
|
1377
|
+
const latestInvocation = latestPanelInvocation(state);
|
|
1378
|
+
const pendingQuestions = (state.questionLog ?? []).filter((record) => record.status === "pending");
|
|
1379
|
+
const unincorporatedEvidence = (state.evidenceRecords ?? []).filter((record) => !record.incorporatedByRevisionId);
|
|
1380
|
+
const proposedPatches = (state.specPatches ?? []).filter((patch) => patch.status === "proposed");
|
|
1381
|
+
const outputPath = options.outputPath
|
|
1382
|
+
? resolve(options.outputPath)
|
|
1383
|
+
: join(options.context.metaDir, "handoffs", `${id}-${slugify(options.context.session.researchSpecification?.title ?? options.context.project.projectName) || "longtable"}.md`);
|
|
1384
|
+
const content = [
|
|
1385
|
+
"# LongTable Handoff",
|
|
1386
|
+
"",
|
|
1387
|
+
`Generated: ${createdAt}`,
|
|
1388
|
+
`Project: ${options.context.project.projectName}`,
|
|
1389
|
+
`Project path: ${options.context.project.projectPath}`,
|
|
1390
|
+
"",
|
|
1391
|
+
"## Current Objective",
|
|
1392
|
+
`- Goal: ${options.context.session.currentGoal}`,
|
|
1393
|
+
...(options.context.session.currentBlocker ? [`- Blocker: ${options.context.session.currentBlocker}`] : []),
|
|
1394
|
+
...(options.context.session.protectedDecision ? [`- Protected decision: ${options.context.session.protectedDecision}`] : []),
|
|
1395
|
+
`- Next action: ${nextAction}`,
|
|
1396
|
+
"",
|
|
1397
|
+
"## Research Specification Status",
|
|
1398
|
+
...(options.context.session.researchSpecification
|
|
1399
|
+
? renderResearchSpecificationSummary(options.context.session.researchSpecification, locale)
|
|
1400
|
+
: [
|
|
1401
|
+
"- No Research Specification is available yet.",
|
|
1402
|
+
"- Start or resume `$longtable-start` before treating the project direction as settled."
|
|
1403
|
+
]),
|
|
1404
|
+
"",
|
|
1405
|
+
...renderLatestPanelForHandoff(latestInvocation),
|
|
1406
|
+
"",
|
|
1407
|
+
"## Pending Researcher Decisions",
|
|
1408
|
+
...renderBulletList(pendingQuestions.map((record) => `${record.id}: ${record.prompt.question} (${formatQuestionOptionValues(record).join("/")})`), "No pending researcher decision questions."),
|
|
1409
|
+
"",
|
|
1410
|
+
"## Unincorporated Evidence",
|
|
1411
|
+
...renderBulletList(unincorporatedEvidence.slice(-10).reverse().map((record) => `${record.id} [${record.sourceKind}]: ${compactLine(record.summary, 180)}`), "No unincorporated evidence records."),
|
|
1412
|
+
"",
|
|
1413
|
+
"## Proposed Specification Patches",
|
|
1414
|
+
...renderBulletList(proposedPatches.map((patch) => `${patch.id}: ${patch.title} (${patch.changes.length} changes)`), "No proposed Research Specification patches."),
|
|
1415
|
+
"",
|
|
1416
|
+
"## Open Questions",
|
|
1417
|
+
...renderBulletList(openQuestions, "No open questions recorded."),
|
|
1418
|
+
"",
|
|
1419
|
+
...renderWorkflowGuidance(options.context, latestInvocation),
|
|
1420
|
+
"",
|
|
1421
|
+
"## Stop Condition",
|
|
1422
|
+
"- Stop when the next research decision is either confirmed by the researcher, preserved as an explicit open tension, or represented as a proposed Research Specification patch waiting for confirmation."
|
|
1423
|
+
].join("\n");
|
|
1424
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
1425
|
+
await writeFile(outputPath, `${content}\n`, "utf8");
|
|
1426
|
+
return {
|
|
1427
|
+
id,
|
|
1428
|
+
createdAt,
|
|
1429
|
+
path: outputPath,
|
|
1430
|
+
content,
|
|
1431
|
+
sourceEvidenceIds: unincorporatedEvidence.map((record) => record.id),
|
|
1432
|
+
pendingQuestionIds: pendingQuestions.map((record) => record.id),
|
|
1433
|
+
proposedPatchIds: proposedPatches.map((patch) => patch.id),
|
|
1434
|
+
...(latestInvocation ? { latestInvocationId: latestInvocation.id } : {})
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1128
1437
|
function createId(prefix) {
|
|
1129
1438
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1130
1439
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.56",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Researcher-facing LongTable CLI",
|
|
6
6
|
"type": "module",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^1.2.0",
|
|
32
|
-
"@longtable/checkpoints": "0.1.
|
|
33
|
-
"@longtable/core": "0.1.
|
|
34
|
-
"@longtable/memory": "0.1.
|
|
35
|
-
"@longtable/provider-claude": "0.1.
|
|
36
|
-
"@longtable/provider-codex": "0.1.
|
|
37
|
-
"@longtable/setup": "0.1.
|
|
32
|
+
"@longtable/checkpoints": "0.1.56",
|
|
33
|
+
"@longtable/core": "0.1.56",
|
|
34
|
+
"@longtable/memory": "0.1.56",
|
|
35
|
+
"@longtable/provider-claude": "0.1.56",
|
|
36
|
+
"@longtable/provider-codex": "0.1.56",
|
|
37
|
+
"@longtable/setup": "0.1.56"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|