@captain_z/zsk 1.8.2 → 1.8.4

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 (57) hide show
  1. package/dist/bin.js +4 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/commands/add-flow.d.ts +3 -7
  4. package/dist/commands/add-flow.js +7 -59
  5. package/dist/commands/add-flow.js.map +1 -1
  6. package/dist/commands/add.js +25 -104
  7. package/dist/commands/add.js.map +1 -1
  8. package/dist/commands/check.js +184 -8
  9. package/dist/commands/check.js.map +1 -1
  10. package/dist/commands/gate.d.ts +2 -0
  11. package/dist/commands/gate.js +2 -0
  12. package/dist/commands/gate.js.map +1 -1
  13. package/dist/core/prepare-sync.d.ts +2 -31
  14. package/dist/core/prepare-sync.js +119 -136
  15. package/dist/core/prepare-sync.js.map +1 -1
  16. package/dist/core/profile-bundle-installation.d.ts +55 -0
  17. package/dist/core/profile-bundle-installation.js +170 -0
  18. package/dist/core/profile-bundle-installation.js.map +1 -0
  19. package/dist/core/source-snapshot-adapters.d.ts +59 -0
  20. package/dist/core/source-snapshot-adapters.js +82 -0
  21. package/dist/core/source-snapshot-adapters.js.map +1 -0
  22. package/dist/core/staffing-plan.d.ts +7 -1
  23. package/dist/core/staffing-plan.js +186 -29
  24. package/dist/core/staffing-plan.js.map +1 -1
  25. package/dist/core/stage-clarity-verification.d.ts +31 -0
  26. package/dist/core/stage-clarity-verification.js +313 -0
  27. package/dist/core/stage-clarity-verification.js.map +1 -0
  28. package/dist/core/stage-quality-artifacts.d.ts +15 -0
  29. package/dist/core/stage-quality-artifacts.js +421 -0
  30. package/dist/core/stage-quality-artifacts.js.map +1 -0
  31. package/dist/core/stage-quality-contracts.d.ts +86 -0
  32. package/dist/core/stage-quality-contracts.js +2 -0
  33. package/dist/core/stage-quality-contracts.js.map +1 -0
  34. package/dist/core/stage-quality-criteria.d.ts +9 -0
  35. package/dist/core/stage-quality-criteria.js +323 -0
  36. package/dist/core/stage-quality-criteria.js.map +1 -0
  37. package/dist/core/stage-quality-rendering.d.ts +13 -0
  38. package/dist/core/stage-quality-rendering.js +122 -0
  39. package/dist/core/stage-quality-rendering.js.map +1 -0
  40. package/dist/core/stage-quality.d.ts +5 -52
  41. package/dist/core/stage-quality.js +40 -432
  42. package/dist/core/stage-quality.js.map +1 -1
  43. package/dist/core/template-registry.js +6 -0
  44. package/dist/core/template-registry.js.map +1 -1
  45. package/package.json +2 -2
  46. package/templates/module/frontend-module/design.md +25 -3
  47. package/templates/module/frontend-module/proposal.md +8 -0
  48. package/templates/module/frontend-module/spec.md +16 -0
  49. package/templates/module/frontend-module/tasks.md +23 -7
  50. package/templates/project-init/.zsk/config.yaml +33 -0
  51. package/templates/project-init/.zsk/docs/PROJECT-CONFIG.md +43 -0
  52. package/templates/project-init/.zsk/docs/SYSTEM-SPEC.md +23 -1
  53. package/templates/project-init/.zsk/raws/index.md +19 -0
  54. package/templates/project-init/.zsk/raws/prepare/design/index.md +18 -0
  55. package/templates/project-init/.zsk/raws/prepare/index.md +33 -0
  56. package/templates/project-init/.zsk/raws/prepare/ux/index.md +19 -0
  57. package/templates/project-init/.zsk/roles.yaml +11 -11
