@workflow-cannon/workspace-kit 0.18.0 → 0.24.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.
Files changed (140) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/doctor-planning-issues.js +3 -22
  3. package/dist/cli/run-command.js +22 -38
  4. package/dist/cli.js +95 -4
  5. package/dist/contracts/command-manifest.d.ts +17 -0
  6. package/dist/contracts/command-manifest.js +1 -0
  7. package/dist/contracts/index.d.ts +1 -1
  8. package/dist/contracts/module-contract.d.ts +12 -11
  9. package/dist/core/agent-instruction-surface.d.ts +33 -0
  10. package/dist/core/agent-instruction-surface.js +46 -0
  11. package/dist/core/config-cli.js +13 -17
  12. package/dist/core/config-metadata.js +61 -2
  13. package/dist/core/index.d.ts +4 -1
  14. package/dist/core/index.js +3 -0
  15. package/dist/core/module-command-router.js +19 -1
  16. package/dist/core/module-registry-resolve.d.ts +27 -0
  17. package/dist/core/module-registry-resolve.js +91 -0
  18. package/dist/core/module-registry.d.ts +14 -0
  19. package/dist/core/module-registry.js +57 -0
  20. package/dist/core/planning/build-plan-session-file.d.ts +29 -0
  21. package/dist/core/planning/build-plan-session-file.js +58 -0
  22. package/dist/core/planning/index.d.ts +17 -0
  23. package/dist/core/planning/index.js +15 -0
  24. package/dist/core/policy.js +18 -8
  25. package/dist/core/state/unified-state-db.d.ts +21 -0
  26. package/dist/core/state/unified-state-db.js +80 -0
  27. package/dist/core/workspace-kit-config.js +8 -0
  28. package/dist/modules/agent-behavior/builtins.d.ts +3 -0
  29. package/dist/modules/agent-behavior/builtins.js +71 -0
  30. package/dist/modules/agent-behavior/explain.d.ts +6 -0
  31. package/dist/modules/agent-behavior/explain.js +46 -0
  32. package/dist/modules/agent-behavior/index.d.ts +4 -0
  33. package/dist/modules/agent-behavior/index.js +461 -0
  34. package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
  35. package/dist/modules/agent-behavior/interview-session-file.js +43 -0
  36. package/dist/modules/agent-behavior/interview.d.ts +13 -0
  37. package/dist/modules/agent-behavior/interview.js +88 -0
  38. package/dist/modules/agent-behavior/persistence.d.ts +6 -0
  39. package/dist/modules/agent-behavior/persistence.js +89 -0
  40. package/dist/modules/agent-behavior/store.d.ts +34 -0
  41. package/dist/modules/agent-behavior/store.js +119 -0
  42. package/dist/modules/agent-behavior/types.d.ts +28 -0
  43. package/dist/modules/agent-behavior/types.js +1 -0
  44. package/dist/modules/agent-behavior/validate.d.ts +11 -0
  45. package/dist/modules/agent-behavior/validate.js +123 -0
  46. package/dist/modules/approvals/index.js +54 -51
  47. package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
  48. package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
  49. package/dist/modules/approvals/review-runtime.js +1 -2
  50. package/dist/modules/documentation/index.js +47 -45
  51. package/dist/modules/documentation/normalizer.d.ts +3 -0
  52. package/dist/modules/documentation/normalizer.js +171 -0
  53. package/dist/modules/documentation/parser.d.ts +7 -0
  54. package/dist/modules/documentation/parser.js +39 -0
  55. package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
  56. package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
  57. package/dist/modules/documentation/renderer.d.ts +23 -0
  58. package/dist/modules/documentation/renderer.js +105 -0
  59. package/dist/modules/documentation/runtime-batch.d.ts +10 -0
  60. package/dist/modules/documentation/runtime-batch.js +67 -0
  61. package/dist/modules/documentation/runtime-config.d.ts +11 -0
  62. package/dist/modules/documentation/runtime-config.js +54 -0
  63. package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
  64. package/dist/modules/documentation/runtime-render-support.js +36 -0
  65. package/dist/modules/documentation/runtime.js +22 -510
  66. package/dist/modules/documentation/types.d.ts +182 -0
  67. package/dist/modules/documentation/validator.d.ts +8 -0
  68. package/dist/modules/documentation/validator.js +234 -0
  69. package/dist/modules/documentation/view-models.d.ts +3 -0
  70. package/dist/modules/documentation/view-models.js +124 -0
  71. package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
  72. package/dist/modules/improvement/improvement-state.d.ts +2 -2
  73. package/dist/modules/improvement/improvement-state.js +52 -23
  74. package/dist/modules/improvement/index.js +140 -138
  75. package/dist/modules/improvement/ingest.d.ts +1 -1
  76. package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
  77. package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
  78. package/dist/modules/index.d.ts +6 -0
  79. package/dist/modules/index.js +17 -0
  80. package/dist/modules/planning/index.js +384 -50
  81. package/dist/modules/planning/question-engine.d.ts +2 -0
  82. package/dist/modules/planning/question-engine.js +8 -1
  83. package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
  84. package/dist/modules/task-engine/index.d.ts +1 -2
  85. package/dist/modules/task-engine/index.js +1 -1143
  86. package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
  87. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
  88. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
  89. package/dist/modules/task-engine/planning-open.d.ts +2 -9
  90. package/dist/modules/task-engine/planning-open.js +4 -15
  91. package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
  92. package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
  93. package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
  94. package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
  95. package/dist/modules/task-engine/strict-task-validation.js +3 -0
  96. package/dist/modules/task-engine/suggestions.js +2 -1
  97. package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
  98. package/dist/modules/task-engine/task-engine-internal.js +1304 -0
  99. package/dist/modules/task-engine/task-type-validation.js +40 -0
  100. package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
  101. package/dist/modules/task-engine/wishlist-intake.js +180 -0
  102. package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
  103. package/dist/modules/task-engine/wishlist-validation.js +19 -0
  104. package/dist/modules/workspace-config/index.js +9 -11
  105. package/package.json +2 -2
  106. package/schemas/agent-behavior-profile.schema.json +52 -0
  107. package/schemas/task-engine-run-contracts.schema.json +80 -5
  108. package/src/modules/documentation/README.md +16 -25
  109. package/src/modules/documentation/RULES.md +9 -9
  110. package/src/modules/documentation/index.ts +54 -49
  111. package/src/modules/documentation/instructions/document-project.md +6 -6
  112. package/src/modules/documentation/instructions/generate-document.md +4 -4
  113. package/src/modules/documentation/normalizer.ts +187 -0
  114. package/src/modules/documentation/parser.ts +41 -0
  115. package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
  116. package/src/modules/documentation/renderer.ts +121 -0
  117. package/src/modules/documentation/runtime-batch.ts +74 -0
  118. package/src/modules/documentation/runtime-config.ts +68 -0
  119. package/src/modules/documentation/runtime-render-support.ts +39 -0
  120. package/src/modules/documentation/runtime.ts +28 -600
  121. package/src/modules/documentation/schemas/documentation-schema.md +37 -54
  122. package/src/modules/documentation/types.ts +228 -0
  123. package/src/modules/documentation/validator.ts +247 -0
  124. package/src/modules/documentation/view-models.ts +132 -0
  125. package/src/modules/documentation/views/agents.view.yaml +18 -0
  126. package/src/modules/documentation/views/architecture.view.yaml +18 -0
  127. package/src/modules/documentation/views/principles.view.yaml +18 -0
  128. package/src/modules/documentation/views/readme.view.yaml +18 -0
  129. package/src/modules/documentation/views/releasing.view.yaml +18 -0
  130. package/src/modules/documentation/views/roadmap.view.yaml +18 -0
  131. package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
  132. package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
  133. package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
  134. package/src/modules/documentation/views/security.view.yaml +18 -0
  135. package/src/modules/documentation/views/support.view.yaml +18 -0
  136. package/src/modules/documentation/views/terms.view.yaml +18 -0
  137. package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
  138. package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
  139. package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
  140. package/src/modules/documentation/state.md +0 -8
