@longtable/mcp 0.1.44 → 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";
@@ -34,6 +34,8 @@ export function renderResearchSpecificationPreview(specification) {
34
34
  `${korean ? "이론 앵커" : "Theory anchors"}: ${compactList(specification.theoryAndFraming.anchors)}`,
35
35
  `${korean ? "코딩 규칙" : "Coding rules"}: ${compactList(specification.measurementCoding.codingRules)}`,
36
36
  `${korean ? "분석 옵션" : "Analysis options"}: ${compactList(specification.methodAnalysis.analysisOptions)}`,
37
+ `${korean ? "Corpus and Access Plan" : "Corpus and Access Plan"}: ${compactList(specification.evidenceAccess.accessRequirements ?? [])}`,
38
+ `${korean ? "근거 기준" : "Evidence standards"}: ${compactList(specification.evidenceAccess.evidenceStandards ?? [])}`,
37
39
  specification.epistemicAlignment.conflictResolutionRule
38
40
  ? `${korean ? "충돌 조정" : "Conflict rule"}: ${specification.epistemicAlignment.conflictResolutionRule}`
39
41
  : undefined,
@@ -43,6 +45,22 @@ export function renderResearchSpecificationPreview(specification) {
43
45
  ].filter(Boolean);
44
46
  return lines.join("\n");
45
47
  }
48
+ function renderResearchSpecificationDecisionContext(specification) {
49
+ const korean = usesKorean(specification);
50
+ const lines = [
51
+ korean ? "Research Specification Preview" : "Research Specification Preview",
52
+ `${korean ? "제목" : "Title"}: ${specification.title}`,
53
+ `${korean ? "목적" : "Purpose"}: ${specification.researchDirection.purpose}`,
54
+ `${korean ? "핵심 construct" : "Core constructs"}: ${compactList(specification.constructOntology.coreConstructs, 2)}`,
55
+ `${korean ? "접근 계획" : "Access plan"}: ${compactList(specification.evidenceAccess.accessRequirements ?? [], 1)}`,
56
+ `${korean ? "열린 질문" : "Open questions"}: ${compactList(specification.openQuestions, 1)}`,
57
+ `${korean ? "다음 행동" : "Next actions"}: ${compactList(specification.nextActions, 1)}`,
58
+ korean
59
+ ? "전체 명세는 tool output과 저장 후 CURRENT.md에서 확인합니다."
60
+ : "The full specification remains in the tool output and, after saving, CURRENT.md."
61
+ ];
62
+ return lines.join("\n");
63
+ }
46
64
  function baseOptions(specification) {
47
65
  const korean = usesKorean(specification);
48
66
  return [
@@ -81,6 +99,7 @@ function baseOptions(specification) {
81
99
  export function buildResearchSpecificationQuestion(specification) {
82
100
  const korean = usesKorean(specification);
83
101
  const preview = renderResearchSpecificationPreview(specification);
102
+ const decisionContext = renderResearchSpecificationDecisionContext(specification);
84
103
  return {
85
104
  prompt: preview,
86
105
  title: korean ? "Research Specification 확인" : "Research Specification Confirmation",
@@ -90,13 +109,16 @@ export function buildResearchSpecificationQuestion(specification) {
90
109
  checkpointKey: "research_specification_confirmation",
91
110
  options: baseOptions(specification),
92
111
  displayReason: korean
93
- ? `${preview}\n\n인터뷰가 단순한 방향 요약을 넘어 연구 명세를 만들 만큼 구체화되었습니다. 저장 전에 범위, construct, 이론, 코딩, 방법, 접근, 정렬을 명시적으로 확인해야 합니다.`
94
- : `${preview}\n\nThe interview has moved beyond a short direction summary into a research specification. Scope, constructs, theory, coding, method, access, and epistemic alignment should be explicit before saving.`
112
+ ? `${decisionContext}\n\n인터뷰가 연구 명세를 만들 만큼 구체화되었습니다. 저장 핵심 범위와 다음 행동만 UI에서 확인합니다.`
113
+ : `${decisionContext}\n\nThe interview is ready for a research specification. The UI shows only the core scope and next action before saving.`
95
114
  };
96
115
  }
97
116
  export function researchSpecificationAnswerConfirms(answer) {
98
117
  return answer === "confirm_specification";
99
118
  }
119
+ export function researchSpecificationAnswerNeedsFollowUp(answer) {
120
+ return answer === "ask_one_more" || answer === "revise_section";
121
+ }
100
122
  export function researchSpecificationAnswerStatus(answer) {
101
123
  if (researchSpecificationAnswerConfirms(answer)) {
102
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.44",
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.44",
30
- "@longtable/cli": "0.1.44",
31
- "@longtable/core": "0.1.44",
32
- "@longtable/provider-claude": "0.1.44",
33
- "@longtable/provider-codex": "0.1.44",
34
- "@longtable/setup": "0.1.44",
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
  },