@longtable/mcp 0.1.45 → 0.1.47

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 CHANGED
@@ -15,7 +15,7 @@ longtable-state
15
15
  Run:
16
16
 
17
17
  ```bash
18
- npx -y @longtable/mcp@0.1.41
18
+ npx -y @longtable/mcp@0.1.47
19
19
  ```
20
20
 
21
21
  Self-test:
@@ -62,4 +62,5 @@ export interface ResearchSpecificationQuestionSpec {
62
62
  export declare function renderResearchSpecificationPreview(specification: ResearchSpecification): string;
63
63
  export declare function buildResearchSpecificationQuestion(specification: ResearchSpecification): ResearchSpecificationQuestionSpec;
64
64
  export declare function researchSpecificationAnswerConfirms(answer: string): boolean;
65
+ export declare function researchSpecificationAnswerNeedsFollowUp(answer: string): boolean;
65
66
  export declare function researchSpecificationAnswerStatus(answer: string): "confirmed" | "active" | "deferred";
@@ -116,6 +116,9 @@ export function buildResearchSpecificationQuestion(specification) {
116
116
  export function researchSpecificationAnswerConfirms(answer) {
117
117
  return answer === "confirm_specification";
118
118
  }
119
+ export function researchSpecificationAnswerNeedsFollowUp(answer) {
120
+ return answer === "ask_one_more" || answer === "revise_section";
121
+ }
119
122
  export function researchSpecificationAnswerStatus(answer) {
120
123
  if (researchSpecificationAnswerConfirms(answer)) {
121
124
  return "confirmed";
package/dist/server.js CHANGED
@@ -13,7 +13,7 @@ import { renderQuestionRecordPrompt } from "@longtable/provider-codex";
13
13
  import { loadSetupOutput } from "@longtable/setup";
14
14
  import { answerWorkspaceQuestion, clearWorkspaceQuestion, createOrUpdateProjectWorkspace, createWorkspaceQuestion, inspectProjectWorkspace, loadProjectContextFromDirectory, loadWorkspaceState, syncCurrentWorkspaceView } from "@longtable/cli";
15
15
  import { buildFirstResearchShapeQuestion, firstResearchShapeAnswerConfirms, firstResearchShapeAnswerStatus } from "./first-research-shape.js";
16
- import { buildResearchSpecificationQuestion, renderResearchSpecificationPreview, researchSpecificationAnswerConfirms, researchSpecificationAnswerStatus } from "./research-specification.js";
16
+ import { buildResearchSpecificationQuestion, renderResearchSpecificationPreview, researchSpecificationAnswerConfirms, researchSpecificationAnswerNeedsFollowUp, researchSpecificationAnswerStatus } from "./research-specification.js";
17
17
  const SERVER_NAME = "longtable-state";
18
18
  const require = createRequire(import.meta.url);
19
19
  const SERVER_VERSION = String(require("../package.json").version ?? "0.0.0");
@@ -47,6 +47,22 @@ const questionOptionSchema = z.object({
47
47
  description: z.string().optional(),
48
48
  recommended: z.boolean().optional()
49
49
  });
50
+ const commitmentFamilySchema = z.enum([
51
+ "scope",
52
+ "construct",
53
+ "coding",
54
+ "method",
55
+ "evidence",
56
+ "epistemic_authority",
57
+ "product_policy"
58
+ ]);
59
+ const epistemicBasisSchema = z.enum([
60
+ "researcher_knowledge",
61
+ "project_state",
62
+ "external_evidence",
63
+ "ai_inference",
64
+ "mixed"
65
+ ]);
50
66
  const firstResearchShapeSchema = z.object({
51
67
  handle: z.string().min(1),
52
68
  currentGoal: z.string().min(1),
@@ -228,6 +244,57 @@ function resolveFirstResearchShapeObligation(state, options) {
228
244
  })
229
245
  };
230
246
  }
247
+ function researchSpecificationFollowUpReason(answer) {
248
+ if (answer === "ask_one_more") {
249
+ return "The researcher chose one more question before saving; after that answer, LongTable must update or read the Research Specification and return to confirmation.";
250
+ }
251
+ if (answer === "revise_section") {
252
+ return "The researcher chose section revision; after the section is revised, LongTable must return to the Research Specification Preview for confirmation.";
253
+ }
254
+ return "Research Specification confirmation remains open.";
255
+ }
256
+ function ensureResearchSpecificationConfirmationObligation(state, specification, options) {
257
+ const existing = (state.questionObligations ?? []).find((obligation) => obligation.kind === "research_specification_confirmation" &&
258
+ obligation.status === "pending" &&
259
+ ((specification.sourceHookId && obligation.sourceHookId === specification.sourceHookId) ||
260
+ (!specification.sourceHookId && obligation.prompt.includes(specification.title))));
261
+ const timestamp = new Date().toISOString();
262
+ const next = {
263
+ ...(existing ?? {
264
+ id: createId("question_obligation"),
265
+ kind: "research_specification_confirmation",
266
+ status: "pending",
267
+ createdAt: timestamp
268
+ }),
269
+ updatedAt: timestamp,
270
+ prompt: `Return to Research Specification Preview before ending the interview: ${specification.title}`,
271
+ reason: researchSpecificationFollowUpReason(options.answer),
272
+ ...(specification.sourceHookId ? { sourceHookId: specification.sourceHookId } : {}),
273
+ ...(options.questionId ? { questionId: options.questionId } : existing?.questionId ? { questionId: existing.questionId } : {}),
274
+ ...(options.decisionId ? { decisionId: options.decisionId } : existing?.decisionId ? { decisionId: existing.decisionId } : {})
275
+ };
276
+ return upsertQuestionObligation(state, next);
277
+ }
278
+ function resolveResearchSpecificationConfirmationObligation(state, specification, options = {}) {
279
+ return {
280
+ ...state,
281
+ questionObligations: (state.questionObligations ?? []).map((obligation) => {
282
+ const matches = obligation.kind === "research_specification_confirmation" && ((specification.sourceHookId && obligation.sourceHookId === specification.sourceHookId) ||
283
+ (options.questionId && obligation.questionId === options.questionId) ||
284
+ (!specification.sourceHookId && obligation.prompt.includes(specification.title)));
285
+ if (!matches || obligation.status !== "pending") {
286
+ return obligation;
287
+ }
288
+ return {
289
+ ...obligation,
290
+ status: options.status ?? "satisfied",
291
+ updatedAt: new Date().toISOString(),
292
+ ...(options.questionId ? { questionId: options.questionId } : obligation.questionId ? { questionId: obligation.questionId } : {}),
293
+ ...(options.decisionId ? { decisionId: options.decisionId } : obligation.decisionId ? { decisionId: obligation.decisionId } : {})
294
+ };
295
+ })
296
+ };
297
+ }
231
298
  function interviewDepth(turns) {
232
299
  if (turns.some((turn) => turn.readyToSummarize === true && turn.quality !== "thin")) {
233
300
  return "ready_to_summarize";
@@ -794,6 +861,23 @@ async function markResearchSpecificationConfirmation(context, specification, ans
794
861
  : hook.linkedDecisionRecordIds
795
862
  };
796
863
  });
864
+ const nextState = researchSpecificationAnswerConfirms(answer)
865
+ ? resolveResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
866
+ questionId,
867
+ decisionId,
868
+ status: "satisfied"
869
+ })
870
+ : researchSpecificationAnswerNeedsFollowUp(answer)
871
+ ? ensureResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
872
+ answer,
873
+ questionId,
874
+ decisionId
875
+ })
876
+ : resolveResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
877
+ questionId,
878
+ decisionId,
879
+ status: "cleared"
880
+ });
797
881
  const session = {
798
882
  ...context.session,
799
883
  researchSpecification: confirmedSpecification,
@@ -801,9 +885,9 @@ async function markResearchSpecificationConfirmation(context, specification, ans
801
885
  };
802
886
  context.session = session;
803
887
  await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
804
- await writeFile(context.stateFilePath, JSON.stringify(state, null, 2), "utf8");
888
+ await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
805
889
  await syncCurrentWorkspaceView(context);
806
- return { state, session, specification: confirmedSpecification };
890
+ return { state: nextState, session, specification: confirmedSpecification };
807
891
  }