@@ -0,0 +1,82 @@
1
+ import { resolve } from "node:path";
2
+ import { resolveSourceSnapshot, } from "./config.js";
3
+ import { inferSourceOrigin } from "./origin-detection.js";
4
+ export async function createSourceSnapshotContext(target, config, entry, opts, previousSnapshotHashFor) {
5
+ const source = entry.source;
6
+ const origin = inferSourceOrigin(source);
7
+ const snapshot = resolveSourceSnapshot(config, entry);
8
+ const snapshotPath = resolve(target, snapshot);
9
+ const previousSnapshotHash = await previousSnapshotHashFor(snapshotPath);
10
+ const base = {
11
+ envelopeVersion: 1,
12
+ sourceKey: entry.id,
13
+ sourcePath: entry.path,
14
+ rawLane: entry.rawLane,
15
+ provider: origin.provider,
16
+ origin: origin.ref,
17
+ snapshot,
18
+ previousSnapshotHash,
19
+ };
20
+ return {
21
+ target,
22
+ config,
23
+ entry,
24
+ source,
25
+ origin,
26
+ opts,
27
+ snapshot,
28
+ snapshotPath,
29
+ previousSnapshotHash,
30
+ base,
31
+ };
32
+ }
33
+ export async function acquireSourceSnapshot(context, handlers) {
34
+ if (context.opts.dryRun)
35
+ return handlers.dryRun(context);
36
+ if (context.origin.method === "local")
37
+ return handlers.local(context);
38
+ const providerAdapter = selectSourceSnapshotProviderAdapter(context.source, context.origin);
39
+ if (providerAdapter) {
40
+ const result = await handlers.provider(context, providerAdapter);
41
+ if (result)
42
+ return result;
43
+ }
44
+ if (context.origin.method === "repository")
45
+ return handlers.repository(context);
46
+ if (context.origin.method === "url")
47
+ return handlers.url(context);
48
+ return handlers.providerManaged(context);
49
+ }
50
+ export function selectSourceSnapshotProviderAdapter(source, origin) {
51
+ const keys = [
52
+ origin.provider,
53
+ origin.kind,
54
+ source.origin?.provider,
55
+ source.origin?.kind,
56
+ source.kind,
57
+ source.type,
58
+ ].map((value) => normalizeAdapterKey(typeof value === "string" ? value : undefined));
59
+ if (keys.some((value) => value.includes("confluence")))
60
+ return "confluence";
61
+ if (keys.some((value) => value.includes("jira")))
62
+ return "jira";
63
+ if (keys.some((value) => value.includes("gitlab")))
64
+ return "gitlab";
65
+ if (keys.some((value) => ["figma", "modao", "mastergo", "design", "design-asset", "design-source"].includes(value))) {
66
+ return "design-source";
67
+ }
68
+ return undefined;
69
+ }
70
+ export function chooseSourceSnapshotStrategy(method) {
71
+ if (method === "local")
72
+ return "local-copy-structured-markdown";
73
+ if (method === "repository")
74
+ return "repository-metadata-only";
75
+ if (method === "url")
76
+ return "playwright-auth-or-direct-fetch";
77
+ return "confirm-acquisition-method";
78
+ }
79
+ function normalizeAdapterKey(value) {
80
+ return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") ?? "";
81
+ }
82
+ //# sourceMappingURL=source-snapshot-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-snapshot-adapters.js","sourceRoot":"","sources":["../../src/core/source-snapshot-adapters.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,13 @@
1
1
  import { type ProjectConfig } from "./config.js";
2
- import { type GateAssessment } from "./stage-quality.js";
2
+ import { type GateAssessment, type ReviewGateMode } from "./stage-quality.js";
3
3
  export type StaffingPlanOptions = {
4
4
  stage?: string;
5
5
  skill?: string;
6
6
  surface?: string;
7
7
  runId?: string;
8
8
  module?: string;
9
+ issue?: string;
10
+ reviewTarget?: string;
9
11
  gateThreshold?: number;
10
12
  acceptRisk?: string;
11
13
  };
