@longtable/cli 0.1.45 → 0.1.48
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 +238 -9
- package/dist/project-session.d.ts +72 -1
- package/dist/project-session.js +717 -46
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router
|
|
|
18
18
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
19
19
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
20
20
|
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, mergeManagedCodexHooksConfig, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
21
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, pruneWorkspaceQuestions, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
21
|
+
import { appendInvocationRecordToWorkspace, applyResearchSpecificationPatch, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, diffResearchSpecifications, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, findUnincorporatedResearchEvidence, proposeResearchSpecificationPatch, pruneWorkspaceQuestions, readResearchSpecificationHistory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
22
22
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
23
23
|
import { createPromptRenderer } from "./prompt-renderer.js";
|
|
24
24
|
const VALID_MODES = new Set([
|
|
@@ -49,8 +49,45 @@ const require = createRequire(import.meta.url);
|
|
|
49
49
|
const LONGTABLE_PACKAGE_VERSION = String(require("../package.json").version ?? "0.0.0");
|
|
50
50
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
51
51
|
const LONGTABLE_MCP_PACKAGE_VERSION = LONGTABLE_PACKAGE_VERSION;
|
|
52
|
+
const LONGTABLE_MCP_PACKAGE_SPEC = `@longtable/mcp@${LONGTABLE_MCP_PACKAGE_VERSION}`;
|
|
52
53
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
53
54
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
55
|
+
const LONGTABLE_MCP_MANAGED_TOOLS = [
|
|
56
|
+
"read_project",
|
|
57
|
+
"read_session",
|
|
58
|
+
"inspect_workspace",
|
|
59
|
+
"create_workspace",
|
|
60
|
+
"begin_interview",
|
|
61
|
+
"append_interview_turn",
|
|
62
|
+
"summarize_interview",
|
|
63
|
+
"summarize_research_specification",
|
|
64
|
+
"read_research_specification",
|
|
65
|
+
"propose_research_spec_patch",
|
|
66
|
+
"apply_research_spec_patch",
|
|
67
|
+
"diff_research_specification",
|
|
68
|
+
"read_research_spec_history",
|
|
69
|
+
"find_unincorporated_evidence",
|
|
70
|
+
"cancel_interview",
|
|
71
|
+
"confirm_first_research_shape",
|
|
72
|
+
"confirm_research_specification",
|
|
73
|
+
"pending_questions",
|
|
74
|
+
"evaluate_checkpoint",
|
|
75
|
+
"create_question",
|
|
76
|
+
"elicit_question",
|
|
77
|
+
"render_question",
|
|
78
|
+
"append_decision",
|
|
79
|
+
"regenerate_current"
|
|
80
|
+
];
|
|
81
|
+
const LONGTABLE_MCP_RESEARCH_SPECIFICATION_TOOLS = [
|
|
82
|
+
"summarize_research_specification",
|
|
83
|
+
"read_research_specification",
|
|
84
|
+
"propose_research_spec_patch",
|
|
85
|
+
"apply_research_spec_patch",
|
|
86
|
+
"diff_research_specification",
|
|
87
|
+
"read_research_spec_history",
|
|
88
|
+
"find_unincorporated_evidence",
|
|
89
|
+
"confirm_research_specification"
|
|
90
|
+
];
|
|
54
91
|
function style(text, prefix) {
|
|
55
92
|
return `${prefix}${text}${ANSI.reset}`;
|
|
56
93
|
}
|
|
@@ -84,7 +121,7 @@ function renderInterviewLaunchSteps(provider) {
|
|
|
84
121
|
`2. run \`${command}\``,
|
|
85
122
|
"3. invoke `$longtable-interview`",
|
|
86
123
|
"",
|
|
87
|
-
"The interview will create or resume `.longtable/`,
|
|
124
|
+
"The interview will create or resume `.longtable/`, may store a short First Research Shape handle, and uses option UI for the final Research Specification confirmation."
|
|
88
125
|
]);
|
|
89
126
|
}
|
|
90
127
|
function renderProgressBar(current, total) {
|
|
@@ -105,6 +142,7 @@ function usage() {
|
|
|
105
142
|
" longtable doctor [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--hooks-path <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
106
143
|
" longtable status [--cwd <path>] [--fix] [--json] [--codex-dir <path>] [--codex-config <path>] [--hooks-path <path>] [--claude-dir <path>] [--codex-prompts-dir <path>] [--codex-runtime-path <file>] [--claude-runtime-path <file>]",
|
|
107
144
|
" longtable audit [questions|roles] [--json]",
|
|
145
|
+
" longtable spec [read|history|diff|unincorporated|apply|propose] [--cwd <path>] [--json] [--spec-file <path>] [--patch-id <id>]",
|
|
108
146
|
" longtable roles [--json]",
|
|
109
147
|
" longtable show [--json] [--path <file>]",
|
|
110
148
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
@@ -155,7 +193,7 @@ function parseArgs(argv) {
|
|
|
155
193
|
const values = {};
|
|
156
194
|
let subcommand = maybeSubcommand;
|
|
157
195
|
const modeCommand = command && VALID_MODES.has(command);
|
|
158
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "decide", "sentinel", "team", "access", "search"].includes(command);
|
|
196
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "prune-questions", "panel", "decide", "sentinel", "team", "access", "search", "spec"].includes(command);
|
|
159
197
|
let startIndex = 1;
|
|
160
198
|
if (modeCommand) {
|
|
161
199
|
subcommand = undefined;
|
|
@@ -164,7 +202,7 @@ function parseArgs(argv) {
|
|
|
164
202
|
else if (command === "codex" || command === "claude" || command === "mcp") {
|
|
165
203
|
startIndex = 2;
|
|
166
204
|
}
|
|
167
|
-
else if ((command === "access" || command === "search") && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
205
|
+
else if ((command === "access" || command === "search" || command === "spec") && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
168
206
|
subcommand = maybeSubcommand;
|
|
169
207
|
startIndex = 2;
|
|
170
208
|
}
|
|
@@ -1131,7 +1169,7 @@ function resolveMcpProviders(value) {
|
|
|
1131
1169
|
function resolveMcpPackageSpec(args) {
|
|
1132
1170
|
return typeof args.package === "string" && args.package.trim()
|
|
1133
1171
|
? args.package.trim()
|
|
1134
|
-
:
|
|
1172
|
+
: LONGTABLE_MCP_PACKAGE_SPEC;
|
|
1135
1173
|
}
|
|
1136
1174
|
function resolveCodexMcpConfigPath(args) {
|
|
1137
1175
|
return resolve(normalizeUserPath(typeof args["codex-config"] === "string" && args["codex-config"].trim()
|
|
@@ -1162,6 +1200,12 @@ function renderCodexMcpBlock(serverName, command, mcpArgs) {
|
|
|
1162
1200
|
`[mcp_servers.${serverName}]`,
|
|
1163
1201
|
`command = ${escapeTomlString(command)}`,
|
|
1164
1202
|
`args = [${mcpArgs.map((arg) => escapeTomlString(arg)).join(", ")}]`,
|
|
1203
|
+
"",
|
|
1204
|
+
...LONGTABLE_MCP_MANAGED_TOOLS.flatMap((tool) => [
|
|
1205
|
+
`[mcp_servers.${serverName}.tools.${tool}]`,
|
|
1206
|
+
"approval_mode = \"approve\"",
|
|
1207
|
+
""
|
|
1208
|
+
]),
|
|
1165
1209
|
LONGTABLE_MCP_MARKER_END
|
|
1166
1210
|
].join("\n");
|
|
1167
1211
|
}
|
|
@@ -1185,10 +1229,55 @@ function codexMcpElicitationsAllowed(config) {
|
|
|
1185
1229
|
function codexLongTableMcpConfigured(config) {
|
|
1186
1230
|
return new RegExp(`\\[mcp_servers\\.${LONGTABLE_MCP_SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\]`).test(config);
|
|
1187
1231
|
}
|
|
1232
|
+
function codexLongTableMcpPackageSpec(config) {
|
|
1233
|
+
const serverName = LONGTABLE_MCP_SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1234
|
+
const match = new RegExp(`\\[mcp_servers\\.${serverName}\\][\\s\\S]*?(?=\\n\\[|$)`).exec(config);
|
|
1235
|
+
if (!match) {
|
|
1236
|
+
return undefined;
|
|
1237
|
+
}
|
|
1238
|
+
const packageMatch = /@longtable\/mcp@[A-Za-z0-9._~+:-]+/.exec(match[0]);
|
|
1239
|
+
return packageMatch?.[0];
|
|
1240
|
+
}
|
|
1241
|
+
function codexLongTableMcpToolConfigured(config, tool) {
|
|
1242
|
+
const serverName = LONGTABLE_MCP_SERVER_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1243
|
+
const escapedTool = tool.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1244
|
+
return new RegExp(`\\[mcp_servers\\.${serverName}\\.tools\\.${escapedTool}\\]`).test(config);
|
|
1245
|
+
}
|
|
1246
|
+
function missingCodexLongTableMcpTools(config) {
|
|
1247
|
+
if (!codexLongTableMcpConfigured(config)) {
|
|
1248
|
+
return [...LONGTABLE_MCP_MANAGED_TOOLS];
|
|
1249
|
+
}
|
|
1250
|
+
return LONGTABLE_MCP_MANAGED_TOOLS.filter((tool) => !codexLongTableMcpToolConfigured(config, tool));
|
|
1251
|
+
}
|
|
1252
|
+
function preserveNonLongTableSectionsFromMarkedBlock(block, serverName) {
|
|
1253
|
+
const body = block
|
|
1254
|
+
.replace(LONGTABLE_MCP_MARKER_START, "")
|
|
1255
|
+
.replace(LONGTABLE_MCP_MARKER_END, "")
|
|
1256
|
+
.trim();
|
|
1257
|
+
if (!body) {
|
|
1258
|
+
return "";
|
|
1259
|
+
}
|
|
1260
|
+
const sections = body.split(/(?=^\[[^\]]+\])/m);
|
|
1261
|
+
const serverHeader = `[mcp_servers.${serverName}]`;
|
|
1262
|
+
const toolPrefix = `[mcp_servers.${serverName}.tools.`;
|
|
1263
|
+
return sections
|
|
1264
|
+
.map((section) => section.trim())
|
|
1265
|
+
.filter(Boolean)
|
|
1266
|
+
.filter((section) => {
|
|
1267
|
+
const header = section.split(/\r?\n/, 1)[0]?.trim() ?? "";
|
|
1268
|
+
return header !== serverHeader && !header.startsWith(toolPrefix);
|
|
1269
|
+
})
|
|
1270
|
+
.join("\n\n");
|
|
1271
|
+
}
|
|
1188
1272
|
function replaceMarkedCodexMcpBlock(existing, block, serverName) {
|
|
1189
|
-
const markerPattern = new RegExp(`${LONGTABLE_MCP_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${LONGTABLE_MCP_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n
|
|
1190
|
-
const serverPattern = new RegExp(`\\n?\\[mcp_servers\\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[|$)
|
|
1191
|
-
const
|
|
1273
|
+
const markerPattern = new RegExp(`${LONGTABLE_MCP_MARKER_START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${LONGTABLE_MCP_MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`);
|
|
1274
|
+
const serverPattern = new RegExp(`\\n?\\[mcp_servers\\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\][\\s\\S]*?(?=\\n\\[|$)`);
|
|
1275
|
+
const toolPattern = new RegExp(`\\n?\\[mcp_servers\\.${serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.tools\\.[^\\]]+\\][\\s\\S]*?(?=\\n\\[|$)`, "g");
|
|
1276
|
+
const withoutMarked = existing.replace(markerPattern, (matched) => {
|
|
1277
|
+
const preserved = preserveNonLongTableSectionsFromMarkedBlock(matched, serverName);
|
|
1278
|
+
return preserved ? `${preserved}\n\n` : "";
|
|
1279
|
+
});
|
|
1280
|
+
const trimmed = withoutMarked.replace(toolPattern, "").replace(serverPattern, "").trimEnd();
|
|
1192
1281
|
return trimmed ? `${trimmed}\n\n${block}\n` : `${block}\n`;
|
|
1193
1282
|
}
|
|
1194
1283
|
async function writeCodexMcpConfig(path, block, serverName, options = {}) {
|
|
@@ -1467,6 +1556,8 @@ async function collectDoctorStatus(args) {
|
|
|
1467
1556
|
const codexMcpConfig = existsSync(codexMcpConfigPath)
|
|
1468
1557
|
? await readFile(codexMcpConfigPath, "utf8")
|
|
1469
1558
|
: "";
|
|
1559
|
+
const codexMcpPackageSpec = codexLongTableMcpPackageSpec(codexMcpConfig);
|
|
1560
|
+
const missingMcpTools = missingCodexLongTableMcpTools(codexMcpConfig);
|
|
1470
1561
|
const codexHooksPath = resolveCodexHooksPath(args);
|
|
1471
1562
|
const codexHooksContent = existsSync(codexHooksPath)
|
|
1472
1563
|
? await readFile(codexHooksPath, "utf8")
|
|
@@ -1502,6 +1593,10 @@ async function collectDoctorStatus(args) {
|
|
|
1502
1593
|
mcpConfigPath: codexMcpConfigPath,
|
|
1503
1594
|
mcpConfigExists: existsSync(codexMcpConfigPath),
|
|
1504
1595
|
longtableMcpConfigured: codexLongTableMcpConfigured(codexMcpConfig),
|
|
1596
|
+
...(codexMcpPackageSpec ? { mcpPackageSpec: codexMcpPackageSpec } : {}),
|
|
1597
|
+
expectedMcpPackageSpec: LONGTABLE_MCP_PACKAGE_SPEC,
|
|
1598
|
+
missingMcpTools,
|
|
1599
|
+
missingResearchSpecificationMcpTools: missingMcpTools.filter((tool) => LONGTABLE_MCP_RESEARCH_SPECIFICATION_TOOLS.includes(tool)),
|
|
1505
1600
|
mcpElicitationsAllowed: codexMcpElicitationsAllowed(codexMcpConfig),
|
|
1506
1601
|
hooksPath: codexHooksPath,
|
|
1507
1602
|
hooksExists: existsSync(codexHooksPath),
|
|
@@ -1547,6 +1642,11 @@ function renderDoctorStatus(status) {
|
|
|
1547
1642
|
: []),
|
|
1548
1643
|
`- MCP config: ${status.providers.codex.mcpConfigExists ? "present" : "missing"} (${status.providers.codex.mcpConfigPath})`,
|
|
1549
1644
|
`- LongTable MCP: ${status.providers.codex.longtableMcpConfigured ? "configured" : "missing"}`,
|
|
1645
|
+
`- MCP package: ${status.providers.codex.mcpPackageSpec ?? "unknown"}${status.providers.codex.mcpPackageSpec === status.providers.codex.expectedMcpPackageSpec ? "" : ` (expected ${status.providers.codex.expectedMcpPackageSpec})`}`,
|
|
1646
|
+
`- MCP managed tools: ${LONGTABLE_MCP_MANAGED_TOOLS.length - status.providers.codex.missingMcpTools.length}/${LONGTABLE_MCP_MANAGED_TOOLS.length} configured`,
|
|
1647
|
+
...(status.providers.codex.missingResearchSpecificationMcpTools.length > 0
|
|
1648
|
+
? [`- Research Specification MCP tools: missing ${status.providers.codex.missingResearchSpecificationMcpTools.join(", ")}`]
|
|
1649
|
+
: ["- Research Specification MCP tools: complete"]),
|
|
1550
1650
|
`- MCP elicitation approval: ${status.providers.codex.mcpElicitationsAllowed ? "allowed" : "not allowed"}`,
|
|
1551
1651
|
`- Codex hooks file: ${status.providers.codex.hooksExists ? "present" : "missing"} (${status.providers.codex.hooksPath})`,
|
|
1552
1652
|
`- codex_hooks feature: ${status.providers.codex.codexHooksEnabled ? "enabled" : "missing"}`,
|
|
@@ -1595,6 +1695,9 @@ function renderDoctorStatus(status) {
|
|
|
1595
1695
|
const canFix = status.providers.codex.missingSkills.length > 0 ||
|
|
1596
1696
|
status.providers.claude.missingSkills.length > 0 ||
|
|
1597
1697
|
status.providers.codex.legacyPromptFilesInstalled.length > 0 ||
|
|
1698
|
+
!status.providers.codex.longtableMcpConfigured ||
|
|
1699
|
+
status.providers.codex.mcpPackageSpec !== status.providers.codex.expectedMcpPackageSpec ||
|
|
1700
|
+
status.providers.codex.missingMcpTools.length > 0 ||
|
|
1598
1701
|
!status.providers.codex.codexHooksEnabled ||
|
|
1599
1702
|
status.providers.codex.missingManagedHookEvents.length > 0 ||
|
|
1600
1703
|
(status.setupExists &&
|
|
@@ -1605,6 +1708,11 @@ function renderDoctorStatus(status) {
|
|
|
1605
1708
|
if (!status.providers.codex.codexHooksEnabled || status.providers.codex.missingManagedHookEvents.length > 0) {
|
|
1606
1709
|
nextActions.push("longtable codex install-hooks");
|
|
1607
1710
|
}
|
|
1711
|
+
if (!status.providers.codex.longtableMcpConfigured ||
|
|
1712
|
+
status.providers.codex.mcpPackageSpec !== status.providers.codex.expectedMcpPackageSpec ||
|
|
1713
|
+
status.providers.codex.missingMcpTools.length > 0) {
|
|
1714
|
+
nextActions.push("longtable mcp install --provider codex --write");
|
|
1715
|
+
}
|
|
1608
1716
|
if (!status.setupExists) {
|
|
1609
1717
|
nextActions.push("longtable setup --provider codex");
|
|
1610
1718
|
}
|
|
@@ -1644,7 +1752,7 @@ function renderRepairSummary(repair) {
|
|
|
1644
1752
|
}
|
|
1645
1753
|
}
|
|
1646
1754
|
if (repair.writtenRuntimeConfigs.length > 0) {
|
|
1647
|
-
lines.push("- wrote
|
|
1755
|
+
lines.push("- wrote configs:");
|
|
1648
1756
|
for (const target of repair.writtenRuntimeConfigs) {
|
|
1649
1757
|
lines.push(` - ${target.provider}: ${target.path}`);
|
|
1650
1758
|
}
|
|
@@ -1700,6 +1808,21 @@ async function repairDoctorStatus(args, status) {
|
|
|
1700
1808
|
writtenRuntimeConfigs: [],
|
|
1701
1809
|
skipped: []
|
|
1702
1810
|
};
|
|
1811
|
+
const mcpRepairNeeded = !status.providers.codex.longtableMcpConfigured ||
|
|
1812
|
+
status.providers.codex.mcpPackageSpec !== status.providers.codex.expectedMcpPackageSpec ||
|
|
1813
|
+
status.providers.codex.missingMcpTools.length > 0;
|
|
1814
|
+
if (mcpRepairNeeded) {
|
|
1815
|
+
const mcpConfigPath = resolveDoctorCodexMcpConfigPath(args);
|
|
1816
|
+
const block = renderCodexMcpBlock(LONGTABLE_MCP_SERVER_NAME, "npx", ["-y", LONGTABLE_MCP_PACKAGE_SPEC]);
|
|
1817
|
+
await writeCodexMcpConfig(mcpConfigPath, block, LONGTABLE_MCP_SERVER_NAME, {
|
|
1818
|
+
enableElicitations: status.providers.codex.mcpElicitationsAllowed
|
|
1819
|
+
});
|
|
1820
|
+
repair.writtenRuntimeConfigs.push({
|
|
1821
|
+
provider: "codex",
|
|
1822
|
+
path: mcpConfigPath,
|
|
1823
|
+
format: "toml"
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1703
1826
|
if (status.providers.codex.missingSkills.length > 0) {
|
|
1704
1827
|
repair.installedCodexSkills = (await installCodexSkills(roles, codexDir, skillSurface)).map((skill) => skill.name);
|
|
1705
1828
|
}
|
|
@@ -2739,6 +2862,108 @@ async function runSearch(subcommand, args) {
|
|
|
2739
2862
|
}
|
|
2740
2863
|
console.log(renderEvidenceRunSummary(run, recordedPath));
|
|
2741
2864
|
}
|
|
2865
|
+
async function requireWorkspaceContext(args) {
|
|
2866
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2867
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2868
|
+
if (!context) {
|
|
2869
|
+
throw new Error("No LongTable workspace was found from the supplied cwd.");
|
|
2870
|
+
}
|
|
2871
|
+
return context;
|
|
2872
|
+
}
|
|
2873
|
+
async function readResearchSpecificationFile(path) {
|
|
2874
|
+
if (!path) {
|
|
2875
|
+
throw new Error("A Research Specification JSON file is required. Use --spec-file <path>.");
|
|
2876
|
+
}
|
|
2877
|
+
return JSON.parse(await readFile(resolve(path), "utf8"));
|
|
2878
|
+
}
|
|
2879
|
+
async function runSpec(subcommand, args) {
|
|
2880
|
+
const context = await requireWorkspaceContext(args);
|
|
2881
|
+
const command = subcommand ?? "read";
|
|
2882
|
+
if (command === "read" || command === "history") {
|
|
2883
|
+
const history = await readResearchSpecificationHistory(context);
|
|
2884
|
+
if (args.json === true) {
|
|
2885
|
+
console.log(JSON.stringify(history, null, 2));
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
console.log("LongTable Research Specification");
|
|
2889
|
+
console.log(`- title: ${history.specification?.title ?? "missing"}`);
|
|
2890
|
+
console.log(`- status: ${history.specification?.confirmedAt ? "confirmed" : history.specification?.status ?? "missing"}`);
|
|
2891
|
+
console.log(`- revisions: ${history.revisions.length}`);
|
|
2892
|
+
console.log(`- patches: ${history.patches.length}`);
|
|
2893
|
+
console.log(`- evidence records: ${history.evidenceRecords.length}`);
|
|
2894
|
+
for (const revision of history.revisions.slice(-5).reverse()) {
|
|
2895
|
+
console.log(`- v${revision.index}: ${revision.title} (${revision.changeSummary.slice(0, 2).join("; ")})`);
|
|
2896
|
+
}
|
|
2897
|
+
return;
|
|
2898
|
+
}
|
|
2899
|
+
if (command === "unincorporated") {
|
|
2900
|
+
const evidenceRecords = await findUnincorporatedResearchEvidence(context);
|
|
2901
|
+
if (args.json === true) {
|
|
2902
|
+
console.log(JSON.stringify({ evidenceRecords }, null, 2));
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
console.log("Unincorporated Research Evidence");
|
|
2906
|
+
for (const record of evidenceRecords.slice(-10).reverse()) {
|
|
2907
|
+
console.log(`- ${record.id} [${record.sourceKind}]: ${record.summary}`);
|
|
2908
|
+
}
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
if (command === "diff") {
|
|
2912
|
+
const specification = await readResearchSpecificationFile(typeof args["spec-file"] === "string" ? args["spec-file"] : undefined);
|
|
2913
|
+
const state = await loadWorkspaceState(context);
|
|
2914
|
+
const changes = diffResearchSpecifications(state.researchSpecification, specification);
|
|
2915
|
+
if (args.json === true) {
|
|
2916
|
+
console.log(JSON.stringify({ changes }, null, 2));
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
console.log("Research Specification Diff");
|
|
2920
|
+
for (const change of changes) {
|
|
2921
|
+
console.log(`- ${change.summary}`);
|
|
2922
|
+
}
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
if (command === "propose") {
|
|
2926
|
+
const specification = await readResearchSpecificationFile(typeof args["spec-file"] === "string" ? args["spec-file"] : undefined);
|
|
2927
|
+
const result = await proposeResearchSpecificationPatch({
|
|
2928
|
+
context,
|
|
2929
|
+
specification,
|
|
2930
|
+
source: "manual",
|
|
2931
|
+
rationale: typeof args.rationale === "string" ? args.rationale : undefined
|
|
2932
|
+
});
|
|
2933
|
+
if (args.json === true) {
|
|
2934
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
console.log("Research Specification patch proposed");
|
|
2938
|
+
console.log(`- patch: ${result.patch.id}`);
|
|
2939
|
+
console.log(`- changes: ${result.changes.length}`);
|
|
2940
|
+
console.log(`- apply: longtable spec apply --patch-id ${result.patch.id}`);
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
if (command === "apply") {
|
|
2944
|
+
const specification = typeof args["spec-file"] === "string"
|
|
2945
|
+
? await readResearchSpecificationFile(args["spec-file"])
|
|
2946
|
+
: undefined;
|
|
2947
|
+
const result = await applyResearchSpecificationPatch({
|
|
2948
|
+
context,
|
|
2949
|
+
patchId: typeof args["patch-id"] === "string" ? args["patch-id"] : undefined,
|
|
2950
|
+
specification,
|
|
2951
|
+
source: "manual",
|
|
2952
|
+
rationale: typeof args.rationale === "string" ? args.rationale : undefined
|
|
2953
|
+
});
|
|
2954
|
+
if (args.json === true) {
|
|
2955
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
console.log("Research Specification patch applied");
|
|
2959
|
+
console.log(`- revision: v${result.revision.index} (${result.revision.id})`);
|
|
2960
|
+
console.log(`- patch: ${result.patch.id}`);
|
|
2961
|
+
console.log(`- decision: ${result.decision?.id ?? result.patch.decisionRecordId ?? "existing/none"}`);
|
|
2962
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
throw new Error(`Unknown spec subcommand: ${command}`);
|
|
2966
|
+
}
|
|
2742
2967
|
async function runQuestion(args) {
|
|
2743
2968
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2744
2969
|
const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
|
|
@@ -3695,6 +3920,10 @@ async function main() {
|
|
|
3695
3920
|
await runSearch(subcommand, values);
|
|
3696
3921
|
return;
|
|
3697
3922
|
}
|
|
3923
|
+
if (command === "spec") {
|
|
3924
|
+
await runSpec(subcommand, values);
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3698
3927
|
if (command === "ask") {
|
|
3699
3928
|
await runAsk(values);
|
|
3700
3929
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, EvidenceRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionCommitmentFamily, QuestionEpistemicBasis, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionRecord, ResearchSpecificationChange, ResearchSpecificationPatch, ResearchSpecificationPatchSource, ResearchSpecificationRevision, ResearchState } from "@longtable/core";
|
|
2
2
|
import type { SetupPersistedOutput } from "@longtable/setup";
|
|
3
3
|
export type ProjectDisagreementPreference = "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
4
4
|
export type StartInterviewSignal = "phenomenon" | "audience" | "artifact" | "evidence" | "assumption" | "decision_risk" | "voice";
|
|
@@ -40,6 +40,9 @@ export interface ResearchSpecification {
|
|
|
40
40
|
createdAt?: string;
|
|
41
41
|
updatedAt?: string;
|
|
42
42
|
sourceHookId?: string;
|
|
43
|
+
latestRevisionId?: string;
|
|
44
|
+
sourceEvidenceIds?: string[];
|
|
45
|
+
sectionEvidence?: Record<string, string[]>;
|
|
43
46
|
researchDirection: {
|
|
44
47
|
question?: string;
|
|
45
48
|
purpose: string;
|
|
@@ -121,6 +124,10 @@ export type LongTableWorkspaceState = ResearchState & {
|
|
|
121
124
|
hooks?: LongTableHookRun[];
|
|
122
125
|
firstResearchShape?: FirstResearchShape;
|
|
123
126
|
researchSpecification?: ResearchSpecification;
|
|
127
|
+
interviewTurns?: LongTableInterviewTurn[];
|
|
128
|
+
evidenceRecords?: EvidenceRecord[];
|
|
129
|
+
specPatches?: ResearchSpecificationPatch[];
|
|
130
|
+
specRevisions?: ResearchSpecificationRevision[];
|
|
124
131
|
};
|
|
125
132
|
export interface LongTableProjectRecord {
|
|
126
133
|
schemaVersion: 1;
|
|
@@ -206,6 +213,10 @@ export interface LongTableWorkspaceInspection {
|
|
|
206
213
|
pendingObligations: number;
|
|
207
214
|
answeredQuestions: number;
|
|
208
215
|
decisions: number;
|
|
216
|
+
interviewTurns?: number;
|
|
217
|
+
evidenceRecords?: number;
|
|
218
|
+
specPatches?: number;
|
|
219
|
+
specRevisions?: number;
|
|
209
220
|
};
|
|
210
221
|
recentInvocations?: Array<{
|
|
211
222
|
id: string;
|
|
@@ -221,6 +232,8 @@ export interface LongTableWorkspaceInspection {
|
|
|
221
232
|
id: string;
|
|
222
233
|
title: string;
|
|
223
234
|
question: string;
|
|
235
|
+
commitmentFamily?: QuestionCommitmentFamily;
|
|
236
|
+
epistemicBasis?: QuestionEpistemicBasis;
|
|
224
237
|
options: string[];
|
|
225
238
|
required: boolean;
|
|
226
239
|
}>;
|
|
@@ -235,6 +248,8 @@ export interface LongTableWorkspaceInspection {
|
|
|
235
248
|
id: string;
|
|
236
249
|
checkpointKey: string;
|
|
237
250
|
summary: string;
|
|
251
|
+
commitmentFamily?: QuestionCommitmentFamily;
|
|
252
|
+
epistemicBasis?: QuestionEpistemicBasis;
|
|
238
253
|
selectedOption?: string;
|
|
239
254
|
timestamp: string;
|
|
240
255
|
}>;
|
|
@@ -246,6 +261,25 @@ export interface LongTableWorkspaceInspection {
|
|
|
246
261
|
}>;
|
|
247
262
|
}
|
|
248
263
|
export declare function loadWorkspaceState(context: LongTableProjectContext): Promise<LongTableWorkspaceState>;
|
|
264
|
+
export declare function diffResearchSpecifications(before: ResearchSpecification | undefined, after: ResearchSpecification): ResearchSpecificationChange[];
|
|
265
|
+
export declare function applyResearchSpecificationAuditUpdate(state: LongTableWorkspaceState, options: {
|
|
266
|
+
specification: ResearchSpecification;
|
|
267
|
+
timestamp: string;
|
|
268
|
+
source: ResearchSpecificationPatchSource;
|
|
269
|
+
title?: string;
|
|
270
|
+
rationale?: string;
|
|
271
|
+
sourceEvidenceIds?: string[];
|
|
272
|
+
patch?: ResearchSpecificationPatch;
|
|
273
|
+
questionRecordId?: string;
|
|
274
|
+
decisionRecordId?: string;
|
|
275
|
+
createDecisionRecord?: boolean;
|
|
276
|
+
}): {
|
|
277
|
+
state: LongTableWorkspaceState;
|
|
278
|
+
specification: ResearchSpecification;
|
|
279
|
+
patch: ResearchSpecificationPatch;
|
|
280
|
+
revision: ResearchSpecificationRevision;
|
|
281
|
+
decision?: DecisionRecord;
|
|
282
|
+
};
|
|
249
283
|
export declare function syncCurrentWorkspaceView(context: LongTableProjectContext): Promise<string>;
|
|
250
284
|
export declare function appendInvocationRecordToWorkspace(context: LongTableProjectContext, invocation: InvocationRecord, questions?: QuestionRecord[]): Promise<LongTableWorkspaceState>;
|
|
251
285
|
export declare function beginLongTableInterview(options: {
|
|
@@ -294,6 +328,41 @@ export declare function summarizeLongTableResearchSpecification(options: {
|
|
|
294
328
|
state: LongTableWorkspaceState;
|
|
295
329
|
session: LongTableSessionRecord;
|
|
296
330
|
}>;
|
|
331
|
+
export declare function proposeResearchSpecificationPatch(options: {
|
|
332
|
+
context: LongTableProjectContext;
|
|
333
|
+
specification: ResearchSpecification;
|
|
334
|
+
source?: ResearchSpecificationPatchSource;
|
|
335
|
+
rationale?: string;
|
|
336
|
+
sourceEvidenceIds?: string[];
|
|
337
|
+
}): Promise<{
|
|
338
|
+
patch: ResearchSpecificationPatch;
|
|
339
|
+
changes: ResearchSpecificationChange[];
|
|
340
|
+
state: LongTableWorkspaceState;
|
|
341
|
+
}>;
|
|
342
|
+
export declare function applyResearchSpecificationPatch(options: {
|
|
343
|
+
context: LongTableProjectContext;
|
|
344
|
+
patchId?: string;
|
|
345
|
+
specification?: ResearchSpecification;
|
|
346
|
+
source?: ResearchSpecificationPatchSource;
|
|
347
|
+
rationale?: string;
|
|
348
|
+
sourceEvidenceIds?: string[];
|
|
349
|
+
questionRecordId?: string;
|
|
350
|
+
decisionRecordId?: string;
|
|
351
|
+
}): Promise<{
|
|
352
|
+
patch: ResearchSpecificationPatch;
|
|
353
|
+
revision: ResearchSpecificationRevision;
|
|
354
|
+
specification: ResearchSpecification;
|
|
355
|
+
state: LongTableWorkspaceState;
|
|
356
|
+
session: LongTableSessionRecord;
|
|
357
|
+
decision?: DecisionRecord;
|
|
358
|
+
}>;
|
|
359
|
+
export declare function readResearchSpecificationHistory(context: LongTableProjectContext): Promise<{
|
|
360
|
+
specification?: ResearchSpecification;
|
|
361
|
+
revisions: ResearchSpecificationRevision[];
|
|
362
|
+
patches: ResearchSpecificationPatch[];
|
|
363
|
+
evidenceRecords: EvidenceRecord[];
|
|
364
|
+
}>;
|
|
365
|
+
export declare function findUnincorporatedResearchEvidence(context: LongTableProjectContext): Promise<EvidenceRecord[]>;
|
|
297
366
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
298
367
|
export declare function listBlockingWorkspaceObligations(context: LongTableProjectContext): Promise<LongTableQuestionObligation[]>;
|
|
299
368
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
@@ -329,6 +398,8 @@ export declare function createWorkspaceQuestion(options: {
|
|
|
329
398
|
displayReason?: string;
|
|
330
399
|
provider?: ProviderKind;
|
|
331
400
|
required?: boolean;
|
|
401
|
+
commitmentFamily?: QuestionCommitmentFamily;
|
|
402
|
+
epistemicBasis?: QuestionEpistemicBasis;
|
|
332
403
|
}): Promise<{
|
|
333
404
|
question: QuestionRecord;
|
|
334
405
|
state: ResearchState;
|