@ctrl-spc/cs 0.7.2 → 0.7.3
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/login.js +9 -1
- package/dist/mcp.js +171 -22
- package/dist/skills.js +10 -1
- package/package.json +1 -1
package/dist/login.js
CHANGED
|
@@ -137,7 +137,15 @@ function renderPage(state) {
|
|
|
137
137
|
input { padding:8px 10px; font-size:14px; border:1px solid #ccc; border-radius:6px; }
|
|
138
138
|
button[type=submit] { margin-top:6px; padding:9px; border:none; border-radius:6px; background:#111; color:#fff; cursor:pointer; }
|
|
139
139
|
.error { color:#c0392b; font-size:13px; min-height:16px; }
|
|
140
|
-
@media (prefers-color-scheme: dark){
|
|
140
|
+
@media (prefers-color-scheme: dark){
|
|
141
|
+
body{background:#111;color:#eee;} p.sub{color:#999;}
|
|
142
|
+
input{background:#1c1c1c;border-color:#333;color:#eee;}
|
|
143
|
+
button[type=submit]{background:#eee;color:#111;}
|
|
144
|
+
.tabs{border-bottom-color:#333;}
|
|
145
|
+
.tabs button{color:#999;}
|
|
146
|
+
.tabs button.active{color:#fff;border-bottom-color:#fff;}
|
|
147
|
+
.error{color:#ff6b5e;}
|
|
148
|
+
}
|
|
141
149
|
</style></head><body>
|
|
142
150
|
<div id="app">
|
|
143
151
|
<h1>CTRL+SPC</h1><p class="sub">Sign in to link this computer.</p>
|
package/dist/mcp.js
CHANGED
|
@@ -4756,7 +4756,13 @@ export async function listCredentialsHandler(client) {
|
|
|
4756
4756
|
* returns `{secret, username}`; that maps to screen 8's shapes —
|
|
4757
4757
|
* api_key → `{kind, secret}`, login → `{kind, username, password}`.
|
|
4758
4758
|
*/
|
|
4759
|
-
export async function getCredentialHandler(client,
|
|
4759
|
+
export async function getCredentialHandler(client,
|
|
4760
|
+
// `id` is the web's copy-for-agent handle, and it is the ONLY thing that can
|
|
4761
|
+
// resolve the ambiguity the name path can only report: names are unique per
|
|
4762
|
+
// (org, creator), so two credentials the caller can read may share one, and
|
|
4763
|
+
// by name this tool could previously do nothing but tell the user to go
|
|
4764
|
+
// rename something. A pasted id names the row the user actually clicked.
|
|
4765
|
+
args,
|
|
4760
4766
|
// 18d Slice 3. OPTIONAL and trailing, so the existing two-argument call sites
|
|
4761
4767
|
// (and their tests) are untouched. Absent means "not a run" — see
|
|
4762
4768
|
// `rememberSecret`.
|
|
@@ -4765,26 +4771,38 @@ runTodoId = null) {
|
|
|
4765
4771
|
// whatever casing the agent happened to type: the match below is
|
|
4766
4772
|
// case-insensitive, so "talenttrack read token" must still redact as
|
|
4767
4773
|
// "[redacted: TalentTrack read token]".
|
|
4774
|
+
const wantedId = typeof args.id === 'string' ? args.id.trim() : '';
|
|
4775
|
+
if (!wantedId && !args.name) {
|
|
4776
|
+
return errorResult('get_credential requires name — as listed by list_credentials.');
|
|
4777
|
+
}
|
|
4768
4778
|
let match;
|
|
4769
4779
|
try {
|
|
4770
4780
|
const rows = (await must(client
|
|
4771
4781
|
.from('credentials')
|
|
4772
4782
|
.select('id, name, kind, org_id, created_by')
|
|
4773
4783
|
.order('created_at', { ascending: true }))) ?? [];
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4784
|
+
if (wantedId) {
|
|
4785
|
+
// An id names one row, so there is no ambiguity branch to reach. RLS
|
|
4786
|
+
// already filtered the rows, so a credential the caller cannot read is
|
|
4787
|
+
// simply absent and falls through to the uniform not-found below.
|
|
4788
|
+
match = rows.find((row) => row.id === wantedId);
|
|
4789
|
+
}
|
|
4790
|
+
else {
|
|
4791
|
+
const wanted = args.name.toLowerCase();
|
|
4792
|
+
// Uniqueness is per (org, creator): the caller can see two credentials
|
|
4793
|
+
// with the same lowercased name across two of their orgs, or within one
|
|
4794
|
+
// org (their own plus one shared to them by another creator). Never guess
|
|
4795
|
+
// which secret was meant. org_id/created_by are read only to word this
|
|
4796
|
+
// error; they never appear in tool output.
|
|
4797
|
+
const matches = rows.filter((row) => row.name.toLowerCase() === wanted);
|
|
4798
|
+
if (matches.length > 1) {
|
|
4799
|
+
const sameOrg = matches.every((row) => row.org_id === matches[0].org_id);
|
|
4800
|
+
return errorResult(sameOrg
|
|
4801
|
+
? `credential name "${args.name}" is ambiguous — you can access more than one credential with this name in the same organization (e.g. your own and one shared with you); delete or re-create yours under a different name, or ask an org owner to revoke a shared copy`
|
|
4802
|
+
: `credential name "${args.name}" is ambiguous — you have access to credentials with this name in more than one of your organizations; delete or re-create one under a different name`);
|
|
4803
|
+
}
|
|
4804
|
+
match = matches[0];
|
|
4805
|
+
}
|
|
4788
4806
|
}
|
|
4789
4807
|
catch (err) {
|
|
4790
4808
|
return errorResult(`get_credential failed: ${err.message}`);
|
|
@@ -4978,9 +4996,51 @@ export async function listSkillsHandler(client, args) {
|
|
|
4978
4996
|
* survives a compaction — an agent that has lost `get_skill`'s output still has
|
|
4979
4997
|
* the skill's name in the conversation and can still reach its files.
|
|
4980
4998
|
*/
|
|
4981
|
-
|
|
4999
|
+
/**
|
|
5000
|
+
* Resolve a skill by its row id — the handle the web's copy-for-agent button
|
|
5001
|
+
* pastes. An id names ONE row, so neither of the name path's two ambiguity
|
|
5002
|
+
* errors can arise and `org_id` has nothing left to disambiguate: the id
|
|
5003
|
+
* already decided which org's skill this is, and the bundle it belongs to says
|
|
5004
|
+
* which org that was. RLS does the access check, so a row the caller cannot see
|
|
5005
|
+
* is simply not found.
|
|
5006
|
+
*/
|
|
5007
|
+
async function resolveSkillById(client, id, tool) {
|
|
5008
|
+
const rows = (await must(client
|
|
5009
|
+
.from('skills')
|
|
5010
|
+
.select('id, bundle_id, name, description, relative_path, status, status_reason')
|
|
5011
|
+
.eq('id', id)
|
|
5012
|
+
.is('deleted_at', null))) ?? [];
|
|
5013
|
+
const row = rows[0];
|
|
5014
|
+
if (!row) {
|
|
5015
|
+
return {
|
|
5016
|
+
ok: false,
|
|
5017
|
+
error: errorResult(`${tool}: no skill with id ${id}. It may have been deleted, or belong to an organization you ` +
|
|
5018
|
+
'cannot see. Call list_skills to see the skills that exist.'),
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
// The bundle is what carries the org, and only a LIVE bundle counts: a skill
|
|
5022
|
+
// row can outlive the archiving of the bundle that brought it in.
|
|
5023
|
+
const bundles = await liveSkillBundles(client, null);
|
|
5024
|
+
const orgId = bundles.find((bundle) => bundle.id === row.bundle_id)?.org_id;
|
|
5025
|
+
if (!orgId) {
|
|
5026
|
+
return {
|
|
5027
|
+
ok: false,
|
|
5028
|
+
error: errorResult(`${tool}: skill "${row.name}" belongs to a skill pack that is no longer active. ` +
|
|
5029
|
+
'Re-import the pack in the web app.'),
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
return { ok: true, value: { row, orgId } };
|
|
5033
|
+
}
|
|
5034
|
+
async function resolveSkill(client, rawName, rawOrgId, tool,
|
|
5035
|
+
// The web's copy-for-agent handle. A name is still the addressing an agent
|
|
5036
|
+
// reaches for unaided (it survives a compaction; see this section's header),
|
|
5037
|
+
// so `id` is the ALTERNATIVE, not the replacement: it exists because the
|
|
5038
|
+
// Skills page copies `/ctrl-spc skill <id>` and a pasted row must resolve to
|
|
5039
|
+
// the row the user clicked, never to a same-named skill in another org.
|
|
5040
|
+
rawId = undefined) {
|
|
5041
|
+
const wantedId = typeof rawId === 'string' ? rawId.trim() : '';
|
|
4982
5042
|
const wanted = typeof rawName === 'string' ? rawName.trim() : '';
|
|
4983
|
-
if (!wanted) {
|
|
5043
|
+
if (!wantedId && !wanted) {
|
|
4984
5044
|
return {
|
|
4985
5045
|
ok: false,
|
|
4986
5046
|
error: errorResult(`${tool} requires name — the name of the skill to read, as listed by list_skills.`),
|
|
@@ -4989,6 +5049,8 @@ async function resolveSkill(client, rawName, rawOrgId, tool) {
|
|
|
4989
5049
|
const filter = skillOrgFilter(rawOrgId, tool);
|
|
4990
5050
|
if (!filter.ok)
|
|
4991
5051
|
return filter;
|
|
5052
|
+
if (wantedId)
|
|
5053
|
+
return resolveSkillById(client, wantedId, tool);
|
|
4992
5054
|
const bundles = await liveSkillBundles(client, filter.value);
|
|
4993
5055
|
const orgIdByBundle = new Map(bundles.map((bundle) => [bundle.id, bundle.org_id]));
|
|
4994
5056
|
const rows = orgIdByBundle.size
|
|
@@ -5167,7 +5229,7 @@ async function listSkillBundleFiles(client, orgId, bundleId, relativePath) {
|
|
|
5167
5229
|
}
|
|
5168
5230
|
export async function getSkillHandler(client, args) {
|
|
5169
5231
|
try {
|
|
5170
|
-
const resolved = await resolveSkill(client, args?.name, args?.org_id, 'get_skill');
|
|
5232
|
+
const resolved = await resolveSkill(client, args?.name, args?.org_id, 'get_skill', args?.id);
|
|
5171
5233
|
if (!resolved.ok)
|
|
5172
5234
|
return resolved.error;
|
|
5173
5235
|
const { row: match, orgId } = resolved.value;
|
|
@@ -5276,7 +5338,7 @@ export async function readSkillFileHandler(client, args) {
|
|
|
5276
5338
|
if (!wantedPath) {
|
|
5277
5339
|
return errorResult('read_skill_file requires path — one of the paths get_skill listed in bundle_files.');
|
|
5278
5340
|
}
|
|
5279
|
-
const resolved = await resolveSkill(client, args?.name, args?.org_id, 'read_skill_file');
|
|
5341
|
+
const resolved = await resolveSkill(client, args?.name, args?.org_id, 'read_skill_file', args?.id);
|
|
5280
5342
|
if (!resolved.ok)
|
|
5281
5343
|
return resolved.error;
|
|
5282
5344
|
const { row: match, orgId } = resolved.value;
|
|
@@ -5883,6 +5945,58 @@ async function unnarrowedEmptyNote(client, projectId) {
|
|
|
5883
5945
|
}
|
|
5884
5946
|
return NO_PROJECT_DOCUMENTS_NOTE;
|
|
5885
5947
|
}
|
|
5948
|
+
export async function getDocumentHandler(client, args) {
|
|
5949
|
+
const id = typeof args.id === 'string' ? args.id.trim() : '';
|
|
5950
|
+
if (!id) {
|
|
5951
|
+
return errorResult('get_document requires id — the id of the document to read, as the web app’s “Copy for agent” ' +
|
|
5952
|
+
'button pastes it.');
|
|
5953
|
+
}
|
|
5954
|
+
// Shape-checked once, here at the boundary, exactly as resolveContextProject
|
|
5955
|
+
// does with task_id: without it a mistyped id comes back as a Postgres
|
|
5956
|
+
// "invalid input syntax for type uuid", which reads like a bug in the tool.
|
|
5957
|
+
if (!UUID_RE.test(id)) {
|
|
5958
|
+
return errorResult(`get_document: not a valid document id: "${id}".`);
|
|
5959
|
+
}
|
|
5960
|
+
try {
|
|
5961
|
+
// Context documents first, then instructions. RLS scopes both reads, so a
|
|
5962
|
+
// row the caller may not see is simply absent and falls through to the
|
|
5963
|
+
// not-found below.
|
|
5964
|
+
const documents = await must(client
|
|
5965
|
+
.from('project_documents')
|
|
5966
|
+
.select('title, content, codebase_id, type')
|
|
5967
|
+
.eq('id', id));
|
|
5968
|
+
const document = (documents ?? [])[0];
|
|
5969
|
+
if (document)
|
|
5970
|
+
return textResult(shapeDocument(document));
|
|
5971
|
+
const instructions = await must(client.from('agent_instructions').select('title, content, codebase_id').eq('id', id));
|
|
5972
|
+
const instruction = (instructions ?? [])[0];
|
|
5973
|
+
if (instruction) {
|
|
5974
|
+
return textResult({
|
|
5975
|
+
...shapeDocument(instruction),
|
|
5976
|
+
// Said plainly, so an agent that fetched one does not conclude these are
|
|
5977
|
+
// reference material it may choose to consult: it already has them.
|
|
5978
|
+
note: 'This is an agent instruction. Every instruction on this project is already delivered in ' +
|
|
5979
|
+
'your prompt — reading one here does not make it optional.',
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
return errorResult(`get_document: no document with id ${id}. It may have been deleted, or belong to a project you ` +
|
|
5983
|
+
'cannot see. Call get_project_context to read the documents on the project you are working.');
|
|
5984
|
+
}
|
|
5985
|
+
catch (err) {
|
|
5986
|
+
return errorResult(`get_document failed: ${errorMessage(err)}`);
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
/** The one shape both tables report in, so a caller never has to branch on
|
|
5990
|
+
* which table answered. `scope` is what `codebase_id` MEANS — null is the
|
|
5991
|
+
* project's own document, a value is that codebase's. */
|
|
5992
|
+
function shapeDocument(row) {
|
|
5993
|
+
return {
|
|
5994
|
+
title: row.title,
|
|
5995
|
+
...(row.type ? { type: row.type } : {}),
|
|
5996
|
+
scope: row.codebase_id === null ? 'project' : 'codebase',
|
|
5997
|
+
content: row.content,
|
|
5998
|
+
};
|
|
5999
|
+
}
|
|
5886
6000
|
// ---------------------------------------------------------------------------
|
|
5887
6001
|
// Project context (feature 13a, Phase 4) — `propose_project_context`.
|
|
5888
6002
|
//
|
|
@@ -9802,6 +9916,7 @@ export const TOOL_NAMES = [
|
|
|
9802
9916
|
'get_skill',
|
|
9803
9917
|
'read_skill_file',
|
|
9804
9918
|
'get_project_context',
|
|
9919
|
+
'get_document',
|
|
9805
9920
|
'propose_project_context',
|
|
9806
9921
|
'list_product_ideas',
|
|
9807
9922
|
'create_product_idea',
|
|
@@ -11216,7 +11331,16 @@ runTodoIdSource = null) {
|
|
|
11216
11331
|
'comments, artifacts, documents and questions, and name the credential instead. Never refuse the ' +
|
|
11217
11332
|
'work to avoid touching the value.',
|
|
11218
11333
|
inputSchema: {
|
|
11219
|
-
name: z
|
|
11334
|
+
name: z
|
|
11335
|
+
.string()
|
|
11336
|
+
.min(1)
|
|
11337
|
+
.optional()
|
|
11338
|
+
.describe('Credential name, as listed by list_credentials'),
|
|
11339
|
+
id: z
|
|
11340
|
+
.string()
|
|
11341
|
+
.optional()
|
|
11342
|
+
.describe('Credential id, as pasted by the web app\'s "Copy for agent" button. Use it instead of ' +
|
|
11343
|
+
'name; it names exactly one credential, where a name can be ambiguous.'),
|
|
11220
11344
|
},
|
|
11221
11345
|
}, async (args) => {
|
|
11222
11346
|
touchSession(connectionId);
|
|
@@ -11250,7 +11374,12 @@ runTodoIdSource = null) {
|
|
|
11250
11374
|
'about to do. If two organizations hold a skill of the same name this refuses rather than ' +
|
|
11251
11375
|
'guessing: pass org_id to say which you mean. Read-only.',
|
|
11252
11376
|
inputSchema: {
|
|
11253
|
-
name: z.string().min(1).describe('Skill name, as listed by list_skills'),
|
|
11377
|
+
name: z.string().min(1).optional().describe('Skill name, as listed by list_skills'),
|
|
11378
|
+
id: z
|
|
11379
|
+
.string()
|
|
11380
|
+
.optional()
|
|
11381
|
+
.describe('Skill id, as pasted by the web app\'s "Copy for agent" button. Use it instead of name; ' +
|
|
11382
|
+
'it names exactly one skill, so org_id is never needed with it.'),
|
|
11254
11383
|
org_id: z
|
|
11255
11384
|
.string()
|
|
11256
11385
|
.optional()
|
|
@@ -11266,7 +11395,11 @@ runTodoIdSource = null) {
|
|
|
11266
11395
|
'those paths here. WHEN A SKILL POINTS AT A FILE BESIDE IT, FETCH IT AND FOLLOW IT — never ask ' +
|
|
11267
11396
|
'the user to supply a file the organization already stored. Text only, up to 512 KB. Read-only.',
|
|
11268
11397
|
inputSchema: {
|
|
11269
|
-
name: z.string().min(1).describe('Skill name, as listed by list_skills'),
|
|
11398
|
+
name: z.string().min(1).optional().describe('Skill name, as listed by list_skills'),
|
|
11399
|
+
id: z
|
|
11400
|
+
.string()
|
|
11401
|
+
.optional()
|
|
11402
|
+
.describe('Skill id. Use it instead of name; it names exactly one skill.'),
|
|
11270
11403
|
path: z
|
|
11271
11404
|
.string()
|
|
11272
11405
|
.min(1)
|
|
@@ -11313,6 +11446,22 @@ runTodoIdSource = null) {
|
|
|
11313
11446
|
touchSession(connectionId);
|
|
11314
11447
|
return getProjectContextHandler(client, openSessions.get(connectionId) ?? null, args);
|
|
11315
11448
|
});
|
|
11449
|
+
server.registerTool('get_document', {
|
|
11450
|
+
description: 'Read ONE context document or agent instruction by its id — the whole document, not an excerpt. ' +
|
|
11451
|
+
'Use it when someone hands you an id (the web app’s “Copy for agent” button pastes one), or when ' +
|
|
11452
|
+
'get_project_context named a document you need in full. It resolves either kind, so you do not ' +
|
|
11453
|
+
'need to know which one the id belongs to. To read a project’s context as a whole instead, call ' +
|
|
11454
|
+
'get_project_context. Read-only.',
|
|
11455
|
+
inputSchema: {
|
|
11456
|
+
id: z
|
|
11457
|
+
.string()
|
|
11458
|
+
.min(1)
|
|
11459
|
+
.describe('Document id, as the web app’s “Copy for agent” button pastes it'),
|
|
11460
|
+
},
|
|
11461
|
+
}, async (args) => {
|
|
11462
|
+
touchSession(connectionId);
|
|
11463
|
+
return getDocumentHandler(client, args);
|
|
11464
|
+
});
|
|
11316
11465
|
server.registerTool('propose_project_context', {
|
|
11317
11466
|
// The description TEACHES: when to reach for this (after reading a repo),
|
|
11318
11467
|
// what a good proposal is (content it actually read, one line of why),
|
package/dist/skills.js
CHANGED
|
@@ -15,7 +15,7 @@ import { dirname, join } from 'node:path';
|
|
|
15
15
|
*/
|
|
16
16
|
// Verbatim from the web/skill contract the copied `/ctrl-spc work <id>`
|
|
17
17
|
// invocation depends on.
|
|
18
|
-
const SKILL_DESCRIPTION = 'Work a CTRL+SPC work item or artifact by ID — delegates read-only exploration and writes analysis/plans/specs/diagrams/mocks/wireframes back via the ctrl-spc MCP tools. Use when the user pastes /ctrl-spc work <id> or /ctrl-spc artifact <id>.';
|
|
18
|
+
const SKILL_DESCRIPTION = 'Work a CTRL+SPC work item or artifact by ID — delegates read-only exploration and writes analysis/plans/specs/diagrams/mocks/wireframes back via the ctrl-spc MCP tools. Use when the user pastes /ctrl-spc work <id> or /ctrl-spc artifact <id>, and to fetch one object when they paste /ctrl-spc workflow <id>, /ctrl-spc skill <id>, /ctrl-spc credential <id> or /ctrl-spc document <id>.';
|
|
19
19
|
/** Home the skill files are written under. `CTRL_SPC_HOME` is the existing
|
|
20
20
|
* documented name for exactly this (docs/cli-surface-catalog.md), reused
|
|
21
21
|
* rather than renamed so one machine has one sandbox-home concept. */
|
|
@@ -77,6 +77,15 @@ export function installSkills(agents) {
|
|
|
77
77
|
*/
|
|
78
78
|
const PROTOCOL_BODY = `Resolve a CTRL+SPC work item or artifact pasted as \`/ctrl-spc work <id>\` or \`/ctrl-spc artifact <id>\`, then follow the ctrl-spc working protocol:
|
|
79
79
|
|
|
80
|
+
FETCH VERBS — \`workflow\`, \`skill\`, \`credential\` and \`document\` are NOT the working protocol. They name ONE object the user copied so they can use it in this conversation, and nothing below applies to them: no \`begin_work\`, no subagents, no context artifact, no question, no \`end_work\`. Fetch the object, use it for what the user asked, and answer.
|
|
81
|
+
|
|
82
|
+
- \`/ctrl-spc workflow <id>\` — call \`read_workflow_library\` and use the workflow whose \`id\` matches, with the full body of each stage it holds. To RUN it on a work item, call \`start_workflow\` with that workflow id, then follow the working protocol below for the item. Report and stop when the user only asked to see it.
|
|
83
|
+
- \`/ctrl-spc skill <id>\` — call \`get_skill\` with \`id\`. THE RESULT IS A DOCUMENT TO FOLLOW for the rest of this work, not reference material to summarise. Its supporting files come from \`read_skill_file\` with the same \`id\`.
|
|
84
|
+
- \`/ctrl-spc credential <id>\` — call \`get_credential\` with \`id\`, then USE the value to do the work: a header, an environment variable, a command, local config. Never publish it: keep it out of your answer, comments, artifacts and questions, and name the credential instead.
|
|
85
|
+
- \`/ctrl-spc document <id>\` — call \`get_document\` with \`id\`. One context document or one agent instruction, whole. It resolves either kind, so you do not need to know which the id names. To read a project's context as a whole instead, call \`get_project_context\`.
|
|
86
|
+
|
|
87
|
+
Pass the pasted id as \`id\`. It names exactly one object, so \`org_id\` is never needed with it and a duplicate name cannot send you to the wrong one. Everything that follows is the WORK ITEM protocol:
|
|
88
|
+
|
|
80
89
|
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 \`hand_off_stage\` with the work item id and this request id, and stop: the next stage is worked by a fresh agent reading the record. It refuses a stage with unfinished steps and the last stage of the process, where you answer and stop instead. 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.
|
|
81
90
|
|
|
82
91
|
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.
|
package/package.json
CHANGED