@@ -118,6 +120,8 @@ export type StaffingStageGate = {
118
120
  runId: string;
119
121
  stage: string;
120
122
  module?: string;
123
+ reviewTarget?: string;
124
+ reviewMode?: ReviewGateMode;
121
125
  status: GateAssessment["status"];
122
126
  decision: GateAssessment["decision"];
123
127
  score: number;
@@ -133,6 +137,8 @@ export type StaffingPlan = {
133
137
  stage: string;
134
138
  skill: string;
135
139
  module?: string;
140
+ reviewTarget?: string;
141
+ reviewMode?: ReviewGateMode;
136
142
  surface: string;
137
143
  stageGate?: StaffingStageGate;
138
144
  orchestration: StaffingOrchestration;
@@ -3,7 +3,7 @@ import { dirname, join, resolve } from "node:path";
3
3
  import YAML from "yaml";
4
4
  import { flattenProjectSources } from "./config.js";
5
5
  import { inferSourceOrigin } from "./origin-detection.js";
6
- import { gateThresholdForStage, writeGateAssessment } from "./stage-quality.js";
6
+ import { gateThresholdForStage, normalizeReviewTargetPath, reviewModeForReviewTarget, writeGateAssessment, } from "./stage-quality.js";
7
7
  import { getWorkspacePath } from "./workspace-layout.js";
8
8
  const DEFAULT_PACKET_TIMEOUT_POLICY = {
9
9
  heartbeatIntervalMs: 60_000,
@@ -11,6 +11,20 @@ const DEFAULT_PACKET_TIMEOUT_POLICY = {
11
11
  maxAttempts: 2,
12
12
  onTimeout: "fallback-to-leader-sequential",
13
13
  };
14
+ const PREPROPOSAL_ROLE_NAMES = [
15
+ "product-owner",
16
+ "business-analyst",
17
+ "planner",
18
+ "ux-specialist",
19
+ "design-specialist",
20
+ "frontend-engineer",
21
+ "architect",
22
+ "qa-engineer",
23
+ "researcher",
24
+ "verifier",
25
+ "lead-integrator",
26
+ ];
27
+ const PREPROPOSAL_REVIEW_MODES = new Set(["preproposal", "raw-source"]);
14
28
  const BUILTIN_ROLE_POOL = {
15
29
  roles: Object.fromEntries([
16
30
  rolePoolEntry("lead-integrator", {
@@ -29,21 +43,21 @@ const BUILTIN_ROLE_POOL = {
29
43
  }),
30
44
  rolePoolEntry("product-owner", {
31
45
  subagentType: "analyst",
32
- stages: ["prepare", "proposal", "spec", "review", "verify", "acceptance"],
46
+ stages: ["prepare", "preproposal", "proposal", "spec", "review", "verify", "acceptance"],
33
47
  lanes: ["product", "pm", "requirement", "requirements", "srs", "prd"],
34
48
  activation: "when product or requirement evidence is configured or in scope",
35
49
  reason: "interpret product scope, requirement semantics, priorities, and acceptance gaps",
36
50
  }),
37
51
  rolePoolEntry("business-analyst", {
38
52
  subagentType: "analyst",
39
- stages: ["proposal", "spec"],
53
+ stages: ["preproposal", "proposal", "spec"],
40
54
  lanes: ["business", "domain", "process", "rules"],
41
55
  activation: "when domain process or business-rule evidence is configured or in scope",
42
56
  reason: "map domain process, terminology, business rules, and edge cases",
43
57
  }),
44
58
  rolePoolEntry("architect", {
45
59
  subagentType: "architect",
46
- stages: ["proposal", "spec", "design", "review"],
60
+ stages: ["preproposal", "proposal", "spec", "design", "review"],
47
61
  activation: "when architecture, API, system-boundary, or dependency decisions are in scope",
48
62
  reason: "review system boundaries, module relationships, contracts, and technical risks",
49
63
  }),
@@ -56,28 +70,28 @@ const BUILTIN_ROLE_POOL = {
56
70
  }),
57
71
  rolePoolEntry("frontend-engineer", {
58
72
  subagentType: "executor",
59
- stages: ["design"],
73
+ stages: ["preproposal", "design"],
60
74
  lanes: ["frontend", "web", "client", "mobile"],
61
75
  activation: "when frontend implementation, routing, state, or UI integration is in scope",
62
76
  reason: "map UI implementation, routing, state, and component integration risk",
63
77
  }),
64
78
  rolePoolEntry("design-specialist", {
65
79
  subagentType: "designer",
66
- stages: ["prepare", "design"],
80
+ stages: ["prepare", "preproposal"],
67
81
  lanes: ["design", "ui", "visual"],
68
82
  activation: "when design, UI, visual, prototype, or asset sources are configured",
69
83
  reason: "inspect design assets, screens, visual states, and design-source gaps",
70
84
  }),
71
85
  rolePoolEntry("ux-specialist", {
72
86
  subagentType: "designer",
73
- stages: ["prepare"],
87
+ stages: ["prepare", "preproposal"],
74
88
  lanes: ["ux", "ue", "user-experience", "research"],
75
89
  activation: "when UX, UE, user-flow, or research sources are configured",
76
90
  reason: "inspect user flows, interaction constraints, and usability ambiguity",
77
91
  }),
78
92
  rolePoolEntry("qa-engineer", {
79
93
  subagentType: "test-engineer",
80
- stages: ["prepare", "spec", "task", "coding", "review", "verify"],
94
+ stages: ["prepare", "preproposal", "spec", "task", "coding", "fix", "review", "verify"],
81
95
  lanes: ["qa", "test", "testing", "quality"],
82
96
  activation: "when QA, test, acceptance, or quality evidence is configured or in scope",
83
97
  reason: "connect requirements to acceptance scenarios, test coverage, and verification evidence",
@@ -104,19 +118,19 @@ const BUILTIN_ROLE_POOL = {
104
118
  }),
105
119
  rolePoolEntry("researcher", {
106
120
  subagentType: "researcher",
107
- stages: ["prepare"],
121
+ stages: ["prepare", "preproposal"],
108
122
  activation: "optional when current official or external evidence is required",
109
123
  reason: "collect official/current external references only when configured local sources are insufficient",
110
124
  }),
111
125
  rolePoolEntry("planner", {
112
126
  subagentType: "planner",
113
- stages: ["task"],
127
+ stages: ["preproposal", "task"],
114
128
  activation: "when task sequencing, dependencies, and risk flags are in scope",
115
129
  reason: "sequence implementation tasks, dependencies, ownership, and evidence hooks",
116
130
  }),
117
131
  rolePoolEntry("executor", {
118
132
  subagentType: "executor",
119
- stages: ["task", "coding"],
133
+ stages: ["task", "coding", "fix"],
120
134
  activation: "when implementation ownership, write scope, or scoped code changes are in scope",
121
135
  reason: "implement or estimate scoped code ownership and write boundaries",
122
136
  }),
@@ -159,9 +173,13 @@ const ROLE_CONTRACTS = {
159
173
  outputs: [
160
174
  ".zsk/evidence/{scope}/{run}/integration-summary.md",
161
175
  ".zsk/raws/manifest.json when prepare snapshots change",
176
+ ".zsk/raws/manifest.json and .zsk/raws/index.md when preproposal raw resources change",
177
+ ".zsk/raws/prepare/product/{topic}/intake-clarity.md when running preproposal",
178
+ ".zsk/evidence/preproposal/{run}/readiness-review.md when running preproposal",
162
179
  ],
163
180
  evidenceRequired: [
164
181
  "integrated lane status with pass/blocker/waived reason for each role",
182
+ "source-discoverable questions answered before user clarification when running preproposal",
165
183
  "confirmation that shared/global artifacts were updated only after lane validation",
166
184
  ],
167
185
  forbiddenDecisions: [
@@ -173,15 +191,23 @@ const ROLE_CONTRACTS = {
173
191
  "product-owner": {
174
192
  owns: ["business goals, product scope, priorities, requirement semantics, and acceptance meaning"],
175
193
  doesNotOwn: ["backend implementation details", "technical feasibility signoff without architect/engineer evidence"],
176
- outputs: [".zsk/evidence/{scope}/{run}/product-source-report.md"],
177
- evidenceRequired: ["requirement/source IDs or paths behind each product claim", "open requirement gaps and acceptance risks"],
194
+ outputs: [
195
+ ".zsk/evidence/{scope}/{run}/product-source-report.md",
196
+ ".zsk/raws/prepare/product/{topic}/intake-clarity.md when running preproposal",
197
+ ".zsk/raws/prepare/product/{topic}/product-brief.md when running preproposal",
198
+ ".zsk/evidence/preproposal/{run}/checkpoint-product-review.md when running preproposal",
199
+ ],
200
+ evidenceRequired: ["requirement/source IDs or paths behind each product claim", "accepted clarifications, inferred assumptions, and one blocking question when running preproposal", "open requirement gaps and acceptance risks", "product checkpoint verdict before roadmap/decomposition when running preproposal"],
178
201
  forbiddenDecisions: ["Do not rewrite technical design or implementation ownership.", "Do not turn weak/inferred evidence into accepted requirements."],
179
202
  stopCondition: "Product facts, gaps, and acceptance risks are explicit and handed to lead-integrator.",
180
203
  },
181
204
  "business-analyst": {
182
205
  owns: ["domain process, terminology, business rules, and edge cases"],
183
206
  doesNotOwn: ["UI aesthetics", "implementation architecture"],
184
- outputs: [".zsk/evidence/{scope}/{run}/business-rule-map.md"],
207
+ outputs: [
208
+ ".zsk/evidence/{scope}/{run}/business-rule-map.md",
209
+ ".zsk/raws/prepare/product/{topic}/business-context.md when running preproposal",
210
+ ],
185
211
  evidenceRequired: ["business rule source references", "ambiguity list for unresolved process or terminology conflicts"],
186
212
  forbiddenDecisions: ["Do not choose product priority or implementation strategy.", "Do not resolve contradictory business rules silently."],
187
213
  stopCondition: "Rules, edge cases, and unresolved ambiguities are documented for lead-integrator.",
@@ -189,7 +215,11 @@ const ROLE_CONTRACTS = {
189
215
  architect: {
190
216
  owns: ["system boundaries, module relationships, data flow, integration risks, and API/dependency boundaries"],
191
217
  doesNotOwn: ["product priority decisions", "business acceptance signoff"],
192
- outputs: [".zsk/evidence/{scope}/{run}/architecture-review.md"],
218
+ outputs: [
219
+ ".zsk/evidence/{scope}/{run}/architecture-review.md",
220
+ ".zsk/raws/prepare/product/{topic}/roadmap.md when running preproposal",
221
+ ".zsk/evidence/preproposal/{run}/checkpoint-roadmap-review.md when running preproposal",
222
+ ],
193
223
  evidenceRequired: ["referenced files, modules, APIs, or diagrams behind architecture claims", "risk map with owner and verification hook"],
194
224
  forbiddenDecisions: ["Do not override product acceptance or source truth.", "Do not widen implementation scope without lead-integrator handoff."],
195
225
  stopCondition: "Architecture risks and boundary decisions are ready for integration or recorded as blockers.",
@@ -203,26 +233,36 @@ const ROLE_CONTRACTS = {
203
233
  stopCondition: "Backend facts and gaps are reported with source references and no shared artifact edits pending.",
204
234
  },
205
235
  "frontend-engineer": {
206
- owns: ["UI implementation, routing, state, component integration, and frontend framework constraints"],
236
+ owns: ["UI implementation, routing, state, page/module-to-code component mapping, component integration, and frontend framework constraints"],
207
237
  doesNotOwn: ["source-of-truth product decisions", "backend/API correctness signoff"],
208
238
  outputs: [".zsk/evidence/{scope}/{run}/frontend-impact-report.md"],
209
- evidenceRequired: ["component/route/state file references", "UI implementation risks and validation hooks"],
239
+ evidenceRequired: ["component/route/state file references", "page/module-to-code mapping coverage", "UI implementation risks and validation hooks"],
210
240
  forbiddenDecisions: ["Do not reinterpret acceptance criteria without product-owner handoff.", "Do not approve API behavior without backend evidence."],
211
241
  stopCondition: "Frontend impact and validation needs are clear for lead-integrator.",
212
242
  },
213
243
  "design-specialist": {
214
- owns: ["design assets, visual constraints, screen/state inventory, and design snapshot validation"],
244
+ owns: ["design assets, visual constraints, screen/state inventory, provider source maps, and design snapshot validation"],
215
245
  doesNotOwn: ["backend/API correctness", "final product scope acceptance"],
216
- outputs: [".zsk/evidence/{scope}/{run}/design-asset-report.md"],
217
- evidenceRequired: ["design/prototype source references", "screen/state map and missing asset list"],
246
+ outputs: [
247
+ ".zsk/evidence/{scope}/{run}/design-asset-report.md",
248
+ ".zsk/raws/prepare/design/{topic}/design-source-needs.md when running preproposal",
249
+ ".zsk/raws/prepare/design/{topic}/design-source-map.md when provider-backed design sources are used",
250
+ ],
251
+ evidenceRequired: ["design/prototype source references", "screen/state map, provider source coverage, and missing asset list"],
218
252
  forbiddenDecisions: ["Do not infer implementation truth from design assets.", "Do not store auth state or exported private assets outside declared scope."],
219
253
  stopCondition: "Design assets are mapped or blocked with provider/auth evidence.",
220
254
  },
221
255
  "ux-specialist": {
222
- owns: ["user flows, interaction constraints, usability risks, and UX source validation"],
256
+ owns: ["user flows, interaction handoff, interaction constraints, usability risks, and UX source validation"],
223
257
  doesNotOwn: ["backend/API correctness", "business priority decisions"],
224
- outputs: [".zsk/evidence/{scope}/{run}/ux-source-report.md"],
225
- evidenceRequired: ["flow/source references", "usability risks and unresolved interaction questions"],
258
+ outputs: [
259
+ ".zsk/evidence/{scope}/{run}/ux-source-report.md",
260
+ ".zsk/raws/prepare/ux/{topic}/ux-readiness.md when running preproposal",
261
+ ".zsk/raws/prepare/ux/{topic}/interaction-handoff.md when UI, UX, or design-source interaction is triggered",
262
+ ".zsk/evidence/preproposal/{run}/checkpoint-ux-review.md when running preproposal",
263
+ ".zsk/evidence/preproposal/{run}/checkpoint-interaction-review.md when interaction handoff is triggered",
264
+ ],
265
+ evidenceRequired: ["flow/source references", "AI brief draft status", "page/module interaction detail coverage", "interaction/state/a11y coverage", "usability risks and unresolved interaction questions"],
226
266
  forbiddenDecisions: ["Do not approve product scope or technical design alone.", "Do not resolve UX/product conflicts silently."],
227
267
  stopCondition: "UX facts, questions, and risks are ready for lead integration.",
228
268
  },
@@ -237,8 +277,11 @@ const ROLE_CONTRACTS = {
237
277
  "qa-engineer": {
238
278
  owns: ["test cases, regression matrix, acceptance scenarios, Playwright/runtime evidence, and test gaps"],
239
279
  doesNotOwn: ["business signoff", "implementation design ownership"],
240
- outputs: [".zsk/evidence/{scope}/{run}/qa-evidence-report.md"],
241
- evidenceRequired: ["test command/scenario evidence", "acceptance coverage and missing-test gaps"],
280
+ outputs: [
281
+ ".zsk/evidence/{scope}/{run}/qa-evidence-report.md",
282
+ ".zsk/raws/prepare/product/{topic}/scenario-seeds.md when running preproposal",
283
+ ],
284
+ evidenceRequired: ["test command/scenario evidence", "acceptance coverage and missing-test gaps", "scenario seeds and formal-test-case deferral when running preproposal"],
242
285
  forbiddenDecisions: ["Do not treat unrun tests as passing.", "Do not accept partial snapshots as complete evidence."],
243
286
  stopCondition: "Coverage status is pass/fail/blocked with concrete evidence and residual risk.",
244
287
  },
@@ -428,11 +471,11 @@ function seedFromRolePool(role, entries, overrides = {}) {
428
471
  reason: overrides.reason ?? role.reason ?? `configured ${role.role} role`,
429
472
  entries,
430
473
  activation: overrides.activation ?? role.activation,
431
- extraInputs: role.extraInputs,
474
+ extraInputs: unique([...(role.extraInputs ?? []), ...(overrides.extraInputs ?? [])]),
432
475
  writeScope: unique([...(role.writeScope ?? []), ...(overrides.writeScope ?? [])]),
433
476
  outputs: unique([...(role.outputs ?? []), ...(overrides.outputs ?? [])]),
434
477
  evidenceRequired: unique([...(role.evidenceRequired ?? []), ...(overrides.evidenceRequired ?? [])]),
435
- contract: role.contract,
478
+ contract: mergeContractOverrides(role.contract, overrides.contract),
436
479
  };
437
480
  }
438
481
  function roleMatchesStageOrSkill(role, stage, skill) {
@@ -461,6 +504,8 @@ function summarizeStageGate(assessment, assessmentPath, markdownPath, waiverPath
461
504
  runId: assessment.runId,
462
505
  stage: assessment.stage,
463
506
  ...(assessment.module ? { module: assessment.module } : {}),
507
+ ...(assessment.reviewTarget ? { reviewTarget: assessment.reviewTarget } : {}),
508
+ ...(assessment.reviewMode ? { reviewMode: assessment.reviewMode } : {}),
464
509
  status: assessment.status,
465
510
  decision: assessment.decision,
466
511
  score: assessment.score,
@@ -490,6 +535,8 @@ export async function writeStaffingPlan(target, config, opts = {}) {
490
535
  const gateBundle = await writeGateAssessment(target, config, {
491
536
  stage: bundle.plan.stage,
492
537
  module: opts.module,
538
+ issue: opts.issue,
539
+ reviewTarget: bundle.plan.reviewTarget,
493
540
  threshold: opts.gateThreshold ?? gateThresholdForStage(config, bundle.plan.stage),
494
541
  acceptRisk: opts.acceptRisk,
495
542
  });
@@ -515,9 +562,11 @@ export function buildStaffingPlan(target, config, opts = {}, rolePool = BUILTIN_
515
562
  const stage = normalizeStage(opts.stage ?? "prepare");
516
563
  const skill = opts.skill ?? stage;
517
564
  const surface = opts.surface ?? "auto";
565
+ const reviewTarget = stage === "review" ? normalizeReviewTargetPath(target, opts.reviewTarget) : undefined;
566
+ const reviewMode = reviewModeForReviewTarget(reviewTarget);
518
567
  const timeoutPolicy = packetTimeoutPolicyFromConfig(config);
519
568
  const entries = flattenProjectSources(config.sources);
520
- const seeds = seedRoles(stage, skill, entries, rolePool);
569
+ const seeds = seedRoles(stage, skill, entries, rolePool, reviewMode, opts.module);
521
570
  const orchestration = buildOrchestrationPlan(surface, stage, entries, seeds, config);
522
571
  const roles = seeds.map((seed) => buildRole(seed, stage, skill, orchestration, config));
523
572
  const dir = resolve(target, getWorkspacePath(config, "evidenceRoot"), "dispatch", runId);
@@ -542,6 +591,8 @@ export function buildStaffingPlan(target, config, opts = {}, rolePool = BUILTIN_
542
591
  stage,
543
592
  skill,
544
593
  ...(opts.module ? { module: opts.module } : {}),
594
+ ...(reviewTarget ? { reviewTarget } : {}),
595
+ ...(reviewMode ? { reviewMode } : {}),
545
596
  surface,
546
597
  orchestration,
547
598
  uncertaintyPolicy: [
@@ -616,7 +667,7 @@ function markPacketStale(packet, now) {
616
667
  blocker: "No heartbeat was recorded before the packet deadline; route this lane through leader-sequential fallback or re-emit with a new packet.",
617
668
  };
618
669
  }
619
- function seedRoles(stage, skill, entries, rolePool) {
670
+ function seedRoles(stage, skill, entries, rolePool, reviewMode, module) {
620
671
  const seeds = new Map();
621
672
  const poolRoles = Object.values(rolePool.roles);
622
673
  for (const role of poolRoles) {
@@ -661,6 +712,109 @@ function seedRoles(stage, skill, entries, rolePool) {
661
712
  }
662
713
  }
663
714
  }
715
+ if (stage === "preproposal" || skill === "preproposal") {
716
+ const topic = module ?? "{topic}";
717
+ const moduleInputs = module
718
+ ? [
719
+ `.zsk/modules/${module}/module.yaml`,
720
+ `.zsk/modules/${module}/CONTEXT.md`,
721
+ `.zsk/modules/${module}/proposal.md when present`,
722
+ `.zsk/modules/${module}/spec.md when present`,
723
+ `.zsk/modules/${module}/design.md when present`,
724
+ `.zsk/modules/${module}/tasks.md when present`,
725
+ ]
726
+ : [];
727
+ for (const roleName of PREPROPOSAL_ROLE_NAMES) {
728
+ const role = rolePool.roles[roleName];
729
+ if (!role)
730
+ continue;
731
+ addSeed(seeds, seedFromRolePool(role, [], {
732
+ activation: "required for preproposal product/roadmap/UX/readiness checkpoint coverage",
733
+ reason: module
734
+ ? role.reason || `produce and review module-scoped preproposal raw resources for ${module} before formal proposal`
735
+ : role.reason || "produce and review preproposal raw resources before formal proposal",
736
+ writeScope: [
737
+ ".zsk/raws/prepare/product/**",
738
+ ".zsk/raws/prepare/ux/**",
739
+ ".zsk/raws/prepare/design/**",
740
+ ".zsk/evidence/preproposal/**",
741
+ ".zsk/raws/manifest.json",
742
+ ".zsk/raws/index.md",
743
+ ...role.writeScope,
744
+ ],
745
+ outputs: [
746
+ `.zsk/raws/prepare/product/${topic}/intake-clarity.md`,
747
+ `.zsk/raws/prepare/product/${topic}/product-brief.md`,
748
+ `.zsk/raws/prepare/product/${topic}/roadmap.md`,
749
+ `.zsk/raws/prepare/ux/${topic}/ux-readiness.md`,
750
+ `.zsk/raws/prepare/ux/${topic}/interaction-handoff.md when UI/UX/design-source interaction is triggered`,
751
+ `.zsk/raws/prepare/design/${topic}/design-source-needs.md`,
752
+ `.zsk/raws/prepare/design/${topic}/design-source-map.md when provider-backed design sources are used`,
753
+ ".zsk/evidence/preproposal/{run}/checkpoint-*.md",
754
+ ".zsk/evidence/preproposal/{run}/readiness-review.md",
755
+ ...role.outputs,
756
+ ],
757
+ evidenceRequired: [
758
+ ...(module ? [
759
+ `module target is fixed to ${module}; module docs and raw sources are challenged before widening scope`,
760
+ `interaction handoff and design-source map use ${module} as the default raw topic unless a narrower page/topic is justified`,
761
+ ] : []),
762
+ "source-discoverable questions answered before user clarification",
763
+ "accepted clarifications, inferred assumptions, and one blocking question are separated",
764
+ "product checkpoint passes before roadmap/decomposition",
765
+ "roadmap/decomposition checkpoint passes before UX/design-readiness",
766
+ "AI brief drafts and candidate code component mapping exist for source-backed page/module interaction details before asking humans to fill gaps",
767
+ "interaction handoff separates sourced provider facts from accepted decisions, assumptions, and missing page/module interaction details when triggered",
768
+ "UX/design-readiness checkpoint passes before readiness handoff",
769
+ "final readiness review passes or blocks with owner, missing input, impact, and next action",
770
+ ...role.evidenceRequired,
771
+ ],
772
+ ...(moduleInputs.length > 0 ? { extraInputs: moduleInputs } : {}),
773
+ contract: {
774
+ owns: module
775
+ ? [`preproposal lane facts, source gaps, checkpoint evidence, and readiness handoff for module ${module}`]
776
+ : ["preproposal lane facts, source gaps, checkpoint evidence, and readiness handoff for this role"],
777
+ doesNotOwn: ["formal module proposal/spec/design/task artifacts", "formal test cases", "implementation edits"],
778
+ forbiddenDecisions: ["Do not skip checkpoint order.", "Do not treat scenario seeds as formal post-design test cases."],
779
+ stopCondition: "Preproposal lane output is reviewable, or blockers are recorded for the checkpoint owner.",
780
+ },
781
+ }));
782
+ }
783
+ }
784
+ if (stage === "review" && reviewMode && PREPROPOSAL_REVIEW_MODES.has(reviewMode)) {
785
+ for (const roleName of PREPROPOSAL_ROLE_NAMES) {
786
+ const role = rolePool.roles[roleName];
787
+ if (!role)
788
+ continue;
789
+ addSeed(seeds, seedFromRolePool(role, [], {
790
+ activation: `required for ${reviewMode} review target checkpoint coverage`,
791
+ reason: role.reason || `review ${reviewMode} target for product, roadmap, UX, source, and readiness gaps before formal proposal`,
792
+ writeScope: [
793
+ ".zsk/evidence/review/**",
794
+ ".zsk/issues/**",
795
+ ...role.writeScope,
796
+ ],
797
+ outputs: [
798
+ ".zsk/evidence/review/{run}/target-review.md",
799
+ ".zsk/evidence/review/{run}/checkpoint-findings.md",
800
+ ...role.outputs,
801
+ ],
802
+ evidenceRequired: [
803
+ "explicit review-target path and review mode",
804
+ "product, roadmap/decomposition, UX/design-readiness, assumption, risk, source, and blocker checks where relevant",
805
+ "findings grouped by role with pass/blocker/waived result and owner",
806
+ "confirmation that missing module spec/design/tasks/smoke are not blockers for raw/preproposal target review",
807
+ ...role.evidenceRequired,
808
+ ],
809
+ contract: {
810
+ owns: ["role-specific findings for the explicit raw/preproposal review target"],
811
+ doesNotOwn: ["rewriting raw resources during review", "formal module proposal/spec/design/task production", "implementation edits"],
812
+ forbiddenDecisions: ["Do not require module spec/design/tasks/smoke for raw/preproposal target review.", "Do not silently pass missing product/UX/readiness evidence."],
813
+ stopCondition: "Target findings are reviewable, or blockers identify owner, missing input, impact, and next action.",
814
+ },
815
+ }));
816
+ }
817
+ }
664
818
  return [...seeds.values()];
665
819
  }
666
820
  function addSeed(seeds, seed) {
@@ -697,6 +851,7 @@ function buildOrchestrationPlan(surface, stage, entries, seeds, config) {
697
851
  const remoteCount = entries.filter((entry) => inferSourceOrigin(entry.source).remote).length;
698
852
  const activeSeedCount = seeds.filter((seed) => seedIsActive(seed, stage)).length;
699
853
  const stageParallelNeed = (policy.strictReview && ["review", "verify"].includes(stage)) ||
854
+ (stage === "preproposal" && activeSeedCount > 2) ||
700
855
  (["prepare", "design"].includes(stage) && activeSeedCount > 2);
701
856
  const hasIndependentLanes = lanes.size > 1 ||
702
857
  remoteCount > 0 ||
@@ -1022,6 +1177,8 @@ function renderStaffingPlanMarkdown(plan, timeoutPolicy) {
1022
1177
  `- Stage: \`${plan.stage}\``,
1023
1178
  `- Skill: \`${plan.skill}\``,
1024
1179
  ...(plan.module ? [`- Module: \`${plan.module}\``] : []),
1180
+ ...(plan.reviewTarget ? [`- Review target: \`${plan.reviewTarget}\``] : []),
1181
+ ...(plan.reviewMode ? [`- Review mode: \`${plan.reviewMode}\``] : []),
1025
1182
  `- Surface: \`${plan.surface}\``,
1026
1183
  `- Roles: ${plan.roles.length}`,
1027
1184
  `- Active emit lanes: ${plan.emitPackets.length}`,