@longtable/cli 0.1.35 → 0.1.37
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 +201 -4
- package/dist/longtable-codex-native-hook.js +79 -13
- package/dist/project-session.d.ts +10 -1
- package/dist/project-session.js +208 -7
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { buildPersonaGuidance, parseInvocationDirective } from "./persona-router
|
|
|
17
17
|
import { PERSONA_DEFINITIONS, listRoleDefinitions } from "./personas.js";
|
|
18
18
|
import { buildPanelFallback, renderPanelSummary } from "./panel.js";
|
|
19
19
|
import { LONGTABLE_MANAGED_HOOK_EVENTS, codexHooksEnabled, enableCodexHooksFeature, getMissingManagedCodexHookEvents, mergeManagedCodexHooksConfig, removeManagedCodexHooks } from "./codex-hooks.js";
|
|
20
|
-
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
20
|
+
import { appendInvocationRecordToWorkspace, assertWorkspaceNotBlocked, answerWorkspaceQuestion, buildQuestionOpportunitySpecs, clearWorkspaceQuestion, createWorkspaceFollowUpQuestions, createWorkspaceQuestion, createOrUpdateProjectWorkspace, inspectProjectWorkspace, loadWorkspaceState, loadProjectContextFromDirectory, repairWorkspaceStateConsistency, renderProjectWorkspaceSummary, syncCurrentWorkspaceView } from "./project-session.js";
|
|
21
21
|
import { buildTeamDebate, buildTeamReview, renderTeamDebateSummary } from "./debate.js";
|
|
22
22
|
import { createPromptRenderer } from "./prompt-renderer.js";
|
|
23
23
|
const VALID_MODES = new Set([
|
|
@@ -45,7 +45,7 @@ const ANSI = {
|
|
|
45
45
|
green: "\u001B[32m"
|
|
46
46
|
};
|
|
47
47
|
const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
|
|
48
|
-
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.
|
|
48
|
+
const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.37";
|
|
49
49
|
const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
|
|
50
50
|
const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
|
|
51
51
|
function style(text, prefix) {
|
|
@@ -101,6 +101,7 @@ function usage() {
|
|
|
101
101
|
" longtable resume [--cwd <path>] [--json]",
|
|
102
102
|
" 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>]",
|
|
103
103
|
" 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>]",
|
|
104
|
+
" longtable audit [questions|roles] [--json]",
|
|
104
105
|
" longtable roles [--json]",
|
|
105
106
|
" longtable show [--json] [--path <file>]",
|
|
106
107
|
" longtable install [--json] [--path <file>] [--runtime-path <file>]",
|
|
@@ -149,7 +150,7 @@ function parseArgs(argv) {
|
|
|
149
150
|
const values = {};
|
|
150
151
|
let subcommand = maybeSubcommand;
|
|
151
152
|
const modeCommand = command && VALID_MODES.has(command);
|
|
152
|
-
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
153
|
+
const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "audit", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "clear-question", "panel", "decide", "sentinel", "team", "search"].includes(command);
|
|
153
154
|
let startIndex = 1;
|
|
154
155
|
if (modeCommand) {
|
|
155
156
|
subcommand = undefined;
|
|
@@ -162,6 +163,10 @@ function parseArgs(argv) {
|
|
|
162
163
|
subcommand = maybeSubcommand;
|
|
163
164
|
startIndex = 2;
|
|
164
165
|
}
|
|
166
|
+
else if (command === "audit" && maybeSubcommand && !maybeSubcommand.startsWith("--")) {
|
|
167
|
+
subcommand = maybeSubcommand;
|
|
168
|
+
startIndex = 2;
|
|
169
|
+
}
|
|
165
170
|
else if (directCommand) {
|
|
166
171
|
subcommand = undefined;
|
|
167
172
|
startIndex = 1;
|
|
@@ -1742,6 +1747,193 @@ async function runDoctor(args) {
|
|
|
1742
1747
|
}
|
|
1743
1748
|
console.log(renderDoctorStatus(status));
|
|
1744
1749
|
}
|
|
1750
|
+
const QUESTION_AUDIT_FIXTURES = [
|
|
1751
|
+
{
|
|
1752
|
+
id: "harness_philosophy",
|
|
1753
|
+
prompt: "LongTable 훅과 체크포인트가 연구자의 철학에 맞는지, 질문을 멈춰야 하는 지점에서 생성하는지 평가해줘.",
|
|
1754
|
+
expectedKinds: ["harness_design", "question_policy", "philosophical_reflection"]
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
id: "all_needed_questions",
|
|
1758
|
+
prompt: "매번 내가 필요한 질문을 해줘 라고 하지 않아도 필요한 질문을 모두 하는 에이전트로 만들고 싶어.",
|
|
1759
|
+
expectedKinds: ["question_policy"]
|
|
1760
|
+
},
|
|
1761
|
+
{
|
|
1762
|
+
id: "trust_calibration_construct",
|
|
1763
|
+
prompt: "Trust calibration에서 subjective trust와 reliance, switch to AI를 같은 측정으로 봐도 되는지 검토해줘.",
|
|
1764
|
+
expectedKinds: ["research_commitment"]
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
id: "tacit_assumption",
|
|
1768
|
+
prompt: "이 계획에는 말하지 않은 전제와 암묵적 가정이 있는 것 같아.",
|
|
1769
|
+
expectedKinds: ["tacit_assumption"]
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
id: "protected_decision_closure",
|
|
1773
|
+
prompt: "Protected decision closure pressure: measurement. User prompt: Implement the plan.",
|
|
1774
|
+
expectedKinds: ["research_commitment"]
|
|
1775
|
+
},
|
|
1776
|
+
{
|
|
1777
|
+
id: "low_stakes_copyedit",
|
|
1778
|
+
prompt: "문장 끝 공백만 정리해줘.",
|
|
1779
|
+
expectedKinds: []
|
|
1780
|
+
}
|
|
1781
|
+
];
|
|
1782
|
+
function runQuestionAudit() {
|
|
1783
|
+
const fixtures = QUESTION_AUDIT_FIXTURES.map((fixture) => {
|
|
1784
|
+
const opportunities = buildQuestionOpportunitySpecs(fixture.prompt, {
|
|
1785
|
+
includeFallback: false,
|
|
1786
|
+
autoOnly: true
|
|
1787
|
+
});
|
|
1788
|
+
const observedKinds = [...new Set(opportunities.map((opportunity) => opportunity.kind))];
|
|
1789
|
+
const failures = fixture.expectedKinds
|
|
1790
|
+
.filter((kind) => !observedKinds.includes(kind))
|
|
1791
|
+
.map((kind) => `missing expected question kind: ${kind}`);
|
|
1792
|
+
if (fixture.expectedKinds.length === 0 && observedKinds.length > 0) {
|
|
1793
|
+
failures.push(`expected no auto questions, observed: ${observedKinds.join(", ")}`);
|
|
1794
|
+
}
|
|
1795
|
+
return {
|
|
1796
|
+
id: fixture.id,
|
|
1797
|
+
prompt: fixture.prompt,
|
|
1798
|
+
expectedKinds: fixture.expectedKinds,
|
|
1799
|
+
observedKinds,
|
|
1800
|
+
passed: failures.length === 0,
|
|
1801
|
+
failures
|
|
1802
|
+
};
|
|
1803
|
+
});
|
|
1804
|
+
const passedCount = fixtures.filter((fixture) => fixture.passed).length;
|
|
1805
|
+
return {
|
|
1806
|
+
passed: passedCount === fixtures.length,
|
|
1807
|
+
fixtures,
|
|
1808
|
+
totals: {
|
|
1809
|
+
fixtureCount: fixtures.length,
|
|
1810
|
+
passedCount,
|
|
1811
|
+
failedCount: fixtures.length - passedCount
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
const REQUIRED_ROLE_SKILL_SECTIONS = [
|
|
1816
|
+
"## Purpose",
|
|
1817
|
+
"## Role Focus",
|
|
1818
|
+
"## Must-Ask Questions",
|
|
1819
|
+
"## Stop Conditions",
|
|
1820
|
+
"## Output Contract",
|
|
1821
|
+
"## Anti-Patterns",
|
|
1822
|
+
"## Rules"
|
|
1823
|
+
];
|
|
1824
|
+
function buildRoleAuditEntry(provider, spec) {
|
|
1825
|
+
const body = spec.body.join("\n");
|
|
1826
|
+
const missingSections = REQUIRED_ROLE_SKILL_SECTIONS.filter((section) => !body.includes(section));
|
|
1827
|
+
const warnings = [];
|
|
1828
|
+
if (spec.body.length < 35) {
|
|
1829
|
+
warnings.push("role skill is too thin to carry a distinct research perspective");
|
|
1830
|
+
}
|
|
1831
|
+
if (!body.includes("Researcher Checkpoint")) {
|
|
1832
|
+
warnings.push("role skill does not explicitly mention Researcher Checkpoint behavior");
|
|
1833
|
+
}
|
|
1834
|
+
if (!body.includes("evidence")) {
|
|
1835
|
+
warnings.push("role skill does not explicitly mention evidence needs");
|
|
1836
|
+
}
|
|
1837
|
+
return {
|
|
1838
|
+
name: spec.name,
|
|
1839
|
+
provider,
|
|
1840
|
+
lineCount: spec.body.length,
|
|
1841
|
+
missingSections,
|
|
1842
|
+
warnings
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
function runRoleAudit() {
|
|
1846
|
+
const baseSkillNames = new Set([
|
|
1847
|
+
"longtable",
|
|
1848
|
+
"longtable-interview",
|
|
1849
|
+
"longtable-panel",
|
|
1850
|
+
"longtable-explore",
|
|
1851
|
+
"longtable-review"
|
|
1852
|
+
]);
|
|
1853
|
+
const roles = [
|
|
1854
|
+
...buildCodexSkillSpecs(listRoleDefinitions())
|
|
1855
|
+
.filter((spec) => !baseSkillNames.has(spec.name))
|
|
1856
|
+
.map((spec) => buildRoleAuditEntry("codex", spec)),
|
|
1857
|
+
...buildClaudeSkillSpecs(listRoleDefinitions())
|
|
1858
|
+
.filter((spec) => !baseSkillNames.has(spec.name))
|
|
1859
|
+
.map((spec) => buildRoleAuditEntry("claude", spec))
|
|
1860
|
+
];
|
|
1861
|
+
const passedCount = roles.filter((role) => role.missingSections.length === 0 && role.warnings.length === 0).length;
|
|
1862
|
+
return {
|
|
1863
|
+
passed: passedCount === roles.length,
|
|
1864
|
+
roles,
|
|
1865
|
+
totals: {
|
|
1866
|
+
roleCount: roles.length,
|
|
1867
|
+
passedCount,
|
|
1868
|
+
failedCount: roles.length - passedCount
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function renderQuestionAudit(result) {
|
|
1873
|
+
const lines = [
|
|
1874
|
+
"LongTable question audit",
|
|
1875
|
+
`Result: ${result.passed ? "passed" : "failed"} (${result.totals.passedCount}/${result.totals.fixtureCount})`,
|
|
1876
|
+
""
|
|
1877
|
+
];
|
|
1878
|
+
for (const fixture of result.fixtures) {
|
|
1879
|
+
lines.push(`- ${fixture.id}: ${fixture.passed ? "passed" : "failed"}`);
|
|
1880
|
+
lines.push(` expected: ${fixture.expectedKinds.length > 0 ? fixture.expectedKinds.join(", ") : "none"}`);
|
|
1881
|
+
lines.push(` observed: ${fixture.observedKinds.length > 0 ? fixture.observedKinds.join(", ") : "none"}`);
|
|
1882
|
+
for (const failure of fixture.failures) {
|
|
1883
|
+
lines.push(` failure: ${failure}`);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
return lines.join("\n");
|
|
1887
|
+
}
|
|
1888
|
+
function renderRoleAudit(result) {
|
|
1889
|
+
const lines = [
|
|
1890
|
+
"LongTable role skill audit",
|
|
1891
|
+
`Result: ${result.passed ? "passed" : "failed"} (${result.totals.passedCount}/${result.totals.roleCount})`,
|
|
1892
|
+
""
|
|
1893
|
+
];
|
|
1894
|
+
for (const role of result.roles) {
|
|
1895
|
+
const passed = role.missingSections.length === 0 && role.warnings.length === 0;
|
|
1896
|
+
lines.push(`- ${role.provider}:${role.name}: ${passed ? "passed" : "failed"} (${role.lineCount} lines)`);
|
|
1897
|
+
if (role.missingSections.length > 0) {
|
|
1898
|
+
lines.push(` missing: ${role.missingSections.join(", ")}`);
|
|
1899
|
+
}
|
|
1900
|
+
for (const warning of role.warnings) {
|
|
1901
|
+
lines.push(` warning: ${warning}`);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
return lines.join("\n");
|
|
1905
|
+
}
|
|
1906
|
+
async function runAudit(subcommand, args) {
|
|
1907
|
+
if (subcommand === "questions" || subcommand === "question") {
|
|
1908
|
+
const result = runQuestionAudit();
|
|
1909
|
+
console.log(args.json === true ? JSON.stringify(result, null, 2) : renderQuestionAudit(result));
|
|
1910
|
+
if (!result.passed) {
|
|
1911
|
+
exit(1);
|
|
1912
|
+
}
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
if (subcommand === "roles" || subcommand === "role") {
|
|
1916
|
+
const result = runRoleAudit();
|
|
1917
|
+
console.log(args.json === true ? JSON.stringify(result, null, 2) : renderRoleAudit(result));
|
|
1918
|
+
if (!result.passed) {
|
|
1919
|
+
exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
const questions = runQuestionAudit();
|
|
1924
|
+
const roles = runRoleAudit();
|
|
1925
|
+
const result = {
|
|
1926
|
+
passed: questions.passed && roles.passed,
|
|
1927
|
+
questions,
|
|
1928
|
+
roles
|
|
1929
|
+
};
|
|
1930
|
+
console.log(args.json === true
|
|
1931
|
+
? JSON.stringify(result, null, 2)
|
|
1932
|
+
: [renderQuestionAudit(questions), "", renderRoleAudit(roles)].join("\n"));
|
|
1933
|
+
if (!result.passed) {
|
|
1934
|
+
exit(1);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1745
1937
|
async function runCodexPersistInit(args) {
|
|
1746
1938
|
const { flow, provider, answers } = await readPersistAnswers(args);
|
|
1747
1939
|
const outputValue = createPersistedSetupOutput(answers, provider, flow);
|
|
@@ -2627,7 +2819,8 @@ async function runAutomaticFollowUpIfNeeded(prompt, args) {
|
|
|
2627
2819
|
context,
|
|
2628
2820
|
prompt,
|
|
2629
2821
|
provider,
|
|
2630
|
-
required: true
|
|
2822
|
+
required: true,
|
|
2823
|
+
auto: true
|
|
2631
2824
|
});
|
|
2632
2825
|
if (result.questions.length === 0) {
|
|
2633
2826
|
return false;
|
|
@@ -3239,6 +3432,10 @@ async function main() {
|
|
|
3239
3432
|
await runDoctor(values);
|
|
3240
3433
|
return;
|
|
3241
3434
|
}
|
|
3435
|
+
if (command === "audit") {
|
|
3436
|
+
await runAudit(subcommand, values);
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3242
3439
|
if (command === "roles") {
|
|
3243
3440
|
await runRoles(values);
|
|
3244
3441
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
|
-
import { loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
|
|
2
|
+
import { createWorkspaceFollowUpQuestions, loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
|
|
3
3
|
function safeString(value) {
|
|
4
4
|
return typeof value === "string" ? value : "";
|
|
5
5
|
}
|
|
@@ -110,6 +110,37 @@ function looksLikeClosurePrompt(prompt) {
|
|
|
110
110
|
return /\b(final|finalize|commit|ship|submit|revise|rewrite|draft|publish|implement|fix)\b/i.test(normalized)
|
|
111
111
|
|| /최종|확정|커밋|제출|수정|초안|구현|진행|고쳐/.test(normalized);
|
|
112
112
|
}
|
|
113
|
+
function looksLikeExecutionDirective(prompt) {
|
|
114
|
+
const normalized = prompt.trim();
|
|
115
|
+
if (!normalized) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return /\b(proceed|implement|fix|publish|release|deploy|tag|push|ship)\b/i.test(normalized)
|
|
119
|
+
|| /진행|구현|수정|고쳐|배포|릴리즈|태그|푸시|출시/.test(normalized);
|
|
120
|
+
}
|
|
121
|
+
function looksLikeLongTableEngineeringPrompt(prompt) {
|
|
122
|
+
const normalized = prompt.trim();
|
|
123
|
+
if (!normalized) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
return /\b(longtable|hook|checkpoint|mcp|agent|npm|version|global|publish|release|deploy|git)\b/i.test(normalized)
|
|
127
|
+
|| /롱테이블|훅|체크포인트|에이전트|글로벌|배포|버전|릴리즈|깃|깃허브/.test(normalized);
|
|
128
|
+
}
|
|
129
|
+
function shouldAutoCreateQuestionsForPrompt(prompt) {
|
|
130
|
+
return !(looksLikeExecutionDirective(prompt) && looksLikeLongTableEngineeringPrompt(prompt));
|
|
131
|
+
}
|
|
132
|
+
function shouldApplyProtectedDecisionClosure(runtime, prompt) {
|
|
133
|
+
return Boolean(runtime.context.session.protectedDecision) &&
|
|
134
|
+
looksLikeClosurePrompt(prompt) &&
|
|
135
|
+
!looksLikeLongTableEngineeringPrompt(prompt);
|
|
136
|
+
}
|
|
137
|
+
function protectedDecisionClosurePrompt(prompt) {
|
|
138
|
+
return [
|
|
139
|
+
"Protected decision closure pressure.",
|
|
140
|
+
"The protected decision text is stored in the current session.",
|
|
141
|
+
`User prompt: ${prompt}`
|
|
142
|
+
].join("\n");
|
|
143
|
+
}
|
|
113
144
|
function isStateChangingBash(command) {
|
|
114
145
|
const normalized = command.trim();
|
|
115
146
|
if (!normalized) {
|
|
@@ -153,9 +184,24 @@ function buildPendingQuestionContext(question) {
|
|
|
153
184
|
return [
|
|
154
185
|
`Required Researcher Checkpoint is still pending: ${question.prompt.question}`,
|
|
155
186
|
`Options: ${formatQuestionOptions(question)}`,
|
|
156
|
-
`Record it with longtable decide --question ${question.id} --answer <value> if you are outside MCP elicitation
|
|
187
|
+
`Record it with longtable decide --question ${question.id} --answer <value> if you are outside MCP elicitation.`,
|
|
188
|
+
"Do not choose or record an answer unless the researcher explicitly provides the selection."
|
|
157
189
|
].join("\n");
|
|
158
190
|
}
|
|
191
|
+
function buildGeneratedQuestionsContext(questions, created) {
|
|
192
|
+
const lines = [
|
|
193
|
+
created
|
|
194
|
+
? `LongTable created ${questions.length} required Researcher Checkpoint${questions.length === 1 ? "" : "s"} for this prompt.`
|
|
195
|
+
: `LongTable found ${questions.length} pending Researcher Checkpoint${questions.length === 1 ? "" : "s"} for this prompt.`
|
|
196
|
+
];
|
|
197
|
+
for (const question of questions) {
|
|
198
|
+
lines.push(`- ${question.prompt.title}: ${question.prompt.question}`);
|
|
199
|
+
lines.push(` Options: ${formatQuestionOptions(question)}`);
|
|
200
|
+
lines.push(` Record it with longtable decide --question ${question.id} --answer <value> if you are outside MCP elicitation.`);
|
|
201
|
+
}
|
|
202
|
+
lines.push("Do not choose or record answers for these checkpoints unless the researcher explicitly provides the selections.");
|
|
203
|
+
return lines.join("\n");
|
|
204
|
+
}
|
|
159
205
|
function buildPendingObligationContext(obligation) {
|
|
160
206
|
return [
|
|
161
207
|
`Pending LongTable research obligation: ${obligation.prompt}`,
|
|
@@ -192,7 +238,7 @@ function sessionStartContext(runtime) {
|
|
|
192
238
|
sections.push("Treat `.longtable/` state and `CURRENT.md` as the source of truth for this workspace.");
|
|
193
239
|
return sections.filter(Boolean).join("\n\n");
|
|
194
240
|
}
|
|
195
|
-
function userPromptSubmitContext(runtime, prompt) {
|
|
241
|
+
async function userPromptSubmitContext(runtime, prompt) {
|
|
196
242
|
const blockingQuestion = pendingRequiredQuestions(runtime.state)[0];
|
|
197
243
|
if (blockingQuestion) {
|
|
198
244
|
return buildPendingQuestionContext(blockingQuestion);
|
|
@@ -205,7 +251,34 @@ function userPromptSubmitContext(runtime, prompt) {
|
|
|
205
251
|
if (interview) {
|
|
206
252
|
return buildActiveInterviewContext(interview);
|
|
207
253
|
}
|
|
208
|
-
|
|
254
|
+
const generatedQuestions = [];
|
|
255
|
+
let createdQuestions = false;
|
|
256
|
+
if (shouldAutoCreateQuestionsForPrompt(prompt)) {
|
|
257
|
+
const generated = await createWorkspaceFollowUpQuestions({
|
|
258
|
+
context: runtime.context,
|
|
259
|
+
prompt,
|
|
260
|
+
provider: "codex",
|
|
261
|
+
required: true,
|
|
262
|
+
auto: true
|
|
263
|
+
});
|
|
264
|
+
generatedQuestions.push(...generated.questions);
|
|
265
|
+
createdQuestions = createdQuestions || generated.created;
|
|
266
|
+
}
|
|
267
|
+
if (shouldApplyProtectedDecisionClosure(runtime, prompt)) {
|
|
268
|
+
const protectedGenerated = await createWorkspaceFollowUpQuestions({
|
|
269
|
+
context: runtime.context,
|
|
270
|
+
prompt: protectedDecisionClosurePrompt(prompt),
|
|
271
|
+
provider: "codex",
|
|
272
|
+
required: true,
|
|
273
|
+
auto: true
|
|
274
|
+
});
|
|
275
|
+
generatedQuestions.push(...protectedGenerated.questions.filter((question) => !generatedQuestions.some((existing) => existing.id === question.id)));
|
|
276
|
+
createdQuestions = createdQuestions || protectedGenerated.created;
|
|
277
|
+
}
|
|
278
|
+
if (generatedQuestions.length > 0) {
|
|
279
|
+
return buildGeneratedQuestionsContext(generatedQuestions, createdQuestions);
|
|
280
|
+
}
|
|
281
|
+
if (shouldApplyProtectedDecisionClosure(runtime, prompt)) {
|
|
209
282
|
return [
|
|
210
283
|
`This workspace marks ${runtime.context.session.protectedDecision} as a protected decision.`,
|
|
211
284
|
"Before you settle it through drafting, revision, or closure, surface one researcher-facing checkpoint grounded in the current blocker or open questions."
|
|
@@ -251,14 +324,7 @@ function postToolUseOutput(runtime, payload) {
|
|
|
251
324
|
return null;
|
|
252
325
|
}
|
|
253
326
|
function stopOutput(runtime) {
|
|
254
|
-
|
|
255
|
-
if (blockingQuestion) {
|
|
256
|
-
return buildStopBlockOutput("A required LongTable Researcher Checkpoint is still pending.");
|
|
257
|
-
}
|
|
258
|
-
const blockingObligation = pendingObligations(runtime.state)[0];
|
|
259
|
-
if (blockingObligation) {
|
|
260
|
-
return buildStopBlockOutput("A LongTable research obligation is still pending.");
|
|
261
|
-
}
|
|
327
|
+
void runtime;
|
|
262
328
|
return null;
|
|
263
329
|
}
|
|
264
330
|
export async function dispatchCodexHook(payload, cwdOverride) {
|
|
@@ -274,7 +340,7 @@ export async function dispatchCodexHook(payload, cwdOverride) {
|
|
|
274
340
|
return buildAdditionalContextOutput(hookEventName, sessionStartContext(runtime));
|
|
275
341
|
}
|
|
276
342
|
if (hookEventName === "UserPromptSubmit") {
|
|
277
|
-
const additionalContext = userPromptSubmitContext(runtime, readPromptText(payload));
|
|
343
|
+
const additionalContext = await userPromptSubmitContext(runtime, readPromptText(payload));
|
|
278
344
|
return additionalContext
|
|
279
345
|
? buildAdditionalContextOutput(hookEventName, additionalContext)
|
|
280
346
|
: null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DecisionRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionSurface, QuestionRecord, ResearchState } from "@longtable/core";
|
|
1
|
+
import type { DecisionRecord, InvocationRecord, LongTableQuestionObligation, ProviderKind, QuestionOption, QuestionGenerationResult, QuestionOpportunity, QuestionSurface, QuestionRecord, 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";
|
|
@@ -223,12 +223,20 @@ export declare function summarizeLongTableInterview(options: {
|
|
|
223
223
|
export declare function listBlockingWorkspaceQuestions(context: LongTableProjectContext): Promise<QuestionRecord[]>;
|
|
224
224
|
export declare function listBlockingWorkspaceObligations(context: LongTableProjectContext): Promise<LongTableQuestionObligation[]>;
|
|
225
225
|
export declare function assertWorkspaceNotBlocked(context: LongTableProjectContext): Promise<void>;
|
|
226
|
+
type FollowUpQuestionSpec = QuestionOpportunity;
|
|
227
|
+
interface BuildFollowUpQuestionOptions {
|
|
228
|
+
includeFallback?: boolean;
|
|
229
|
+
autoOnly?: boolean;
|
|
230
|
+
}
|
|
231
|
+
export declare function buildQuestionOpportunitySpecs(prompt: string, options?: BuildFollowUpQuestionOptions): FollowUpQuestionSpec[];
|
|
232
|
+
export declare function generateQuestionOpportunities(prompt: string, options?: BuildFollowUpQuestionOptions): QuestionGenerationResult;
|
|
226
233
|
export declare function createWorkspaceFollowUpQuestions(options: {
|
|
227
234
|
context: LongTableProjectContext;
|
|
228
235
|
prompt: string;
|
|
229
236
|
provider?: ProviderKind;
|
|
230
237
|
required?: boolean;
|
|
231
238
|
force?: boolean;
|
|
239
|
+
auto?: boolean;
|
|
232
240
|
}): Promise<{
|
|
233
241
|
questions: QuestionRecord[];
|
|
234
242
|
state: ResearchState;
|
|
@@ -291,3 +299,4 @@ export declare function createOrUpdateProjectWorkspace(options: {
|
|
|
291
299
|
export declare function loadProjectContextFromDirectory(startPath: string): Promise<LongTableProjectContext | null>;
|
|
292
300
|
export declare function inspectProjectWorkspace(startPath: string): Promise<LongTableWorkspaceInspection>;
|
|
293
301
|
export declare function renderProjectWorkspaceSummary(context: LongTableProjectContext): string;
|
|
302
|
+
export {};
|
package/dist/project-session.js
CHANGED
|
@@ -984,17 +984,182 @@ function includesAny(prompt, patterns) {
|
|
|
984
984
|
function followUpQuestionOptions(first, second, third, fourth) {
|
|
985
985
|
return [first, second, third, ...(fourth ? [fourth] : [])];
|
|
986
986
|
}
|
|
987
|
-
function
|
|
987
|
+
export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
988
988
|
const normalized = prompt.toLowerCase();
|
|
989
989
|
const specs = [];
|
|
990
990
|
function push(spec) {
|
|
991
991
|
if (!specs.some((candidate) => candidate.key === spec.key)) {
|
|
992
|
-
specs.push(
|
|
992
|
+
specs.push({
|
|
993
|
+
required: true,
|
|
994
|
+
confidence: "medium",
|
|
995
|
+
autoEligible: false,
|
|
996
|
+
cues: [],
|
|
997
|
+
...spec
|
|
998
|
+
});
|
|
993
999
|
}
|
|
994
1000
|
}
|
|
1001
|
+
if (includesAny(normalized, [
|
|
1002
|
+
/\blongtable\b/,
|
|
1003
|
+
/\bharness\b/,
|
|
1004
|
+
/\bhook\b/,
|
|
1005
|
+
/\bcheckpoint\b/,
|
|
1006
|
+
/\bagents?\b/,
|
|
1007
|
+
/\bmcp\b/,
|
|
1008
|
+
/\bover[- ]?engineer/,
|
|
1009
|
+
/롱테이블|하네스|훅|체크포인트|에이전트|오버\s*엔지니어링|과설계/
|
|
1010
|
+
])) {
|
|
1011
|
+
push({
|
|
1012
|
+
key: "harness_question_harness",
|
|
1013
|
+
kind: "harness_design",
|
|
1014
|
+
title: "Question harness target",
|
|
1015
|
+
question: "Which LongTable failure should the question system optimize against first?",
|
|
1016
|
+
whyNow: "Harness changes can optimize for visible activity while still missing the moments where researcher judgment should stop the run.",
|
|
1017
|
+
options: followUpQuestionOptions({ value: "missing_questions", label: "Missing necessary questions", description: "Prioritize stopping at unstated assumptions, construct collapse, or closure pressure.", recommended: true }, { value: "false_interruptions", label: "Unhelpful interruptions", description: "Reduce questions that slow execution without protecting a real research judgment." }, { value: "role_surface_quality", label: "Role and skill quality", description: "Audit whether agents and markdown skills ask the right questions for their roles." }, { value: "state_traceability", label: "State traceability", description: "Prioritize durable question, answer, and checkpoint records over UI convenience." }),
|
|
1018
|
+
confidence: "high",
|
|
1019
|
+
autoEligible: true,
|
|
1020
|
+
cues: ["longtable", "harness", "hook", "checkpoint", "agent", "mcp"]
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
if (includesAny(normalized, [
|
|
1024
|
+
/\bneeded questions?\b/,
|
|
1025
|
+
/\bnecessary questions?\b/,
|
|
1026
|
+
/\bask (all|every|more)\b/,
|
|
1027
|
+
/\bquestion generation\b/,
|
|
1028
|
+
/\bclarifying questions?\b/,
|
|
1029
|
+
/필요한\s*질문|질문을\s*(모두|많이|생성)|질문.*(?:생성|멈춰|지점)|질문이\s*모두|질문\s*생성|물어봐|질문해/
|
|
1030
|
+
])) {
|
|
1031
|
+
push({
|
|
1032
|
+
key: "needed_question_policy",
|
|
1033
|
+
kind: "question_policy",
|
|
1034
|
+
title: "Question policy",
|
|
1035
|
+
question: "When LongTable detects missing context, what questioning rule should it follow before continuing?",
|
|
1036
|
+
whyNow: "A question-heavy agent needs a policy for when questions are required, batched, advisory, or too disruptive.",
|
|
1037
|
+
options: followUpQuestionOptions({ value: "ask_required_first", label: "Ask required blockers first", description: "Stop only for questions that protect a real decision or construct boundary.", recommended: true }, { value: "batch_all_questions", label: "Batch all plausible questions", description: "Generate a fuller question queue even if some items are advisory." }, { value: "continue_with_assumptions", label: "Continue with assumptions", description: "Proceed but record assumptions and unresolved questions explicitly." }),
|
|
1038
|
+
confidence: "high",
|
|
1039
|
+
autoEligible: true,
|
|
1040
|
+
cues: ["needed_questions", "question_generation"]
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (includesAny(normalized, [
|
|
1044
|
+
/\bphilosoph/i,
|
|
1045
|
+
/\breflect(ion|ive)?\b/,
|
|
1046
|
+
/\bfundamental questions?\b/,
|
|
1047
|
+
/\bprinciple\b/,
|
|
1048
|
+
/\bagency\b/,
|
|
1049
|
+
/철학|성찰|근본(?:적)?\s*질문|원칙|내\s*철학|연구자성|주체성/
|
|
1050
|
+
])) {
|
|
1051
|
+
push({
|
|
1052
|
+
key: "philosophical_checkpoint_boundary",
|
|
1053
|
+
kind: "philosophical_reflection",
|
|
1054
|
+
title: "Checkpoint philosophy",
|
|
1055
|
+
question: "What should a checkpoint philosophically protect in this LongTable run?",
|
|
1056
|
+
whyNow: "A checkpoint can either protect researcher agency or become a procedural interruption; LongTable should not choose that philosophy silently.",
|
|
1057
|
+
options: followUpQuestionOptions({ value: "researcher_agency", label: "Researcher agency", description: "Stop when LongTable is about to decide for the researcher.", recommended: true }, { value: "construct_integrity", label: "Construct integrity", description: "Stop when distinct ideas may be collapsed into one claim." }, { value: "usability_burden", label: "Usability burden", description: "Stop only when the benefit justifies interrupting the workflow." }),
|
|
1058
|
+
autoEligible: true,
|
|
1059
|
+
cues: ["philosophy", "reflection", "researcher_agency"]
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
if (includesAny(normalized, [
|
|
1063
|
+
/\bprotected decision\b/,
|
|
1064
|
+
/\bclosure pressure\b/,
|
|
1065
|
+
/\bsettle\b/,
|
|
1066
|
+
/\bfinalize\b/,
|
|
1067
|
+
/\bcommit\b/,
|
|
1068
|
+
/보호된\s*결정|확정|최종|마무리|결정\s*압력/
|
|
1069
|
+
])) {
|
|
1070
|
+
push({
|
|
1071
|
+
key: "protected_decision_closure",
|
|
1072
|
+
kind: "research_commitment",
|
|
1073
|
+
title: "Protected decision closure",
|
|
1074
|
+
question: "What should LongTable do with the protected decision before proceeding?",
|
|
1075
|
+
whyNow: "The prompt creates closure pressure around a decision the workspace says should not settle silently.",
|
|
1076
|
+
options: followUpQuestionOptions({ value: "keep_open", label: "Keep it open", description: "Do not treat the decision as settled; preserve it as an explicit blocker.", recommended: true }, { value: "ask_researcher", label: "Ask the researcher now", description: "Pause execution until the researcher chooses the decision boundary." }, { value: "proceed_with_record", label: "Proceed with record", description: "Continue only after recording the assumption and residual risk." }),
|
|
1077
|
+
confidence: "high",
|
|
1078
|
+
autoEligible: true,
|
|
1079
|
+
cues: ["protected_decision", "closure_pressure"]
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
if (includesAny(normalized, [
|
|
1083
|
+
/\btrust\b/,
|
|
1084
|
+
/\breliance\b/,
|
|
1085
|
+
/\bcalibration\b/,
|
|
1086
|
+
/\bconstruct\b/,
|
|
1087
|
+
/\bmeasurement\b/,
|
|
1088
|
+
/\bdefinition\b/,
|
|
1089
|
+
/신뢰|의존|의존성|캘리브레이션|교정|보정|개념|구성개념|측정|정의/
|
|
1090
|
+
])) {
|
|
1091
|
+
push({
|
|
1092
|
+
key: "construct_boundary_commitment",
|
|
1093
|
+
kind: "research_commitment",
|
|
1094
|
+
title: "Construct boundary",
|
|
1095
|
+
question: "Which construct boundary should LongTable keep explicit before treating the direction as settled?",
|
|
1096
|
+
whyNow: "Trust, reliance, calibration, and measurement choices can look compatible while requiring different evidence and analysis rules.",
|
|
1097
|
+
options: followUpQuestionOptions({ value: "separate_trust_reliance", label: "Separate trust from reliance", description: "Treat subjective trust as a predictor or correlate rather than the same thing as reliance.", recommended: true }, { value: "condition_calibration", label: "Condition calibration carefully", description: "Define calibration using AI correctness and participant correctness instead of switch behavior alone." }, { value: "hold_construct_open", label: "Keep construct open", description: "Do not settle the construct boundary until evidence or design constraints are clearer." }),
|
|
1098
|
+
confidence: "high",
|
|
1099
|
+
autoEligible: true,
|
|
1100
|
+
cues: ["trust", "reliance", "calibration", "construct", "measurement"]
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (includesAny(normalized, [
|
|
1104
|
+
/\bknowledge gap\b/,
|
|
1105
|
+
/\bgap\b/,
|
|
1106
|
+
/\bunknown\b/,
|
|
1107
|
+
/\buncertain\b/,
|
|
1108
|
+
/\bnot sure\b/,
|
|
1109
|
+
/\bblocker\b/,
|
|
1110
|
+
/지식\s*공백|모르겠|불확실|블로커|막히|애매/
|
|
1111
|
+
])) {
|
|
1112
|
+
push({
|
|
1113
|
+
key: "knowledge_gap_probe",
|
|
1114
|
+
kind: "knowledge_gap",
|
|
1115
|
+
title: "Knowledge gap",
|
|
1116
|
+
question: "Which unknown should LongTable resolve before it recommends or implements a direction?",
|
|
1117
|
+
whyNow: "The request signals uncertainty; moving directly to advice can hide the real blocker.",
|
|
1118
|
+
options: followUpQuestionOptions({ value: "scope_gap", label: "Scope is unclear", description: "Clarify what is included, excluded, and high stakes.", recommended: true }, { value: "evidence_gap", label: "Evidence is missing", description: "Clarify what sources or data should anchor the answer." }, { value: "decision_gap", label: "Decision is unsettled", description: "Clarify the researcher judgment that must stay open." }),
|
|
1119
|
+
autoEligible: true,
|
|
1120
|
+
cues: ["knowledge_gap", "uncertainty", "blocker"]
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
if (includesAny(normalized, [
|
|
1124
|
+
/\bassumption\b/,
|
|
1125
|
+
/\bimplicit\b/,
|
|
1126
|
+
/\btacit\b/,
|
|
1127
|
+
/\bunstated\b/,
|
|
1128
|
+
/전제|가정|암묵|묵시|숨은\s*가정|말하지\s*않은/
|
|
1129
|
+
])) {
|
|
1130
|
+
push({
|
|
1131
|
+
key: "tacit_assumption_probe",
|
|
1132
|
+
kind: "tacit_assumption",
|
|
1133
|
+
title: "Tacit assumption",
|
|
1134
|
+
question: "Which assumption should LongTable make explicit before proceeding?",
|
|
1135
|
+
whyNow: "Tacit assumptions often determine the answer while remaining invisible in the artifact.",
|
|
1136
|
+
options: followUpQuestionOptions({ value: "researcher_intent", label: "Researcher intent", description: "Clarify the user's intended boundary or priority.", recommended: true }, { value: "evidence_standard", label: "Evidence standard", description: "Clarify what would count as adequate support." }, { value: "system_behavior", label: "System behavior", description: "Clarify what the agent or hook should do at runtime." }),
|
|
1137
|
+
autoEligible: true,
|
|
1138
|
+
cues: ["assumption", "implicit", "tacit"]
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
if (includesAny(normalized, [
|
|
1142
|
+
/\btrade[- ]?off\b/,
|
|
1143
|
+
/\bconflict\b/,
|
|
1144
|
+
/\btension\b/,
|
|
1145
|
+
/\bvs\.?\b/,
|
|
1146
|
+
/갈등|긴장|상충|균형|대립|비교/
|
|
1147
|
+
])) {
|
|
1148
|
+
push({
|
|
1149
|
+
key: "value_conflict_boundary",
|
|
1150
|
+
kind: "value_conflict",
|
|
1151
|
+
title: "Value conflict",
|
|
1152
|
+
question: "Which tradeoff should LongTable keep visible instead of resolving silently?",
|
|
1153
|
+
whyNow: "Competing values such as rigor, usability, speed, and authorship require researcher judgment.",
|
|
1154
|
+
options: followUpQuestionOptions({ value: "rigor_over_speed", label: "Rigor over speed", description: "Ask more before changing the research direction.", recommended: true }, { value: "usability_over_coverage", label: "Usability over coverage", description: "Limit questions to those that unblock the current task." }, { value: "record_disagreement", label: "Record disagreement", description: "Proceed only after preserving the conflict in state or output." }),
|
|
1155
|
+
autoEligible: true,
|
|
1156
|
+
cues: ["tradeoff", "conflict", "tension"]
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
995
1159
|
if (includesAny(normalized, [/\brubrics?\b/, /루브릭|채점기준/])) {
|
|
996
1160
|
push({
|
|
997
1161
|
key: "rubric_update_basis",
|
|
1162
|
+
kind: "research_commitment",
|
|
998
1163
|
title: "Rubric update basis",
|
|
999
1164
|
question: "How should LongTable use the available materials to update the rubric?",
|
|
1000
1165
|
whyNow: "Rubric updates can silently change grading criteria if LongTable guesses the calibration basis.",
|
|
@@ -1004,6 +1169,7 @@ function buildFollowUpQuestionSpecs(prompt) {
|
|
|
1004
1169
|
if (includesAny(normalized, [/\bexemplar\b/, /\bbest submission\b/, /\bselected submission\b/, /\bTA\b/i, /우수\s*답안|예시|선정|조교/])) {
|
|
1005
1170
|
push({
|
|
1006
1171
|
key: "exemplar_use",
|
|
1172
|
+
kind: "evidence_risk",
|
|
1007
1173
|
title: "Exemplar use",
|
|
1008
1174
|
question: "How should LongTable use selected exemplars or TA guidance?",
|
|
1009
1175
|
whyNow: "Exemplars can either calibrate criteria privately or become visible evidence inside the output.",
|
|
@@ -1013,6 +1179,7 @@ function buildFollowUpQuestionSpecs(prompt) {
|
|
|
1013
1179
|
if (includesAny(normalized, [/\binstruction/, /\bguidance\b/, /\bsource\b/, /\bfile\b/, /\bdocx?\b/, /지침|가이드|문서|파일|자료/])) {
|
|
1014
1180
|
push({
|
|
1015
1181
|
key: "source_authority",
|
|
1182
|
+
kind: "source_authority",
|
|
1016
1183
|
title: "Source authority",
|
|
1017
1184
|
question: "If sources conflict or leave gaps, which source should LongTable privilege?",
|
|
1018
1185
|
whyNow: "Without an authority rule, LongTable may resolve conflicts by convenience rather than researcher intent.",
|
|
@@ -1022,6 +1189,7 @@ function buildFollowUpQuestionSpecs(prompt) {
|
|
|
1022
1189
|
if (includesAny(normalized, [/\bdeliver\b/, /\boutput\b/, /\btracked?[- ]?change/, /\bdocx?\b/, /\bmarkdown\b/, /\btable\b/, /전달|산출물|결과물|수정\s*표시|트랙|형식|포맷/])) {
|
|
1023
1190
|
push({
|
|
1024
1191
|
key: "delivery_format",
|
|
1192
|
+
kind: "delivery_format",
|
|
1025
1193
|
title: "Delivery format",
|
|
1026
1194
|
question: "How should LongTable deliver the clarified output?",
|
|
1027
1195
|
whyNow: "Format and change-tracking choices affect whether the result is usable for review or handoff.",
|
|
@@ -1031,6 +1199,7 @@ function buildFollowUpQuestionSpecs(prompt) {
|
|
|
1031
1199
|
if (includesAny(normalized, [/\bupdate\b/, /\bchange\b/, /\bedit\b/, /\bfix\b/, /\bimplement\b/, /\bbuild\b/, /\bcreate\b/, /업데이트|수정|변경|구현|만들|고쳐/])) {
|
|
1032
1200
|
push({
|
|
1033
1201
|
key: "autonomy_boundary",
|
|
1202
|
+
kind: "autonomy_boundary",
|
|
1034
1203
|
title: "Autonomy boundary",
|
|
1035
1204
|
question: "How much should LongTable do before checking back with you?",
|
|
1036
1205
|
whyNow: "Execution requests can move from advice to authorship or artifact ownership unless the boundary is explicit.",
|
|
@@ -1040,22 +1209,35 @@ function buildFollowUpQuestionSpecs(prompt) {
|
|
|
1040
1209
|
if (includesAny(normalized, [/\bperformance\b/, /\btest\b/, /\bevaluate\b/, /\bcheck\b/, /\bbenchmark\b/, /성능|테스트|평가|체크|검증/])) {
|
|
1041
1210
|
push({
|
|
1042
1211
|
key: "evaluation_target",
|
|
1212
|
+
kind: "evaluation_target",
|
|
1043
1213
|
title: "Evaluation target",
|
|
1044
1214
|
question: "What should LongTable treat as the main performance target?",
|
|
1045
1215
|
whyNow: "Performance checks can optimize for UX, correctness, trigger sensitivity, or delivery reliability.",
|
|
1046
1216
|
options: followUpQuestionOptions({ value: "question_sensitivity", label: "Question sensitivity", description: "Check whether LongTable asks at the right knowledge-gap moments.", recommended: true }, { value: "renderer_convenience", label: "Renderer convenience", description: "Check whether the most convenient question UI is used." }, { value: "state_reliability", label: "State reliability", description: "Check whether questions and answers persist correctly." })
|
|
1047
1217
|
});
|
|
1048
1218
|
}
|
|
1049
|
-
if (specs.length === 0) {
|
|
1219
|
+
if (specs.length === 0 && options.includeFallback !== false) {
|
|
1050
1220
|
push({
|
|
1051
1221
|
key: "general_missing_context",
|
|
1222
|
+
kind: "general_missing_context",
|
|
1052
1223
|
title: "Missing context",
|
|
1053
1224
|
question: "What should LongTable clarify before proceeding?",
|
|
1054
1225
|
whyNow: "The request can be answered in multiple ways, and choosing silently would hide a researcher judgment.",
|
|
1055
1226
|
options: followUpQuestionOptions({ value: "scope", label: "Clarify scope first", description: "Ask what is included and excluded before acting.", recommended: true }, { value: "criteria", label: "Clarify success criteria", description: "Ask what would count as a good result." }, { value: "proceed", label: "Proceed with visible assumptions", description: "Continue, but make assumptions explicit." })
|
|
1056
1227
|
});
|
|
1057
1228
|
}
|
|
1058
|
-
return specs;
|
|
1229
|
+
return options.autoOnly === true ? specs.filter((spec) => spec.autoEligible) : specs;
|
|
1230
|
+
}
|
|
1231
|
+
function buildFollowUpQuestionSpecs(prompt) {
|
|
1232
|
+
return buildQuestionOpportunitySpecs(prompt);
|
|
1233
|
+
}
|
|
1234
|
+
export function generateQuestionOpportunities(prompt, options = {}) {
|
|
1235
|
+
const opportunities = buildQuestionOpportunitySpecs(prompt, options);
|
|
1236
|
+
return {
|
|
1237
|
+
promptSignature: prompt.replace(/\s+/g, " ").trim().toLowerCase().slice(0, 160),
|
|
1238
|
+
opportunities,
|
|
1239
|
+
blocking: opportunities.some((opportunity) => opportunity.required)
|
|
1240
|
+
};
|
|
1059
1241
|
}
|
|
1060
1242
|
const FOLLOW_UP_PROMPT_PREFIX = "Follow-up prompt:";
|
|
1061
1243
|
function hasFollowUpPrompt(record, prompt) {
|
|
@@ -1077,7 +1259,24 @@ export async function createWorkspaceFollowUpQuestions(options) {
|
|
|
1077
1259
|
const preferredSurfaces = options.provider === "claude"
|
|
1078
1260
|
? ["native_structured", "terminal_selector", "numbered"]
|
|
1079
1261
|
: ["mcp_elicitation", "terminal_selector", "numbered"];
|
|
1080
|
-
const
|
|
1262
|
+
const specs = buildQuestionOpportunitySpecs(options.prompt, {
|
|
1263
|
+
includeFallback: options.force === true ? true : options.auto !== true,
|
|
1264
|
+
autoOnly: options.auto === true
|
|
1265
|
+
});
|
|
1266
|
+
if (specs.length === 0) {
|
|
1267
|
+
return { questions: [], state, created: false, alreadyAnswered: false };
|
|
1268
|
+
}
|
|
1269
|
+
const existingPendingByCheckpoint = new Map((state.questionLog ?? [])
|
|
1270
|
+
.filter((record) => record.status === "pending" && record.prompt.source === "runtime_guidance")
|
|
1271
|
+
.map((record) => [record.prompt.checkpointKey, record]));
|
|
1272
|
+
const pendingMatches = specs
|
|
1273
|
+
.map((spec) => existingPendingByCheckpoint.get(`follow_up_${spec.key}`))
|
|
1274
|
+
.filter((record) => Boolean(record));
|
|
1275
|
+
const specsToCreate = specs.filter((spec) => !existingPendingByCheckpoint.has(`follow_up_${spec.key}`));
|
|
1276
|
+
if (specsToCreate.length === 0) {
|
|
1277
|
+
return { questions: pendingMatches, state, created: false, alreadyAnswered: false };
|
|
1278
|
+
}
|
|
1279
|
+
const questions = specsToCreate.map((spec) => ({
|
|
1081
1280
|
id: createId("question_record"),
|
|
1082
1281
|
createdAt,
|
|
1083
1282
|
updatedAt: createdAt,
|
|
@@ -1091,10 +1290,12 @@ export async function createWorkspaceFollowUpQuestions(options) {
|
|
|
1091
1290
|
options: spec.options,
|
|
1092
1291
|
allowOther: true,
|
|
1093
1292
|
otherLabel: "Other",
|
|
1094
|
-
required: options.required ??
|
|
1293
|
+
required: options.required ?? spec.required,
|
|
1095
1294
|
source: "runtime_guidance",
|
|
1096
1295
|
rationale: [
|
|
1097
1296
|
spec.whyNow,
|
|
1297
|
+
`Question kind: ${spec.kind}`,
|
|
1298
|
+
`Question confidence: ${spec.confidence}`,
|
|
1098
1299
|
`${FOLLOW_UP_PROMPT_PREFIX} ${options.prompt}`
|
|
1099
1300
|
],
|
|
1100
1301
|
preferredSurfaces: preferredSurfaces
|
|
@@ -1103,7 +1304,7 @@ export async function createWorkspaceFollowUpQuestions(options) {
|
|
|
1103
1304
|
const updated = appendQuestionRecords(state, questions);
|
|
1104
1305
|
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
1105
1306
|
await syncCurrentWorkspaceView(options.context);
|
|
1106
|
-
return { questions, state: updated, created: true, alreadyAnswered: false };
|
|
1307
|
+
return { questions: [...pendingMatches, ...questions], state: updated, created: true, alreadyAnswered: false };
|
|
1107
1308
|
}
|
|
1108
1309
|
export async function createWorkspaceQuestion(options) {
|
|
1109
1310
|
const state = await loadResearchState(options.context.stateFilePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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.37",
|
|
33
|
+
"@longtable/core": "0.1.37",
|
|
34
|
+
"@longtable/memory": "0.1.37",
|
|
35
|
+
"@longtable/provider-claude": "0.1.37",
|
|
36
|
+
"@longtable/provider-codex": "0.1.37",
|
|
37
|
+
"@longtable/setup": "0.1.37"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|