@@ -1,12 +1,26 @@
1
1
  import { PLANNING_WORKFLOW_DESCRIPTORS, PLANNING_WORKFLOW_TYPES } from "./types.js";
2
2
  import { nextPlanningQuestions, resolvePlanningConfig, resolvePlanningRulePack } from "./question-engine.js";
3
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) {
4
+ import { openPlanningStores, validateKnownTaskTypeRequirements, validateWishlistContentFields, allocateNextTaskNumericId, taskEntityFromNewIntake, clearBuildPlanSession, persistBuildPlanSession } from "../../core/planning/index.js";
5
+ import { planningStrictValidationEnabled } from "../task-engine/planning-config.js";
6
+ import { validateTaskSetForStrictMode } from "../task-engine/strict-task-validation.js";
7
+ function resolveOutputMode(args) {
8
+ const raw = typeof args.outputMode === "string" ? args.outputMode.trim() : "";
9
+ if (raw === "") {
10
+ return { ok: true, mode: "wishlist" };
11
+ }
12
+ if (raw === "wishlist" || raw === "tasks" || raw === "response") {
13
+ return { ok: true, mode: raw };
14
+ }
15
+ return {
16
+ ok: false,
17
+ message: "build-plan outputMode must be one of: wishlist, tasks, response"
18
+ };
19
+ }
20
+ function nextTaskId(tasks) {
7
21
  let max = 0;
8
- for (const item of items) {
9
- const match = /^W(\d+)$/.exec(item.id);
22
+ for (const task of tasks) {
23
+ const match = /^T(\d+)$/.exec(task.id);
10
24
  if (!match)
11
25
  continue;
12
26
  const parsed = Number(match[1]);
@@ -14,10 +28,75 @@ function nextWishlistId(items) {
14
28
  max = Math.max(max, parsed);
15
29
  }
16
30
  }
17
- return `W${max + 1}`;
31
+ return `T${max + 1}`;
32
+ }
33
+ function findMissingAnsweredQuestions(questions, answers) {
34
+ return questions.filter((q) => {
35
+ const value = answers[q.id];
36
+ return !(typeof value === "string" && value.trim().length > 0);
37
+ });
38
+ }
39
+ function toNormalizedText(value) {
40
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
41
+ }
42
+ function buildScoringHints(args) {
43
+ const { planningType, answers, unresolvedCriticalCount, unresolvedAdaptiveCount } = args;
44
+ const signals = [
45
+ toNormalizedText(answers.complexity),
46
+ toNormalizedText(answers.riskPriority),
47
+ toNormalizedText(answers.timeline),
48
+ toNormalizedText(answers.compatibilityRisk)
49
+ ];
50
+ const hasSignal = signals.some((s) => s.length > 0) || unresolvedCriticalCount > 0 || unresolvedAdaptiveCount > 0;
51
+ if (!hasSignal) {
52
+ return null;
53
+ }
54
+ let riskScore = Math.min(100, unresolvedCriticalCount * 20 + unresolvedAdaptiveCount * 10);
55
+ if (signals.some((s) => s.includes("high") || s.includes("critical"))) {
56
+ riskScore = Math.min(100, riskScore + 20);
57
+ }
58
+ const effortScore = Math.min(100, 30 +
59
+ unresolvedCriticalCount * 10 +
60
+ unresolvedAdaptiveCount * 5 +
61
+ (signals.some((s) => s.includes("high")) ? 15 : 0));
62
+ const orderingScore = Math.min(100, 40 + unresolvedCriticalCount * 8 + (planningType === "task-ordering" ? 15 : 0) + (planningType === "sprint-phase" ? 10 : 0));
63
+ const classify = (score) => {
64
+ if (score >= 70)
65
+ return "high";
66
+ if (score >= 40)
67
+ return "medium";
68
+ return "low";
69
+ };
70
+ return {
71
+ schemaVersion: 1,
72
+ effort: { score: effortScore, level: classify(effortScore) },
73
+ risk: { score: riskScore, level: classify(riskScore) },
74
+ ordering: {
75
+ score: orderingScore,
76
+ level: classify(orderingScore),
77
+ recommendedStrategy: riskScore >= 70 ? "risk-first" : orderingScore >= 60 ? "dependency-first" : "balanced"
78
+ }
79
+ };
80
+ }
81
+ async function persistInterviewSnapshot(workspacePath, args) {
82
+ const cg = args.cliGuidance;
83
+ const completionPct = typeof cg.completionPct === "number" ? cg.completionPct : 0;
84
+ const answeredCritical = typeof cg.answeredCritical === "number" ? cg.answeredCritical : 0;
85
+ const totalCritical = typeof cg.totalCritical === "number" ? cg.totalCritical : 0;
86
+ const resumeCli = typeof cg.suggestedNextCommand === "string" ? cg.suggestedNextCommand : "";
87
+ await persistBuildPlanSession(workspacePath, {
88
+ planningType: args.planningType,
89
+ outputMode: args.outputMode,
90
+ status: args.status,
91
+ completionPct,
92
+ answeredCritical,
93
+ totalCritical,
94
+ answers: args.answers,
95
+ resumeCli
96
+ });
18
97
  }
19
98
  function toCliGuidance(args) {
20
- const { planningType, answers, unresolvedCriticalCount, totalCriticalCount, finalize } = args;
99
+ const { planningType, answers, unresolvedCriticalCount, totalCriticalCount, finalize, outputMode } = args;
21
100
  const answeredCritical = Math.max(0, totalCriticalCount - unresolvedCriticalCount);
22
101
  const completionPct = totalCriticalCount > 0 ? Math.round((answeredCritical / totalCriticalCount) * 100) : 100;
23
102
  return {
@@ -27,28 +106,26 @@ function toCliGuidance(args) {
27
106
  suggestedNextCommand: `workspace-kit run build-plan '${JSON.stringify({
28
107
  planningType,
29
108
  answers,
30
- finalize: finalize === true
109
+ finalize: finalize === true,
110
+ outputMode: outputMode ?? "wishlist"
31
111
  })}'`
32
112
  };
33
113
  }
34
114
  export const planningModule = {
35
115
  registration: {
36
116
  id: "planning",
37
- version: "0.1.0",
117
+ version: "0.2.0",
38
118
  contractVersion: "1",
119
+ stateSchema: 1,
39
120
  capabilities: ["planning"],
40
121
  dependsOn: ["task-engine"],
122
+ optionalPeers: [],
41
123
  enabledByDefault: true,
42
124
  config: {
43
125
  path: "src/modules/planning/config.md",
44
126
  format: "md",
45
127
  description: "Planning module configuration contract."
46
128
  },
47
- state: {
48
- path: "src/modules/planning/state.md",
49
- format: "md",
50
- description: "Planning module runtime state contract."
51
- },
52
129
  instructions: {
53
130
  directory: "src/modules/planning/instructions",
54
131
  entries: [
@@ -77,12 +154,22 @@ export const planningModule = {
77
154
  code: "planning-types-listed",
78
155
  message: `Found ${PLANNING_WORKFLOW_DESCRIPTORS.length} planning workflow types`,
79
156
  data: {
157
+ responseSchemaVersion: 1,
80
158
  planningTypes: PLANNING_WORKFLOW_DESCRIPTORS
81
159
  }
82
160
  };
83
161
  }
84
162
  if (command.name === "build-plan") {
85
163
  const args = command.args ?? {};
164
+ const outputModeResolved = resolveOutputMode(args);
165
+ if (!outputModeResolved.ok) {
166
+ return {
167
+ ok: false,
168
+ code: "invalid-planning-output-mode",
169
+ message: outputModeResolved.message
170
+ };
171
+ }
172
+ const outputMode = outputModeResolved.mode;
86
173
  const planningType = typeof args.planningType === "string" ? args.planningType.trim() : "";
87
174
  if (!PLANNING_WORKFLOW_TYPES.includes(planningType)) {
88
175
  return {
@@ -101,61 +188,141 @@ export const planningModule = {
101
188
  const finalize = args.finalize === true;
102
189
  const { missingCritical, adaptiveFollowups } = nextPlanningQuestions(planningType, answers, ctx.effectiveConfig);
103
190
  const config = resolvePlanningConfig(ctx.effectiveConfig);
191
+ const unresolvedAdaptive = findMissingAnsweredQuestions(adaptiveFollowups, answers);
192
+ const scoringHints = buildScoringHints({
193
+ planningType,
194
+ answers,
195
+ unresolvedCriticalCount: missingCritical.length,
196
+ unresolvedAdaptiveCount: unresolvedAdaptive.length
197
+ });
104
198
  if (finalize && missingCritical.length > 0) {
105
199
  if (!config.hardBlockCriticalUnknowns) {
200
+ const cliGuidance = toCliGuidance({
201
+ planningType,
202
+ answers,
203
+ unresolvedCriticalCount: missingCritical.length,
204
+ totalCriticalCount,
205
+ outputMode
206
+ });
207
+ await persistInterviewSnapshot(ctx.workspacePath, {
208
+ planningType,
209
+ outputMode,
210
+ status: "ready-with-warnings",
211
+ answers,
212
+ cliGuidance
213
+ });
106
214
  return {
107
215
  ok: true,
108
216
  code: "planning-ready-with-warnings",
109
217
  message: `Finalize allowed with unresolved critical questions because planning.hardBlockCriticalUnknowns=false`,
110
218
  data: {
219
+ responseSchemaVersion: 1,
111
220
  planningType,
221
+ outputMode,
112
222
  status: "ready-with-warnings",
113
223
  unresolvedCritical: missingCritical,
114
224
  nextQuestions: [...missingCritical, ...adaptiveFollowups],
225
+ scoringHints,
115
226
  capturedAnswers: answers,
116
- cliGuidance: toCliGuidance({
117
- planningType,
118
- answers,
119
- unresolvedCriticalCount: missingCritical.length,
120
- totalCriticalCount
121
- })
227
+ cliGuidance
122
228
  }
123
229
  };
124
230
  }
231
+ const cliGuidanceBlocked = toCliGuidance({
232
+ planningType,
233
+ answers,
234
+ unresolvedCriticalCount: missingCritical.length,
235
+ totalCriticalCount,
236
+ finalize: true,
237
+ outputMode
238
+ });
239
+ await persistInterviewSnapshot(ctx.workspacePath, {
240
+ planningType,
241
+ outputMode,
242
+ status: "blocked-critical-unknowns",
243
+ answers,
244
+ cliGuidance: cliGuidanceBlocked
245
+ });
125
246
  return {
126
247
  ok: false,
127
248
  code: "planning-critical-unknowns",
128
249
  message: `Cannot finalize ${planningType}: unresolved critical questions (${missingCritical.map((q) => q.id).join(", ")})`,
129
250
  data: {
251
+ responseSchemaVersion: 1,
130
252
  planningType,
253
+ outputMode,
131
254
  unresolvedCritical: missingCritical,
132
255
  nextQuestions: [...missingCritical, ...adaptiveFollowups],
133
- cliGuidance: toCliGuidance({
134
- planningType,
135
- answers,
136
- unresolvedCriticalCount: missingCritical.length,
137
- totalCriticalCount,
138
- finalize: true
139
- })
256
+ scoringHints,
257
+ cliGuidance: cliGuidanceBlocked
140
258
  }
141
259
  };
142
260
  }
261
+ if (finalize && unresolvedAdaptive.length > 0 && config.adaptiveFinalizePolicy === "block") {
262
+ const cliGuidanceAdaptive = toCliGuidance({
263
+ planningType,
264
+ answers,
265
+ unresolvedCriticalCount: 0,
266
+ totalCriticalCount,
267
+ finalize: true,
268
+ outputMode
269
+ });
270
+ await persistInterviewSnapshot(ctx.workspacePath, {
271
+ planningType,
272
+ outputMode,
273
+ status: "blocked-adaptive-unknowns",
274
+ answers,
275
+ cliGuidance: cliGuidanceAdaptive
276
+ });
277
+ return {
278
+ ok: false,
279
+ code: "planning-adaptive-unknowns",
280
+ message: `Cannot finalize ${planningType}: unresolved adaptive follow-ups (${unresolvedAdaptive
281
+ .map((q) => q.id)
282
+ .join(", ")})`,
283
+ data: {
284
+ responseSchemaVersion: 1,
285
+ planningType,
286
+ outputMode,
287
+ unresolvedAdaptive,
288
+ unresolvedCritical: [],
289
+ nextQuestions: unresolvedAdaptive,
290
+ scoringHints,
291
+ cliGuidance: cliGuidanceAdaptive
292
+ }
293
+ };
294
+ }
295
+ const adaptiveWarnings = finalize && unresolvedAdaptive.length > 0 && config.adaptiveFinalizePolicy === "warn"
296
+ ? unresolvedAdaptive
297
+ : [];
143
298
  if (missingCritical.length > 0) {
299
+ const cliGuidanceQuestions = toCliGuidance({
300
+ planningType,
301
+ answers,
302
+ unresolvedCriticalCount: missingCritical.length,
303
+ totalCriticalCount,
304
+ outputMode
305
+ });
306
+ await persistInterviewSnapshot(ctx.workspacePath, {
307
+ planningType,
308
+ outputMode,
309
+ status: "needs-input",
310
+ answers,
311
+ cliGuidance: cliGuidanceQuestions
312
+ });
144
313
  return {
145
314
  ok: true,
146
315
  code: "planning-questions",
147
316
  message: `${missingCritical.length} critical planning questions require answers before finalize`,
148
317
  data: {
318
+ responseSchemaVersion: 1,
149
319
  planningType,
320
+ outputMode,
150
321
  status: "needs-input",
151
322
  unresolvedCritical: missingCritical,
152
323
  nextQuestions: [...missingCritical, ...adaptiveFollowups],
153
- cliGuidance: toCliGuidance({
154
- planningType,
155
- answers,
156
- unresolvedCriticalCount: missingCritical.length,
157
- totalCriticalCount
158
- })
324
+ scoringHints,
325
+ cliGuidance: cliGuidanceQuestions
159
326
  }
160
327
  };
161
328
  }
@@ -165,18 +332,156 @@ export const planningModule = {
165
332
  answers,
166
333
  unresolvedCriticalQuestionIds: unresolvedIds
167
334
  });
335
+ if (outputMode === "response") {
336
+ await clearBuildPlanSession(ctx.workspacePath);
337
+ return {
338
+ ok: true,
339
+ code: "planning-response-ready",
340
+ message: `Planning interview complete for ${planningType}; returning response-only artifact`,
341
+ data: {
342
+ responseSchemaVersion: 1,
343
+ planningType,
344
+ descriptor,
345
+ outputMode,
346
+ scaffoldVersion: 3,
347
+ status: "ready-for-response",
348
+ unresolvedCritical: [],
349
+ adaptiveWarnings,
350
+ adaptiveFollowups,
351
+ scoringHints,
352
+ capturedAnswers: answers,
353
+ artifact,
354
+ cliGuidance: toCliGuidance({
355
+ planningType,
356
+ answers,
357
+ unresolvedCriticalCount: 0,
358
+ totalCriticalCount,
359
+ finalize,
360
+ outputMode
361
+ })
362
+ }
363
+ };
364
+ }
365
+ if (outputMode === "tasks") {
366
+ const persistTasks = args.persistTasks === true;
367
+ const taskType = typeof args.taskType === "string" && args.taskType.trim().length > 0
368
+ ? args.taskType.trim()
369
+ : "task";
370
+ const taskPriority = typeof args.taskPriority === "string" && ["P1", "P2", "P3"].includes(args.taskPriority)
371
+ ? args.taskPriority
372
+ : undefined;
373
+ const stores = await openPlanningStores(ctx);
374
+ const store = stores.taskStore;
375
+ const plannedTaskId = nextTaskId(store.getAllTasks());
376
+ const planRef = `planning:${planningType}:${new Date().toISOString()}`;
377
+ const scopeFromArtifact = artifact.candidateFeaturesOrChanges.length > 0
378
+ ? artifact.candidateFeaturesOrChanges
379
+ : artifact.goals;
380
+ const criteriaFromSignals = typeof answers.successSignals === "string" && answers.successSignals.trim().length > 0
381
+ ? [answers.successSignals.trim()]
382
+ : [];
383
+ const task = {
384
+ id: plannedTaskId,
385
+ title: artifact.goals[0] && artifact.goals[0].trim().length > 0
386
+ ? artifact.goals[0].trim()
387
+ : `${descriptor?.title ?? planningType} task output`,
388
+ type: taskType,
389
+ status: "proposed",
390
+ createdAt: new Date().toISOString(),
391
+ updatedAt: new Date().toISOString(),
392
+ priority: taskPriority,
393
+ phase: typeof args.taskPhase === "string" ? args.taskPhase : undefined,
394
+ approach: typeof answers.approach === "string" && answers.approach.trim().length > 0
395
+ ? answers.approach
396
+ : artifact.approach,
397
+ technicalScope: scopeFromArtifact.length > 0 ? scopeFromArtifact : undefined,
398
+ acceptanceCriteria: criteriaFromSignals.length > 0
399
+ ? criteriaFromSignals
400
+ : ["Task output reviewed and refined from planning artifact."],
401
+ metadata: {
402
+ planRef,
403
+ planningProvenance: {
404
+ planningType,
405
+ outputMode,
406
+ capturedAnswerKeys: Object.keys(artifact.sourceAnswers).sort()
407
+ }
408
+ }
409
+ };
410
+ const knownTypeValidationError = validateKnownTaskTypeRequirements(task);
411
+ if (knownTypeValidationError) {
412
+ return {
413
+ ok: false,
414
+ code: knownTypeValidationError.code,
415
+ message: knownTypeValidationError.message
416
+ };
417
+ }
418
+ if (persistTasks) {
419
+ if (store.getTask(task.id)) {
420
+ return {
421
+ ok: false,
422
+ code: "duplicate-task-id",
423
+ message: `Task '${task.id}' already exists`
424
+ };
425
+ }
426
+ store.addTask(task);
427
+ await store.save();
428
+ }
429
+ await clearBuildPlanSession(ctx.workspacePath);
430
+ return {
431
+ ok: true,
432
+ code: persistTasks ? "planning-task-output-created" : "planning-task-output-preview",
433
+ message: persistTasks
434
+ ? `Planning task output persisted as '${task.id}'`
435
+ : "Planning task output prepared (preview only; set persistTasks=true to write)",
436
+ data: {
437
+ responseSchemaVersion: 1,
438
+ planningType,
439
+ descriptor,
440
+ outputMode,
441
+ persistTasks,
442
+ scaffoldVersion: 3,
443
+ status: persistTasks ? "task-output-created" : "task-output-preview",
444
+ unresolvedCritical: [],
445
+ adaptiveWarnings,
446
+ adaptiveFollowups,
447
+ scoringHints,
448
+ capturedAnswers: answers,
449
+ artifact,
450
+ taskOutputs: [task],
451
+ provenance: {
452
+ planRef,
453
+ outputMode,
454
+ persistedTaskIds: persistTasks ? [task.id] : [],
455
+ suggestedTaskIds: [task.id]
456
+ },
457
+ cliGuidance: toCliGuidance({
458
+ planningType,
459
+ answers,
460
+ unresolvedCriticalCount: 0,
461
+ totalCriticalCount,
462
+ finalize: true,
463
+ outputMode
464
+ })
465
+ }
466
+ };
467
+ }
168
468
  if (!finalize || !createWishlist) {
469
+ await clearBuildPlanSession(ctx.workspacePath);
169
470
  return {
170
471
  ok: true,
171
- code: "planning-ready",
172
- message: `Planning interview complete for ${planningType}; artifact ready`,
472
+ code: "planning-wishlist-ready",
473
+ message: `Planning interview complete for ${planningType}; wishlist artifact ready`,
173
474
  data: {
475
+ responseSchemaVersion: 1,
174
476
  planningType,
175
477
  descriptor,
478
+ outputMode,
176
479
  scaffoldVersion: 3,
177
- status: "ready-for-artifact",
480
+ status: "ready-for-wishlist",
178
481
  unresolvedCritical: [],
482
+ adaptiveWarnings,
179
483
  adaptiveFollowups,
484
+ scoringHints,
180
485
  capturedAnswers: answers,
181
486
  artifact,
182
487
  cliGuidance: toCliGuidance({
@@ -184,17 +489,15 @@ export const planningModule = {
184
489
  answers,
185
490
  unresolvedCriticalCount: 0,
186
491
  totalCriticalCount,
187
- finalize: createWishlist
492
+ finalize: createWishlist,
493
+ outputMode
188
494
  })
189
495
  }
190
496
  };
191
497
  }
192
498
  const stores = await openPlanningStores(ctx);
193
- const wishlist = await stores.openWishlist();
194
- const wishlistId = nextWishlistId(wishlist.getAllItems());
195
499
  const now = new Date().toISOString();
196
500
  const intake = {
197
- id: wishlistId,
198
501
  title: typeof args.title === "string" && args.title.trim().length > 0
199
502
  ? args.title.trim()
200
503
  : `${descriptor?.title ?? planningType} plan artifact`,
@@ -220,7 +523,7 @@ export const planningModule = {
220
523
  ? args.evidenceRef.trim()
221
524
  : `planning:${planningType}:${now}`
222
525
  };
223
- const valid = validateWishlistIntakePayload(intake);
526
+ const valid = validateWishlistContentFields(intake);
224
527
  if (!valid.ok) {
225
528
  return {
226
529
  ok: false,
@@ -228,35 +531,64 @@ export const planningModule = {
228
531
  message: valid.errors.join("; ")
229
532
  };
230
533
  }
231
- const item = buildWishlistItemFromIntake(intake, now);
232
- item.updatedAt = now;
233
- item.metadata = {
534
+ const taskId = allocateNextTaskNumericId(stores.taskStore.getAllTasks());
535
+ const task = taskEntityFromNewIntake(intake, taskId, now, {
234
536
  planningType,
235
537
  artifactSchemaVersion: artifact.schemaVersion,
236
538
  artifact
237
- };
238
- wishlist.addItem(item);
239
- await wishlist.save();
539
+ });
540
+ const typeErr = validateKnownTaskTypeRequirements(task);
541
+ if (typeErr) {
542
+ return { ok: false, code: typeErr.code, message: typeErr.message };
543
+ }
544
+ if (planningStrictValidationEnabled({ effectiveConfig: ctx.effectiveConfig })) {
545
+ const strictIssue = validateTaskSetForStrictMode([...stores.taskStore.getAllTasks(), task]);
546
+ if (strictIssue) {
547
+ return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
548
+ }
549
+ }
550
+ try {
551
+ if (stores.kind === "sqlite") {
552
+ stores.sqliteDual.withTransaction(() => {
553
+ stores.taskStore.addTask(task);
554
+ });
555
+ }
556
+ else {
557
+ stores.taskStore.addTask(task);
558
+ await stores.taskStore.save();
559
+ }
560
+ }
561
+ catch (e) {
562
+ const msg = e instanceof Error ? e.message : String(e);
563
+ return { ok: false, code: "invalid-planning-artifact", message: msg };
564
+ }
565
+ await clearBuildPlanSession(ctx.workspacePath);
240
566
  return {
241
567
  ok: true,
242
568
  code: "planning-artifact-created",
243
- message: `Planning artifact created as wishlist item ${wishlistId}`,
569
+ message: `Planning artifact created as wishlist intake task ${taskId}`,
244
570
  data: {
571
+ responseSchemaVersion: 1,
245
572
  planningType,
246
573
  descriptor,
574
+ outputMode,
247
575
  scaffoldVersion: 3,
248
576
  status: "artifact-created",
249
- wishlistId,
577
+ wishlistId: taskId,
578
+ taskId,
579
+ adaptiveWarnings,
250
580
  artifact,
251
581
  unresolvedCritical: [],
252
582
  adaptiveFollowups,
583
+ scoringHints,
253
584
  capturedAnswers: answers,
254
585
  cliGuidance: toCliGuidance({
255
586
  planningType,
256
587
  answers,
257
588
  unresolvedCriticalCount: 0,
258
589
  totalCriticalCount,
259
- finalize: true
590
+ finalize: true,
591
+ outputMode
260
592
  })
261
593
  }
262
594
  };
@@ -278,9 +610,11 @@ export const planningModule = {
278
610
  code: "planning-rules-explained",
279
611
  message: `Effective planning rules for ${planningType}`,
280
612
  data: {
613
+ responseSchemaVersion: 1,
281
614
  planningType,
282
615
  defaultQuestionDepth: config.depth,
283
616
  hardBlockCriticalUnknowns: config.hardBlockCriticalUnknowns,
617
+ adaptiveFinalizePolicy: config.adaptiveFinalizePolicy,
284
618
  baseQuestions: rulePack.baseQuestions,
285
619
  adaptiveQuestions: rulePack.adaptiveQuestions
286
620
  }
@@ -7,6 +7,7 @@ export type PlanningQuestion = {
7
7
  critical: boolean;
8
8
  };
9
9
  export type PlanningQuestionDepth = "minimal" | "guided" | "adaptive";
10
+ export type AdaptiveFinalizePolicy = "off" | "warn" | "block";
10
11
  export type PlanningRulePack = {
11
12
  baseQuestions: PlanningQuestion[];
12
13
  adaptiveQuestions: PlanningQuestion[];
@@ -14,6 +15,7 @@ export type PlanningRulePack = {
14
15
  export declare function resolvePlanningConfig(config: Record<string, unknown> | undefined): {
15
16
  depth: PlanningQuestionDepth;
16
17
  hardBlockCriticalUnknowns: boolean;
18
+ adaptiveFinalizePolicy: AdaptiveFinalizePolicy;
17
19
  rulePacks: Partial<Record<PlanningWorkflowType, PlanningRulePack>>;
18
20
  };
19
21
  export declare function resolvePlanningRulePack(planningType: PlanningWorkflowType, config: Record<string, unknown> | undefined): PlanningRulePack;
@@ -206,10 +206,17 @@ function parseDepth(value) {
206
206
  }
207
207
  return "adaptive";
208
208
  }
209
+ function parseAdaptiveFinalizePolicy(value) {
210
+ if (value === "off" || value === "warn" || value === "block") {
211
+ return value;
212
+ }
213
+ return "off";
214
+ }
209
215
  export function resolvePlanningConfig(config) {
210
216
  const planning = asRecord(config?.planning);
211
217
  const depth = parseDepth(planning?.defaultQuestionDepth);
212
218
  const hardBlockCriticalUnknowns = planning?.hardBlockCriticalUnknowns !== false;
219
+ const adaptiveFinalizePolicy = parseAdaptiveFinalizePolicy(planning?.adaptiveFinalizePolicy);
213
220
  const rulesRoot = asRecord(planning?.rulePacks);
214
221
  const rulePacks = {};
215
222
  if (rulesRoot) {
@@ -227,7 +234,7 @@ export function resolvePlanningConfig(config) {
227
234
  };
228
235
  }
229
236
  }
230
- return { depth, hardBlockCriticalUnknowns, rulePacks };
237
+ return { depth, hardBlockCriticalUnknowns, adaptiveFinalizePolicy, rulePacks };
231
238
  }
232
239
  export function resolvePlanningRulePack(planningType, config) {
233
240
  const resolved = resolvePlanningConfig(config);