@longtable/cli 0.1.38 → 0.1.40
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 +15 -10
- package/dist/cli.js +96 -23
- package/dist/longtable-codex-native-hook.js +47 -30
- package/dist/project-session.d.ts +9 -0
- package/dist/project-session.js +56 -3
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -156,16 +156,21 @@ longtable codex install-skills
|
|
|
156
156
|
longtable claude install-skills
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`$longtable`
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
By default, provider skills use the compact surface: `longtable`,
|
|
160
|
+
`longtable-interview`, and five short role shortcuts: `longtable-methods`,
|
|
161
|
+
`longtable-measure`, `longtable-theory`, `longtable-reviewer`, and
|
|
162
|
+
`longtable-voice`. `$longtable` remains the general router and can still invoke
|
|
163
|
+
editor, ethics, venue, panel, explore, or review behavior when the request calls
|
|
164
|
+
for it.
|
|
165
|
+
|
|
166
|
+
Power users can install the legacy full surface explicitly:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
longtable codex install-skills --surface full
|
|
170
|
+
longtable claude install-skills --surface full
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Do not depend on `/prompts`; current Codex builds may reject it.
|
|
169
174
|
|
|
170
175
|
## Panel Orchestration
|
|
171
176
|
|
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, buildQuestionOpportunitySpecs, 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, pruneWorkspaceQuestions, 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.39";
|
|
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) {
|
|
@@ -120,16 +120,17 @@ function usage() {
|
|
|
120
120
|
" longtable decide [--question <id>] --answer <value-or-text> [--rationale <text>] [--provider codex|claude] [--cwd <path>] [--json]",
|
|
121
121
|
" longtable explore|review|critique|draft|commit|submit [--prompt <text>] [--role <role[,role]>] [--panel] [--show-conflicts] [--show-deliberation] [--print] [--json] [--stage <stage>] [--setup <path>] [--cwd <path>]",
|
|
122
122
|
" longtable codex persist-init [--answers-json <json> | --stdin | full setup flags] [--install-skills] [--install-prompts] [--json]",
|
|
123
|
-
" longtable codex install-skills [--dir <path>]",
|
|
123
|
+
" longtable codex install-skills [--surface compact|full] [--dir <path>]",
|
|
124
124
|
" longtable codex remove-skills [--dir <path>]",
|
|
125
125
|
" longtable codex install-prompts [--dir <path>]",
|
|
126
126
|
" longtable codex remove-prompts [--dir <path>]",
|
|
127
127
|
" longtable codex install-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
128
128
|
" longtable codex remove-hooks [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
129
|
-
" longtable codex status [--dir <path>] [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
130
|
-
" longtable claude install-skills [--dir <path>]",
|
|
129
|
+
" longtable codex status [--surface compact|full] [--dir <path>] [--codex-config <path>] [--hooks-path <path>] [--json]",
|
|
130
|
+
" longtable claude install-skills [--surface compact|full] [--dir <path>]",
|
|
131
131
|
" longtable claude remove-skills [--dir <path>]",
|
|
132
|
-
" longtable claude status [--dir <path>] [--json]",
|
|
132
|
+
" longtable claude status [--surface compact|full] [--dir <path>] [--json]",
|
|
133
|
+
" longtable prune-questions [--cwd <path>] [--dry-run] [--json]",
|
|
133
134
|
" longtable mcp install --provider all",
|
|
134
135
|
"",
|
|
135
136
|
"Examples:",
|
|
@@ -150,7 +151,7 @@ function parseArgs(argv) {
|
|
|
150
151
|
const values = {};
|
|
151
152
|
let subcommand = maybeSubcommand;
|
|
152
153
|
const modeCommand = command && VALID_MODES.has(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);
|
|
154
|
+
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", "search"].includes(command);
|
|
154
155
|
let startIndex = 1;
|
|
155
156
|
if (modeCommand) {
|
|
156
157
|
subcommand = undefined;
|
|
@@ -238,6 +239,16 @@ function questionSection(questionId) {
|
|
|
238
239
|
function formatModeLabel(mode) {
|
|
239
240
|
return `${mode[0].toUpperCase()}${mode.slice(1)}`;
|
|
240
241
|
}
|
|
242
|
+
function parseSkillSurface(args) {
|
|
243
|
+
const value = args.surface;
|
|
244
|
+
if (value === undefined || value === true) {
|
|
245
|
+
return "compact";
|
|
246
|
+
}
|
|
247
|
+
if (value === "compact" || value === "full") {
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
throw new Error("Invalid --surface value. Use compact or full.");
|
|
251
|
+
}
|
|
241
252
|
function stripWrappingQuotes(value) {
|
|
242
253
|
const trimmed = value.trim();
|
|
243
254
|
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) ||
|
|
@@ -1416,6 +1427,7 @@ function setupForProvider(setup, provider) {
|
|
|
1416
1427
|
}
|
|
1417
1428
|
async function collectDoctorStatus(args) {
|
|
1418
1429
|
const roles = listRoleDefinitions();
|
|
1430
|
+
const skillSurface = parseSkillSurface(args);
|
|
1419
1431
|
const setupOverride = typeof args.setup === "string"
|
|
1420
1432
|
? args.setup
|
|
1421
1433
|
: typeof args.path === "string"
|
|
@@ -1458,11 +1470,11 @@ async function collectDoctorStatus(args) {
|
|
|
1458
1470
|
const missingManagedHookEvents = codexHooksContent
|
|
1459
1471
|
? (getMissingManagedCodexHookEvents(codexHooksContent) ?? [...LONGTABLE_MANAGED_HOOK_EVENTS])
|
|
1460
1472
|
: [...LONGTABLE_MANAGED_HOOK_EVENTS];
|
|
1461
|
-
const expectedCodexSkills = buildCodexSkillSpecs(roles).map((skill) => skill.name);
|
|
1462
|
-
const expectedClaudeSkills = buildClaudeSkillSpecs(roles).map((skill) => skill.name);
|
|
1473
|
+
const expectedCodexSkills = buildCodexSkillSpecs(roles, skillSurface).map((skill) => skill.name);
|
|
1474
|
+
const expectedClaudeSkills = buildClaudeSkillSpecs(roles, skillSurface).map((skill) => skill.name);
|
|
1463
1475
|
const [codexSkills, claudeSkills, codexAliases, workspace] = await Promise.all([
|
|
1464
|
-
listInstalledCodexSkills(roles, codexDir),
|
|
1465
|
-
listInstalledClaudeSkills(roles, claudeDir),
|
|
1476
|
+
listInstalledCodexSkills(roles, codexDir, skillSurface),
|
|
1477
|
+
listInstalledClaudeSkills(roles, claudeDir, skillSurface),
|
|
1466
1478
|
listInstalledCodexPromptAliases(codexPromptsDir),
|
|
1467
1479
|
inspectProjectWorkspace(typeof args.cwd === "string" ? args.cwd : cwd())
|
|
1468
1480
|
]);
|
|
@@ -1646,6 +1658,7 @@ function renderRepairSummary(repair) {
|
|
|
1646
1658
|
}
|
|
1647
1659
|
async function repairDoctorStatus(args, status) {
|
|
1648
1660
|
const roles = listRoleDefinitions();
|
|
1661
|
+
const skillSurface = parseSkillSurface(args);
|
|
1649
1662
|
const codexDir = typeof args["codex-dir"] === "string"
|
|
1650
1663
|
? args["codex-dir"]
|
|
1651
1664
|
: typeof args.dir === "string"
|
|
@@ -1684,10 +1697,10 @@ async function repairDoctorStatus(args, status) {
|
|
|
1684
1697
|
skipped: []
|
|
1685
1698
|
};
|
|
1686
1699
|
if (status.providers.codex.missingSkills.length > 0) {
|
|
1687
|
-
repair.installedCodexSkills = (await installCodexSkills(roles, codexDir)).map((skill) => skill.name);
|
|
1700
|
+
repair.installedCodexSkills = (await installCodexSkills(roles, codexDir, skillSurface)).map((skill) => skill.name);
|
|
1688
1701
|
}
|
|
1689
1702
|
if (status.providers.claude.missingSkills.length > 0) {
|
|
1690
|
-
repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir)).map((skill) => skill.name);
|
|
1703
|
+
repair.installedClaudeSkills = (await installClaudeSkills(roles, claudeDir, skillSurface)).map((skill) => skill.name);
|
|
1691
1704
|
}
|
|
1692
1705
|
if (status.providers.codex.legacyPromptFilesInstalled.length > 0) {
|
|
1693
1706
|
repair.removedLegacyPromptFiles = await removeCodexPromptAliases(codexPromptsDir);
|
|
@@ -1851,10 +1864,10 @@ function runRoleAudit() {
|
|
|
1851
1864
|
"longtable-review"
|
|
1852
1865
|
]);
|
|
1853
1866
|
const roles = [
|
|
1854
|
-
...buildCodexSkillSpecs(listRoleDefinitions())
|
|
1867
|
+
...buildCodexSkillSpecs(listRoleDefinitions(), "full")
|
|
1855
1868
|
.filter((spec) => !baseSkillNames.has(spec.name))
|
|
1856
1869
|
.map((spec) => buildRoleAuditEntry("codex", spec)),
|
|
1857
|
-
...buildClaudeSkillSpecs(listRoleDefinitions())
|
|
1870
|
+
...buildClaudeSkillSpecs(listRoleDefinitions(), "full")
|
|
1858
1871
|
.filter((spec) => !baseSkillNames.has(spec.name))
|
|
1859
1872
|
.map((spec) => buildRoleAuditEntry("claude", spec))
|
|
1860
1873
|
];
|
|
@@ -2671,6 +2684,41 @@ async function runClearQuestion(args) {
|
|
|
2671
2684
|
console.log(`- state: ${context.stateFilePath}`);
|
|
2672
2685
|
console.log(`- current: ${context.currentFilePath}`);
|
|
2673
2686
|
}
|
|
2687
|
+
async function runPruneQuestions(args) {
|
|
2688
|
+
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2689
|
+
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2690
|
+
if (!context) {
|
|
2691
|
+
throw new Error("No LongTable project workspace was found here. Run this inside a project or pass --cwd.");
|
|
2692
|
+
}
|
|
2693
|
+
const dryRun = args["dry-run"] === true;
|
|
2694
|
+
const result = await pruneWorkspaceQuestions({
|
|
2695
|
+
context,
|
|
2696
|
+
dryRun
|
|
2697
|
+
});
|
|
2698
|
+
if (args.json === true) {
|
|
2699
|
+
console.log(JSON.stringify({
|
|
2700
|
+
dryRun,
|
|
2701
|
+
removedCount: result.removedQuestions.length,
|
|
2702
|
+
removedQuestions: result.removedQuestions.map((question) => ({
|
|
2703
|
+
id: question.id,
|
|
2704
|
+
title: question.prompt.title,
|
|
2705
|
+
reason: question.clearedReason
|
|
2706
|
+
})),
|
|
2707
|
+
files: {
|
|
2708
|
+
state: context.stateFilePath,
|
|
2709
|
+
current: context.currentFilePath
|
|
2710
|
+
}
|
|
2711
|
+
}, null, 2));
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
console.log(dryRun ? "LongTable question prune preview" : "LongTable questions pruned");
|
|
2715
|
+
console.log(`- removed false-positive cleared questions: ${result.removedQuestions.length}`);
|
|
2716
|
+
for (const question of result.removedQuestions) {
|
|
2717
|
+
console.log(` - ${question.id}: ${question.prompt.title}`);
|
|
2718
|
+
}
|
|
2719
|
+
console.log(`- state: ${context.stateFilePath}`);
|
|
2720
|
+
console.log(`- current: ${context.currentFilePath}`);
|
|
2721
|
+
}
|
|
2674
2722
|
function isInteractiveTerminal() {
|
|
2675
2723
|
return Boolean(input.isTTY && output.isTTY);
|
|
2676
2724
|
}
|
|
@@ -2805,10 +2853,24 @@ async function runClarify(args) {
|
|
|
2805
2853
|
console.log(`- state: ${context.stateFilePath}`);
|
|
2806
2854
|
console.log(`- current: ${context.currentFilePath}`);
|
|
2807
2855
|
}
|
|
2856
|
+
function looksLikeProductOrToolingPrompt(prompt) {
|
|
2857
|
+
return /\b(longlongtable|hook|checkpoint|mcp|agents?|skills?|ux|interface|setup|install|cli|npm|version|global|release|deploy|git|github|readme|docs?|documentation|workflow|package|router|autocomplete|simulation test)\b/i.test(prompt)
|
|
2858
|
+
|| /롱테이블|훅|체크포인트|에이전트|스킬|사용성|인터페이스|설치|세팅|글로벌|배포|버전|릴리즈|깃|깃허브|문서화된\s*절차|패키지|라우터|자동완성|시뮬레이션\s*테스트/.test(prompt);
|
|
2859
|
+
}
|
|
2860
|
+
function looksLikeResearchCommitmentPrompt(prompt) {
|
|
2861
|
+
const researchCue = /\b(research|study|paper|manuscript|journal|article|method|methodology|measurement|construct|theory|analysis|model|data|participant|sample|scale|survey|instrument|validity|hypothesis|literature|meta[- ]?analysis|gold standard|coding|trust|reliance|calibration)\b/i.test(prompt)
|
|
2862
|
+
|| /연구|논문|원고|저널|방법론|방법|연구\s*설계|측정|구성개념|개념|이론|분석|모형|모델|데이터|참가자|표본|샘플|척도|설문|도구|타당도|가설|문헌|메타\s*분석|골드\s*스탠더드|코딩|신뢰|의존|캘리브레이션|교정|보정/.test(prompt);
|
|
2863
|
+
const closureCue = /\b(final|finalize|commit|ship|submit|publish|freeze|settle|decide|lock|record|apply|incorporate)\b/i.test(prompt)
|
|
2864
|
+
|| /최종|확정|커밋|제출|투고|고정|결정|기록|반영/.test(prompt);
|
|
2865
|
+
return researchCue && closureCue;
|
|
2866
|
+
}
|
|
2808
2867
|
async function runAutomaticFollowUpIfNeeded(prompt, args) {
|
|
2809
2868
|
if (args["no-clarify"] === true || args.print === true || args.json === true) {
|
|
2810
2869
|
return false;
|
|
2811
2870
|
}
|
|
2871
|
+
if (looksLikeProductOrToolingPrompt(prompt) || !looksLikeResearchCommitmentPrompt(prompt)) {
|
|
2872
|
+
return false;
|
|
2873
|
+
}
|
|
2812
2874
|
const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
|
|
2813
2875
|
const context = await loadProjectContextFromDirectory(workingDirectory);
|
|
2814
2876
|
if (!context) {
|
|
@@ -2820,7 +2882,8 @@ async function runAutomaticFollowUpIfNeeded(prompt, args) {
|
|
|
2820
2882
|
prompt,
|
|
2821
2883
|
provider,
|
|
2822
2884
|
required: true,
|
|
2823
|
-
auto: true
|
|
2885
|
+
auto: true,
|
|
2886
|
+
requiredOnly: true
|
|
2824
2887
|
});
|
|
2825
2888
|
if (result.questions.length === 0) {
|
|
2826
2889
|
return false;
|
|
@@ -3241,11 +3304,12 @@ async function runResume(args) {
|
|
|
3241
3304
|
async function runCodexSubcommand(subcommand, args) {
|
|
3242
3305
|
const customDir = typeof args.dir === "string" ? args.dir : undefined;
|
|
3243
3306
|
const roles = listRoleDefinitions();
|
|
3307
|
+
const skillSurface = parseSkillSurface(args);
|
|
3244
3308
|
if (subcommand === "install-skills") {
|
|
3245
|
-
const installed = await installCodexSkills(roles, customDir);
|
|
3246
|
-
console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)}`);
|
|
3309
|
+
const installed = await installCodexSkills(roles, customDir, skillSurface);
|
|
3310
|
+
console.log(`Installed ${installed.length} LongTable Codex skills in ${resolveCodexSkillsDir(customDir)} (${skillSurface} surface)`);
|
|
3247
3311
|
console.log("Use them inside Codex with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
|
|
3248
|
-
console.log("
|
|
3312
|
+
console.log("Use `$longtable` as the general router; compact installs expose only the most common role shortcuts.");
|
|
3249
3313
|
for (const skill of installed) {
|
|
3250
3314
|
console.log(`- ${skill.name}`);
|
|
3251
3315
|
}
|
|
@@ -3295,7 +3359,7 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3295
3359
|
}
|
|
3296
3360
|
if (subcommand === "status") {
|
|
3297
3361
|
const aliases = await listInstalledCodexPromptAliases(customDir);
|
|
3298
|
-
const skills = await listInstalledCodexSkills(roles, customDir);
|
|
3362
|
+
const skills = await listInstalledCodexSkills(roles, customDir, skillSurface);
|
|
3299
3363
|
const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
|
|
3300
3364
|
const runtimePath = resolveDefaultRuntimeConfigPath("codex", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
|
|
3301
3365
|
const configPath = resolveCodexMcpConfigPath(args);
|
|
@@ -3308,6 +3372,7 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3308
3372
|
runtimePath,
|
|
3309
3373
|
runtimeExists: existsSync(runtimePath),
|
|
3310
3374
|
skillsDir: resolveCodexSkillsDir(customDir),
|
|
3375
|
+
skillSurface,
|
|
3311
3376
|
skillsInstalled: skills.map((skill) => skill.name),
|
|
3312
3377
|
promptsDir: resolveCodexPromptsDir(customDir),
|
|
3313
3378
|
legacyPromptFilesInstalled: aliases.map((alias) => alias.name),
|
|
@@ -3327,6 +3392,7 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3327
3392
|
console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
|
|
3328
3393
|
console.log(`- codex runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
|
|
3329
3394
|
console.log(`- skills dir: ${status.skillsDir}`);
|
|
3395
|
+
console.log(`- skill surface: ${status.skillSurface}`);
|
|
3330
3396
|
if (skills.length === 0) {
|
|
3331
3397
|
console.log("- skills: none");
|
|
3332
3398
|
}
|
|
@@ -3358,9 +3424,10 @@ async function runCodexSubcommand(subcommand, args) {
|
|
|
3358
3424
|
async function runClaudeSubcommand(subcommand, args) {
|
|
3359
3425
|
const customDir = typeof args.dir === "string" ? args.dir : undefined;
|
|
3360
3426
|
const roles = listRoleDefinitions();
|
|
3427
|
+
const skillSurface = parseSkillSurface(args);
|
|
3361
3428
|
if (subcommand === "install-skills") {
|
|
3362
|
-
const installed = await installClaudeSkills(roles, customDir);
|
|
3363
|
-
console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)}`);
|
|
3429
|
+
const installed = await installClaudeSkills(roles, customDir, skillSurface);
|
|
3430
|
+
console.log(`Installed ${installed.length} LongTable Claude skills in ${resolveClaudeSkillsDir(customDir)} (${skillSurface} surface)`);
|
|
3364
3431
|
console.log("Use them inside Claude Code with natural-language triggers such as `lt explore: ...` or `lt panel: ...`.");
|
|
3365
3432
|
for (const skill of installed) {
|
|
3366
3433
|
console.log(`- ${skill.name}`);
|
|
@@ -3373,7 +3440,7 @@ async function runClaudeSubcommand(subcommand, args) {
|
|
|
3373
3440
|
return;
|
|
3374
3441
|
}
|
|
3375
3442
|
if (subcommand === "status") {
|
|
3376
|
-
const skills = await listInstalledClaudeSkills(roles, customDir);
|
|
3443
|
+
const skills = await listInstalledClaudeSkills(roles, customDir, skillSurface);
|
|
3377
3444
|
const setupPath = resolveDefaultSetupPath(typeof args.path === "string" ? args.path : undefined).path;
|
|
3378
3445
|
const runtimePath = resolveDefaultRuntimeConfigPath("claude", typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined).path;
|
|
3379
3446
|
const status = {
|
|
@@ -3382,6 +3449,7 @@ async function runClaudeSubcommand(subcommand, args) {
|
|
|
3382
3449
|
runtimePath,
|
|
3383
3450
|
runtimeExists: existsSync(runtimePath),
|
|
3384
3451
|
skillsDir: resolveClaudeSkillsDir(customDir),
|
|
3452
|
+
skillSurface,
|
|
3385
3453
|
skillsInstalled: skills.map((skill) => skill.name)
|
|
3386
3454
|
};
|
|
3387
3455
|
if (args.json === true) {
|
|
@@ -3392,6 +3460,7 @@ async function runClaudeSubcommand(subcommand, args) {
|
|
|
3392
3460
|
console.log(`- setup: ${status.setupExists ? "present" : "missing"} (${setupPath})`);
|
|
3393
3461
|
console.log(`- claude runtime artifact: ${status.runtimeExists ? "present" : "missing"} (${runtimePath})`);
|
|
3394
3462
|
console.log(`- skills dir: ${status.skillsDir}`);
|
|
3463
|
+
console.log(`- skill surface: ${status.skillSurface}`);
|
|
3395
3464
|
if (skills.length === 0) {
|
|
3396
3465
|
console.log("- skills: none");
|
|
3397
3466
|
}
|
|
@@ -3472,6 +3541,10 @@ async function main() {
|
|
|
3472
3541
|
await runClearQuestion(values);
|
|
3473
3542
|
return;
|
|
3474
3543
|
}
|
|
3544
|
+
if (command === "prune-questions") {
|
|
3545
|
+
await runPruneQuestions(values);
|
|
3546
|
+
return;
|
|
3547
|
+
}
|
|
3475
3548
|
if (command === "panel") {
|
|
3476
3549
|
await runPanelCommand(values);
|
|
3477
3550
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
|
-
import { createWorkspaceFollowUpQuestions, loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
|
|
2
|
+
import { buildQuestionOpportunitySpecs, createWorkspaceFollowUpQuestions, loadProjectContextFromDirectory, loadWorkspaceState, pendingQuestionObligations } from "./index.js";
|
|
3
3
|
function safeString(value) {
|
|
4
4
|
return typeof value === "string" ? value : "";
|
|
5
5
|
}
|
|
@@ -107,43 +107,50 @@ function looksLikeClosurePrompt(prompt) {
|
|
|
107
107
|
if (!normalized) {
|
|
108
108
|
return false;
|
|
109
109
|
}
|
|
110
|
-
return /\b(final|finalize|commit|ship|submit|
|
|
111
|
-
||
|
|
110
|
+
return /\b(final|finalize|commit|ship|submit|publish|freeze|settle|decide|lock|record|apply|incorporate)\b/i.test(normalized)
|
|
111
|
+
|| /최종|확정|커밋|제출|투고|고정|결정|기록|반영/.test(normalized);
|
|
112
112
|
}
|
|
113
|
-
function
|
|
113
|
+
function looksLikeLongTableProductOrToolingPrompt(prompt) {
|
|
114
114
|
const normalized = prompt.trim();
|
|
115
115
|
if (!normalized) {
|
|
116
116
|
return false;
|
|
117
117
|
}
|
|
118
|
-
return /\b(
|
|
119
|
-
||
|
|
118
|
+
return /\b(longlongtable|hook|checkpoint|mcp|agents?|skills?|ux|interface|setup|install|cli|npm|version|global|release|deploy|git|github|readme|docs?|documentation|workflow|package|router|autocomplete|simulation test)\b/i.test(normalized)
|
|
119
|
+
|| /롱테이블|훅|체크포인트|에이전트|스킬|사용성|인터페이스|설치|세팅|글로벌|배포|버전|릴리즈|깃|깃허브|문서화된\s*절차|패키지|라우터|자동완성|시뮬레이션\s*테스트/.test(normalized);
|
|
120
120
|
}
|
|
121
|
-
function
|
|
121
|
+
function looksLikeResearchDomainPrompt(prompt) {
|
|
122
122
|
const normalized = prompt.trim();
|
|
123
123
|
if (!normalized) {
|
|
124
124
|
return false;
|
|
125
125
|
}
|
|
126
|
-
return /\b(
|
|
127
|
-
||
|
|
126
|
+
return /\b(research|study|paper|manuscript|journal|article|method|methodology|measurement|construct|theory|analysis|model|data|participant|sample|scale|survey|instrument|validity|hypothesis|literature|meta[- ]?analysis|gold standard|coding|trust|reliance|calibration)\b/i.test(normalized)
|
|
127
|
+
|| /연구|논문|원고|저널|방법론|방법|연구\s*설계|측정|구성개념|개념|이론|분석|모형|모델|데이터|참가자|표본|샘플|척도|설문|도구|타당도|가설|문헌|메타\s*분석|골드\s*스탠더드|코딩|신뢰|의존|캘리브레이션|교정|보정/.test(normalized);
|
|
128
128
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
if (!normalized) {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
return /\b(explain|why|how|diagnos(?:e|is)|debug|inspect|check|verify|status|simulate|simulation|test|logs?)\b/i.test(normalized)
|
|
135
|
-
|| /설명|왜|어떻게|진단|디버그|확인|점검|상태|시뮬레이션|테스트|로그|불필요/.test(normalized);
|
|
129
|
+
function looksLikeResearchCommitmentPrompt(prompt) {
|
|
130
|
+
return looksLikeResearchDomainPrompt(prompt) && looksLikeClosurePrompt(prompt);
|
|
136
131
|
}
|
|
137
|
-
function
|
|
138
|
-
if (
|
|
139
|
-
return
|
|
132
|
+
function buildResponseOnlyAdvisoryQuestions(prompt) {
|
|
133
|
+
if (looksLikeLongTableProductOrToolingPrompt(prompt)) {
|
|
134
|
+
return [];
|
|
140
135
|
}
|
|
141
|
-
|
|
136
|
+
const opportunities = buildQuestionOpportunitySpecs(prompt, {
|
|
137
|
+
includeFallback: false,
|
|
138
|
+
autoOnly: true
|
|
139
|
+
});
|
|
140
|
+
if (opportunities.length === 0) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
if (!looksLikeResearchDomainPrompt(prompt) && !/\b(needed questions?|necessary questions?|clarifying questions?|question generation|assumptions?|uncertain|not sure|gap|tension|trade[- ]?off)\b/i.test(prompt) && !/필요한\s*질문|질문\s*생성|물어봐|질문해|전제|가정|불확실|모르겠|공백|긴장|상충|균형/.test(prompt)) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
return opportunities.slice(0, 3);
|
|
147
|
+
}
|
|
148
|
+
function shouldCreateRequiredQuestionsForPrompt(prompt) {
|
|
149
|
+
return !looksLikeLongTableProductOrToolingPrompt(prompt) && looksLikeResearchCommitmentPrompt(prompt);
|
|
142
150
|
}
|
|
143
151
|
function shouldApplyProtectedDecisionClosure(runtime, prompt) {
|
|
144
152
|
return Boolean(runtime.context.session.protectedDecision) &&
|
|
145
|
-
|
|
146
|
-
!looksLikeLongTableEngineeringPrompt(prompt);
|
|
153
|
+
shouldCreateRequiredQuestionsForPrompt(prompt);
|
|
147
154
|
}
|
|
148
155
|
function protectedDecisionClosurePrompt(prompt) {
|
|
149
156
|
return [
|
|
@@ -213,6 +220,16 @@ function buildGeneratedQuestionsContext(questions, created) {
|
|
|
213
220
|
lines.push("Do not choose or record answers for these checkpoints unless the researcher explicitly provides the selections.");
|
|
214
221
|
return lines.join("\n");
|
|
215
222
|
}
|
|
223
|
+
function buildAdvisoryQuestionsContext(questions) {
|
|
224
|
+
const lines = [
|
|
225
|
+
`LongTable surfaced ${questions.length} response-only advisory question${questions.length === 1 ? "" : "s"} for this prompt.`,
|
|
226
|
+
"Use these only if they help the reply. Do not create QuestionRecord entries, call longtable decide, or answer for the researcher unless the prompt explicitly asks to commit a research decision."
|
|
227
|
+
];
|
|
228
|
+
for (const question of questions) {
|
|
229
|
+
lines.push(`- ${question.title}: ${question.question}`);
|
|
230
|
+
}
|
|
231
|
+
return lines.join("\n");
|
|
232
|
+
}
|
|
216
233
|
function buildPendingObligationContext(obligation) {
|
|
217
234
|
return [
|
|
218
235
|
`Pending LongTable research obligation: ${obligation.prompt}`,
|
|
@@ -264,13 +281,14 @@ async function userPromptSubmitContext(runtime, prompt) {
|
|
|
264
281
|
}
|
|
265
282
|
const generatedQuestions = [];
|
|
266
283
|
let createdQuestions = false;
|
|
267
|
-
if (
|
|
284
|
+
if (shouldCreateRequiredQuestionsForPrompt(prompt)) {
|
|
268
285
|
const generated = await createWorkspaceFollowUpQuestions({
|
|
269
286
|
context: runtime.context,
|
|
270
287
|
prompt,
|
|
271
288
|
provider: "codex",
|
|
272
289
|
required: true,
|
|
273
|
-
auto: true
|
|
290
|
+
auto: true,
|
|
291
|
+
requiredOnly: true
|
|
274
292
|
});
|
|
275
293
|
generatedQuestions.push(...generated.questions);
|
|
276
294
|
createdQuestions = createdQuestions || generated.created;
|
|
@@ -281,7 +299,8 @@ async function userPromptSubmitContext(runtime, prompt) {
|
|
|
281
299
|
prompt: protectedDecisionClosurePrompt(prompt),
|
|
282
300
|
provider: "codex",
|
|
283
301
|
required: true,
|
|
284
|
-
auto: true
|
|
302
|
+
auto: true,
|
|
303
|
+
requiredOnly: true
|
|
285
304
|
});
|
|
286
305
|
generatedQuestions.push(...protectedGenerated.questions.filter((question) => !generatedQuestions.some((existing) => existing.id === question.id)));
|
|
287
306
|
createdQuestions = createdQuestions || protectedGenerated.created;
|
|
@@ -289,11 +308,9 @@ async function userPromptSubmitContext(runtime, prompt) {
|
|
|
289
308
|
if (generatedQuestions.length > 0) {
|
|
290
309
|
return buildGeneratedQuestionsContext(generatedQuestions, createdQuestions);
|
|
291
310
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"Before you settle it through drafting, revision, or closure, surface one researcher-facing checkpoint grounded in the current blocker or open questions."
|
|
296
|
-
].join("\n");
|
|
311
|
+
const advisoryQuestions = buildResponseOnlyAdvisoryQuestions(prompt);
|
|
312
|
+
if (advisoryQuestions.length > 0) {
|
|
313
|
+
return buildAdvisoryQuestionsContext(advisoryQuestions);
|
|
297
314
|
}
|
|
298
315
|
return null;
|
|
299
316
|
}
|
|
@@ -227,6 +227,7 @@ type FollowUpQuestionSpec = QuestionOpportunity;
|
|
|
227
227
|
interface BuildFollowUpQuestionOptions {
|
|
228
228
|
includeFallback?: boolean;
|
|
229
229
|
autoOnly?: boolean;
|
|
230
|
+
requiredOnly?: boolean;
|
|
230
231
|
}
|
|
231
232
|
export declare function buildQuestionOpportunitySpecs(prompt: string, options?: BuildFollowUpQuestionOptions): FollowUpQuestionSpec[];
|
|
232
233
|
export declare function generateQuestionOpportunities(prompt: string, options?: BuildFollowUpQuestionOptions): QuestionGenerationResult;
|
|
@@ -237,6 +238,7 @@ export declare function createWorkspaceFollowUpQuestions(options: {
|
|
|
237
238
|
required?: boolean;
|
|
238
239
|
force?: boolean;
|
|
239
240
|
auto?: boolean;
|
|
241
|
+
requiredOnly?: boolean;
|
|
240
242
|
}): Promise<{
|
|
241
243
|
questions: QuestionRecord[];
|
|
242
244
|
state: ResearchState;
|
|
@@ -277,6 +279,13 @@ export declare function clearWorkspaceQuestion(options: {
|
|
|
277
279
|
question: QuestionRecord;
|
|
278
280
|
state: ResearchState;
|
|
279
281
|
}>;
|
|
282
|
+
export declare function pruneWorkspaceQuestions(options: {
|
|
283
|
+
context: LongTableProjectContext;
|
|
284
|
+
dryRun?: boolean;
|
|
285
|
+
}): Promise<{
|
|
286
|
+
removedQuestions: QuestionRecord[];
|
|
287
|
+
state: ResearchState;
|
|
288
|
+
}>;
|
|
280
289
|
export declare function repairWorkspaceStateConsistency(options: {
|
|
281
290
|
context: LongTableProjectContext;
|
|
282
291
|
}): Promise<{
|
package/dist/project-session.js
CHANGED
|
@@ -990,7 +990,7 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
|
990
990
|
function push(spec) {
|
|
991
991
|
if (!specs.some((candidate) => candidate.key === spec.key)) {
|
|
992
992
|
specs.push({
|
|
993
|
-
required:
|
|
993
|
+
required: false,
|
|
994
994
|
confidence: "medium",
|
|
995
995
|
autoEligible: false,
|
|
996
996
|
cues: [],
|
|
@@ -1076,6 +1076,7 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
|
1076
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
1077
|
confidence: "high",
|
|
1078
1078
|
autoEligible: true,
|
|
1079
|
+
required: true,
|
|
1079
1080
|
cues: ["protected_decision", "closure_pressure"]
|
|
1080
1081
|
});
|
|
1081
1082
|
}
|
|
@@ -1226,7 +1227,11 @@ export function buildQuestionOpportunitySpecs(prompt, options = {}) {
|
|
|
1226
1227
|
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." })
|
|
1227
1228
|
});
|
|
1228
1229
|
}
|
|
1229
|
-
|
|
1230
|
+
let selected = options.autoOnly === true ? specs.filter((spec) => spec.autoEligible) : specs;
|
|
1231
|
+
if (options.requiredOnly === true) {
|
|
1232
|
+
selected = selected.filter((spec) => spec.kind === "research_commitment");
|
|
1233
|
+
}
|
|
1234
|
+
return selected;
|
|
1230
1235
|
}
|
|
1231
1236
|
function buildFollowUpQuestionSpecs(prompt) {
|
|
1232
1237
|
return buildQuestionOpportunitySpecs(prompt);
|
|
@@ -1261,7 +1266,8 @@ export async function createWorkspaceFollowUpQuestions(options) {
|
|
|
1261
1266
|
: ["mcp_elicitation", "terminal_selector", "numbered"];
|
|
1262
1267
|
const specs = buildQuestionOpportunitySpecs(options.prompt, {
|
|
1263
1268
|
includeFallback: options.force === true ? true : options.auto !== true,
|
|
1264
|
-
autoOnly: options.auto === true
|
|
1269
|
+
autoOnly: options.auto === true,
|
|
1270
|
+
requiredOnly: options.requiredOnly === true
|
|
1265
1271
|
});
|
|
1266
1272
|
if (specs.length === 0) {
|
|
1267
1273
|
return { questions: [], state, created: false, alreadyAnswered: false };
|
|
@@ -1513,6 +1519,53 @@ export async function clearWorkspaceQuestion(options) {
|
|
|
1513
1519
|
state: updated
|
|
1514
1520
|
};
|
|
1515
1521
|
}
|
|
1522
|
+
function isPrunableFalsePositiveQuestion(record) {
|
|
1523
|
+
if (record.status !== "cleared") {
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
return /false-positive|duplicated automatic hook/i.test(record.clearedReason ?? "");
|
|
1527
|
+
}
|
|
1528
|
+
export async function pruneWorkspaceQuestions(options) {
|
|
1529
|
+
const state = await loadResearchState(options.context.stateFilePath);
|
|
1530
|
+
const removedQuestions = (state.questionLog ?? []).filter(isPrunableFalsePositiveQuestion);
|
|
1531
|
+
if (removedQuestions.length === 0 || options.dryRun) {
|
|
1532
|
+
return {
|
|
1533
|
+
removedQuestions,
|
|
1534
|
+
state
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
const removedIds = new Set(removedQuestions.map((question) => question.id));
|
|
1538
|
+
const updated = {
|
|
1539
|
+
...state,
|
|
1540
|
+
questionLog: (state.questionLog ?? []).filter((question) => !removedIds.has(question.id)),
|
|
1541
|
+
questionObligations: (state.questionObligations ?? []).filter((obligation) => !obligation.questionId || !removedIds.has(obligation.questionId)),
|
|
1542
|
+
invocationLog: (state.invocationLog ?? []).map((record) => ({
|
|
1543
|
+
...record,
|
|
1544
|
+
...(record.panelResult
|
|
1545
|
+
? {
|
|
1546
|
+
panelResult: {
|
|
1547
|
+
...record.panelResult,
|
|
1548
|
+
linkedQuestionRecordIds: record.panelResult.linkedQuestionRecordIds.filter((id) => !removedIds.has(id))
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
: {}),
|
|
1552
|
+
...(record.teamDebateRun
|
|
1553
|
+
? {
|
|
1554
|
+
teamDebateRun: {
|
|
1555
|
+
...record.teamDebateRun,
|
|
1556
|
+
linkedQuestionRecordIds: record.teamDebateRun.linkedQuestionRecordIds.filter((id) => !removedIds.has(id))
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
: {})
|
|
1560
|
+
}))
|
|
1561
|
+
};
|
|
1562
|
+
await writeFile(options.context.stateFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
1563
|
+
await syncCurrentWorkspaceView(options.context);
|
|
1564
|
+
return {
|
|
1565
|
+
removedQuestions,
|
|
1566
|
+
state: updated
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1516
1569
|
export async function repairWorkspaceStateConsistency(options) {
|
|
1517
1570
|
const state = await loadResearchState(options.context.stateFilePath);
|
|
1518
1571
|
const repaired = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longtable/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
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.40",
|
|
33
|
+
"@longtable/core": "0.1.40",
|
|
34
|
+
"@longtable/memory": "0.1.40",
|
|
35
|
+
"@longtable/provider-claude": "0.1.40",
|
|
36
|
+
"@longtable/provider-codex": "0.1.40",
|
|
37
|
+
"@longtable/setup": "0.1.40"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.10.1",
|