tascan-mcp 3.14.0 → 3.15.0

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 CHANGED
@@ -268,6 +268,16 @@ API keys are scoped to your organization and support rate limiting (60 requests/
268
268
 
269
269
  ## Changelog
270
270
 
271
+ ### v3.15.0 — 2026-09-19
272
+
273
+ - `tascan_create_cycle` gains an optional `reviews[]` array — a multi-lens review panel (design item 14a): 1–8 lenses, each `{lens, brief, provider: openai|anthropic|gemini, model?, blocking? (default true), max_tool_calls? (openai only)}`, forwarded to `POST /coord/cycles`; at least one lens must be blocking. Omit it for the single OpenAI review.
274
+ - `tascan_get_cycle_report` prints `review[<lens>]` for panel review steps; legacy steps print `review` unchanged.
275
+ - 79 tools, no new tool, scopes unchanged.
276
+
277
+ ### v3.14.1 — 2026-09-18
278
+ - `tascan_create_cycle`: new optional `repo` argument — which codebase on the executor the build runs in, an alias from the executor's allowlist (`tascan-agent/repos.json`; default `tascan`). `artifact_paths` are relative to that repo. An unknown alias is refused by the executor (`repo_unknown`) and the cycle parks — the protocol can now build, review and gate work in any project the executor lists, not only TaScan itself (migration 161).
279
+ - 78 tools.
280
+
271
281
  ### v3.14.0 — 2026-09-18
272
282
  - **Coordination layer live** (migration 155): `tascan_create_cycle`, `tascan_get_cycle_report`, `tascan_get_build`, `tascan_get_build_file`, `tascan_post_message` now run against the released schema — build → independent review → one human decision → integrate, with signed receipts at every step.
273
283
  - **Scheduled SMS** (migration 157): `tascan_schedule_sms` (full tier), `tascan_list_scheduled_sms` (read), `tascan_cancel_scheduled_sms` (full) — TaScan's own 5-minute scheduler sends through the same guarded lane as `tascan_send_sms`; nothing outside TaScan has to stay awake.
package/index.js CHANGED
@@ -46,7 +46,7 @@ async function api(method, path, body) {
46
46
  api.key = API_KEY; // raw-fetch tools read the key from here (S99: those functions now require auth)
47
47
 
48
48
  const server = new Server(
49
- { name: 'tascan', version: '3.14.0' },
49
+ { name: 'tascan', version: '3.15.0' },
50
50
  { capabilities: { tools: {} } }
51
51
  );
52
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tascan-mcp",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "mcpName": "io.github.snowbikemike/tascan-mcp",
5
5
  "description": "TaScan MCP Server — Closed-loop autonomous operations protocol. 70 tools for projects, tasks, workers, QR codes, NFC tags, worker marketplace with consented SMS invites, geofenced zones with rules (work site / hazard + AI-verified PPE checkpoints / containment / restricted, breach alerts, zone compliance audit), AI condition assessment, worker passports, verification-gated gig payments, client invoicing from verified work (overtime / day rate / per diem / expenses), shareable report links (completion, client service report, project, evidence pack), signed Action Receipts (Ed25519 JWS), cross-entity search, duplicate-worker detection, worker identity merge, AI issue analysis, and autonomous remediation dispatch. 15 patents filed (470+ claims). Task. Scan. Done.",
6
6
  "type": "module",
package/scopes.cjs CHANGED
@@ -179,6 +179,8 @@ const TOOL_SCOPES = {
179
179
  tascan_get_receipt: R, // W3 receipt.js (GET /api/v1/receipts/:id — read tier there too)
180
180
  // coordination layer (protocol v0.2 design item 1, migration 155) — read tier: the reviewer's ['read'] key may call these
181
181
  tascan_get_build: R, tascan_get_build_file: R, tascan_get_cycle_report: R,
182
+ // design item 13c: one call that summarizes a whole project for a chat client
183
+ tascan_project_digest: R,
182
184
  // write here; REST upgrades POST /tasks/:id/messages to agent:dispatch (:code for CODE/SHELL askers) when the task is a cycle task
183
185
  tascan_post_message: W,
184
186
  // creating a cycle queues a CODE:/SHELL: prompt on the AI Inbox → agent:dispatch:code, always
@@ -228,6 +230,7 @@ const ROUTE_SCOPES = {
228
230
  project: { GET: R, PUT: W, DELETE: W },
229
231
  project_lists: { GET: R, POST: W },
230
232
  project_responses: { GET: R },
233
+ project_digest: { GET: R }, // design item 13c: GET /projects/:id/digest
231
234
  list: { GET: R, PUT: W, DELETE: W },
232
235
  list_tasks: { GET: R, POST: INBOX_WRITE },
233
236
  list_qr: { POST: W },
package/tools.cjs CHANGED
@@ -1992,20 +1992,37 @@ const TOOLS = [
1992
1992
  // SECURITY DEFINER RPCs behind human completions. The cycle report is the audit surface.
1993
1993
  {
1994
1994
  name: 'tascan_create_cycle',
1995
- description: 'Start an unattended build-review-decide cycle (protocol v0.2). Queues T1 CODE: (or SHELL:) with your build_brief on the AI Inbox and T2 REVIEW: with your review_brief, born blocked on T1. The local executor builds, stores the exact bytes of artifact_paths as a bundle (build_ref = sha256 over the manifest), the independent reviewer reviews THAT bundle, an approve verdict mints a Decision task for the human authority (one SMS), Approve mints an Integrate task for the deploy id. Revise verdicts spawn revisions (cap max_revisions, default 3); reject, human Reject, scope violations or exhausted revisions PARK the cycle (a Parked task with Resume with notes / Close). REQUIRES agent:dispatch:code. Duplicate protection: the same idempotency_key, or the same briefs + paths, within 24 h returns the existing root instead of queueing again (created=false). Track with tascan_get_cycle_report (root_id). Nothing spawns a cycle on its own.',
1995
+ description: 'Start an unattended build-review-decide cycle (protocol v0.2). Queues T1 CODE: (or SHELL:) with your build_brief on the AI Inbox and T2 REVIEW: with your review_brief, born blocked on T1. The local executor builds, stores the exact bytes of artifact_paths as a bundle (build_ref = sha256 over the manifest), the independent reviewer reviews THAT bundle, an approve verdict mints a Decision task for the human authority (one SMS), Approve mints an Integrate task for the deploy id. Revise verdicts spawn revisions (cap max_revisions, default 3); reject, human Reject, scope violations or exhausted revisions PARK the cycle (a Parked task with Resume with notes / Close). REQUIRES agent:dispatch:code. Duplicate protection: the same idempotency_key, or the same briefs + paths, within 24 h returns the existing root instead of queueing again (created=false). Optional `reviews[]` attaches a multi-lens review panel (design item 14a) in place of the single OpenAI review — one review task mints per lens and every blocking lens must approve before the Decision task mints. Track with tascan_get_cycle_report (root_id). Nothing spawns a cycle on its own.',
1996
1996
  inputSchema: {
1997
1997
  type: 'object',
1998
1998
  properties: {
1999
1999
  project_id: { type: 'string', description: 'Working project (UUID). Its Decisions and Agent Questions lists are created on the first cycle (coord_ensure_lists). Requires the org human authority to be configured (coord_set_authority) — otherwise 403.' },
2000
2000
  title: { type: 'string', description: 'Short human title (1-200 chars). T1 becomes "CODE: <title>", T2 "REVIEW: <title>".' },
2001
2001
  build_brief: { type: 'string', description: 'The executor prompt (1-40000 chars). Executed verbatim by the local Claude Code runner as a CODE:/SHELL: task — write it as a complete instruction, name the files, forbid nothing the runner already forbids (deploy/commit/push are refused by its context).' },
2002
- review_brief: { type: 'string', description: 'The reviewer prompt (1-40000 chars). The reviewer reads the stored bundle (tascan_get_build / tascan_get_build_file), the task text and the filtered trail; it returns approve, revise, reject or needs_input as strict JSON bound to the build_ref.' },
2002
+ review_brief: { type: 'string', description: 'The reviewer prompt (1-40000 chars). The reviewer reads the stored bundle (tascan_get_build / tascan_get_build_file), the task text and the filtered trail; it returns approve, revise, reject or needs_input as strict JSON bound to the build_ref. Still required even when `reviews[]` is given (kept as T2\'s legacy description text).' },
2003
2003
  artifact_paths: { type: 'array', items: { type: 'string' }, description: 'Repo-relative paths the build binds (1-64). Exactly these files are stored as the bundle and hashed into build_ref; files the executor touches OUTSIDE them fail the scope check and park the cycle. No .., not absolute, no drive letter, no backslash.' },
2004
2004
  task_type: { type: 'string', enum: ['CODE', 'SHELL'], description: 'T1 prefix (default CODE).' },
2005
2005
  max_revisions: { type: 'integer', description: 'Revision cap (default 3): at most max_revisions + 1 builds and reviews.' },
2006
2006
  max_questions: { type: 'integer', description: 'Questions a runner may ask per task before the attempt fails (default 3).' },
2007
+ repo: { type: 'string', description: 'Which codebase on the executor the build runs in — an alias from the executor\'s allowlist (tascan-agent/repos.json), e.g. "tascan" (default), "merchskipper", "inkskipper", "rangerlizzy", "cardvault", "take1", "fitcheck", "safegrid", "eternity", "maniacs". artifact_paths are relative to that repo. An unknown alias is refused by the executor (repo_unknown) and the cycle parks.' },
2007
2008
  max_cost_micro_usd: { type: 'integer', description: 'Per-cycle spend cap summed over every attempt, in micro-USD (default 5000000 = USD 5). A claim that could overrun it is refused (budget_exhausted).' },
2008
2009
  preview_url: { type: 'string', description: 'Optional https preview link shown on the Decision task.' },
2010
+ reviews: {
2011
+ type: 'array',
2012
+ description: 'Optional multi-lens review panel (design item 14a) instead of today\'s single OpenAI review_brief lens — 1-8 entries, each: {lens: slug matching ^[a-z][a-z0-9-]{0,39}$ unique per array (e.g. "code-correctness", "security", "ui-ux"), brief: 1-8000 chars of cycle-specific context for that lens, provider: "openai"|"anthropic"|"gemini", model?: 1-100 chars, blocking?: boolean (default true — at least one entry must be blocking or the whole array is refused), max_tool_calls?: integer (openai only)}. One REVIEW: task mints per lens, all blocked on T1; the Decision task mints only once every blocking lens has approved the same build. Omit for today\'s single OpenAI code review.',
2013
+ items: {
2014
+ type: 'object',
2015
+ properties: {
2016
+ lens: { type: 'string', description: 'Slug, ^[a-z][a-z0-9-]{0,39}$, unique in the array.' },
2017
+ brief: { type: 'string', description: 'Cycle-specific context for this lens (1-8000 chars).' },
2018
+ provider: { type: 'string', enum: ['openai', 'anthropic', 'gemini'] },
2019
+ model: { type: 'string', description: 'Optional, 1-100 chars.' },
2020
+ blocking: { type: 'boolean', description: 'Default true. At least one entry in the array must be blocking.' },
2021
+ max_tool_calls: { type: 'integer', description: 'Optional, provider openai only.' }
2022
+ },
2023
+ required: ['lens', 'brief', 'provider']
2024
+ }
2025
+ },
2009
2026
  checkpoint_title: { type: 'string', description: 'Optional title template for the Decision task (default "Decision: <title>").' },
2010
2027
  checkpoint_description: { type: 'string', description: 'Optional description template for the Decision task; the build_ref, preview and findings summary are appended.' },
2011
2028
  integrate_title: { type: 'string', description: 'Optional title template for the Integrate task (default "Integrate: <title>").' },
@@ -2019,7 +2036,9 @@ const TOOLS = [
2019
2036
  const paths = Array.isArray(args.artifact_paths) ? args.artifact_paths : (typeof args.artifact_paths === 'string' ? [args.artifact_paths] : []);
2020
2037
  if (!paths.length) throw new Error('artifact_paths is required (1-64 repo-relative paths)');
2021
2038
  const options = {};
2039
+ if (args.repo) options.repo = String(args.repo);
2022
2040
  for (const k of ['task_type', 'max_revisions', 'max_questions', 'max_cost_micro_usd', 'preview_url']) if (args[k] != null && args[k] !== '') options[k] = args[k];
2041
+ if (Array.isArray(args.reviews) && args.reviews.length) options.reviews = args.reviews;
2023
2042
  const templates = {};
2024
2043
  if (args.checkpoint_title || args.checkpoint_description) templates.checkpoint = { ...(args.checkpoint_title ? { title: args.checkpoint_title } : {}), ...(args.checkpoint_description ? { description: args.checkpoint_description } : {}) };
2025
2044
  if (args.integrate_title || args.integrate_description) templates.integrate = { ...(args.integrate_title ? { title: args.integrate_title } : {}), ...(args.integrate_description ? { description: args.integrate_description } : {}) };
@@ -2031,7 +2050,10 @@ const TOOLS = [
2031
2050
  if (result.duplicate || d.created === false) {
2032
2051
  return `DUPLICATE — nothing was queued. A cycle with the same ${args.idempotency_key ? 'idempotency_key' : 'briefs and paths'} exists from the last 24 h (status ${d.status || 'active'}).\nroot_id: ${d.root_id}\nreview_id: ${d.review_id || 'n/a'}\n\nRead it: tascan_get_cycle_report root_id=${d.root_id}`;
2033
2052
  }
2034
- return `Cycle created.\nroot_id (T1 ${options.task_type || 'CODE'}: build): ${d.root_id}\nreview_id (T2 REVIEW, blocked on T1): ${d.review_id}\nbrief_sha256: ${d.brief_sha256}\ndecisions_list_id: ${d.decisions_list_id}\nquestions_list_id: ${d.questions_list_id}\nartifact_paths: ${paths.join(', ')}\n\nNext: the local executor claims T1 on its next poll (≤ 30 s), stores the bundle and finishes with build_ref; T2 is released from that finish. Follow with tascan_get_cycle_report root_id=${d.root_id} or tascan_get_task task_id=${d.root_id} (agent + trail blocks). Nobody is texted until the reviewer approves.`;
2053
+ const reviewLine = Array.isArray(d.review_ids) && d.review_ids.length > 0
2054
+ ? `review_ids (T2 REVIEW panel, ${d.review_ids.length} lens${d.review_ids.length > 1 ? 'es' : ''}, blocked on T1): ${d.review_ids.join(', ')}`
2055
+ : `review_id (T2 REVIEW, blocked on T1): ${d.review_id}`;
2056
+ return `Cycle created.\nroot_id (T1 ${options.task_type || 'CODE'}: build): ${d.root_id}\n${reviewLine}\nbrief_sha256: ${d.brief_sha256}\ndecisions_list_id: ${d.decisions_list_id}\nquestions_list_id: ${d.questions_list_id}\nartifact_paths: ${paths.join(', ')}\n\nNext: the local executor claims T1 on its next poll (≤ 30 s), stores the bundle and finishes with build_ref; T2 is released from that finish. Follow with tascan_get_cycle_report root_id=${d.root_id} or tascan_get_task task_id=${d.root_id} (agent + trail blocks). Nobody is texted until the reviewer approves.`;
2035
2057
  }
2036
2058
  },
2037
2059
  {
@@ -2140,7 +2162,10 @@ const TOOLS = [
2140
2162
  ''
2141
2163
  ];
2142
2164
  lines.push('STEPS');
2143
- (s.steps || []).forEach(st => lines.push(` ${st.kind} r${st.revision}${st.seq ? '.' + st.seq : ''} ${st.title} [${st.blocked_by ? 'blocked' : st.completed ? 'completed' : st.released_at ? 'released' : 'open'}; ${st.attempts || 0} attempt(s)] ${st.id}`));
2165
+ // design item 14a-1b: a review step whose coord carries a lens (multi-lens panel, migration 168) prints
2166
+ // 'review[<lens>]'; a legacy single-review step (no lens key) prints plain 'review', byte-identical to before.
2167
+ const kindLabel = st => st.kind === 'review' && st.lens ? `review[${st.lens}]` : st.kind;
2168
+ (s.steps || []).forEach(st => lines.push(` ${kindLabel(st)} r${st.revision}${st.seq ? '.' + st.seq : ''} ${st.title} [${st.blocked_by ? 'blocked' : st.completed ? 'completed' : st.released_at ? 'released' : 'open'}; ${st.attempts || 0} attempt(s)] ${st.id}`));
2144
2169
  if (s.verdicts && s.verdicts.length) { lines.push('', 'VERDICTS'); s.verdicts.forEach(v => lines.push(` r${v.revision} ${v.verdict || 'missing'} on ${short(v.build_ref)} ${v.from_reviewer ? '' : '(NOT the reviewer identity) '}completion ${v.completion_id}`)); }
2145
2170
  if (s.decisions && s.decisions.length) { lines.push('', 'DECISIONS'); s.decisions.forEach(v => lines.push(` ${v.decision} by ${v.actor_id} on ${short(v.build_ref)} completion ${v.completion_id || 'n/a'}`)); }
2146
2171
  if (s.questions && s.questions.length) { lines.push('', 'QUESTIONS'); s.questions.forEach(q => lines.push(` to ${q.addressee}: ${q.title} — ${q.answered ? 'answered' : 'AWAITING ANSWER'} (${q.id})`)); }
@@ -2150,6 +2175,24 @@ const TOOLS = [
2150
2175
  if (args.full) out += '\n\n' + JSON.stringify(d, null, 2);
2151
2176
  return out.length > 12000 ? out.slice(0, 12000) + '\n…[truncated at 12000 chars — read sections with tascan_get_task / tascan_get_receipt]' : out;
2152
2177
  }
2178
+ },
2179
+ {
2180
+ name: 'tascan_project_digest',
2181
+ description: 'One call that gives a chat client (ChatGPT, Claude) a whole TaScan project in about 2,000 words: the project, every task list with task counts, open decisions and questions, the last 5 coordination cycles with verdicts and spend, the latest 5 receipts, and total spend — returned as Markdown (capped at 12,000 chars). Read tier. Use it before asking a human to paste anything.',
2182
+ inputSchema: {
2183
+ type: 'object',
2184
+ properties: {
2185
+ project_id: { type: 'string', description: 'Project ID (UUID).' }
2186
+ },
2187
+ required: ['project_id']
2188
+ },
2189
+ annotations: { title: 'Project Digest', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
2190
+ handler: async (args, api) => {
2191
+ const id = String(args.project_id || '').trim().toLowerCase();
2192
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id)) throw new Error('project_id must be a UUID');
2193
+ const result = await api('GET', `/projects/${id}/digest`);
2194
+ return result.markdown || '(no digest returned)';
2195
+ }
2153
2196
  }
2154
2197
  ];
2155
2198