808
892
  async function markAlreadyConfirmedResearchSpecification(context, specification) {
809
893
  const state = asInterviewState(await loadWorkspaceState(context));
@@ -832,6 +916,9 @@ async function markAlreadyConfirmedResearchSpecification(context, specification)
832
916
  researchSpecification: confirmedSpecification
833
917
  };
834
918
  });
919
+ const nextState = resolveResearchSpecificationConfirmationObligation(state, confirmedSpecification, {
920
+ status: "satisfied"
921
+ });
835
922
  const session = {
836
923
  ...context.session,
837
924
  researchSpecification: confirmedSpecification,
@@ -839,9 +926,9 @@ async function markAlreadyConfirmedResearchSpecification(context, specification)
839
926
  };
840
927
  context.session = session;
841
928
  await writeFile(context.sessionFilePath, JSON.stringify(session, null, 2), "utf8");
842
- await writeFile(context.stateFilePath, JSON.stringify(state, null, 2), "utf8");
929
+ await writeFile(context.stateFilePath, JSON.stringify(nextState, null, 2), "utf8");
843
930
  await syncCurrentWorkspaceView(context);
844
- return { state, session, specification: confirmedSpecification };
931
+ return { state: nextState, session, specification: confirmedSpecification };
845
932
  }
846
933
  function statusForElicitationError(error) {
847
934
  const message = error instanceof Error ? error.message : String(error);
@@ -1428,9 +1515,11 @@ export function createLongTableMcpServer() {
1428
1515
  options: z.array(questionOptionSchema).optional(),
1429
1516
  displayReason: z.string().optional(),
1430
1517
  provider: z.enum(["codex", "claude"]).optional(),
1431
- required: z.boolean().optional()
1518
+ required: z.boolean().optional(),
1519
+ commitmentFamily: commitmentFamilySchema.optional(),
1520
+ epistemicBasis: epistemicBasisSchema.optional()
1432
1521
  })
