@schoolai/shipyard-mcp 0.1.3-next.464 → 0.1.3-next.467

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.
@@ -28494,7 +28494,7 @@ init_cjs_shims();
28494
28494
  // ../../packages/schema/dist/index.mjs
28495
28495
  init_cjs_shims();
28496
28496
 
28497
- // ../../packages/schema/dist/yjs-helpers-CFQmkNYl.mjs
28497
+ // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
28498
28498
  init_cjs_shims();
28499
28499
 
28500
28500
  // ../../packages/schema/dist/plan.mjs
@@ -42665,7 +42665,7 @@ var PRReviewCommentSchema = external_exports.object({
42665
42665
  resolved: external_exports.boolean().optional()
42666
42666
  });
42667
42667
 
42668
- // ../../packages/schema/dist/yjs-helpers-CFQmkNYl.mjs
42668
+ // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
42669
42669
  function assertNever2(value) {
42670
42670
  throw new Error(`Unhandled discriminated union member: ${JSON.stringify(value)}`);
42671
42671
  }
@@ -863,7 +863,7 @@ function createHandedOffConversationVersion(params) {
863
863
  return ConversationVersionSchema.parse(version);
864
864
  }
865
865
 
866
- // ../../packages/schema/dist/yjs-helpers-CFQmkNYl.mjs
866
+ // ../../packages/schema/dist/yjs-helpers-BBG4tC2R.mjs
867
867
  import { z as z2 } from "zod";
868
868
  import { nanoid as nanoid2 } from "nanoid";
869
869
  import * as Y from "yjs";
@@ -1176,6 +1176,30 @@ function applyStatusTransitionFields(map, transition) {
1176
1176
  assertNever(transition);
1177
1177
  }
1178
1178
  }
