gsd-pi 2.75.0-dev.a44b82572 → 2.75.0-dev.e41b70b10

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.
Files changed (111) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +2 -0
  2. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
  3. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
  6. package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
  8. package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
  9. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
  10. package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
  11. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  12. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  13. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
  14. package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
  15. package/dist/resources/extensions/search-the-web/native-search.js +13 -2
  16. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  17. package/dist/web/standalone/.next/BUILD_ID +1 -1
  18. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  19. package/dist/web/standalone/.next/build-manifest.json +2 -2
  20. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  21. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.html +1 -1
  38. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  45. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  47. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  48. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  49. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  50. package/package.json +1 -1
  51. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  52. package/packages/mcp-server/dist/workflow-tools.js +102 -65
  53. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  54. package/packages/mcp-server/src/workflow-tools.test.ts +255 -0
  55. package/packages/mcp-server/src/workflow-tools.ts +108 -65
  56. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  57. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  58. package/packages/pi-ai/dist/index.d.ts +1 -0
  59. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  60. package/packages/pi-ai/dist/index.js +1 -0
  61. package/packages/pi-ai/dist/index.js.map +1 -1
  62. package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
  63. package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
  64. package/packages/pi-ai/dist/providers/api-family.js +47 -0
  65. package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
  66. package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
  67. package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
  68. package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
  69. package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
  70. package/packages/pi-ai/src/index.ts +1 -0
  71. package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
  72. package/packages/pi-ai/src/providers/api-family.ts +57 -0
  73. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  74. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  75. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  78. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
  82. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  83. package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
  84. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
  85. package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
  86. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  87. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
  88. package/src/resources/extensions/gsd/auto/phases.ts +3 -0
  89. package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
  90. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
  91. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
  92. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
  93. package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
  94. package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
  95. package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
  96. package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
  97. package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
  98. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  99. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  100. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
  101. package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
  102. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
  103. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
  104. package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
  105. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
  106. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
  107. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
  108. package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
  109. package/src/resources/extensions/search-the-web/native-search.ts +13 -3
  110. /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
  111. /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
@@ -386,6 +386,261 @@ describe("workflow MCP tools", () => {
386
386
  }
387
387
  });
388
388
 
