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.
- package/dist/resources/extensions/gsd/auto/phases.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
- package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
- package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
- package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
- package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
- package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
- package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
- package/dist/resources/extensions/search-the-web/native-search.js +13 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +102 -65
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/workflow-tools.test.ts +255 -0
- package/packages/mcp-server/src/workflow-tools.ts +108 -65
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/dist/index.d.ts +1 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +1 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
- package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.js +47 -0
- package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
- package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
- package/packages/pi-ai/src/index.ts +1 -0
- package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
- package/packages/pi-ai/src/providers/api-family.ts +57 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
- package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
- package/src/resources/extensions/gsd/auto/phases.ts +3 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
- package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
- package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
- package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
- package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
- package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
- package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
- package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
- package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
- package/src/resources/extensions/search-the-web/native-search.ts +13 -3
- /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
- /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:
|
|
823
|
-
title:
|
|
824
|
-
vision:
|
|
825
|
-
slices: z.array(
|
|
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:
|
|
846
|
-
whyItMatters:
|
|
888
|
+
risk: nonEmptyString("risk"),
|
|
889
|
+
whyItMatters: nonEmptyString("whyItMatters"),
|
|
847
890
|
})).optional().describe("Structured risk entries"),
|
|
848
891
|
proofStrategy: z.array(z.object({
|
|
849
|
-
riskOrUnknown:
|
|
850
|
-
retireIn:
|
|
851
|
-
whatWillBeProven:
|
|
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:
|
|
866
|
-
sliceId:
|
|
867
|
-
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:
|
|
870
|
-
title:
|
|
871
|
-
description:
|
|
872
|
-
estimate:
|
|
873
|
-
files:
|
|
874
|
-
verify:
|
|
875
|
-
inputs:
|
|
876
|
-
expectedOutput:
|
|
877
|
-
observabilityImpact:
|
|
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:
|
|
889
|
-
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:
|
|
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:
|
|
921
|
-
title:
|
|
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:
|
|
930
|
-
completedSliceId:
|
|
931
|
-
verdict:
|
|
932
|
-
assessment:
|
|
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:
|
|
956
|
-
sliceId:
|
|
957
|
-
blockerTaskId:
|
|
958
|
-
blockerDescription:
|
|
959
|
-
whatChanged:
|
|
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:
|
|
962
|
-
title:
|
|
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:
|
|
978
|
-
milestoneId:
|
|
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:
|
|
1074
|
-
sliceId:
|
|
1075
|
-
taskId:
|
|
1076
|
-
title:
|
|
1077
|
-
description:
|
|
1078
|
-
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:
|
|
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:
|
|
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:
|
|
1098
|
-
sliceId:
|
|
1099
|
-
milestoneId:
|
|
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"),
|