@ctrl-spc/cli 1.3.6 → 1.3.7
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/mcp.js +72 -4
- package/dist/protocol.js +8 -4
- package/dist/skills.js +3 -3
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -800,6 +800,25 @@ function isRealQuestion(text) {
|
|
|
800
800
|
const trimmed = text?.trim() ?? '';
|
|
801
801
|
return trimmed.length >= 4 && /[\p{L}\p{N}]/u.test(trimmed) && trimmed.includes('?');
|
|
802
802
|
}
|
|
803
|
+
function decisionQuestionConfiguration(answerMode, rawOptions) {
|
|
804
|
+
const mode = answerMode ?? 'free_text';
|
|
805
|
+
const options = rawOptions ?? [];
|
|
806
|
+
if (mode === 'free_text') {
|
|
807
|
+
return options.length === 0
|
|
808
|
+
? { answerMode: mode, options }
|
|
809
|
+
: 'Free-text questions cannot include choices.';
|
|
810
|
+
}
|
|
811
|
+
if (options.length < 2 || options.length > 12) {
|
|
812
|
+
return 'Single- and multi-select questions require between 2 and 12 choices.';
|
|
813
|
+
}
|
|
814
|
+
if (options.some(option => option.length === 0 || option !== option.trim())) {
|
|
815
|
+
return 'Decision choices must be non-empty and cannot start or end with spaces.';
|
|
816
|
+
}
|
|
817
|
+
if (new Set(options.map(option => option.toLocaleLowerCase())).size !== options.length) {
|
|
818
|
+
return 'Decision choices must be unique, including capitalization-only differences.';
|
|
819
|
+
}
|
|
820
|
+
return { answerMode: mode, options };
|
|
821
|
+
}
|
|
803
822
|
function isPersistedAgentQuestion(row, taskId, agentRunId) {
|
|
804
823
|
if (row.task_id !== taskId || row.agent_run_id !== agentRunId || typeof row.body !== 'string')
|
|
805
824
|
return false;
|
|
@@ -819,14 +838,19 @@ export async function askQuestionHandler(deps, _attribution, args, agentRun) {
|
|
|
819
838
|
if (!isRealQuestion(args.question)) {
|
|
820
839
|
return errorResult('ask_question requires real question text containing a question mark.');
|
|
821
840
|
}
|
|
841
|
+
const configuration = decisionQuestionConfiguration(args.answer_mode, args.options);
|
|
842
|
+
if (typeof configuration === 'string')
|
|
843
|
+
return errorResult(configuration);
|
|
822
844
|
const question = args.question.trim();
|
|
823
845
|
let opened;
|
|
824
846
|
try {
|
|
825
|
-
opened = await must(deps.client.rpc('
|
|
847
|
+
opened = await must(deps.client.rpc('ask_agent_question_v2', {
|
|
826
848
|
p_run: agentRun.runId,
|
|
827
849
|
p_category: args.category,
|
|
828
850
|
p_context: args.context.trim(),
|
|
829
851
|
p_question: question,
|
|
852
|
+
p_answer_mode: configuration.answerMode,
|
|
853
|
+
p_options: configuration.options,
|
|
830
854
|
p_related_artifact: args.related_artifact_id ?? null,
|
|
831
855
|
}));
|
|
832
856
|
}
|
|
@@ -853,6 +877,8 @@ export async function askQuestionHandler(deps, _attribution, args, agentRun) {
|
|
|
853
877
|
category: args.category,
|
|
854
878
|
text: question,
|
|
855
879
|
context: args.context.trim(),
|
|
880
|
+
answer_mode: configuration.answerMode,
|
|
881
|
+
options: configuration.options,
|
|
856
882
|
comment_id: commentId,
|
|
857
883
|
decision_id: decisionId,
|
|
858
884
|
workflow_question_id: workflowQuestionId,
|
|
@@ -869,7 +895,40 @@ export async function recordUserInputHandler(deps, _attribution, session, args)
|
|
|
869
895
|
const capabilityError = workflowCapabilityError(active, 'record_user_input', 'record_user_input');
|
|
870
896
|
if (capabilityError)
|
|
871
897
|
return capabilityError;
|
|
898
|
+
const structuredResponses = args.responses;
|
|
872
899
|
const decisionIds = args.decision_ids ?? args.question_ids;
|
|
900
|
+
if (structuredResponses && (args.decision_ids || args.question_ids || args.answer !== undefined)) {
|
|
901
|
+
return errorResult('record_user_input accepts either structured responses or the legacy decision_ids plus answer shape, not both.');
|
|
902
|
+
}
|
|
903
|
+
if (structuredResponses) {
|
|
904
|
+
if (structuredResponses.length === 0) {
|
|
905
|
+
return errorResult('record_user_input requires at least one structured decision response.');
|
|
906
|
+
}
|
|
907
|
+
const ids = structuredResponses.map(response => response.decision_id);
|
|
908
|
+
if (ids.some(id => !id?.trim()) || new Set(ids).size !== ids.length) {
|
|
909
|
+
return errorResult('record_user_input requires one unique decision_id per structured response.');
|
|
910
|
+
}
|
|
911
|
+
if (structuredResponses.some(response => {
|
|
912
|
+
const selected = response.selected_options ?? [];
|
|
913
|
+
return new Set(selected).size !== selected.length;
|
|
914
|
+
})) {
|
|
915
|
+
return errorResult('record_user_input cannot submit the same selected option more than once.');
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
active.workflow = normalizeWorkflowContext(await must(deps.client.rpc('record_task_decision_responses', {
|
|
919
|
+
p_run: active.runId,
|
|
920
|
+
p_responses: structuredResponses,
|
|
921
|
+
})));
|
|
922
|
+
return textResult({
|
|
923
|
+
recorded: true,
|
|
924
|
+
decision_ids: ids,
|
|
925
|
+
workflow: active.workflow,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
catch (err) {
|
|
929
|
+
return coordinationError('record_user_input', err);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
873
932
|
if (!Array.isArray(decisionIds) || decisionIds.length === 0) {
|
|
874
933
|
return errorResult('record_user_input requires at least one decision id returned by ask_question.');
|
|
875
934
|
}
|
|
@@ -1969,12 +2028,16 @@ export async function startMcpServer(deps) {
|
|
|
1969
2028
|
return updateArtifactHandler(bindProject(handlerDeps, run.projectId), args, run);
|
|
1970
2029
|
});
|
|
1971
2030
|
server.registerTool('ask_question', {
|
|
1972
|
-
description: 'Persist a categorized, actionable
|
|
2031
|
+
description: 'Persist a categorized, actionable Decision after complete context exploration. Choose free_text, single_select, or multi_select. Choice modes require 2-12 unique options and always allow optional written context in the web app.',
|
|
1973
2032
|
inputSchema: {
|
|
1974
2033
|
task_id: z.string(),
|
|
1975
2034
|
category: z.enum(['clarification', 'reproduction', 'intent', 'dependency', 'checkout', 'collision', 'split']),
|
|
1976
2035
|
context: z.string().min(1).describe('Why this decision is needed and what it affects'),
|
|
1977
2036
|
question: z.string(),
|
|
2037
|
+
answer_mode: z.enum(['free_text', 'single_select', 'multi_select']).optional()
|
|
2038
|
+
.describe('Defaults to free_text. Use single_select for exactly one choice or multi_select for one or more choices.'),
|
|
2039
|
+
options: z.array(z.string()).min(2).max(12).optional()
|
|
2040
|
+
.describe('Required for single_select and multi_select; omit for free_text.'),
|
|
1978
2041
|
related_artifact_id: z.string().nullable().optional(),
|
|
1979
2042
|
},
|
|
1980
2043
|
}, async (args) => {
|
|
@@ -1998,11 +2061,16 @@ export async function startMcpServer(deps) {
|
|
|
1998
2061
|
return result;
|
|
1999
2062
|
});
|
|
2000
2063
|
server.registerTool('record_user_input', {
|
|
2001
|
-
description: 'Persist
|
|
2064
|
+
description: 'Persist answers on first-class Decision records returned by ask_question. Use responses for structured free-text, single-select, or multi-select answers. Legacy decision_ids plus answer remains free-text only. This creates no answer or approval artifact.',
|
|
2002
2065
|
inputSchema: {
|
|
2003
2066
|
decision_ids: z.array(z.string()).min(1).optional(),
|
|
2004
2067
|
question_ids: z.array(z.string()).min(1).optional().describe('Compatibility alias for decision_ids'),
|
|
2005
|
-
answer: z.string(),
|
|
2068
|
+
answer: z.string().optional().describe('Legacy free-text answer used with decision_ids or question_ids'),
|
|
2069
|
+
responses: z.array(z.object({
|
|
2070
|
+
decision_id: z.string().min(1),
|
|
2071
|
+
selected_options: z.array(z.string()).optional(),
|
|
2072
|
+
answer_note: z.string().nullable().optional(),
|
|
2073
|
+
})).min(1).optional().describe('Structured answers. Free text uses answer_note; choice modes use selected_options and optional answer_note.'),
|
|
2006
2074
|
},
|
|
2007
2075
|
}, async (args) => {
|
|
2008
2076
|
const missing = requireExploredSessionRun(session, 'record_user_input');
|
package/dist/protocol.js
CHANGED
|
@@ -25,8 +25,8 @@ in the same stage. Only final user approval moves the item to Done.
|
|
|
25
25
|
\`workflow.enabled\` is false. After \`record_context_exploration\` succeeds,
|
|
26
26
|
if the task does not explicitly request a build or name a deliverable, the immediate
|
|
27
27
|
next tool call is \`ask_question\` with category \`intent\` and the exact question:
|
|
28
|
-
“What should I produce for this feature
|
|
29
|
-
diagram
|
|
28
|
+
“What should I produce for this feature?” using \`answer_mode = multi_select\` and
|
|
29
|
+
options \`build\`, \`plan\`, \`spec\`, \`diagram\`, \`mock\`, and \`wireframe\`. Then call \`end_work\` with reason
|
|
30
30
|
\`pending_user_answer\` and outcome \`blocked\`. Do not call \`get_task\` again,
|
|
31
31
|
\`update_task\`, \`create_artifact\`, \`reserve_work_paths\`, or any intervening tool.
|
|
32
32
|
The context artifact is the only artifact allowed before the answer. A read-only
|
|
@@ -112,8 +112,12 @@ is saved to the work item. I am applying the task's explicit intent gate now.”
|
|
|
112
112
|
final task revision, and every live artifact ID; continue only in a later pasted run.
|
|
113
113
|
7. **Persist every accepted answer and durable finding.** \`ask_question\` creates a
|
|
114
114
|
first-class decision and returns its decision ID for the web app to display and answer.
|
|
115
|
+
Choose the answer mode deliberately: \`free_text\` for an open response,
|
|
116
|
+
\`single_select\` for exactly one choice, or \`multi_select\` for one or more choices.
|
|
117
|
+
Choice modes require 2–12 concise, unique options and also accept optional written context.
|
|
115
118
|
When the user answers in the agent conversation instead, call \`record_user_input\` with
|
|
116
|
-
|
|
119
|
+
structured responses for each decision; the legacy decision IDs plus answer shape is
|
|
120
|
+
free-text only. Decisions
|
|
117
121
|
are not answer artifacts and are not copied into the task description.
|
|
118
122
|
Never leave an accepted answer or finding only in terminal, chat, or a subagent report.
|
|
119
123
|
8. **Reserve the exact write surface only when allowed.** Read-only workflow stages never
|
|
@@ -159,5 +163,5 @@ export const PROTOCOL_SHORT = 'ctrl-spc protocol (compact — full text: MCP pro
|
|
|
159
163
|
'2) Every item needs read-only subagents covering scope IDs once. Codex spawn_agent MUST use fork_turns:none; its self-contained prompt has exact IDs/paths/focus, no copied command. Child calls no ctrl-spc tools and writes nothing. If unavailable, record blocked; stop. ' +
|
|
160
164
|
'3) Parent must record_context_exploration before deciding, asking, planning, reserving, or editing. Before success narrate process only—no findings/conclusions/impact/decisions. Later summaries add no unpersisted facts. ' +
|
|
161
165
|
'4) Workflow stage data decides intent. Without a workflow, intent comes only from task, prompt, and accepted answers—never from findings. ' +
|
|
162
|
-
'5) record_context_exploration revises one task context across stages/agents. ask_question creates decisions answered in web; chat answers use record_user_input and create no answer artifact. Same purpose: update_artifact; different purpose: create_artifact. Plan approval stays on the plan, not in Decisions. Cover every active scope. No progress comments or terminal-only findings. ' +
|
|
166
|
+
'5) record_context_exploration revises one task context across stages/agents. ask_question creates free_text, single_select, or multi_select decisions answered in web; chat answers use structured record_user_input and create no answer artifact. Same purpose: update_artifact; different purpose: create_artifact. Plan approval stays on the plan, not in Decisions. Cover every active scope. No progress comments or terminal-only findings. ' +
|
|
163
167
|
'6) Call reserve_work_paths only when the stage allows writes and target the exact checkout; never edit collisions. release_work_paths, then end_work with persistence_receipt={final_task_revision,artifact_ids,outcome}.';
|
package/dist/skills.js
CHANGED
|
@@ -40,7 +40,7 @@ function protocolBody(mcpUrl) {
|
|
|
40
40
|
|
|
41
41
|
WORKFLOW AUTHORITY — when \`get_task.workflow.enabled\` is true, follow only its current stage, stored instructions, capabilities, requirements, and gate. Never infer or skip a stage. Persist each required artifact, then call \`submit_workflow_stage\`. Review gates advance only after explicit user approval in the web app or conversation; requested changes stay in the same stage. Only final approval sets Done.
|
|
42
42
|
|
|
43
|
-
HARD STOP — unclear novel feature (No workflow only): when \`workflow.enabled\` is false, after \`record_context_exploration\` succeeds, if the task does not explicitly request a build or name a deliverable, the next tool call MUST be \`ask_question\` with category \`intent
|
|
43
|
+
HARD STOP — unclear novel feature (No workflow only): when \`workflow.enabled\` is false, after \`record_context_exploration\` succeeds, if the task does not explicitly request a build or name a deliverable, the next tool call MUST be \`ask_question\` with category \`intent\`, the exact question “What should I produce for this feature?”, \`answer_mode = multi_select\`, and options \`build\`, \`plan\`, \`spec\`, \`diagram\`, \`mock\`, and \`wireframe\`. Then call \`end_work\` with reason \`pending_user_answer\` and outcome \`blocked\`. In that run, never call \`get_task\` again, \`update_task\`, \`create_artifact\`, \`reserve_work_paths\`, or any other tool between the context artifact and \`ask_question\`. The context artifact is the only allowed artifact. Do not copy findings into the task description before the user answers. A read-only execution sandbox is not a missing checkout and must not change this intent question. The user may select one or more: build, plan, spec, diagram, mock, wireframe.
|
|
44
44
|
|
|
45
45
|
Before the context artifact exists, never state or imply a requested deliverable and never mention repository counts, contents, tests, findings, or likely impact. If progress commentary is required, use only: “The required CTRL+SPC context review is in progress; no decision or repository change has been made.” After the context artifact and before the intent question, either say nothing or use only: “The required context review is saved to the work item. I am applying the task's explicit intent gate now.”
|
|
46
46
|
|
|
@@ -49,8 +49,8 @@ Before the context artifact exists, never state or imply a requested deliverable
|
|
|
49
49
|
3. Repair missing context — if \`begin_work\` reports an unavailable checkout, warn the user and offer to fetch or link it. Never fetch without approval. If the user explicitly continues without it, call \`acknowledge_unavailable_checkout\`, then call \`begin_work\` again after every missing scope is fetched or acknowledged.
|
|
50
50
|
4. Delegate exploration — every pasted work item MUST use at least one read-only subagent. After \`begin_work\` joins, partition every available repository scope exactly once across bounded subagent invocations. Every child prompt must explicitly list its assigned \`repository_scope_ids\`; across the batch each available ID appears exactly once. When using Codex \`spawn_agent\`, MUST pass \`fork_turns: "none"\`; never pass \`all\` or a recent-turn count. Give each child a self-contained assignment containing only its exact scope IDs, checkout paths, inspection focus, read-only restrictions, and required report shape. Never include or inherit the parent \`/ctrl-spc work\` command. The child must not call any ctrl-spc tool—including \`get_task\` or \`begin_work\`—or pick up the work item; it only inspects assigned paths and returns its report. If zero scopes are available after explicit acknowledgements, one child inspects the task/artifact context and reports those unavailable scopes without pretending they were reviewed. Children inspect only: no file mutation, path reservation, ctrl-spc call, \`tee\`, shell redirection, temp/capture file, install, formatter, cache-generating command, or any command that can write. Inspection-first is sufficient; do not decide intent before reports are persisted. Each report uses \`{ agent, focus, repository_scope_ids, inspected_paths, findings, likely_impact, unresolved_questions }\`. If no subagent is available, do not explore directly: call \`record_context_exploration\` with \`blocked_reason = subagents_unavailable\`, tell the user, and stop.
|
|
51
51
|
5. Persist context before deciding — the parent consolidates every child report and MUST call \`record_context_exploration\` to create or update the work item's stable-purpose context artifact before deciding what to do, asking a task question, planning, reserving, or editing. Every agent and workflow stage revises the same task-level context artifact instead of creating another card. Pass the structured reports plus a separate coverage array; include every active repository scope exactly once, and give unavailable scopes their acknowledgement. Before the tool succeeds, commentary may state process only: never disclose findings, conclusions, likely impact, or decisions. After persistence, terminal summaries may reference the persisted artifact/question but must add no unpersisted facts.
|
|
52
|
-
6. Decide or ask deterministically — assigned workflow stage data decides the work. Without a workflow, classify intent only from the work item's explicit wording and accepted answers, never from findings. Findings do not authorize a deliverable. Infer and perform the action only when the item explicitly names it. Otherwise persist only the necessary question with \`ask_question\`. For a bug, ask for reproduction steps, expected behavior, or actual behavior only when each is missing and not discoverable. For a novel feature with no explicit build action or deliverable, apply the HARD STOP above: the mandatory next mutating call after \`record_context_exploration\` is
|
|
53
|
-
7. Persist every accepted answer and finding — \`ask_question\` creates first-class decisions and returns decision IDs for the web app to display and answer. When the user answers in the agent conversation instead, use \`record_user_input\` with
|
|
52
|
+
6. Decide or ask deterministically — assigned workflow stage data decides the work. Without a workflow, classify intent only from the work item's explicit wording and accepted answers, never from findings. Findings do not authorize a deliverable. Infer and perform the action only when the item explicitly names it. Otherwise persist only the necessary question with \`ask_question\`. Choose \`free_text\` for an open response, \`single_select\` for exactly one choice, or \`multi_select\` for one or more choices; choice modes require 2–12 concise, unique options. For a bug, ask for reproduction steps, expected behavior, or actual behavior only when each is missing and not discoverable. For a novel feature with no explicit build action or deliverable, apply the HARD STOP above: the mandatory next mutating call after \`record_context_exploration\` is the specified multi-select \`ask_question\`. Generic goals such as “improve,” “coordinate,” “prepare,” or “support” are not deliverables. In this state do not call \`update_task\`, \`create_artifact\`, \`reserve_work_paths\`, or \`create_task\`; do not decide, edit, or complete. The context analysis is the only allowed artifact before the answer. Ask before overriding an unmet dependency, splitting work, fetching, or accepting a collision handoff. After \`ask_question\` succeeds, do no more work in that run: call \`end_work\` with reason \`pending_user_answer\` and outcome \`blocked\`, then wait for a later pasted run.
|
|
53
|
+
7. Persist every accepted answer and finding — \`ask_question\` creates first-class decisions and returns decision IDs for the web app to display and answer. When the user answers in the agent conversation instead, use \`record_user_input\` with one structured response per Decision: \`decision_id\`, \`selected_options\`, and optional \`answer_note\`. The legacy decision IDs plus exact-answer shape is free-text only. Decisions create no answer artifact and are not copied into the task description. Do not use progress comments; do not leave findings only in the terminal.
|
|
54
54
|
8. Reserve before writing — read-only stages never reserve writes or edit files. When the current stage allows writes, call \`reserve_work_paths\` for the exact checkout and paths; never edit through a collision. Never edit a conflicting path; narrow the reservation or use an isolated Git worktree.
|
|
55
55
|
9. Persist every substantive non-question output — give every artifact a clear title and stable \`purpose_key\`. Call \`create_artifact\` for a new purpose and \`update_artifact\` with the latest revision when the same purpose already exists; a different purpose creates a different artifact. Every artifact must have exactly one coverage disposition for every active repository scope. For workflow tasks, submit required artifact IDs and roles through \`submit_workflow_stage\`; the database decides whether to advance or wait for review. Plan approval is displayed on the submitted plan and is not a decision artifact. Never write planning documents into a repository.
|
|
56
56
|
10. Split only with approval — when work is too large, use \`ask_question\`; on approval create dependency-linked items with \`create_task\`, a shared feature tag, and matching board order.
|