1179
+ function resetPlanToDraft(ydoc, actor) {
1180
+ const metadataResult = getPlanMetadataWithValidation(ydoc);
1181
+ if (!metadataResult.success) return {
1182
+ success: false,
1183
+ error: metadataResult.error
1184
+ };
1185
+ if (metadataResult.data.status === "draft") return {
1186
+ success: false,
1187
+ error: "Plan is already in draft status"
1188
+ };
1189
+ ydoc.transact(() => {
1190
+ const map = ydoc.getMap(YDOC_KEYS.METADATA);
1191
+ map.set("status", "draft");
1192
+ map.delete("reviewRequestId");
1193
+ map.delete("reviewedAt");
1194
+ map.delete("reviewedBy");
1195
+ map.delete("reviewComment");
1196
+ map.delete("completedAt");
1197
+ map.delete("completedBy");
1198
+ map.delete("snapshotUrl");
1199
+ map.set("updatedAt", Date.now());
1200
+ }, actor ? { actor } : void 0);
1201
+ return { success: true };
1202
+ }
1179
1203
  function transitionPlanStatus(ydoc, transition, actor) {
1180
1204
  const metadataResult = getPlanMetadataWithValidation(ydoc);
1181
1205
  if (!metadataResult.success) return {
@@ -2816,6 +2840,7 @@ export {
2816
2840
  getPlanMetadata,
2817
2841
  getPlanMetadataWithValidation,
2818
2842
  setPlanMetadata,
2843
+ resetPlanToDraft,
2819
2844
  transitionPlanStatus,
2820
2845
  initPlanMetadata,
2821
2846
  getStepCompletions,
@@ -3,7 +3,7 @@ import {
3
3
  YDOC_KEYS,
4
4
  createInputRequest,
5
5
  logPlanEvent
6
- } from "./chunk-75NZRAN5.js";
6
+ } from "./chunk-76JWRTPI.js";
7
7
  import {
8
8
  logger
9
9
  } from "./chunk-GSGLHRWX.js";
@@ -187,6 +187,7 @@ import {
187
187
  removePlanIndexEntry,
188
188
  removePlanTag,
189
189
  removeViewedByFromIndex,
190
+ resetPlanToDraft,
190
191
  resolvePRReviewComment,
191
192
  revokeUser,
192
193
  setAgentPresence,
@@ -203,7 +204,7 @@ import {
203
204
  updateLinkedPRStatus,
204
205
  updatePlanIndexViewedBy,
205
206
  validateA2AMessages
206
- } from "./chunk-75NZRAN5.js";
207
+ } from "./chunk-76JWRTPI.js";
207
208
  import "./chunk-JSBRDJBE.js";
208
209
  export {
209
210
  A2ADataPartSchema,
@@ -394,6 +395,7 @@ export {
394
395
  removePlanIndexEntry,
395
396
  removePlanTag,
396
397
  removeViewedByFromIndex,
398
+ resetPlanToDraft,
397
399
  resolvePRReviewComment,
398
400
  revokeUser,
399
401
  setAgentPresence,
@@ -19,7 +19,7 @@ import {
19
19
  } from "./chunk-EBNL5ZX7.js";
20
20
  import {
21
21
  InputRequestManager
22
- } from "./chunk-MZTJGBLT.js";
22
+ } from "./chunk-BWP37ADP.js";
23
23
  import {
24
24
  ArtifactSchema,
25
25
  DeliverableSchema,
@@ -63,12 +63,13 @@ import {
63
63
  logPlanEvent,
64
64
  parseClaudeCodeOrigin,
65
65
  parseThreads,
66
+ resetPlanToDraft,
66
67
  setAgentPresence,
67
68
  setPlanIndexEntry,
68
69
  setPlanMetadata,
69
70
  touchPlanIndexEntry,
70
71
  transitionPlanStatus
71
- } from "./chunk-75NZRAN5.js";
72
+ } from "./chunk-76JWRTPI.js";
72
73
  import {
73
74
  loadEnv,
74
75
  logger
@@ -4149,7 +4150,9 @@ USAGE (for non-hook agents):
4149
4150
  1. Call this tool to get monitoring script
4150
4151
  2. Run script in background: bash <script> &
4151
4152
  3. Script polls registry server for status changes
4152
- 4. Exits when status becomes 'approved' or 'changes_requested'`,
4153
+ 4. Exits when status becomes 'in_progress' (approved) or 'changes_requested' (needs work)
4154
+
4155
+ REQUIREMENTS: The script requires 'jq' for URL encoding. Install with: brew install jq (macOS) or apt install jq (Linux)`,
4153
4156
  inputSchema: {
4154
4157
  type: "object",
4155
4158
  properties: {
@@ -4167,25 +4170,64 @@ USAGE (for non-hook agents):
4167
4170
  const { planId, pollIntervalSeconds = 30 } = input;
4168
4171
  const registryPort2 = registryConfig.REGISTRY_PORT[0];
4169
4172
  const trpcUrl = `http://localhost:${registryPort2}/trpc`;
4170
- const script = `# Subscribe to status and comment changes via tRPC
4171
- CLIENT_ID=$(curl -sf -X POST "${trpcUrl}/subscription.create" \\
4173
+ const statusInProgress = PlanStatusValues.find((s) => s === "in_progress");
4174
+ const statusChangesRequested = PlanStatusValues.find((s) => s === "changes_requested");
4175
+ if (!statusInProgress || !statusChangesRequested) {
4176
+ throw new Error("Required status values not found in PlanStatusValues");
4177
+ }
4178
+ const script = `#!/bin/bash
4179
+ # Monitor plan "${planId}" for approval status changes
4180
+ # Polls the Shipyard registry server and exits when approved/rejected
4181
+
4182
+ # Check for required dependency
4183
+ if ! command -v jq &> /dev/null; then
4184
+ echo "Error: jq is required but not installed."
4185
+ echo "Install with: brew install jq (macOS) or apt install jq (Linux)"
4186
+ exit 1
4187
+ fi
4188
+
4189
+ TRPC_URL="${trpcUrl}"
4190
+ PLAN_ID="${planId}"
4191
+ POLL_INTERVAL=${pollIntervalSeconds}
4192
+
4193
+ # Subscribe to status changes via tRPC mutation
4194
+ echo "Subscribing to plan changes..."
4195
+ RESPONSE=$(curl -sf -X POST "$TRPC_URL/subscription.create" \\
4172
4196
  -H "Content-Type: application/json" \\
4173
- -d '{"planId":"${planId}","subscribe":["status","comments"],"windowMs":5000,"threshold":1}' \\
4174
- | grep -o '"clientId":"[^"]*"' | cut -d'"' -f4)
4175
-
4176
- echo "Subscribed. Monitoring plan..."
4177
-
4178
- # Poll for changes via tRPC
4179
- while sleep ${pollIntervalSeconds}; do
4180
- result=$(curl -sf -X POST "${trpcUrl}/subscription.getChanges" \\
4181
- -H "Content-Type: application/json" \\
4182
- -d '{"planId":"${planId}","clientId":"'"$CLIENT_ID"'"}' 2>/dev/null)
4183
- ready=$(echo "$result" | grep -o '"ready":true')
4184
- if [ -n "$ready" ]; then
4185
- changes=$(echo "$result" | grep -o '"changes":"[^"]*"' | cut -d'"' -f4)
4186
- echo "Changes: $changes"
4187
- # Exit on status change to approved/changes_requested
4188
- echo "$changes" | grep -qE "Status:.*(approved|changes_requested)" && exit 0
4197
+ -d '{"planId":"'"$PLAN_ID"'","subscribe":["status","comments"],"windowMs":5000,"threshold":1}')
4198
+
4199
+ # Extract clientId from response: {"result":{"data":{"clientId":"..."}}}
4200
+ CLIENT_ID=$(echo "$RESPONSE" | sed -n 's/.*"clientId":"\\([^"]*\\)".*/\\1/p')
4201
+
4202
+ if [ -z "$CLIENT_ID" ]; then
4203
+ echo "Failed to subscribe. Is the Shipyard registry server running?"
4204
+ echo "Response: $RESPONSE"
4205
+ exit 1
4206
+ fi
4207
+
4208
+ echo "Subscribed with clientId: $CLIENT_ID"
4209
+ echo "Polling every $POLL_INTERVAL seconds..."
4210
+
4211
+ # Poll for changes via tRPC query (GET with url-encoded input)
4212
+ while true; do
4213
+ sleep $POLL_INTERVAL
4214
+
4215
+ # URL-encode the input JSON for GET request
4216
+ INPUT='{"planId":"'"$PLAN_ID"'","clientId":"'"$CLIENT_ID"'"}'
4217
+ ENCODED_INPUT=$(printf '%s' "$INPUT" | jq -sRr @uri)
4218
+
4219
+ RESULT=$(curl -sf "$TRPC_URL/subscription.getChanges?input=$ENCODED_INPUT" 2>/dev/null)
4220
+
4221
+ # Check if changes are ready: {"result":{"data":{"ready":true,"changes":"..."}}}
4222
+ if echo "$RESULT" | grep -q '"ready":true'; then
4223
+ CHANGES=$(echo "$RESULT" | sed -n 's/.*"changes":"\\([^"]*\\)".*/\\1/p')
4224
+ echo "Changes detected: $CHANGES"
4225
+
4226
+ # Exit on status change to in_progress (approved) or changes_requested (needs work)
4227
+ if echo "$CHANGES" | grep -qE "Status:.*(${statusInProgress}|${statusChangesRequested})"; then
4228
+ echo "Plan status changed. Exiting."
4229
+ exit 0
4230
+ fi
4189
4231
  fi
4190
4232
  done`;
4191
4233
  return {
@@ -4198,10 +4240,13 @@ done`;
4198
4240
  ${script}
4199
4241
  \`\`\`
4200
4242
 
4201
- > Subscribes to status and comment changes with server-side batching.
4202
- > Batching: 5s window or 1 change threshold (whichever comes first).
4203
- > Exits when status becomes approved/changes_requested.
4204
- > Most agent environments support background bash notifications.`
4243
+ **Usage:** Save to a file and run in background: \`bash script.sh &\`
4244
+
4245
+ The script:
4246
+ - Subscribes to status/comment changes via tRPC
4247
+ - Polls every ${pollIntervalSeconds} seconds
4248
+ - Exits when status becomes in_progress (approved) or changes_requested (needs work)
4249
+ - Requires \`jq\` for URL encoding (install: brew install jq)`
4205
4250
  }
4206
4251
  ]
4207
4252
  };
@@ -4495,7 +4540,40 @@ async function applyOperation(blocks, operation, editor) {
4495
4540
 
4496
4541
  // src/tools/update-plan.ts
4497
4542
  import { ServerBlockNoteEditor as ServerBlockNoteEditor7 } from "@blocknote/server-util";
4543
+ import { nanoid as nanoid7 } from "nanoid";
4498
4544
  import { z as z11 } from "zod";
4545
+ function buildStatusTransition(targetStatus, actorName) {
4546
+ const now = Date.now();
4547
+ switch (targetStatus) {
4548
+ case "pending_review":
4549
+ return {
4550
+ status: "pending_review",
4551
+ reviewRequestId: nanoid7()
4552
+ };
4553
+ case "changes_requested":
4554
+ return {
4555
+ status: "changes_requested",
4556
+ reviewedAt: now,
4557
+ reviewedBy: actorName
4558
+ };
4559
+ case "in_progress":
4560
+ return {
4561
+ status: "in_progress",
4562
+ reviewedAt: now,
4563
+ reviewedBy: actorName
4564
+ };
4565
+ case "completed":
4566
+ return {
4567
+ status: "completed",
4568
+ completedAt: now,
4569
+ completedBy: actorName
4570
+ };
4571
+ case "draft":
4572
+ return null;
4573
+ default:
4574
+ return null;
4575
+ }
4576
+ }
4499
4577
  var UpdatePlanInput = z11.object({
4500
4578
  planId: z11.string().describe("The plan ID to update"),
4501
4579
  sessionToken: z11.string().describe("Session token from create_plan"),
@@ -4545,6 +4623,7 @@ STATUSES:
4545
4623
  required: ["planId", "sessionToken"]
4546
4624
  }
4547
4625
  },
4626
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: tool handler orchestrates validation, snapshots, and state transitions
4548
4627
  handler: async (args) => {
4549
4628
  const input = UpdatePlanInput.parse(args);
4550
4629
  const doc = await getOrCreateDoc3(input.planId);
@@ -4572,12 +4651,6 @@ STATUSES:
4572
4651
  isError: true
4573
4652
  };
4574
4653
  }
4575
- const updates = {
4576
- updatedAt: Date.now()
4577
- };
4578
- if (input.title) updates.title = input.title;
4579
- if (input.status) updates.status = input.status;
4580
- if (input.tags !== void 0) updates.tags = input.tags;
4581
4654
  const statusChanged = input.status && input.status !== existingMetadata.status;
4582
4655
  if (statusChanged && input.status) {
4583
4656
  const editor = ServerBlockNoteEditor7.create();
@@ -4586,8 +4659,52 @@ STATUSES:
4586
4659
  const reason = `Status changed to ${input.status}`;
4587
4660
  const snapshot = createPlanSnapshot(doc, reason, actorName, input.status, blocks);
4588
4661
  addSnapshot(doc, snapshot);
4662
+ if (input.status === "draft") {
4663
+ const resetResult = resetPlanToDraft(doc, actorName);
4664
+ if (!resetResult.success) {
4665
+ return {
4666
+ content: [
4667
+ {
4668
+ type: "text",
4669
+ text: `Failed to reset plan to draft: ${resetResult.error}`
4670
+ }
4671
+ ],
4672
+ isError: true
4673
+ };
4674
+ }
4675
+ } else {
4676
+ const transition = buildStatusTransition(input.status, actorName);
4677
+ if (!transition) {
4678
+ return {
4679
+ content: [
4680
+ {
4681
+ type: "text",
4682
+ text: `Invalid status: ${input.status}`
4683
+ }
4684
+ ],
4685
+ isError: true
4686
+ };
4687
+ }
4688
+ const transitionResult = transitionPlanStatus(doc, transition, actorName);
4689
+ if (!transitionResult.success) {
4690
+ return {
4691
+ content: [
4692
+ {
4693
+ type: "text",
4694
+ text: `Failed to transition status: ${transitionResult.error}`
4695
+ }
4696
+ ],
4697
+ isError: true
4698
+ };
4699
+ }
4700
+ }
4701
+ }
4702
+ const updates = {};
4703
+ if (input.title) updates.title = input.title;
4704
+ if (input.tags !== void 0) updates.tags = input.tags;
4705
+ if (Object.keys(updates).length > 0) {
4706
+ setPlanMetadata(doc, updates, actorName);
4589
4707
  }
4590
- setPlanMetadata(doc, updates, actorName);
4591
4708
  const indexDoc = await getOrCreateDoc3(PLAN_INDEX_DOC_NAME);
4592
4709
  if (existingMetadata.ownerId) {
4593
4710
  setPlanIndexEntry(indexDoc, {
@@ -4788,17 +4905,36 @@ Parameters:
4788
4905
  - pollIntervalSeconds (number, optional): Polling interval (default: 30)
4789
4906
 
4790
4907
  Returns:
4791
- - script: Bash script that polls registry server and exits when status becomes 'changes_requested' or 'in_progress'
4908
+ - script: Bash script that polls registry server and exits when status becomes 'in_progress' (approved) or 'changes_requested' (needs work)
4792
4909
 
4793
- Use this for agents WITHOUT hook support (Cursor, Devin, etc). The script can be run in background.
4910
+ **IMPORTANT:** This is ONLY for agents WITHOUT hook support (Cursor, Devin, Windsurf, etc).
4911
+ Claude Code users have automatic blocking via the shipyard hook - you don't need this.
4794
4912
 
4795
- Example:
4913
+ **Complete workflow for non-hook agents (example user code):**
4796
4914
  \`\`\`typescript
4915
+ // 1. Create plan and get the monitoring script in ONE code block
4916
+ const plan = await createPlan({
4917
+ title: "My Feature Implementation",
4918
+ content: "- [ ] Screenshot of working feature {#deliverable}"
4919
+ });
4920
+
4921
+ // 2. Get the polling script (returns bash script as string)
4797
4922
  const { script } = await setupReviewNotification(plan.planId, 15);
4798
- // Agent runs this script in background to wait for approval
4799
- console.log(script);
4923
+
4924
+ // 3. Return both so the agent can run the script
4925
+ return {
4926
+ planId: plan.planId,
4927
+ sessionToken: plan.sessionToken,
4928
+ monitoringScript: script,
4929
+ instructions: "Run the monitoring script in background: bash <script> &"
4930
+ };
4800
4931
  \`\`\`
4801
4932
 
4933
+ The agent then runs the returned bash script in the background. The script will:
4934
+ - Poll the registry server every N seconds
4935
+ - Print status changes to stdout
4936
+ - Exit with code 0 when the plan is approved/rejected
4937
+
4802
4938
  ---
4803
4939
 
4804
4940
  ### requestUserInput(opts): Promise<{ success, response?, status, reason? }>
@@ -5085,7 +5221,7 @@ async function setupReviewNotification(planId, pollIntervalSeconds) {
5085
5221
  return { script, fullResponse: text };
5086
5222
  }
5087
5223
  async function requestUserInput(opts) {
5088
- const { InputRequestManager: InputRequestManager2 } = await import("./input-request-manager-43QSOJS3.js");
5224
+ const { InputRequestManager: InputRequestManager2 } = await import("./input-request-manager-73GSTOIB.js");
5089
5225
  const ydoc = await getOrCreateDoc3(PLAN_INDEX_DOC_NAME);
5090
5226
  const manager = new InputRequestManager2();
5091
5227
  const params = opts.type === "choice" ? {
@@ -5129,12 +5265,12 @@ async function requestUserInput(opts) {
5129
5265
  };
5130
5266
  }
5131
5267
  async function postActivityUpdate(opts) {
5132
- const { logPlanEvent: logPlanEvent2 } = await import("./dist-HIDISXZO.js");
5268
+ const { logPlanEvent: logPlanEvent2 } = await import("./dist-ORKL4P3L.js");
5133
5269
  const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-6PHKR2FY.js");
5134
- const { nanoid: nanoid7 } = await import("nanoid");
5270
+ const { nanoid: nanoid8 } = await import("nanoid");
5135
5271
  const doc = await getOrCreateDoc3(opts.planId);
5136
5272
  const actorName = await getGitHubUsername2();
5137
- const requestId = nanoid7();
5273
+ const requestId = nanoid8();
5138
5274
  const eventId = logPlanEvent2(
5139
5275
  doc,
5140
5276
  "agent_activity",
@@ -5152,7 +5288,7 @@ async function postActivityUpdate(opts) {
5152
5288
  return { success: true, eventId, requestId };
5153
5289
  }
5154
5290
  async function resolveActivityRequest(opts) {
5155
- const { logPlanEvent: logPlanEvent2, getPlanEvents } = await import("./dist-HIDISXZO.js");
5291
+ const { logPlanEvent: logPlanEvent2, getPlanEvents } = await import("./dist-ORKL4P3L.js");
5156
5292
  const { getGitHubUsername: getGitHubUsername2 } = await import("./server-identity-6PHKR2FY.js");
5157
5293
  const doc = await getOrCreateDoc3(opts.planId);
5158
5294
  const actorName = await getGitHubUsername2();
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  InputRequestManager
3
- } from "./chunk-MZTJGBLT.js";
4
- import "./chunk-75NZRAN5.js";
3
+ } from "./chunk-BWP37ADP.js";
4
+ import "./chunk-76JWRTPI.js";
5
5
  import "./chunk-GSGLHRWX.js";
6
6
  import "./chunk-JSBRDJBE.js";
7
7
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schoolai/shipyard-mcp",
3
- "version": "0.1.3-next.464",
3
+ "version": "0.1.3-next.467",
4
4
  "description": "Shipyard MCP server and CLI tools for distributed planning with CRDTs",
5
5
  "type": "module",
6
6
  "bin": {