@workflow-cannon/workspace-kit 0.17.0 → 0.18.0

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.
@@ -69,6 +69,46 @@ const REGISTRY = {
69
69
  exposure: "public",
70
70
  writableLayers: ["project", "user"]
71
71
  },
72
+ "planning.defaultQuestionDepth": {
73
+ key: "planning.defaultQuestionDepth",
74
+ type: "string",
75
+ description: "Planning interview depth mode: minimal (critical only), guided (critical + static follow-ups), or adaptive (context-driven follow-ups).",
76
+ default: "adaptive",
77
+ allowedValues: ["minimal", "guided", "adaptive"],
78
+ domainScope: "project",
79
+ owningModule: "planning",
80
+ sensitive: false,
81
+ requiresRestart: false,
82
+ requiresApproval: false,
83
+ exposure: "maintainer",
84
+ writableLayers: ["project", "user"]
85
+ },
86
+ "planning.hardBlockCriticalUnknowns": {
87
+ key: "planning.hardBlockCriticalUnknowns",
88
+ type: "boolean",
89
+ description: "When true, planning finalize requests fail until critical unknown questions are answered.",
90
+ default: true,
91
+ domainScope: "project",
92
+ owningModule: "planning",
93
+ sensitive: false,
94
+ requiresRestart: false,
95
+ requiresApproval: false,
96
+ exposure: "maintainer",
97
+ writableLayers: ["project", "user"]
98
+ },
99
+ "planning.rulePacks": {
100
+ key: "planning.rulePacks",
101
+ type: "object",
102
+ description: "Optional object overrides for planning rule packs by workflow type (`baseQuestions` and `adaptiveQuestions`).",
103
+ default: {},
104
+ domainScope: "project",
105
+ owningModule: "planning",
106
+ sensitive: false,
107
+ requiresRestart: false,
108
+ requiresApproval: false,
109
+ exposure: "maintainer",
110
+ writableLayers: ["project", "user"]
111
+ },
72
112
  "policy.extraSensitiveModuleCommands": {
73
113
  key: "policy.extraSensitiveModuleCommands",
74
114
  type: "array",
@@ -46,7 +46,11 @@ export const KIT_CONFIG_DEFAULTS = {
46
46
  */
47
47
  export const MODULE_CONFIG_CONTRIBUTIONS = {
48
48
  approvals: {},
49
- planning: {}
49
+ planning: {
50
+ defaultQuestionDepth: "adaptive",
51
+ hardBlockCriticalUnknowns: true,
52
+ rulePacks: {}
53
+ }
50
54
  };
51
55
  export function deepMerge(target, source) {
52
56
  const out = { ...target };
@@ -0,0 +1,19 @@
1
+ import type { PlanningWorkflowType } from "./types.js";
2
+ export type PlanningWishlistArtifact = {
3
+ schemaVersion: 1;
4
+ planningType: PlanningWorkflowType;
5
+ generatedAt: string;
6
+ goals: string[];
7
+ approach: string;
8
+ majorTechnicalDecisions: string[];
9
+ candidateFeaturesOrChanges: string[];
10
+ assumptions: string[];
11
+ openQuestions: string[];
12
+ risksAndConstraints: string[];
13
+ sourceAnswers: Record<string, string>;
14
+ };
15
+ export declare function composePlanningWishlistArtifact(args: {
16
+ planningType: PlanningWorkflowType;
17
+ answers: Record<string, unknown>;
18
+ unresolvedCriticalQuestionIds: string[];
19
+ }): PlanningWishlistArtifact;
@@ -0,0 +1,72 @@
1
+ function answer(answers, key, fallback) {
2
+ const value = answers[key];
3
+ if (typeof value !== "string" || value.trim().length === 0) {
4
+ return fallback;
5
+ }
6
+ return value.trim();
7
+ }
8
+ function list(answers, key) {
9
+ const value = answers[key];
10
+ if (Array.isArray(value)) {
11
+ return value.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
12
+ }
13
+ if (typeof value === "string" && value.trim().length > 0) {
14
+ return [value.trim()];
15
+ }
16
+ return [];
17
+ }
18
+ export function composePlanningWishlistArtifact(args) {
19
+ const { planningType, answers, unresolvedCriticalQuestionIds } = args;
20
+ const sourceAnswers = {};
21
+ for (const [key, value] of Object.entries(answers)) {
22
+ if (typeof value === "string" && value.trim().length > 0) {
23
+ sourceAnswers[key] = value.trim();
24
+ }
25
+ }
26
+ const goals = list(answers, "goals");
27
+ if (goals.length === 0) {
28
+ const inferredGoal = answer(answers, "goal", "") ||
29
+ answer(answers, "featureGoal", "") ||
30
+ answer(answers, "changeGoal", "");
31
+ if (inferredGoal)
32
+ goals.push(inferredGoal);
33
+ }
34
+ const candidate = list(answers, "candidateFeaturesOrChanges");
35
+ if (candidate.length === 0) {
36
+ const inferred = answer(answers, "scope", "");
37
+ if (inferred)
38
+ candidate.push(inferred);
39
+ }
40
+ const majorTechnicalDecisions = list(answers, "majorTechnicalDecisions");
41
+ const decisionRationale = answer(answers, "decisionRationale", "");
42
+ if (decisionRationale && majorTechnicalDecisions.length === 0) {
43
+ majorTechnicalDecisions.push(decisionRationale);
44
+ }
45
+ const assumptions = list(answers, "assumptions");
46
+ const openQuestions = [
47
+ ...list(answers, "openQuestions"),
48
+ ...unresolvedCriticalQuestionIds.map((id) => `Unresolved critical question: ${id}`)
49
+ ];
50
+ const risksAndConstraints = list(answers, "risksAndConstraints");
51
+ const constraints = answer(answers, "constraints", "");
52
+ if (constraints) {
53
+ risksAndConstraints.push(`Constraint: ${constraints}`);
54
+ }
55
+ const riskPriority = answer(answers, "riskPriority", "");
56
+ if (riskPriority) {
57
+ risksAndConstraints.push(`Risk priority: ${riskPriority}`);
58
+ }
59
+ return {
60
+ schemaVersion: 1,
61
+ planningType,
62
+ generatedAt: new Date().toISOString(),
63
+ goals,
64
+ approach: answer(answers, "approach", "Context-driven workflow guided by planning rules."),
65
+ majorTechnicalDecisions,
66
+ candidateFeaturesOrChanges: candidate,
67
+ assumptions,
68
+ openQuestions,
69
+ risksAndConstraints,
70
+ sourceAnswers
71
+ };
72
+ }
@@ -1,3 +1,36 @@
1
+ import { PLANNING_WORKFLOW_DESCRIPTORS, PLANNING_WORKFLOW_TYPES } from "./types.js";
2
+ import { nextPlanningQuestions, resolvePlanningConfig, resolvePlanningRulePack } from "./question-engine.js";
3
+ import { composePlanningWishlistArtifact } from "./artifact.js";
4
+ import { openPlanningStores } from "../task-engine/planning-open.js";
5
+ import { buildWishlistItemFromIntake, validateWishlistIntakePayload } from "../task-engine/wishlist-validation.js";
6
+ function nextWishlistId(items) {
7
+ let max = 0;
8
+ for (const item of items) {
9
+ const match = /^W(\d+)$/.exec(item.id);
10
+ if (!match)
11
+ continue;
12
+ const parsed = Number(match[1]);
13
+ if (Number.isFinite(parsed)) {
14
+ max = Math.max(max, parsed);
15
+ }
16
+ }
17
+ return `W${max + 1}`;
18
+ }
19
+ function toCliGuidance(args) {
20
+ const { planningType, answers, unresolvedCriticalCount, totalCriticalCount, finalize } = args;
21
+ const answeredCritical = Math.max(0, totalCriticalCount - unresolvedCriticalCount);
22
+ const completionPct = totalCriticalCount > 0 ? Math.round((answeredCritical / totalCriticalCount) * 100) : 100;
23
+ return {
24
+ answeredCritical,
25
+ totalCritical: totalCriticalCount,
26
+ completionPct,
27
+ suggestedNextCommand: `workspace-kit run build-plan '${JSON.stringify({
28
+ planningType,
29
+ answers,
30
+ finalize: finalize === true
31
+ })}'`
32
+ };
33
+ }
1
34
  export const planningModule = {
2
35
  registration: {
3
36
  id: "planning",
@@ -23,8 +56,240 @@ export const planningModule = {
23
56
  name: "build-plan",
24
57
  file: "build-plan.md",
25
58
  description: "Generate a dependency-aware execution plan."
59
+ },
60
+ {
61
+ name: "list-planning-types",
62
+ file: "list-planning-types.md",
63
+ description: "List supported planning workflow types and their intent."
64
+ },
65
+ {
66
+ name: "explain-planning-rules",
67
+ file: "explain-planning-rules.md",
68
+ description: "Explain effective planning defaults and rule packs for a workflow type."
26
69
  }
27
70
  ]
28
71
  }
72
+ },
73
+ async onCommand(command, ctx) {
74
+ if (command.name === "list-planning-types") {
75
+ return {
76
+ ok: true,
77
+ code: "planning-types-listed",
78
+ message: `Found ${PLANNING_WORKFLOW_DESCRIPTORS.length} planning workflow types`,
79
+ data: {
80
+ planningTypes: PLANNING_WORKFLOW_DESCRIPTORS
81
+ }
82
+ };
83
+ }
84
+ if (command.name === "build-plan") {
85
+ const args = command.args ?? {};
86
+ const planningType = typeof args.planningType === "string" ? args.planningType.trim() : "";
87
+ if (!PLANNING_WORKFLOW_TYPES.includes(planningType)) {
88
+ return {
89
+ ok: false,
90
+ code: "invalid-planning-type",
91
+ message: "build-plan requires planningType of: task-breakdown, sprint-phase, task-ordering, new-feature, change"
92
+ };
93
+ }
94
+ const descriptor = PLANNING_WORKFLOW_DESCRIPTORS.find((x) => x.type === planningType);
95
+ const resolvedRulePack = resolvePlanningRulePack(planningType, ctx.effectiveConfig);
96
+ const totalCriticalCount = resolvedRulePack.baseQuestions.length;
97
+ const answers = typeof args.answers === "object" && args.answers !== null && !Array.isArray(args.answers)
98
+ ? args.answers
99
+ : {};
100
+ const createWishlist = args.createWishlist !== false;
101
+ const finalize = args.finalize === true;
102
+ const { missingCritical, adaptiveFollowups } = nextPlanningQuestions(planningType, answers, ctx.effectiveConfig);
103
+ const config = resolvePlanningConfig(ctx.effectiveConfig);
104
+ if (finalize && missingCritical.length > 0) {
105
+ if (!config.hardBlockCriticalUnknowns) {
106
+ return {
107
+ ok: true,
108
+ code: "planning-ready-with-warnings",
109
+ message: `Finalize allowed with unresolved critical questions because planning.hardBlockCriticalUnknowns=false`,
110
+ data: {
111
+ planningType,
112
+ status: "ready-with-warnings",
113
+ unresolvedCritical: missingCritical,
114
+ nextQuestions: [...missingCritical, ...adaptiveFollowups],
115
+ capturedAnswers: answers,
116
+ cliGuidance: toCliGuidance({
117
+ planningType,
118
+ answers,
119
+ unresolvedCriticalCount: missingCritical.length,
120
+ totalCriticalCount
121
+ })
122
+ }
123
+ };
124
+ }
125
+ return {
126
+ ok: false,
127
+ code: "planning-critical-unknowns",
128
+ message: `Cannot finalize ${planningType}: unresolved critical questions (${missingCritical.map((q) => q.id).join(", ")})`,
129
+ data: {
130
+ planningType,
131
+ unresolvedCritical: missingCritical,
132
+ nextQuestions: [...missingCritical, ...adaptiveFollowups],
133
+ cliGuidance: toCliGuidance({
134
+ planningType,
135
+ answers,
136
+ unresolvedCriticalCount: missingCritical.length,
137
+ totalCriticalCount,
138
+ finalize: true
139
+ })
140
+ }
141
+ };
142
+ }
143
+ if (missingCritical.length > 0) {
144
+ return {
145
+ ok: true,
146
+ code: "planning-questions",
147
+ message: `${missingCritical.length} critical planning questions require answers before finalize`,
148
+ data: {
149
+ planningType,
150
+ status: "needs-input",
151
+ unresolvedCritical: missingCritical,
152
+ nextQuestions: [...missingCritical, ...adaptiveFollowups],
153
+ cliGuidance: toCliGuidance({
154
+ planningType,
155
+ answers,
156
+ unresolvedCriticalCount: missingCritical.length,
157
+ totalCriticalCount
158
+ })
159
+ }
160
+ };
161
+ }
162
+ const unresolvedIds = missingCritical.map((q) => q.id);
163
+ const artifact = composePlanningWishlistArtifact({
164
+ planningType: planningType,
165
+ answers,
166
+ unresolvedCriticalQuestionIds: unresolvedIds
167
+ });
168
+ if (!finalize || !createWishlist) {
169
+ return {
170
+ ok: true,
171
+ code: "planning-ready",
172
+ message: `Planning interview complete for ${planningType}; artifact ready`,
173
+ data: {
174
+ planningType,
175
+ descriptor,
176
+ scaffoldVersion: 3,
177
+ status: "ready-for-artifact",
178
+ unresolvedCritical: [],
179
+ adaptiveFollowups,
180
+ capturedAnswers: answers,
181
+ artifact,
182
+ cliGuidance: toCliGuidance({
183
+ planningType,
184
+ answers,
185
+ unresolvedCriticalCount: 0,
186
+ totalCriticalCount,
187
+ finalize: createWishlist
188
+ })
189
+ }
190
+ };
191
+ }
192
+ const stores = await openPlanningStores(ctx);
193
+ const wishlist = await stores.openWishlist();
194
+ const wishlistId = nextWishlistId(wishlist.getAllItems());
195
+ const now = new Date().toISOString();
196
+ const intake = {
197
+ id: wishlistId,
198
+ title: typeof args.title === "string" && args.title.trim().length > 0
199
+ ? args.title.trim()
200
+ : `${descriptor?.title ?? planningType} plan artifact`,
201
+ problemStatement: typeof answers.problemStatement === "string"
202
+ ? answers.problemStatement
203
+ : typeof answers.featureGoal === "string"
204
+ ? answers.featureGoal
205
+ : "Planning artifact generated from guided workflow.",
206
+ expectedOutcome: typeof answers.expectedOutcome === "string"
207
+ ? answers.expectedOutcome
208
+ : "Clear, reviewable planning artifact for execution decomposition.",
209
+ impact: typeof answers.impact === "string" ? answers.impact : "Improved planning quality and delivery confidence.",
210
+ constraints: typeof answers.constraints === "string"
211
+ ? answers.constraints
212
+ : artifact.risksAndConstraints.join("; ") || "None explicitly provided.",
213
+ successSignals: typeof answers.successSignals === "string"
214
+ ? answers.successSignals
215
+ : "Critical questions answered and artifact accepted by operators.",
216
+ requestor: typeof args.requestor === "string" && args.requestor.trim().length > 0
217
+ ? args.requestor.trim()
218
+ : ctx.resolvedActor ?? "planning-module",
219
+ evidenceRef: typeof args.evidenceRef === "string" && args.evidenceRef.trim().length > 0
220
+ ? args.evidenceRef.trim()
221
+ : `planning:${planningType}:${now}`
222
+ };
223
+ const valid = validateWishlistIntakePayload(intake);
224
+ if (!valid.ok) {
225
+ return {
226
+ ok: false,
227
+ code: "invalid-planning-artifact",
228
+ message: valid.errors.join("; ")
229
+ };
230
+ }
231
+ const item = buildWishlistItemFromIntake(intake, now);
232
+ item.updatedAt = now;
233
+ item.metadata = {
234
+ planningType,
235
+ artifactSchemaVersion: artifact.schemaVersion,
236
+ artifact
237
+ };
238
+ wishlist.addItem(item);
239
+ await wishlist.save();
240
+ return {
241
+ ok: true,
242
+ code: "planning-artifact-created",
243
+ message: `Planning artifact created as wishlist item ${wishlistId}`,
244
+ data: {
245
+ planningType,
246
+ descriptor,
247
+ scaffoldVersion: 3,
248
+ status: "artifact-created",
249
+ wishlistId,
250
+ artifact,
251
+ unresolvedCritical: [],
252
+ adaptiveFollowups,
253
+ capturedAnswers: answers,
254
+ cliGuidance: toCliGuidance({
255
+ planningType,
256
+ answers,
257
+ unresolvedCriticalCount: 0,
258
+ totalCriticalCount,
259
+ finalize: true
260
+ })
261
+ }
262
+ };
263
+ }
264
+ if (command.name === "explain-planning-rules") {
265
+ const args = command.args ?? {};
266
+ const planningType = typeof args.planningType === "string" ? args.planningType.trim() : "";
267
+ if (!PLANNING_WORKFLOW_TYPES.includes(planningType)) {
268
+ return {
269
+ ok: false,
270
+ code: "invalid-planning-type",
271
+ message: "explain-planning-rules requires planningType of: task-breakdown, sprint-phase, task-ordering, new-feature, change"
272
+ };
273
+ }
274
+ const config = resolvePlanningConfig(ctx.effectiveConfig);
275
+ const rulePack = resolvePlanningRulePack(planningType, ctx.effectiveConfig);
276
+ return {
277
+ ok: true,
278
+ code: "planning-rules-explained",
279
+ message: `Effective planning rules for ${planningType}`,
280
+ data: {
281
+ planningType,
282
+ defaultQuestionDepth: config.depth,
283
+ hardBlockCriticalUnknowns: config.hardBlockCriticalUnknowns,
284
+ baseQuestions: rulePack.baseQuestions,
285
+ adaptiveQuestions: rulePack.adaptiveQuestions
286
+ }
287
+ };
288
+ }
289
+ return {
290
+ ok: false,
291
+ code: "unsupported-command",
292
+ message: `Planning module does not support command '${command.name}'`
293
+ };
29
294
  }
30
295
  };
@@ -0,0 +1,23 @@
1
+ import { type PlanningWorkflowType } from "./types.js";
2
+ export type PlanningQuestion = {
3
+ id: string;
4
+ prompt: string;
5
+ examples: string[];
6
+ whyItMatters: string;
7
+ critical: boolean;
8
+ };
9
+ export type PlanningQuestionDepth = "minimal" | "guided" | "adaptive";
10
+ export type PlanningRulePack = {
11
+ baseQuestions: PlanningQuestion[];
12
+ adaptiveQuestions: PlanningQuestion[];
13
+ };
14
+ export declare function resolvePlanningConfig(config: Record<string, unknown> | undefined): {
15
+ depth: PlanningQuestionDepth;
16
+ hardBlockCriticalUnknowns: boolean;
17
+ rulePacks: Partial<Record<PlanningWorkflowType, PlanningRulePack>>;
18
+ };
19
+ export declare function resolvePlanningRulePack(planningType: PlanningWorkflowType, config: Record<string, unknown> | undefined): PlanningRulePack;
20
+ export declare function nextPlanningQuestions(planningType: PlanningWorkflowType, answers: Record<string, unknown>, config?: Record<string, unknown>): {
21
+ missingCritical: PlanningQuestion[];
22
+ adaptiveFollowups: PlanningQuestion[];
23
+ };
@@ -0,0 +1,277 @@
1
+ const BASE_QUESTIONS = {
2
+ "task-breakdown": [
3
+ {
4
+ id: "goal",
5
+ prompt: "What is the primary goal of this work?",
6
+ examples: ["Improve release reliability", "Deliver planning module CLI flow"],
7
+ whyItMatters: "Clarifies what success looks like before decomposition.",
8
+ critical: true
9
+ },
10
+ {
11
+ id: "constraints",
12
+ prompt: "What constraints must we respect?",
13
+ examples: ["No breaking changes", "Single release", "CLI-first"],
14
+ whyItMatters: "Prevents planning paths that cannot be delivered safely.",
15
+ critical: true
16
+ },
17
+ {
18
+ id: "successSignals",
19
+ prompt: "How will we know the plan worked?",
20
+ examples: ["All gates pass", "Operators complete flow in under 5 minutes"],
21
+ whyItMatters: "Anchors acceptance criteria and evidence quality.",
22
+ critical: true
23
+ }
24
+ ],
25
+ "sprint-phase": [
26
+ {
27
+ id: "goal",
28
+ prompt: "What is the phase objective?",
29
+ examples: ["Ship planning module v1", "Complete stabilization hardening"],
30
+ whyItMatters: "Defines what this phase must deliver.",
31
+ critical: true
32
+ },
33
+ {
34
+ id: "timeline",
35
+ prompt: "What timeline window should this phase target?",
36
+ examples: ["Single release train", "Two sprint sequence"],
37
+ whyItMatters: "Sets ordering and scope pressure explicitly.",
38
+ critical: true
39
+ },
40
+ {
41
+ id: "criticalPath",
42
+ prompt: "What dependencies define the critical path?",
43
+ examples: ["Scaffold before adaptive engine", "Rules before artifact generation"],
44
+ whyItMatters: "Avoids deadlocks and unrealistic sequencing.",
45
+ critical: true
46
+ }
47
+ ],
48
+ "task-ordering": [
49
+ {
50
+ id: "goal",
51
+ prompt: "What outcome should ordering optimize for?",
52
+ examples: ["Risk reduction first", "Fastest path to demo"],
53
+ whyItMatters: "Provides ranking criteria for sequencing choices.",
54
+ critical: true
55
+ },
56
+ {
57
+ id: "dependencyIntent",
58
+ prompt: "What dependency constraints are mandatory?",
59
+ examples: ["T347 depends on T345", "Validation follows schema contracts"],
60
+ whyItMatters: "Ensures ordering respects hard blockers.",
61
+ critical: true
62
+ },
63
+ {
64
+ id: "riskPriority",
65
+ prompt: "Which risks should be front-loaded?",
66
+ examples: ["Policy approval edge cases", "Migration complexity"],
67
+ whyItMatters: "Improves confidence and rollback safety.",
68
+ critical: true
69
+ }
70
+ ],
71
+ "new-feature": [
72
+ {
73
+ id: "featureGoal",
74
+ prompt: "What user problem should the feature solve?",
75
+ examples: ["Guide planning interviews", "Improve dashboard visibility"],
76
+ whyItMatters: "Prevents building features disconnected from need.",
77
+ critical: true
78
+ },
79
+ {
80
+ id: "placement",
81
+ prompt: "Where should this feature be surfaced?",
82
+ examples: ["CLI command", "Extension dashboard panel", "Admin-only view"],
83
+ whyItMatters: "Drives architecture, permissions, and UX decisions.",
84
+ critical: true
85
+ },
86
+ {
87
+ id: "technology",
88
+ prompt: "What technology/runtime constraints apply?",
89
+ examples: ["TypeScript + CLI runtime", "No new native dependencies"],
90
+ whyItMatters: "Bounds implementation options early.",
91
+ critical: true
92
+ },
93
+ {
94
+ id: "targetAudience",
95
+ prompt: "Who is the target audience?",
96
+ examples: ["AI Agent Operators", "Developers", "Maintainers"],
97
+ whyItMatters: "Shapes defaults and interaction style.",
98
+ critical: true
99
+ }
100
+ ],
101
+ "change": [
102
+ {
103
+ id: "changeGoal",
104
+ prompt: "What behavior or system change is needed?",
105
+ examples: ["Replace brittle flow", "Refactor planning config handling"],
106
+ whyItMatters: "Separates desired outcome from implementation details.",
107
+ critical: true
108
+ },
109
+ {
110
+ id: "compatibilityRisk",
111
+ prompt: "What compatibility risks exist?",
112
+ examples: ["CLI contract changes", "state schema shifts"],
113
+ whyItMatters: "Supports migration-safe release planning.",
114
+ critical: true
115
+ },
116
+ {
117
+ id: "rollbackPlan",
118
+ prompt: "What rollback strategy is available?",
119
+ examples: ["Feature flag off switch", "Revert to prior command behavior"],
120
+ whyItMatters: "Maintains trustworthiness under failure.",
121
+ critical: true
122
+ }
123
+ ]
124
+ };
125
+ const BASE_ADAPTIVE_QUESTIONS = {
126
+ "task-breakdown": [
127
+ {
128
+ id: "handoffRisk",
129
+ prompt: "Where is handoff risk highest in this breakdown?",
130
+ examples: ["Schema-to-runtime boundary", "CLI-to-extension behavior"],
131
+ whyItMatters: "Highlights where extra tests or review checks are needed.",
132
+ critical: false
133
+ }
134
+ ],
135
+ "sprint-phase": [
136
+ {
137
+ id: "phaseExitSignal",
138
+ prompt: "What is the explicit exit signal for this phase?",
139
+ examples: ["All tasks completed and release gates pass", "PR merged with parity evidence"],
140
+ whyItMatters: "Prevents ambiguous “done” states.",
141
+ critical: false
142
+ }
143
+ ],
144
+ "task-ordering": [
145
+ {
146
+ id: "parallelization",
147
+ prompt: "Which work items can run in parallel safely?",
148
+ examples: ["Docs in parallel with runtime hardening", "UI after API contracts are stable"],
149
+ whyItMatters: "Improves throughput without violating dependencies.",
150
+ critical: false
151
+ }
152
+ ],
153
+ "new-feature": [
154
+ {
155
+ id: "decisionRationale",
156
+ prompt: "What major technical decision is most likely and why?",
157
+ examples: ["CLI-first before UI for safer rollout", "Reuse existing module config patterns"],
158
+ whyItMatters: "Captures rationale for later review and refinement.",
159
+ critical: false
160
+ }
161
+ ],
162
+ "change": [
163
+ {
164
+ id: "migrationNotes",
165
+ prompt: "What migration or rollout notes should operators receive?",
166
+ examples: ["No migration required", "Enable setting X before command Y"],
167
+ whyItMatters: "Protects compatibility and operator trust.",
168
+ critical: false
169
+ }
170
+ ]
171
+ };
172
+ function asRecord(value) {
173
+ return value && typeof value === "object" && !Array.isArray(value)
174
+ ? value
175
+ : null;
176
+ }
177
+ function parseQuestionList(value) {
178
+ if (!Array.isArray(value)) {
179
+ return [];
180
+ }
181
+ const parsed = [];
182
+ for (const entry of value) {
183
+ const row = asRecord(entry);
184
+ if (!row)
185
+ continue;
186
+ const id = typeof row.id === "string" ? row.id.trim() : "";
187
+ const prompt = typeof row.prompt === "string" ? row.prompt.trim() : "";
188
+ const whyItMatters = typeof row.whyItMatters === "string" ? row.whyItMatters.trim() : "";
189
+ if (!id || !prompt || !whyItMatters)
190
+ continue;
191
+ parsed.push({
192
+ id,
193
+ prompt,
194
+ whyItMatters,
195
+ examples: Array.isArray(row.examples)
196
+ ? row.examples.filter((x) => typeof x === "string")
197
+ : [],
198
+ critical: row.critical === true
199
+ });
200
+ }
201
+ return parsed;
202
+ }
203
+ function parseDepth(value) {
204
+ if (value === "minimal" || value === "guided" || value === "adaptive") {
205
+ return value;
206
+ }
207
+ return "adaptive";
208
+ }
209
+ export function resolvePlanningConfig(config) {
210
+ const planning = asRecord(config?.planning);
211
+ const depth = parseDepth(planning?.defaultQuestionDepth);
212
+ const hardBlockCriticalUnknowns = planning?.hardBlockCriticalUnknowns !== false;
213
+ const rulesRoot = asRecord(planning?.rulePacks);
214
+ const rulePacks = {};
215
+ if (rulesRoot) {
216
+ for (const workflowType of Object.keys(BASE_QUESTIONS)) {
217
+ const pack = asRecord(rulesRoot[workflowType]);
218
+ if (!pack)
219
+ continue;
220
+ const baseQuestions = parseQuestionList(pack.baseQuestions);
221
+ const adaptiveQuestions = parseQuestionList(pack.adaptiveQuestions);
222
+ if (baseQuestions.length === 0 && adaptiveQuestions.length === 0)
223
+ continue;
224
+ rulePacks[workflowType] = {
225
+ baseQuestions: baseQuestions.length > 0 ? baseQuestions : BASE_QUESTIONS[workflowType],
226
+ adaptiveQuestions
227
+ };
228
+ }
229
+ }
230
+ return { depth, hardBlockCriticalUnknowns, rulePacks };
231
+ }
232
+ export function resolvePlanningRulePack(planningType, config) {
233
+ const resolved = resolvePlanningConfig(config);
234
+ const override = resolved.rulePacks[planningType];
235
+ return {
236
+ baseQuestions: override?.baseQuestions ?? BASE_QUESTIONS[planningType],
237
+ adaptiveQuestions: override?.adaptiveQuestions ?? BASE_ADAPTIVE_QUESTIONS[planningType]
238
+ };
239
+ }
240
+ export function nextPlanningQuestions(planningType, answers, config) {
241
+ const { depth } = resolvePlanningConfig(config);
242
+ const rules = resolvePlanningRulePack(planningType, config);
243
+ const base = rules.baseQuestions;
244
+ const missingCritical = base.filter((q) => {
245
+ const value = answers[q.id];
246
+ return !(typeof value === "string" && value.trim().length > 0);
247
+ });
248
+ if (depth === "minimal") {
249
+ return { missingCritical, adaptiveFollowups: [] };
250
+ }
251
+ const adaptiveFollowups = [...rules.adaptiveQuestions];
252
+ if (depth === "guided") {
253
+ return { missingCritical, adaptiveFollowups };
254
+ }
255
+ const complexity = typeof answers.complexity === "string" ? answers.complexity.toLowerCase() : "";
256
+ if (complexity === "high") {
257
+ adaptiveFollowups.push({
258
+ id: "mitigationPlan",
259
+ prompt: "What mitigation strategy addresses high complexity risks?",
260
+ examples: ["Ship in slices", "Add parity checks before release"],
261
+ whyItMatters: "Reduces probability of destabilizing execution.",
262
+ critical: false
263
+ });
264
+ }
265
+ if (planningType === "new-feature" && !answers["decisionRationale"]) {
266
+ if (!adaptiveFollowups.some((q) => q.id === "decisionRationale")) {
267
+ adaptiveFollowups.push({
268
+ id: "decisionRationale",
269
+ prompt: "What major technical decision is most likely and why?",
270
+ examples: ["CLI-first before UI for safer rollout", "Reuse existing module config patterns"],
271
+ whyItMatters: "Captures rationale for later review and refinement.",
272
+ critical: false
273
+ });
274
+ }
275
+ }
276
+ return { missingCritical, adaptiveFollowups };
277
+ }
@@ -0,0 +1,9 @@
1
+ export declare const PLANNING_WORKFLOW_TYPES: readonly ["task-breakdown", "sprint-phase", "task-ordering", "new-feature", "change"];
2
+ export type PlanningWorkflowType = (typeof PLANNING_WORKFLOW_TYPES)[number];
3
+ export type PlanningWorkflowDescriptor = {
4
+ type: PlanningWorkflowType;
5
+ title: string;
6
+ description: string;
7
+ outcomeFocus: string;
8
+ };
9
+ export declare const PLANNING_WORKFLOW_DESCRIPTORS: PlanningWorkflowDescriptor[];
@@ -0,0 +1,39 @@
1
+ export const PLANNING_WORKFLOW_TYPES = [
2
+ "task-breakdown",
3
+ "sprint-phase",
4
+ "task-ordering",
5
+ "new-feature",
6
+ "change"
7
+ ];
8
+ export const PLANNING_WORKFLOW_DESCRIPTORS = [
9
+ {
10
+ type: "task-breakdown",
11
+ title: "Task Breakdown",
12
+ description: "Break work into coherent, reviewable slices with explicit acceptance criteria.",
13
+ outcomeFocus: "Clear decomposition into actionable units."
14
+ },
15
+ {
16
+ type: "sprint-phase",
17
+ title: "Sprint / Phase Plan",
18
+ description: "Define phase goals, sequencing, and completion checkpoints.",
19
+ outcomeFocus: "Time-ordered delivery framing with clear phase boundaries."
20
+ },
21
+ {
22
+ type: "task-ordering",
23
+ title: "Task Ordering",
24
+ description: "Prioritize and sequence tasks with dependency awareness.",
25
+ outcomeFocus: "Deterministic ordering with explicit dependency intent."
26
+ },
27
+ {
28
+ type: "new-feature",
29
+ title: "New Feature",
30
+ description: "Guide discovery for new feature goals, architecture choices, and rollout fit.",
31
+ outcomeFocus: "High-confidence feature direction and constraints."
32
+ },
33
+ {
34
+ type: "change",
35
+ title: "Change / Refactor",
36
+ description: "Assess behavior-impacting changes and migration/compatibility risks.",
37
+ outcomeFocus: "Safe, justified change plan with risk controls."
38
+ }
39
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-cannon/workspace-kit",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": false,
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "license": "MIT",
@@ -6,13 +6,13 @@
6
6
  "type": "object",
7
7
  "required": ["schemaVersion", "packageVersion", "moduleId", "commands"],
8
8
  "additionalProperties": false,
9
- "packageVersion": "0.17.0",
9
+ "packageVersion": "0.18.0",
10
10
  "properties": {
11
11
  "schemaVersion": {
12
12
  "const": 1
13
13
  },
14
14
  "packageVersion": {
15
- "const": "0.17.0",
15
+ "const": "0.18.0",
16
16
  "description": "Must match package.json version."
17
17
  },
18
18
  "moduleId": {