@stevecastaneda/paseo-progress 1.14.4 → 1.14.5

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
@@ -178,6 +178,10 @@ The progress file, `progress.jsonl`, holds one JSON event per line, each with a
178
178
 
179
179
  ## Changelog
180
180
 
181
+ ### 1.14.5
182
+
183
+ - [Fixed] **Agents record your answer on the first try.** In Claude Code, an agent that hadn't loaded the answer tool guessed its field names, and the call failed once before the agent retried. The skill now has agents load every Progress tool at once, and again after Claude Code compacts a long chat. Asking a question also tells the agent which fields the answer takes.
184
+
181
185
  ### 1.14.4
182
186
 
183
187
  - [Improved] **Agents learn what Paseo's preview blocks.** Paseo opens an HTML deliverable in a sandboxed preview, where some common page code fails without a visible error. Saving to localStorage, sessionStorage, cookies or IndexedDB throws, so a page that loads its saved state first stops before it draws anything. `alert`, `confirm`, `prompt`, `window.open` and form submits do nothing, `navigator.clipboard` throws, and one syntax error stops a whole script. The skill and the command help now list these, with what to do instead.
@@ -661,6 +661,7 @@ __export(util_exports, {
661
661
  required: () => required,
662
662
  safeExtend: () => safeExtend,
663
663
  shallowClone: () => shallowClone,
664
+ sizeCaps: () => sizeCaps,
664
665
  slugify: () => slugify,
665
666
  stringifyPrimitive: () => stringifyPrimitive,
666
667
  toZod: () => toZod,
@@ -1180,6 +1181,18 @@ function prefixIssues(path, issues) {
1180
1181
  function unwrapMessage(message) {
1181
1182
  return typeof message === "string" ? message : message?.message;
1182
1183
  }
1184
+ function sizeCaps(defChecks) {
1185
+ const caps = [];
1186
+ for (const ch of defChecks ?? []) {
1187
+ const d = ch._zod.def;
1188
+ if (!/_(length|size)$|_equals$/.test(d.check))
1189
+ break;
1190
+ const max = d.maximum ?? d.length ?? d.size;
1191
+ if (max >= 0)
1192
+ caps.push([max, ch]);
1193
+ }
1194
+ return caps;
1195
+ }
1183
1196
  function attachSchema(issues, start, inst) {
1184
1197
  var _a3;
1185
1198
  for (let i = start; i < issues.length; i++) {
@@ -2600,7 +2613,7 @@ ${content.join("\n")}
2600
2613
  var version = {
2601
2614
  major: 4,
2602
2615
  minor: 6,
2603
- patch: 5
2616
+ patch: 7
2604
2617
  };
2605
2618
 
2606
2619
  // node_modules/zod/v4/core/schemas.js
@@ -3356,6 +3369,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
3356
3369
  return payload;
3357
3370
  };
3358
3371
  });
3372
+ function capSize(inst, size) {
3373
+ const caps = sizeCaps(inst._zod.def.checks);
3374
+ if (!caps.length)
3375
+ return;
3376
+ const cap = Math.min(...caps.map((c) => c[0]));
3377
+ const parse3 = inst._zod.parse;
3378
+ inst._zod.parse = (payload, ctx) => {
3379
+ const n = size(payload.value);
3380
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
3381
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
3382
+ if (n > max && ch._zod.def.when(payload)) {
3383
+ const start = payload.issues.length;
3384
+ ch._zod.check(payload);
3385
+ payload.issues[start].continue = false;
3386
+ attachSchema(payload.issues, start, inst);
3387
+ return payload;
3388
+ }
3389
+ }
3390
+ }
3391
+ return parse3(payload, ctx);
3392
+ };
3393
+ }
3394
+ var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
3395
+ var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
3396
+ var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
3359
3397
  function handleArrayResult(result, final, index) {
3360
3398
  if (result.issues.length) {
3361
3399
  final.issues.push(...prefixIssues(index, result.issues));
@@ -3399,6 +3437,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
3399
3437
  }
3400
3438
  return payload;
3401
3439
  };
3440
+ capSize(inst, arraySize);
3402
3441
  });
3403
3442
  function handlePropertyResult(result, final, key, input2, optin, optout) {
3404
3443
  const isPresent = key in input2;
@@ -4355,6 +4394,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
4355
4394
  return Promise.all(proms).then(() => payload);
4356
4395
  return payload;
4357
4396
  };
4397
+ capSize(inst, mapSize);
4358
4398
  });
4359
4399
  function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
4360
4400
  if (keyResult.issues.length) {
@@ -4421,6 +4461,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
4421
4461
  return Promise.all(proms).then(() => payload);
4422
4462
  return payload;
4423
4463
  };