389
+ it("other workflow tools reject empty required strings at the schema layer", async () => {
390
+ const base = makeTmpBase();
391
+ try {
392
+ const server = makeMockServer();
393
+ registerWorkflowTools(server as any);
394
+
395
+ const expectRejection = async (toolName: string, args: Record<string, unknown>, expectedField: string) => {
396
+ const tool = server.tools.find((t) => t.name === toolName);
397
+ assert.ok(tool, `${toolName} should be registered`);
398
+ let caught: unknown;
399
+ try {
400
+ await tool!.handler(args);
401
+ } catch (err) {
402
+ caught = err;
403
+ }
404
+ assert.ok(caught, `${toolName} should reject empty ${expectedField}`);
405
+ const message = caught instanceof Error ? caught.message : String(caught);
406
+ assert.ok(
407
+ message.includes(expectedField),
408
+ `${toolName} error should mention ${expectedField}, got: ${message}`,
409
+ );
410
+ };
411
+
412
+ // Empty sliceId top-level
413
+ await expectRejection("gsd_plan_slice", {
414
+ projectDir: base,
415
+ milestoneId: "M001",
416
+ sliceId: "",
417
+ goal: "Persist slice plan.",
418
+ tasks: [],
419
+ }, "sliceId");
420
+
421
+ // Empty task verify inside tasks array
422
+ await expectRejection("gsd_plan_slice", {
423
+ projectDir: base,
424
+ milestoneId: "M001",
425
+ sliceId: "S01",
426
+ goal: "Persist slice plan.",
427
+ tasks: [
428
+ {
429
+ taskId: "T01",
430
+ title: "Add bridge",
431
+ description: "Implement bridge.",
432
+ estimate: "15m",
433
+ files: ["src/x.ts"],
434
+ verify: "",
435
+ inputs: ["ROADMAP.md"],
436
+ expectedOutput: ["S01-PLAN.md"],
437
+ },
438
+ ],
439
+ }, "verify");
440
+
441
+ // Empty element inside files[] array
442
+ await expectRejection("gsd_plan_slice", {
443
+ projectDir: base,
444
+ milestoneId: "M001",
445
+ sliceId: "S01",
446
+ goal: "Persist slice plan.",
447
+ tasks: [
448
+ {
449
+ taskId: "T01",
450
+ title: "Add bridge",
451
+ description: "Implement bridge.",
452
+ estimate: "15m",
453
+ files: ["src/x.ts", " "],
454
+ verify: "node --test",
455
+ inputs: ["ROADMAP.md"],
456
+ expectedOutput: ["S01-PLAN.md"],
457
+ },
458
+ ],
459
+ }, "files");
460
+
461
+ // Empty milestoneId on gsd_plan_task
462
+ await expectRejection("gsd_plan_task", {
463
+ projectDir: base,
464
+ milestoneId: "",
465
+ sliceId: "S01",
466
+ taskId: "T01",
467
+ title: "t",
468
+ description: "d",
469
+ estimate: "1m",
470
+ files: [],
471
+ verify: "v",
472
+ inputs: [],
473
+ expectedOutput: [],
474
+ }, "milestoneId");
475
+
476
+ // Empty observabilityImpact explicitly rejected (optional-but-non-empty)
477
+ await expectRejection("gsd_plan_task", {
478
+ projectDir: base,
479
+ milestoneId: "M001",
480
+ sliceId: "S01",
481
+ taskId: "T01",
482
+ title: "t",
483
+ description: "d",
484
+ estimate: "1m",
485
+ files: [],
486
+ verify: "v",
487
+ inputs: [],
488
+ expectedOutput: [],
489
+ observabilityImpact: " ",
490
+ }, "observabilityImpact");
491
+
492
+ // Empty assessment on gsd_reassess_roadmap
493
+ await expectRejection("gsd_reassess_roadmap", {
494
+ projectDir: base,
495
+ milestoneId: "M001",
496
+ completedSliceId: "S01",
497
+ verdict: "roadmap-confirmed",
498
+ assessment: "",
499
+ sliceChanges: { modified: [], added: [], removed: [] },
500
+ }, "assessment");
501
+
502
+ // Empty keyRisks[i].risk on gsd_plan_milestone top-level arrays
503
+ await expectRejection("gsd_plan_milestone", {
504
+ projectDir: base,
505
+ milestoneId: "M001",
506
+ title: "T",
507
+ vision: "V",
508
+ slices: [],
509
+ keyRisks: [{ risk: "", whyItMatters: "because." }],
510
+ }, "risk");
511
+
512
+ // Empty blockerDescription on gsd_replan_slice
513
+ await expectRejection("gsd_replan_slice", {
514
+ projectDir: base,
515
+ milestoneId: "M001",
516
+ sliceId: "S01",
517
+ blockerTaskId: "T01",
518
+ blockerDescription: "",
519
+ whatChanged: "x",
520
+ updatedTasks: [],
521
+ removedTaskIds: [],
522
+ }, "blockerDescription");
523
+
524
+ // Empty milestoneId on gsd_task_complete
525
+ await expectRejection("gsd_task_complete", {
526
+ projectDir: base,
527
+ taskId: "T01",
528
+ sliceId: "S01",
529
+ milestoneId: "",
530
+ oneLiner: "ol",
531
+ narrative: "n",
532
+ verification: "v",
533
+ }, "milestoneId");
534
+ } finally {
535
+ cleanup(base);
536
+ }
537
+ });
538
+
539
+ it("gsd_plan_milestone rejects empty slice fields up front with all violations", async () => {
540
+ const base = makeTmpBase();
541
+ try {
542
+ const server = makeMockServer();
543
+ registerWorkflowTools(server as any);
544
+ const milestoneTool = server.tools.find((t) => t.name === "gsd_plan_milestone");
545
+ assert.ok(milestoneTool, "milestone planning tool should be registered");
546
+
547
+ let caught: unknown;
548
+ try {
549
+ await milestoneTool!.handler({
550
+ projectDir: base,
551
+ milestoneId: "M001",
552
+ title: "Workflow MCP planning",
553
+ vision: "Plan milestone over MCP.",
554
+ slices: [
555
+ {
556
+ sliceId: "S01",
557
+ title: "Bridge planning",
558
+ risk: "medium",
559
+ depends: [],
560
+ demo: "Milestone plan persists through MCP.",
561
+ goal: "Persist roadmap state.",
562
+ successCriteria: "",
563
+ proofLevel: "",
564
+ integrationClosure: " ",
565
+ observabilityImpact: "",
566
+ },
567
+ ],
568
+ });
569
+ } catch (err) {
570
+ caught = err;
571
+ }
572
+ assert.ok(caught, "empty slice fields should be rejected");
573
+ const message = caught instanceof Error ? caught.message : String(caught);
574
+ for (const field of ["successCriteria", "proofLevel", "integrationClosure", "observabilityImpact"]) {
575
+ assert.ok(
576
+ message.includes(field),
577
+ `parse error should mention ${field}, got: ${message}`,
578
+ );
579
+ }
580
+ } finally {
581
+ cleanup(base);
582
+ }
583
+ });
584
+
585
+ it("gsd_plan_milestone requires sketchScope when isSketch=true and skips heavy fields", async () => {
586
+ const base = makeTmpBase();
587
+ try {
588
+ const server = makeMockServer();
589
+ registerWorkflowTools(server as any);
590
+ const milestoneTool = server.tools.find((t) => t.name === "gsd_plan_milestone");
591
+ assert.ok(milestoneTool, "milestone planning tool should be registered");
592
+
593
+ let caught: unknown;
594
+ try {
595
+ await milestoneTool!.handler({
596
+ projectDir: base,
597
+ milestoneId: "M001",
598
+ title: "Sketch milestone",
599
+ vision: "Sketch first, refine later.",
600
+ slices: [
601
+ {
602
+ sliceId: "S01",
603
+ title: "Sketch slice",
604
+ risk: "low",
605
+ depends: [],
606
+ demo: "Stub demo.",
607
+ goal: "Stub goal.",
608
+ isSketch: true,
609
+ sketchScope: "",
610
+ },
611
+ ],
612
+ });
613
+ } catch (err) {
614
+ caught = err;
615
+ }
616
+ assert.ok(caught, "empty sketchScope should be rejected when isSketch=true");
617
+ const message = caught instanceof Error ? caught.message : String(caught);
618
+ assert.ok(message.includes("sketchScope"), `expected sketchScope error, got: ${message}`);
619
+
620
+ const sketchResult = await milestoneTool!.handler({
621
+ projectDir: base,
622
+ milestoneId: "M001",
623
+ title: "Sketch milestone",
624
+ vision: "Sketch first, refine later.",
625
+ slices: [
626
+ {
627
+ sliceId: "S01",
628
+ title: "Sketch slice",
629
+ risk: "low",
630
+ depends: [],
631
+ demo: "Stub demo.",
632
+ goal: "Stub goal.",
633
+ isSketch: true,
634
+ sketchScope: "Defer heavy planning fields until refine-slice.",
635
+ },
636
+ ],
637
+ });
638
+ assert.match((sketchResult as any).content[0].text as string, /Planned milestone M001/);
639
+ } finally {
640
+ cleanup(base);
641
+ }
642
+ });
643
+
389
644
  it("gsd_requirement_save opens the DB before inline requirement writes", async () => {
390
645
  const base = makeTmpBase();
391
646
  try {
@@ -817,38 +817,81 @@ const projectDirParam = z
817
817
  .optional()
818
818
  .describe("Optional. Omit this field — the server defaults to its current working directory, which is already the correct project or worktree root.");
819
819
 
820
+ const nonEmptyString = (field: string) =>
821
+ z.string().trim().min(1, `${field} must be a non-empty string`);
822
+
823
+ // Optional non-empty string: accepts omitted/undefined but rejects "" or
824
+ // whitespace. Mirrors executor guards of the form
825
+ // `value !== undefined && !isNonEmptyString(value)` — e.g. plan-task's
826
+ // observabilityImpact. Do not preprocess "" to undefined; the executor
827
+ // treats them differently.
828
+ const optionalNonEmptyString = (field: string) => nonEmptyString(field).optional();
829
+
830
+ // Array of non-empty strings. Mirrors executor guards that call
831
+ // `validateStringArray` or `arr.some((item) => !isNonEmptyString(item))`.
832
+ const nonEmptyStringArray = (field: string) =>
833
+ z.array(nonEmptyString(`${field}[]`));
834
+
835
+ // Matches the executor's `isNonEmptyString` (trim + length>0) so Zod rejects
836
+ // empty/whitespace fields at parse time. Without this, MCP callers pass "" for
837
+ // the heavy planning fields, Zod accepts it, and the executor rejects one
838
+ // field per call — forcing the agent into a retry loop to discover every gap.
839
+ const planMilestoneSliceSchema = z.object({
840
+ sliceId: nonEmptyString("sliceId"),
841
+ title: nonEmptyString("title"),
842
+ risk: nonEmptyString("risk"),
843
+ depends: z.array(z.string()),
844
+ demo: nonEmptyString("demo"),
845
+ goal: nonEmptyString("goal"),
846
+ // ADR-011: heavy planning fields are optional for sketch slices; required for full slices.
847
+ successCriteria: z.string().optional(),
848
+ proofLevel: z.string().optional(),
849
+ integrationClosure: z.string().optional(),
850
+ observabilityImpact: z.string().optional(),
851
+ // ADR-011 sketch-then-refine fields.
852
+ isSketch: z.boolean().optional().describe("ADR-011: true marks this slice as a sketch awaiting refine-slice expansion"),
853
+ sketchScope: z.string().optional().describe("ADR-011: 2-3 sentence scope boundary, required when isSketch=true"),
854
+ }).superRefine((slice, ctx) => {
855
+ if (slice.isSketch === true) {
856
+ if (typeof slice.sketchScope !== "string" || slice.sketchScope.trim().length === 0) {
857
+ ctx.addIssue({
858
+ code: z.ZodIssueCode.custom,
859
+ path: ["sketchScope"],
860
+ message: "sketchScope must be a non-empty string when isSketch is true",
861
+ });
862
+ }
863
+ return;
864
+ }
865
+ const required = ["successCriteria", "proofLevel", "integrationClosure", "observabilityImpact"] as const;
866
+ for (const field of required) {
867
+ const value = slice[field];
868
+ if (typeof value !== "string" || value.trim().length === 0) {
869
+ ctx.addIssue({
870
+ code: z.ZodIssueCode.custom,
871
+ path: [field],
872
+ message: `${field} must be a non-empty string`,
873
+ });
874
+ }
875
+ }
876
+ });
877
+
820
878
  const planMilestoneParams = {
821
879
  projectDir: projectDirParam,
822
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
823
- title: z.string().describe("Milestone title"),
824
- vision: z.string().describe("Milestone vision"),
825
- slices: z.array(z.object({
826
- sliceId: z.string(),
827
- title: z.string(),
828
- risk: z.string(),
829
- depends: z.array(z.string()),
830
- demo: z.string(),
831
- goal: z.string(),
832
- // ADR-011: heavy planning fields are optional for sketch slices; required for full slices.
833
- successCriteria: z.string().optional(),
834
- proofLevel: z.string().optional(),
835
- integrationClosure: z.string().optional(),
836
- observabilityImpact: z.string().optional(),
837
- // ADR-011 sketch-then-refine fields.
838
- isSketch: z.boolean().optional().describe("ADR-011: true marks this slice as a sketch awaiting refine-slice expansion"),
839
- sketchScope: z.string().optional().describe("ADR-011: 2-3 sentence scope boundary, required when isSketch=true"),
840
- })).describe("Planned slices for the milestone"),
880
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
881
+ title: nonEmptyString("title").describe("Milestone title"),
882
+ vision: nonEmptyString("vision").describe("Milestone vision"),
883
+ slices: z.array(planMilestoneSliceSchema).describe("Planned slices for the milestone"),
841
884
  status: z.string().optional().describe("Milestone status"),
842
885
  dependsOn: z.array(z.string()).optional().describe("Milestone dependencies"),
843
886
  successCriteria: z.array(z.string()).optional().describe("Top-level success criteria bullets"),
844
887
  keyRisks: z.array(z.object({
845
- risk: z.string(),
846
- whyItMatters: z.string(),
888
+ risk: nonEmptyString("risk"),
889
+ whyItMatters: nonEmptyString("whyItMatters"),
847
890
  })).optional().describe("Structured risk entries"),
848
891
  proofStrategy: z.array(z.object({
849
- riskOrUnknown: z.string(),
850
- retireIn: z.string(),
851
- whatWillBeProven: z.string(),
892
+ riskOrUnknown: nonEmptyString("riskOrUnknown"),
893
+ retireIn: nonEmptyString("retireIn"),
894
+ whatWillBeProven: nonEmptyString("whatWillBeProven"),
852
895
  })).optional().describe("Structured proof strategy entries"),
853
896
  verificationContract: z.string().optional(),
854
897
  verificationIntegration: z.string().optional(),
@@ -862,19 +905,19 @@ const planMilestoneSchema = z.object(planMilestoneParams);
862
905
 
863
906
  const planSliceParams = {
864
907
  projectDir: projectDirParam,
865
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
866
- sliceId: z.string().describe("Slice ID (e.g. S01)"),
867
- goal: z.string().describe("Slice goal"),
908
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
909
+ sliceId: nonEmptyString("sliceId").describe("Slice ID (e.g. S01)"),
910
+ goal: nonEmptyString("goal").describe("Slice goal"),
868
911
  tasks: z.array(z.object({
869
- taskId: z.string(),
870
- title: z.string(),
871
- description: z.string(),
872
- estimate: z.string(),
873
- files: z.array(z.string()),
874
- verify: z.string(),
875
- inputs: z.array(z.string()),
876
- expectedOutput: z.array(z.string()),
877
- observabilityImpact: z.string().optional(),
912
+ taskId: nonEmptyString("taskId"),
913
+ title: nonEmptyString("title"),
914
+ description: nonEmptyString("description"),
915
+ estimate: nonEmptyString("estimate"),
916
+ files: nonEmptyStringArray("files"),
917
+ verify: nonEmptyString("verify"),
918
+ inputs: nonEmptyStringArray("inputs"),
919
+ expectedOutput: nonEmptyStringArray("expectedOutput"),
920
+ observabilityImpact: optionalNonEmptyString("observabilityImpact"),
878
921
  })).describe("Planned tasks for the slice"),
879
922
  successCriteria: z.string().optional(),
880
923
  proofLevel: z.string().optional(),
@@ -885,8 +928,8 @@ const planSliceSchema = z.object(planSliceParams);
885
928
 
886
929
  const completeMilestoneParams = {
887
930
  projectDir: projectDirParam,
888
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
889
- title: z.string().describe("Milestone title"),
931
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
932
+ title: nonEmptyString("title").describe("Milestone title"),
890
933
  oneLiner: z.string().describe("One-sentence summary of what the milestone achieved"),
891
934
  narrative: z.string().describe("Detailed narrative of what happened during the milestone"),
892
935
  verificationPassed: z.boolean().describe("Must be true after milestone verification succeeds"),
@@ -903,7 +946,7 @@ const completeMilestoneSchema = z.object(completeMilestoneParams);
903
946
 
904
947
  const validateMilestoneParams = {
905
948
  projectDir: projectDirParam,
906
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
949
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
907
950
  verdict: z.enum(["pass", "needs-attention", "needs-remediation"]).describe("Validation verdict"),
908
951
  remediationRound: z.number().describe("Remediation round (0 for first validation)"),
909
952
  successCriteriaChecklist: z.string().describe("Markdown checklist of success criteria with evidence"),
@@ -917,8 +960,8 @@ const validateMilestoneParams = {
917
960
  const validateMilestoneSchema = z.object(validateMilestoneParams);
918
961
 
919
962
  const roadmapSliceChangeSchema = z.object({
920
- sliceId: z.string(),
921
- title: z.string(),
963
+ sliceId: nonEmptyString("sliceId"),
964
+ title: nonEmptyString("title"),
922
965
  risk: z.string().optional(),
923
966
  depends: z.array(z.string()).optional(),
924
967
  demo: z.string().optional(),
@@ -926,10 +969,10 @@ const roadmapSliceChangeSchema = z.object({
926
969
 
927
970
  const reassessRoadmapParams = {
928
971
  projectDir: projectDirParam,
929
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
930
- completedSliceId: z.string().describe("Slice ID that just completed"),
931
- verdict: z.string().describe("Assessment verdict such as roadmap-confirmed or roadmap-adjusted"),
932
- assessment: z.string().describe("Assessment text explaining the roadmap decision"),
972
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
973
+ completedSliceId: nonEmptyString("completedSliceId").describe("Slice ID that just completed"),
974
+ verdict: nonEmptyString("verdict").describe("Assessment verdict such as roadmap-confirmed or roadmap-adjusted"),
975
+ assessment: nonEmptyString("assessment").describe("Assessment text explaining the roadmap decision"),
933
976
  sliceChanges: z.object({
934
977
  modified: z.array(roadmapSliceChangeSchema),
935
978
  added: z.array(roadmapSliceChangeSchema),
@@ -952,14 +995,14 @@ const saveGateResultSchema = z.object(saveGateResultParams);
952
995
 
953
996
  const replanSliceParams = {
954
997
  projectDir: projectDirParam,
955
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
956
- sliceId: z.string().describe("Slice ID (e.g. S01)"),
957
- blockerTaskId: z.string().describe("Task ID that discovered the blocker"),
958
- blockerDescription: z.string().describe("Description of the blocker"),
959
- whatChanged: z.string().describe("Summary of what changed in the plan"),
998
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
999
+ sliceId: nonEmptyString("sliceId").describe("Slice ID (e.g. S01)"),
1000
+ blockerTaskId: nonEmptyString("blockerTaskId").describe("Task ID that discovered the blocker"),
1001
+ blockerDescription: nonEmptyString("blockerDescription").describe("Description of the blocker"),
1002
+ whatChanged: nonEmptyString("whatChanged").describe("Summary of what changed in the plan"),
960
1003
  updatedTasks: z.array(z.object({
961
- taskId: z.string(),
962
- title: z.string(),
1004
+ taskId: nonEmptyString("taskId"),
1005
+ title: nonEmptyString("title"),
963
1006
  description: z.string(),
964
1007
  estimate: z.string(),
965
1008
  files: z.array(z.string()),
@@ -974,8 +1017,8 @@ const replanSliceSchema = z.object(replanSliceParams);
974
1017
 
975
1018
  const sliceCompleteParams = {
976
1019
  projectDir: projectDirParam,
977
- sliceId: z.string().describe("Slice ID (e.g. S01)"),
978
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
1020
+ sliceId: nonEmptyString("sliceId").describe("Slice ID (e.g. S01)"),
1021
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
979
1022
  sliceTitle: z.string().describe("Title of the slice"),
980
1023
  oneLiner: z.string().describe("One-line summary of what the slice accomplished"),
981
1024
  narrative: z.string().describe("Detailed narrative of what happened across all tasks"),
@@ -1070,17 +1113,17 @@ const milestoneGenerateIdSchema = z.object(milestoneGenerateIdParams);
1070
1113
 
1071
1114
  const planTaskParams = {
1072
1115
  projectDir: projectDirParam,
1073
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
1074
- sliceId: z.string().describe("Slice ID (e.g. S01)"),
1075
- taskId: z.string().describe("Task ID (e.g. T01)"),
1076
- title: z.string().describe("Task title"),
1077
- description: z.string().describe("Task description / steps block"),
1078
- estimate: z.string().describe("Task estimate"),
1116
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
1117
+ sliceId: nonEmptyString("sliceId").describe("Slice ID (e.g. S01)"),
1118
+ taskId: nonEmptyString("taskId").describe("Task ID (e.g. T01)"),
1119
+ title: nonEmptyString("title").describe("Task title"),
1120
+ description: nonEmptyString("description").describe("Task description / steps block"),
1121
+ estimate: nonEmptyString("estimate").describe("Task estimate"),
1079
1122
  files: z.array(z.string()).describe("Files likely touched"),
1080
- verify: z.string().describe("Verification command or block"),
1123
+ verify: nonEmptyString("verify").describe("Verification command or block"),
1081
1124
  inputs: z.array(z.string()).describe("Input files or references"),
1082
1125
  expectedOutput: z.array(z.string()).describe("Expected output files or artifacts"),
1083
- observabilityImpact: z.string().optional().describe("Task observability impact"),
1126
+ observabilityImpact: optionalNonEmptyString("observabilityImpact").describe("Task observability impact"),
1084
1127
  };
1085
1128
  const planTaskSchema = z.object(planTaskParams);
1086
1129
 
@@ -1094,9 +1137,9 @@ const skipSliceSchema = z.object(skipSliceParams);
1094
1137
 
1095
1138
  const taskCompleteParams = {
1096
1139
  projectDir: projectDirParam,
1097
- taskId: z.string().describe("Task ID (e.g. T01)"),
1098
- sliceId: z.string().describe("Slice ID (e.g. S01)"),
1099
- milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
1140
+ taskId: nonEmptyString("taskId").describe("Task ID (e.g. T01)"),
1141
+ sliceId: nonEmptyString("sliceId").describe("Slice ID (e.g. S01)"),
1142
+ milestoneId: nonEmptyString("milestoneId").describe("Milestone ID (e.g. M001)"),
1100
1143
  oneLiner: z.string().describe("One-line summary of what was accomplished"),
1101
1144
  narrative: z.string().describe("Detailed narrative of what happened during the task"),
1102
1145
  verification: z.string().describe("What was verified and how"),