@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 +1 -1
- package/dist/research-specification.d.ts +1 -0
- package/dist/research-specification.js +24 -2
- package/dist/server.js +105 -10
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -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
|
-
? `${
|
|
94
|
-
: `${
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
30
|
-
"@longtable/cli": "0.1.
|
|
31
|
-
"@longtable/core": "0.1.
|
|
32
|
-
"@longtable/provider-claude": "0.1.
|
|
33
|
-
"@longtable/provider-codex": "0.1.
|
|
34
|
-
"@longtable/setup": "0.1.
|
|
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
|
},
|