@workflow-cannon/workspace-kit 0.17.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.
- package/README.md +23 -9
- package/dist/cli/doctor-planning-issues.js +3 -22
- package/dist/cli/run-command.js +22 -38
- package/dist/cli.js +95 -4
- package/dist/contracts/command-manifest.d.ts +17 -0
- package/dist/contracts/command-manifest.js +1 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +12 -11
- package/dist/core/agent-instruction-surface.d.ts +33 -0
- package/dist/core/agent-instruction-surface.js +46 -0
- package/dist/core/config-cli.js +13 -17
- package/dist/core/config-metadata.js +101 -2
- package/dist/core/index.d.ts +4 -1
- package/dist/core/index.js +3 -0
- package/dist/core/module-command-router.js +19 -1
- package/dist/core/module-registry-resolve.d.ts +27 -0
- package/dist/core/module-registry-resolve.js +91 -0
- package/dist/core/module-registry.d.ts +14 -0
- package/dist/core/module-registry.js +57 -0
- package/dist/core/planning/build-plan-session-file.d.ts +29 -0
- package/dist/core/planning/build-plan-session-file.js +58 -0
- package/dist/core/planning/index.d.ts +17 -0
- package/dist/core/planning/index.js +15 -0
- package/dist/core/policy.js +18 -8
- package/dist/core/state/unified-state-db.d.ts +21 -0
- package/dist/core/state/unified-state-db.js +80 -0
- package/dist/core/workspace-kit-config.js +13 -1
- package/dist/modules/agent-behavior/builtins.d.ts +3 -0
- package/dist/modules/agent-behavior/builtins.js +71 -0
- package/dist/modules/agent-behavior/explain.d.ts +6 -0
- package/dist/modules/agent-behavior/explain.js +46 -0
- package/dist/modules/agent-behavior/index.d.ts +4 -0
- package/dist/modules/agent-behavior/index.js +461 -0
- package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
- package/dist/modules/agent-behavior/interview-session-file.js +43 -0
- package/dist/modules/agent-behavior/interview.d.ts +13 -0
- package/dist/modules/agent-behavior/interview.js +88 -0
- package/dist/modules/agent-behavior/persistence.d.ts +6 -0
- package/dist/modules/agent-behavior/persistence.js +89 -0
- package/dist/modules/agent-behavior/store.d.ts +34 -0
- package/dist/modules/agent-behavior/store.js +119 -0
- package/dist/modules/agent-behavior/types.d.ts +28 -0
- package/dist/modules/agent-behavior/types.js +1 -0
- package/dist/modules/agent-behavior/validate.d.ts +11 -0
- package/dist/modules/agent-behavior/validate.js +123 -0
- package/dist/modules/approvals/index.js +54 -51
- package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
- package/dist/modules/approvals/review-runtime.js +1 -2
- package/dist/modules/documentation/index.js +47 -45
- package/dist/modules/documentation/normalizer.d.ts +3 -0
- package/dist/modules/documentation/normalizer.js +171 -0
- package/dist/modules/documentation/parser.d.ts +7 -0
- package/dist/modules/documentation/parser.js +39 -0
- package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
- package/dist/modules/documentation/renderer.d.ts +23 -0
- package/dist/modules/documentation/renderer.js +105 -0
- package/dist/modules/documentation/runtime-batch.d.ts +10 -0
- package/dist/modules/documentation/runtime-batch.js +67 -0
- package/dist/modules/documentation/runtime-config.d.ts +11 -0
- package/dist/modules/documentation/runtime-config.js +54 -0
- package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
- package/dist/modules/documentation/runtime-render-support.js +36 -0
- package/dist/modules/documentation/runtime.js +22 -510
- package/dist/modules/documentation/types.d.ts +182 -0
- package/dist/modules/documentation/validator.d.ts +8 -0
- package/dist/modules/documentation/validator.js +234 -0
- package/dist/modules/documentation/view-models.d.ts +3 -0
- package/dist/modules/documentation/view-models.js +124 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
- package/dist/modules/improvement/improvement-state.d.ts +2 -2
- package/dist/modules/improvement/improvement-state.js +52 -23
- package/dist/modules/improvement/index.js +140 -138
- package/dist/modules/improvement/ingest.d.ts +1 -1
- package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules/index.js +17 -0
- package/dist/modules/planning/artifact.d.ts +19 -0
- package/dist/modules/planning/artifact.js +72 -0
- package/dist/modules/planning/index.js +605 -6
- package/dist/modules/planning/question-engine.d.ts +25 -0
- package/dist/modules/planning/question-engine.js +284 -0
- package/dist/modules/planning/types.d.ts +9 -0
- package/dist/modules/planning/types.js +39 -0
- package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
- package/dist/modules/task-engine/index.d.ts +1 -2
- package/dist/modules/task-engine/index.js +1 -1143
- package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
- package/dist/modules/task-engine/planning-open.d.ts +2 -9
- package/dist/modules/task-engine/planning-open.js +4 -15
- package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
- package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
- package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
- package/dist/modules/task-engine/strict-task-validation.js +3 -0
- package/dist/modules/task-engine/suggestions.js +2 -1
- package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
- package/dist/modules/task-engine/task-engine-internal.js +1304 -0
- package/dist/modules/task-engine/task-type-validation.js +40 -0
- package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
- package/dist/modules/task-engine/wishlist-intake.js +180 -0
- package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
- package/dist/modules/task-engine/wishlist-validation.js +19 -0
- package/dist/modules/workspace-config/index.js +9 -11
- package/package.json +2 -2
- package/schemas/agent-behavior-profile.schema.json +52 -0
- package/schemas/task-engine-run-contracts.schema.json +80 -5
- package/src/modules/documentation/README.md +16 -25
- package/src/modules/documentation/RULES.md +9 -9
- package/src/modules/documentation/index.ts +54 -49
- package/src/modules/documentation/instructions/document-project.md +6 -6
- package/src/modules/documentation/instructions/generate-document.md +4 -4
- package/src/modules/documentation/normalizer.ts +187 -0
- package/src/modules/documentation/parser.ts +41 -0
- package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
- package/src/modules/documentation/renderer.ts +121 -0
- package/src/modules/documentation/runtime-batch.ts +74 -0
- package/src/modules/documentation/runtime-config.ts +68 -0
- package/src/modules/documentation/runtime-render-support.ts +39 -0
- package/src/modules/documentation/runtime.ts +28 -600
- package/src/modules/documentation/schemas/documentation-schema.md +37 -54
- package/src/modules/documentation/types.ts +228 -0
- package/src/modules/documentation/validator.ts +247 -0
- package/src/modules/documentation/view-models.ts +132 -0
- package/src/modules/documentation/views/agents.view.yaml +18 -0
- package/src/modules/documentation/views/architecture.view.yaml +18 -0
- package/src/modules/documentation/views/principles.view.yaml +18 -0
- package/src/modules/documentation/views/readme.view.yaml +18 -0
- package/src/modules/documentation/views/releasing.view.yaml +18 -0
- package/src/modules/documentation/views/roadmap.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
- package/src/modules/documentation/views/security.view.yaml +18 -0
- package/src/modules/documentation/views/support.view.yaml +18 -0
- package/src/modules/documentation/views/terms.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
- package/src/modules/documentation/state.md +0 -8
|
@@ -1,21 +1,131 @@
|
|
|
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, 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) {
|
|
21
|
+
let max = 0;
|
|
22
|
+
for (const task of tasks) {
|
|
23
|
+
const match = /^T(\d+)$/.exec(task.id);
|
|
24
|
+
if (!match)
|
|
25
|
+
continue;
|
|
26
|
+
const parsed = Number(match[1]);
|
|
27
|
+
if (Number.isFinite(parsed)) {
|
|
28
|
+
max = Math.max(max, parsed);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
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
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function toCliGuidance(args) {
|
|
99
|
+
const { planningType, answers, unresolvedCriticalCount, totalCriticalCount, finalize, outputMode } = args;
|
|
100
|
+
const answeredCritical = Math.max(0, totalCriticalCount - unresolvedCriticalCount);
|
|
101
|
+
const completionPct = totalCriticalCount > 0 ? Math.round((answeredCritical / totalCriticalCount) * 100) : 100;
|
|
102
|
+
return {
|
|
103
|
+
answeredCritical,
|
|
104
|
+
totalCritical: totalCriticalCount,
|
|
105
|
+
completionPct,
|
|
106
|
+
suggestedNextCommand: `workspace-kit run build-plan '${JSON.stringify({
|
|
107
|
+
planningType,
|
|
108
|
+
answers,
|
|
109
|
+
finalize: finalize === true,
|
|
110
|
+
outputMode: outputMode ?? "wishlist"
|
|
111
|
+
})}'`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
1
114
|
export const planningModule = {
|
|
2
115
|
registration: {
|
|
3
116
|
id: "planning",
|
|
4
|
-
version: "0.
|
|
117
|
+
version: "0.2.0",
|
|
5
118
|
contractVersion: "1",
|
|
119
|
+
stateSchema: 1,
|
|
6
120
|
capabilities: ["planning"],
|
|
7
121
|
dependsOn: ["task-engine"],
|
|
122
|
+
optionalPeers: [],
|
|
8
123
|
enabledByDefault: true,
|
|
9
124
|
config: {
|
|
10
125
|
path: "src/modules/planning/config.md",
|
|
11
126
|
format: "md",
|
|
12
127
|
description: "Planning module configuration contract."
|
|
13
128
|
},
|
|
14
|
-
state: {
|
|
15
|
-
path: "src/modules/planning/state.md",
|
|
16
|
-
format: "md",
|
|
17
|
-
description: "Planning module runtime state contract."
|
|
18
|
-
},
|
|
19
129
|
instructions: {
|
|
20
130
|
directory: "src/modules/planning/instructions",
|
|
21
131
|
entries: [
|
|
@@ -23,8 +133,497 @@ export const planningModule = {
|
|
|
23
133
|
name: "build-plan",
|
|
24
134
|
file: "build-plan.md",
|
|
25
135
|
description: "Generate a dependency-aware execution plan."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "list-planning-types",
|
|
139
|
+
file: "list-planning-types.md",
|
|
140
|
+
description: "List supported planning workflow types and their intent."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "explain-planning-rules",
|
|
144
|
+
file: "explain-planning-rules.md",
|
|
145
|
+
description: "Explain effective planning defaults and rule packs for a workflow type."
|
|
26
146
|
}
|
|
27
147
|
]
|
|
28
148
|
}
|
|
149
|
+
},
|
|
150
|
+
async onCommand(command, ctx) {
|
|
151
|
+
if (command.name === "list-planning-types") {
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
code: "planning-types-listed",
|
|
155
|
+
message: `Found ${PLANNING_WORKFLOW_DESCRIPTORS.length} planning workflow types`,
|
|
156
|
+
data: {
|
|
157
|
+
responseSchemaVersion: 1,
|
|
158
|
+
planningTypes: PLANNING_WORKFLOW_DESCRIPTORS
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (command.name === "build-plan") {
|
|
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;
|
|
173
|
+
const planningType = typeof args.planningType === "string" ? args.planningType.trim() : "";
|
|
174
|
+
if (!PLANNING_WORKFLOW_TYPES.includes(planningType)) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
code: "invalid-planning-type",
|
|
178
|
+
message: "build-plan requires planningType of: task-breakdown, sprint-phase, task-ordering, new-feature, change"
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const descriptor = PLANNING_WORKFLOW_DESCRIPTORS.find((x) => x.type === planningType);
|
|
182
|
+
const resolvedRulePack = resolvePlanningRulePack(planningType, ctx.effectiveConfig);
|
|
183
|
+
const totalCriticalCount = resolvedRulePack.baseQuestions.length;
|
|
184
|
+
const answers = typeof args.answers === "object" && args.answers !== null && !Array.isArray(args.answers)
|
|
185
|
+
? args.answers
|
|
186
|
+
: {};
|
|
187
|
+
const createWishlist = args.createWishlist !== false;
|
|
188
|
+
const finalize = args.finalize === true;
|
|
189
|
+
const { missingCritical, adaptiveFollowups } = nextPlanningQuestions(planningType, answers, ctx.effectiveConfig);
|
|
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
|
+
});
|
|
198
|
+
if (finalize && missingCritical.length > 0) {
|
|
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
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
ok: true,
|
|
216
|
+
code: "planning-ready-with-warnings",
|
|
217
|
+
message: `Finalize allowed with unresolved critical questions because planning.hardBlockCriticalUnknowns=false`,
|
|
218
|
+
data: {
|
|
219
|
+
responseSchemaVersion: 1,
|
|
220
|
+
planningType,
|
|
221
|
+
outputMode,
|
|
222
|
+
status: "ready-with-warnings",
|
|
223
|
+
unresolvedCritical: missingCritical,
|
|
224
|
+
nextQuestions: [...missingCritical, ...adaptiveFollowups],
|
|
225
|
+
scoringHints,
|
|
226
|
+
capturedAnswers: answers,
|
|
227
|
+
cliGuidance
|
|
228
|
+
}
|
|
229
|
+
};
|
|
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
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
code: "planning-critical-unknowns",
|
|
249
|
+
message: `Cannot finalize ${planningType}: unresolved critical questions (${missingCritical.map((q) => q.id).join(", ")})`,
|
|
250
|
+
data: {
|
|
251
|
+
responseSchemaVersion: 1,
|
|
252
|
+
planningType,
|
|
253
|
+
outputMode,
|
|
254
|
+
unresolvedCritical: missingCritical,
|
|
255
|
+
nextQuestions: [...missingCritical, ...adaptiveFollowups],
|
|
256
|
+
scoringHints,
|
|
257
|
+
cliGuidance: cliGuidanceBlocked
|
|
258
|
+
}
|
|
259
|
+
};
|
|
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
|
+
: [];
|
|
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
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
ok: true,
|
|
315
|
+
code: "planning-questions",
|
|
316
|
+
message: `${missingCritical.length} critical planning questions require answers before finalize`,
|
|
317
|
+
data: {
|
|
318
|
+
responseSchemaVersion: 1,
|
|
319
|
+
planningType,
|
|
320
|
+
outputMode,
|
|
321
|
+
status: "needs-input",
|
|
322
|
+
unresolvedCritical: missingCritical,
|
|
323
|
+
nextQuestions: [...missingCritical, ...adaptiveFollowups],
|
|
324
|
+
scoringHints,
|
|
325
|
+
cliGuidance: cliGuidanceQuestions
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const unresolvedIds = missingCritical.map((q) => q.id);
|
|
330
|
+
const artifact = composePlanningWishlistArtifact({
|
|
331
|
+
planningType: planningType,
|
|
332
|
+
answers,
|
|
333
|
+
unresolvedCriticalQuestionIds: unresolvedIds
|
|
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
|
+
}
|
|
468
|
+
if (!finalize || !createWishlist) {
|
|
469
|
+
await clearBuildPlanSession(ctx.workspacePath);
|
|
470
|
+
return {
|
|
471
|
+
ok: true,
|
|
472
|
+
code: "planning-wishlist-ready",
|
|
473
|
+
message: `Planning interview complete for ${planningType}; wishlist artifact ready`,
|
|
474
|
+
data: {
|
|
475
|
+
responseSchemaVersion: 1,
|
|
476
|
+
planningType,
|
|
477
|
+
descriptor,
|
|
478
|
+
outputMode,
|
|
479
|
+
scaffoldVersion: 3,
|
|
480
|
+
status: "ready-for-wishlist",
|
|
481
|
+
unresolvedCritical: [],
|
|
482
|
+
adaptiveWarnings,
|
|
483
|
+
adaptiveFollowups,
|
|
484
|
+
scoringHints,
|
|
485
|
+
capturedAnswers: answers,
|
|
486
|
+
artifact,
|
|
487
|
+
cliGuidance: toCliGuidance({
|
|
488
|
+
planningType,
|
|
489
|
+
answers,
|
|
490
|
+
unresolvedCriticalCount: 0,
|
|
491
|
+
totalCriticalCount,
|
|
492
|
+
finalize: createWishlist,
|
|
493
|
+
outputMode
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const stores = await openPlanningStores(ctx);
|
|
499
|
+
const now = new Date().toISOString();
|
|
500
|
+
const intake = {
|
|
501
|
+
title: typeof args.title === "string" && args.title.trim().length > 0
|
|
502
|
+
? args.title.trim()
|
|
503
|
+
: `${descriptor?.title ?? planningType} plan artifact`,
|
|
504
|
+
problemStatement: typeof answers.problemStatement === "string"
|
|
505
|
+
? answers.problemStatement
|
|
506
|
+
: typeof answers.featureGoal === "string"
|
|
507
|
+
? answers.featureGoal
|
|
508
|
+
: "Planning artifact generated from guided workflow.",
|
|
509
|
+
expectedOutcome: typeof answers.expectedOutcome === "string"
|
|
510
|
+
? answers.expectedOutcome
|
|
511
|
+
: "Clear, reviewable planning artifact for execution decomposition.",
|
|
512
|
+
impact: typeof answers.impact === "string" ? answers.impact : "Improved planning quality and delivery confidence.",
|
|
513
|
+
constraints: typeof answers.constraints === "string"
|
|
514
|
+
? answers.constraints
|
|
515
|
+
: artifact.risksAndConstraints.join("; ") || "None explicitly provided.",
|
|
516
|
+
successSignals: typeof answers.successSignals === "string"
|
|
517
|
+
? answers.successSignals
|
|
518
|
+
: "Critical questions answered and artifact accepted by operators.",
|
|
519
|
+
requestor: typeof args.requestor === "string" && args.requestor.trim().length > 0
|
|
520
|
+
? args.requestor.trim()
|
|
521
|
+
: ctx.resolvedActor ?? "planning-module",
|
|
522
|
+
evidenceRef: typeof args.evidenceRef === "string" && args.evidenceRef.trim().length > 0
|
|
523
|
+
? args.evidenceRef.trim()
|
|
524
|
+
: `planning:${planningType}:${now}`
|
|
525
|
+
};
|
|
526
|
+
const valid = validateWishlistContentFields(intake);
|
|
527
|
+
if (!valid.ok) {
|
|
528
|
+
return {
|
|
529
|
+
ok: false,
|
|
530
|
+
code: "invalid-planning-artifact",
|
|
531
|
+
message: valid.errors.join("; ")
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const taskId = allocateNextTaskNumericId(stores.taskStore.getAllTasks());
|
|
535
|
+
const task = taskEntityFromNewIntake(intake, taskId, now, {
|
|
536
|
+
planningType,
|
|
537
|
+
artifactSchemaVersion: artifact.schemaVersion,
|
|
538
|
+
artifact
|
|
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);
|
|
566
|
+
return {
|
|
567
|
+
ok: true,
|
|
568
|
+
code: "planning-artifact-created",
|
|
569
|
+
message: `Planning artifact created as wishlist intake task ${taskId}`,
|
|
570
|
+
data: {
|
|
571
|
+
responseSchemaVersion: 1,
|
|
572
|
+
planningType,
|
|
573
|
+
descriptor,
|
|
574
|
+
outputMode,
|
|
575
|
+
scaffoldVersion: 3,
|
|
576
|
+
status: "artifact-created",
|
|
577
|
+
wishlistId: taskId,
|
|
578
|
+
taskId,
|
|
579
|
+
adaptiveWarnings,
|
|
580
|
+
artifact,
|
|
581
|
+
unresolvedCritical: [],
|
|
582
|
+
adaptiveFollowups,
|
|
583
|
+
scoringHints,
|
|
584
|
+
capturedAnswers: answers,
|
|
585
|
+
cliGuidance: toCliGuidance({
|
|
586
|
+
planningType,
|
|
587
|
+
answers,
|
|
588
|
+
unresolvedCriticalCount: 0,
|
|
589
|
+
totalCriticalCount,
|
|
590
|
+
finalize: true,
|
|
591
|
+
outputMode
|
|
592
|
+
})
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (command.name === "explain-planning-rules") {
|
|
597
|
+
const args = command.args ?? {};
|
|
598
|
+
const planningType = typeof args.planningType === "string" ? args.planningType.trim() : "";
|
|
599
|
+
if (!PLANNING_WORKFLOW_TYPES.includes(planningType)) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
code: "invalid-planning-type",
|
|
603
|
+
message: "explain-planning-rules requires planningType of: task-breakdown, sprint-phase, task-ordering, new-feature, change"
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const config = resolvePlanningConfig(ctx.effectiveConfig);
|
|
607
|
+
const rulePack = resolvePlanningRulePack(planningType, ctx.effectiveConfig);
|
|
608
|
+
return {
|
|
609
|
+
ok: true,
|
|
610
|
+
code: "planning-rules-explained",
|
|
611
|
+
message: `Effective planning rules for ${planningType}`,
|
|
612
|
+
data: {
|
|
613
|
+
responseSchemaVersion: 1,
|
|
614
|
+
planningType,
|
|
615
|
+
defaultQuestionDepth: config.depth,
|
|
616
|
+
hardBlockCriticalUnknowns: config.hardBlockCriticalUnknowns,
|
|
617
|
+
adaptiveFinalizePolicy: config.adaptiveFinalizePolicy,
|
|
618
|
+
baseQuestions: rulePack.baseQuestions,
|
|
619
|
+
adaptiveQuestions: rulePack.adaptiveQuestions
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
ok: false,
|
|
625
|
+
code: "unsupported-command",
|
|
626
|
+
message: `Planning module does not support command '${command.name}'`
|
|
627
|
+
};
|
|
29
628
|
}
|
|
30
629
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
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 AdaptiveFinalizePolicy = "off" | "warn" | "block";
|
|
11
|
+
export type PlanningRulePack = {
|
|
12
|
+
baseQuestions: PlanningQuestion[];
|
|
13
|
+
adaptiveQuestions: PlanningQuestion[];
|
|
14
|
+
};
|
|
15
|
+
export declare function resolvePlanningConfig(config: Record<string, unknown> | undefined): {
|
|
16
|
+
depth: PlanningQuestionDepth;
|
|
17
|
+
hardBlockCriticalUnknowns: boolean;
|
|
18
|
+
adaptiveFinalizePolicy: AdaptiveFinalizePolicy;
|
|
19
|
+
rulePacks: Partial<Record<PlanningWorkflowType, PlanningRulePack>>;
|
|
20
|
+
};
|
|
21
|
+
export declare function resolvePlanningRulePack(planningType: PlanningWorkflowType, config: Record<string, unknown> | undefined): PlanningRulePack;
|
|
22
|
+
export declare function nextPlanningQuestions(planningType: PlanningWorkflowType, answers: Record<string, unknown>, config?: Record<string, unknown>): {
|
|
23
|
+
missingCritical: PlanningQuestion[];
|
|
24
|
+
adaptiveFollowups: PlanningQuestion[];
|
|
25
|
+
};
|