4464
+ capSize(inst, setSize);
4424
4465
  });
4425
4466
  function handleSetResult(result, final) {
4426
4467
  if (result.issues.length) {
@@ -13951,9 +13992,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
13951
13992
  doc.write(`}`);
13952
13993
  return outputVar;
13953
13994
  }
13995
+ function generateSizeGate(doc, schema, size) {
13996
+ for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
13997
+ doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
13998
+ }
13999
+ }
13954
14000
  function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
13955
14001
  const def = schema._zod.def;
13956
14002
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
14003
+ generateSizeGate(doc, schema, `${accessor}.length`);
13957
14004
  const outputVar = buildsValue ? newVar(ctx) : null;
13958
14005
  const iVar = newVar(ctx);
13959
14006
  const elemVar = newVar(ctx);
@@ -14353,6 +14400,7 @@ function literalPropertyKey(ctx, key) {
14353
14400
  function generateMapCheck(doc, ctx, schema, accessor) {
14354
14401
  const def = schema._zod.def;
14355
14402
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
14403
+ generateSizeGate(doc, schema, `${accessor}.size`);
14356
14404
  const outputVar = newVar(ctx);
14357
14405
  const kVar = newVar(ctx);
14358
14406
  const valVar = newVar(ctx);
@@ -14369,6 +14417,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
14369
14417
  function generateSetCheck(doc, ctx, schema, accessor) {
14370
14418
  const def = schema._zod.def;
14371
14419
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
14420
+ generateSizeGate(doc, schema, `${accessor}.size`);
14372
14421
  const outputVar = newVar(ctx);
14373
14422
  const valVar = newVar(ctx);
14374
14423
  doc.write(`const ${outputVar} = new Set();`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stevecastaneda/paseo-progress",
3
- "version": "1.14.4",
3
+ "version": "1.14.5",
4
4
  "description": "A live progress dashboard per worktree for Paseo, drawn from a file agents write with the Progress tools or the paseo-progress command.",
5
5
  "keywords": [
6
6
  "coding-agents",
@@ -57,7 +57,7 @@
57
57
  "react": "19.1.0",
58
58
  "react-native": "0.81.5",
59
59
  "typescript": "^5.9.3",
60
- "zod": "^4.4.3"
60
+ "zod": "^4.6.7"
61
61
  },
62
62
  "engines": {
63
63
  "node": "^22.18.0 || >=23.6.0"
package/server/tool.ts CHANGED
@@ -56,6 +56,21 @@ function commandTool<Input extends z.ZodType>(title: string, command: string, in
56
56
  });
57
57
  }
58
58
 
59
+ // Claude Code hides a tool's inputs until the agent loads it, so an agent
60
+ // that skipped loading the answer tool guesses its field names.
61
+ export const ANSWER_NOTE =
62
+ "When the user replies, record it with progress_question_answer: id is this question's reference, choice is the option letter or their own short answer, and words is what they said.";
63
+
64
+ function withNote(tool: Tool, note: string): Tool {
65
+ return {
66
+ ...tool,
67
+ async execute(input, context) {
68
+ const result = await tool.execute(input, context);
69
+ return result.isError ? result : { ...result, text: `${result.text}\n${note}` };
70
+ },
71
+ };
72
+ }
73
+
59
74
  const ticketId = (what: string) => z.string().describe(`${what}, like T03`);
60
75
  const estimate = z.number().int().describe("Whole minutes, like 90");
61
76
  const ticketSource = z.string().describe("Spec file path or http(s) link where the ticket is written up");
@@ -102,41 +117,44 @@ const tools: Record<ProgressToolName, Tool> = {
102
117
  ({ id, status, stage, title, estimateMinutes, note, waitsFor, source }) =>
103
118
  commandArgs("ticket update", [id], { status, stage, title, estimate: estimateMinutes, note, "waits-for": waitsFor, source }),
104
119
  ),
105
- progress_question_ask: commandTool(
106
- "Ask question",
107
- "question ask",
108
- z.object({
109
- title: z.string().describe("A short title"),
110
- question: z.string(),
111
- options: z.array(
112
- z.object({
113
- letter: z.string().describe("A, B, C, ..."),
114
- label: z.string(),
115
- consequence: z.string().describe("What picking it means for the user"),
116
- }),
117
- ),
118
- recommend: z.string().describe("The option letter you would pick"),
119
- big: z.string().optional().describe("Only for a big question: which test it meets, in a few plain words"),
120
- background: z.string().optional(),
121
- files: z
122
- .array(
120
+ progress_question_ask: withNote(
121
+ commandTool(
122
+ "Ask question",
123
+ "question ask",
124
+ z.object({
125
+ title: z.string().describe("A short title"),
126
+ question: z.string(),
127
+ options: z.array(
123
128
  z.object({
124
- target: z.string().describe("A file path or http(s) link, like a screenshot"),
125
- label: z.string().optional(),
129
+ letter: z.string().describe("A, B, C, ..."),
130
+ label: z.string(),
131
+ consequence: z.string().describe("What picking it means for the user"),
126
132
  }),
127
- )
128
- .optional(),
129
- ticket: ticketId("The ticket it's about").optional(),
130
- }),
131
- ({ title, question, options, recommend, big, background, files, ticket }) =>
132
- commandArgs("question ask", [title, question], {
133
- option: options.map(({ letter, label, consequence }) => `${letter}=${label} | ${consequence}`),
134
- recommend,
135
- big,
136
- background,
137
- file: files?.map(({ target, label }) => (label ? `${target}=${label}` : target)),
138
- ticket,
133
+ ),
134
+ recommend: z.string().describe("The option letter you would pick"),
135
+ big: z.string().optional().describe("Only for a big question: which test it meets, in a few plain words"),
136
+ background: z.string().optional(),
137
+ files: z
138
+ .array(
139
+ z.object({
140
+ target: z.string().describe("A file path or http(s) link, like a screenshot"),
141
+ label: z.string().optional(),
142
+ }),
143
+ )
144
+ .optional(),
145
+ ticket: ticketId("The ticket it's about").optional(),
139
146
  }),
147
+ ({ title, question, options, recommend, big, background, files, ticket }) =>
148
+ commandArgs("question ask", [title, question], {
149
+ option: options.map(({ letter, label, consequence }) => `${letter}=${label} | ${consequence}`),
150
+ recommend,
151
+ big,
152
+ background,
153
+ file: files?.map(({ target, label }) => (label ? `${target}=${label}` : target)),
154
+ ticket,
155
+ }),
156
+ ),
157
+ ANSWER_NOTE,
140
158
  ),
141
159
  progress_question_answer: commandTool(
142
160
  "Record answer",
@@ -7,9 +7,15 @@ description: Keep the Paseo Progress dashboard live with the Progress tools or t
7
7
 
8
8
  The user watches a live dashboard in Paseo for this worktree. Keep it live: record each change the moment it happens, so the dashboard is a side effect of the work. Write every line for a product manager, in plain words.
9
9
 
10
- Use the Progress tools when you have them: `progress_show`, `progress_start`, `progress_ticket_add`, `progress_ticket_update`, `progress_question_ask`, `progress_question_answer`, `progress_deliverable_add`, `progress_activity_add`, `progress_ticker`, `progress_finish`, and `paseo_progress` for any other command. Paseo hands them to agents when the user turns on Paseo tools. Claude Code names them like `mcp__paseo__progress_show` and keeps them hidden until you search, so search your tools for `progress_` before your first update. Otherwise, run the `paseo-progress` command from inside the worktree. When a tool call says the tool isn't found, use the command for the rest of that reply.
10
+ Use the Progress tools when you have them. Paseo hands them to agents when the user turns on Paseo tools. Claude Code hides their inputs until you load them, and a compacted conversation loses what you loaded. So before your first update, and again after each compaction, load them all in one `ToolSearch` call with `max_results` 11 and this query:
11
11
 
12
- The tools and the command take the same values and print the same answers. The steps below use the command's words: `--estimate` is `estimateMinutes` in the tools, `--waits-for` is `waitsFor`. `paseo-progress --help` is the reference for every command and flag.
12
+ ```text
13
+ select:mcp__paseo__progress_show,mcp__paseo__progress_start,mcp__paseo__progress_ticket_add,mcp__paseo__progress_ticket_update,mcp__paseo__progress_question_ask,mcp__paseo__progress_question_answer,mcp__paseo__progress_deliverable_add,mcp__paseo__progress_activity_add,mcp__paseo__progress_ticker,mcp__paseo__progress_finish,mcp__paseo__paseo_progress
14
+ ```
15
+
16
+ Without the tools, run the `paseo-progress` command from inside the worktree. When a tool call says the tool isn't found, use the command for the rest of that reply.
17
+
18
+ The tools and the command take the same values and print the same answers. The steps below use the command's words: `--estimate` is `estimateMinutes` in the tools, `--waits-for` is `waitsFor`, and `question answer Q7 A` is `id` and `choice`. `paseo-progress --help` is the reference for every command and flag.
13
19
 
14
20
  ## 1. Set up
15
21