1433
- }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required }) => {
1522
+ }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required, commitmentFamily, epistemicBasis }) => {
1434
1523
  try {
1435
1524
  const context = await requireContext(inputCwd);
1436
1525
  const result = await createWorkspaceQuestion({
@@ -1442,7 +1531,9 @@ export function createLongTableMcpServer() {
1442
1531
  questionOptions: options,
1443
1532
  displayReason,
1444
1533
  provider,
1445
- required
1534
+ required,
1535
+ commitmentFamily: commitmentFamily,
1536
+ epistemicBasis: epistemicBasis
1446
1537
  });
1447
1538
  return textResult({
1448
1539
  question: result.question,
@@ -1465,9 +1556,11 @@ export function createLongTableMcpServer() {
1465
1556
  displayReason: z.string().optional(),
1466
1557
  provider: z.enum(["codex", "claude"]).default("codex"),
1467
1558
  required: z.boolean().optional(),
1559
+ commitmentFamily: commitmentFamilySchema.optional(),
1560
+ epistemicBasis: epistemicBasisSchema.optional(),
1468
1561
  fallbackOnly: z.boolean().default(false).describe("Create and render the checkpoint without calling MCP elicitation.")
1469
1562
  })
1470
- }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required, fallbackOnly }) => {
1563
+ }, async ({ cwd: inputCwd, prompt, title, question, checkpointKey, options, displayReason, provider, required, commitmentFamily, epistemicBasis, fallbackOnly }) => {
1471
1564
  try {
1472
1565
  const context = await requireContext(inputCwd);
1473
1566
  const created = await createWorkspaceQuestion({
@@ -1479,7 +1572,9 @@ export function createLongTableMcpServer() {
1479
1572
  questionOptions: options,
1480
1573
  displayReason,
1481
1574
  provider,
1482
- required
1575
+ required,
1576
+ commitmentFamily: commitmentFamily,
1577
+ epistemicBasis: epistemicBasis
1483
1578
  });
1484
1579
  const fallback = renderQuestionFallback(created.question, provider);
1485
1580
  if (fallbackOnly) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/mcp",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "private": false,
5
5
  "description": "LongTable MCP transport for workspace state and Researcher Checkpoints",
6
6
  "type": "module",
@@ -26,12 +26,12 @@
26
26
  "self-test": "node ./dist/server.js --self-test"
27
27
  },
28
28
  "dependencies": {
29
- "@longtable/checkpoints": "0.1.45",
30
- "@longtable/cli": "0.1.45",
31
- "@longtable/core": "0.1.45",
32
- "@longtable/provider-claude": "0.1.45",
33
- "@longtable/provider-codex": "0.1.45",
34
- "@longtable/setup": "0.1.45",
29
+ "@longtable/checkpoints": "0.1.47",
30
+ "@longtable/cli": "0.1.47",
31
+ "@longtable/core": "0.1.47",
32
+ "@longtable/provider-claude": "0.1.47",
33
+ "@longtable/provider-codex": "0.1.47",
34
+ "@longtable/setup": "0.1.47",
35
35
  "@modelcontextprotocol/sdk": "^1.29.0",
36
36
  "zod": "^4.0.0"
37
37
  },