@veewo/claw-core 0.1.62 → 0.1.63

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 (41) hide show
  1. package/README.md +16 -16
  2. package/dist/src/context.js +1 -0
  3. package/dist/src/context.js.map +1 -1
  4. package/dist/src/effective-config.d.ts +2 -0
  5. package/dist/src/effective-config.js +10 -0
  6. package/dist/src/effective-config.js.map +1 -0
  7. package/dist/src/embedding-config.d.ts +9 -0
  8. package/dist/src/embedding-config.js +21 -0
  9. package/dist/src/embedding-config.js.map +1 -0
  10. package/dist/src/errors.d.ts +1 -1
  11. package/dist/src/errors.js.map +1 -1
  12. package/dist/src/index.d.ts +1 -0
  13. package/dist/src/index.js +1 -0
  14. package/dist/src/index.js.map +1 -1
  15. package/dist/src/init.js +1 -0
  16. package/dist/src/init.js.map +1 -1
  17. package/dist/src/io.d.ts +4 -0
  18. package/dist/src/io.js +77 -0
  19. package/dist/src/io.js.map +1 -1
  20. package/dist/src/paths.d.ts +1 -0
  21. package/dist/src/paths.js +25 -1
  22. package/dist/src/paths.js.map +1 -1
  23. package/dist/src/plan-templates.d.ts +25 -2
  24. package/dist/src/plan-templates.js +582 -28
  25. package/dist/src/plan-templates.js.map +1 -1
  26. package/dist/src/plan.js +301 -213
  27. package/dist/src/plan.js.map +1 -1
  28. package/dist/src/project-check.js +10 -0
  29. package/dist/src/project-check.js.map +1 -1
  30. package/dist/src/project-config-defaults.d.ts +6 -0
  31. package/dist/src/project-config-defaults.js +5 -0
  32. package/dist/src/project-config-defaults.js.map +1 -0
  33. package/dist/src/templates/plans/default.d.ts +44 -14
  34. package/dist/src/templates/plans/default.js +44 -10
  35. package/dist/src/templates/plans/default.js.map +1 -1
  36. package/dist/src/types.d.ts +15 -1
  37. package/dist/src/workflow-guidance.config.json +396 -395
  38. package/dist/src/workflow-guidance.d.ts +2 -1
  39. package/dist/src/workflow-guidance.js +166 -21
  40. package/dist/src/workflow-guidance.js.map +1 -1
  41. package/package.json +44 -44
@@ -1,26 +1,81 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
  import { ClawError } from "./errors.js";
5
- import { defaultPlanTemplate } from "./templates/plans/default.js";
6
- const PLAN_TEMPLATES = [defaultPlanTemplate];
6
+ import { defaultPlanTemplate, } from "./templates/plans/default.js";
7
+ const PLAN_TEMPLATES = [normalizePlanLikeTemplate(defaultPlanTemplate, { source: "builtin" })];
7
8
  export async function resolveSeedPlanTemplate(params) {
8
9
  const normalized = params.templateName?.trim().toLowerCase() || defaultPlanTemplate.id;
9
- const projectTemplate = params.projectRoot ? await loadProjectSeedTemplate(params.projectRoot, normalized) : null;
10
+ const projectTemplate = params.projectRoot ? await loadProjectPlanTemplate(params.projectRoot, normalized) : null;
10
11
  if (projectTemplate) {
11
12
  return projectTemplate;
12
13
  }
13
- const match = PLAN_TEMPLATES.find((template) => template.id.toLowerCase() === normalized || template.aliases.some((alias) => alias.toLowerCase() === normalized));
14
+ const projectSkillTemplate = params.projectRoot ? await loadProjectSkillPlanTemplate(params.projectRoot, normalized) : null;
15
+ if (projectSkillTemplate) {
16
+ return projectSkillTemplate;
17
+ }
18
+ const projectPackageTemplate = params.projectRoot ? await loadProjectPackagePlanTemplate(params.projectRoot, normalized) : null;
19
+ if (projectPackageTemplate) {
20
+ return projectPackageTemplate;
21
+ }
22
+ const globalTemplate = await loadGlobalPlanTemplate(normalized);
23
+ if (globalTemplate) {
24
+ return globalTemplate;
25
+ }
26
+ const globalSkillTemplate = await loadGlobalSkillPlanTemplate(normalized);
27
+ if (globalSkillTemplate) {
28
+ return globalSkillTemplate;
29
+ }
30
+ const globalPackageTemplate = await loadGlobalPackagePlanTemplate(normalized);
31
+ if (globalPackageTemplate) {
32
+ return globalPackageTemplate;
33
+ }
34
+ const match = PLAN_TEMPLATES.find((template) => template.id.toLowerCase() === normalized);
14
35
  if (!match) {
15
36
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown plan template "${params.templateName ?? normalized}".`, {
16
37
  templateName: params.templateName ?? normalized,
17
- availableTemplates: PLAN_TEMPLATES.flatMap((template) => [template.id, ...template.aliases]),
38
+ availableTemplates: PLAN_TEMPLATES.map((template) => template.id),
18
39
  });
19
40
  }
20
41
  return match;
21
42
  }
22
- async function loadProjectSeedTemplate(projectRoot, normalizedTemplateName) {
23
- const templatesDir = path.join(projectRoot, ".claw", "templates");
43
+ export async function resolvePlanTemplateFile(templatePath) {
44
+ const raw = templatePath.endsWith(".json")
45
+ ? JSON.parse(fs.readFileSync(templatePath, "utf-8"))
46
+ : await import(pathToFileURL(templatePath).href).then((module) => module.default ?? module);
47
+ return validatePlanTemplateSource(raw, templatePath, "project");
48
+ }
49
+ export function validatePlanTemplateSource(raw, templatePath, source = "project") {
50
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
51
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid plan template at ${templatePath}.`, {
52
+ templatePath,
53
+ });
54
+ }
55
+ return normalizePlanLikeTemplate(validatePlanLikeTemplate(raw, templatePath), {
56
+ source,
57
+ templatePath: source === "project" ? templatePath : undefined,
58
+ });
59
+ }
60
+ async function loadProjectPlanTemplate(projectRoot, normalizedTemplateName) {
61
+ return loadPlanTemplateFromDirectory(path.join(projectRoot, ".claw", "templates"), normalizedTemplateName);
62
+ }
63
+ async function loadProjectSkillPlanTemplate(projectRoot, normalizedTemplateName) {
64
+ return loadPlanTemplateFromSkillRoots(resolveProjectSkillRoots(projectRoot), normalizedTemplateName);
65
+ }
66
+ async function loadProjectPackagePlanTemplate(projectRoot, normalizedTemplateName) {
67
+ return loadPlanTemplateFromTemplateDirs(resolveProjectPackageTemplateDirs(projectRoot), normalizedTemplateName);
68
+ }
69
+ async function loadGlobalPlanTemplate(normalizedTemplateName) {
70
+ return loadPlanTemplateFromDirectory(path.join(os.homedir(), ".claw", "templates"), normalizedTemplateName);
71
+ }
72
+ async function loadGlobalSkillPlanTemplate(normalizedTemplateName) {
73
+ return loadPlanTemplateFromSkillRoots(resolveGlobalSkillRoots(), normalizedTemplateName);
74
+ }
75
+ async function loadGlobalPackagePlanTemplate(normalizedTemplateName) {
76
+ return loadPlanTemplateFromTemplateDirs(resolveGlobalPackageTemplateDirs(), normalizedTemplateName);
77
+ }
78
+ async function loadPlanTemplateFromDirectory(templatesDir, normalizedTemplateName) {
24
79
  if (!fs.existsSync(templatesDir)) {
25
80
  return null;
26
81
  }
@@ -40,34 +95,533 @@ async function loadProjectSeedTemplate(projectRoot, normalizedTemplateName) {
40
95
  candidatePaths: candidateEntries.map((entryName) => path.join(templatesDir, entryName)),
41
96
  });
42
97
  }
43
- const templatePath = path.join(templatesDir, candidateEntries[0]);
44
- const raw = templatePath.endsWith(".json")
45
- ? JSON.parse(fs.readFileSync(templatePath, "utf-8"))
46
- : await import(pathToFileURL(templatePath).href).then((module) => module.default ?? module);
47
- return validateSeedPlanTemplate(raw, templatePath);
98
+ return resolvePlanTemplateFile(path.join(templatesDir, candidateEntries[0]));
48
99
  }
49
- function validateSeedPlanTemplate(raw, templatePath) {
50
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
51
- throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid plan template at ${templatePath}.`, {
52
- templatePath,
100
+ async function loadPlanTemplateFromTemplateDirs(templateDirs, normalizedTemplateName) {
101
+ const matches = [];
102
+ for (const templateDir of templateDirs) {
103
+ const match = await loadPlanTemplateFromDirectory(templateDir, normalizedTemplateName);
104
+ if (match?.templatePath) {
105
+ matches.push(match.templatePath);
106
+ }
107
+ }
108
+ if (matches.length > 1) {
109
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Multiple package plan templates matched "${normalizedTemplateName}".`, {
110
+ templateName: normalizedTemplateName,
111
+ candidatePaths: matches,
112
+ });
113
+ }
114
+ return matches.length === 1 ? resolvePlanTemplateFile(matches[0]) : null;
115
+ }
116
+ async function loadPlanTemplateFromSkillRoots(skillRoots, normalizedTemplateName) {
117
+ const matches = [];
118
+ for (const skillRoot of skillRoots) {
119
+ for (const templatePath of collectSkillTemplateFiles(skillRoot)) {
120
+ const template = await resolvePlanTemplateFile(templatePath);
121
+ if (template.id.toLowerCase() === normalizedTemplateName) {
122
+ matches.push({ path: templatePath, signature: signatureForTemplateConflict(template) });
123
+ }
124
+ }
125
+ }
126
+ if (matches.length > 1) {
127
+ const uniqueSignatures = new Set(matches.map((match) => match.signature));
128
+ if (uniqueSignatures.size === 1) {
129
+ return resolvePlanTemplateFile(matches[0].path);
130
+ }
131
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Multiple skill-local plan templates matched "${normalizedTemplateName}".`, {
132
+ templateName: normalizedTemplateName,
133
+ candidatePaths: matches.map((match) => match.path),
53
134
  });
54
135
  }
136
+ return matches.length === 1 ? resolvePlanTemplateFile(matches[0].path) : null;
137
+ }
138
+ function signatureForTemplateConflict(template) {
139
+ const { source: _source, templatePath: _templatePath, ...portableTemplate } = template;
140
+ return JSON.stringify(portableTemplate);
141
+ }
142
+ function resolveProjectSkillRoots(projectRoot) {
143
+ const roots = [path.join(projectRoot, "skills")];
144
+ const packagesDir = path.join(projectRoot, "packages");
145
+ if (!fs.existsSync(packagesDir)) {
146
+ return roots;
147
+ }
148
+ for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
149
+ if (!entry.isDirectory()) {
150
+ continue;
151
+ }
152
+ roots.push(path.join(packagesDir, entry.name, "skills"));
153
+ }
154
+ return roots;
155
+ }
156
+ function resolveProjectPackageTemplateDirs(projectRoot) {
157
+ const templateDirs = [];
158
+ const packagesDir = path.join(projectRoot, "packages");
159
+ if (!fs.existsSync(packagesDir)) {
160
+ return templateDirs;
161
+ }
162
+ for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
163
+ if (!entry.isDirectory()) {
164
+ continue;
165
+ }
166
+ templateDirs.push(path.join(packagesDir, entry.name, "templates"));
167
+ }
168
+ return templateDirs;
169
+ }
170
+ function resolveGlobalSkillRoots() {
171
+ const homeDir = os.homedir();
172
+ const roots = [
173
+ path.join(homeDir, ".agents", "skills"),
174
+ path.join(homeDir, ".codex", "skills"),
175
+ ];
176
+ const cacheRoot = path.join(homeDir, ".codex", "plugins", "cache");
177
+ if (fs.existsSync(cacheRoot)) {
178
+ for (const vendor of fs.readdirSync(cacheRoot, { withFileTypes: true })) {
179
+ if (!vendor.isDirectory()) {
180
+ continue;
181
+ }
182
+ const vendorDir = path.join(cacheRoot, vendor.name);
183
+ for (const plugin of fs.readdirSync(vendorDir, { withFileTypes: true })) {
184
+ if (!plugin.isDirectory()) {
185
+ continue;
186
+ }
187
+ const pluginDir = path.join(vendorDir, plugin.name);
188
+ roots.push(path.join(pluginDir, "skills"));
189
+ for (const version of fs.readdirSync(pluginDir, { withFileTypes: true })) {
190
+ if (!version.isDirectory()) {
191
+ continue;
192
+ }
193
+ roots.push(path.join(pluginDir, version.name, "skills"));
194
+ }
195
+ }
196
+ }
197
+ }
198
+ return roots;
199
+ }
200
+ function resolveGlobalPackageTemplateDirs() {
201
+ const homeDir = os.homedir();
202
+ const templateDirs = [];
203
+ const cacheRoot = path.join(homeDir, ".codex", "plugins", "cache");
204
+ if (!fs.existsSync(cacheRoot)) {
205
+ return templateDirs;
206
+ }
207
+ for (const vendor of fs.readdirSync(cacheRoot, { withFileTypes: true })) {
208
+ if (!vendor.isDirectory()) {
209
+ continue;
210
+ }
211
+ const vendorDir = path.join(cacheRoot, vendor.name);
212
+ for (const plugin of fs.readdirSync(vendorDir, { withFileTypes: true })) {
213
+ if (!plugin.isDirectory()) {
214
+ continue;
215
+ }
216
+ const pluginDir = path.join(vendorDir, plugin.name);
217
+ templateDirs.push(path.join(pluginDir, "templates"));
218
+ for (const version of fs.readdirSync(pluginDir, { withFileTypes: true })) {
219
+ if (!version.isDirectory()) {
220
+ continue;
221
+ }
222
+ templateDirs.push(path.join(pluginDir, version.name, "templates"));
223
+ }
224
+ }
225
+ }
226
+ return templateDirs;
227
+ }
228
+ function collectSkillTemplateFiles(skillRoot) {
229
+ if (!fs.existsSync(skillRoot)) {
230
+ return [];
231
+ }
232
+ const templateFiles = [];
233
+ for (const entry of fs.readdirSync(skillRoot, { withFileTypes: true })) {
234
+ if (!entry.isDirectory()) {
235
+ continue;
236
+ }
237
+ const skillDir = path.join(skillRoot, entry.name);
238
+ for (const candidateName of [
239
+ "TEMPLATE.json",
240
+ "TEMPLATE.js",
241
+ "TEMPLATE.mjs",
242
+ "TEMPLATE.cjs",
243
+ "CLAW-TEMPLATE.json",
244
+ "CLAW-TEMPLATE.js",
245
+ "CLAW-TEMPLATE.mjs",
246
+ "CLAW-TEMPLATE.cjs",
247
+ ]) {
248
+ const candidatePath = path.join(skillDir, candidateName);
249
+ if (fs.existsSync(candidatePath)) {
250
+ templateFiles.push(candidatePath);
251
+ }
252
+ }
253
+ }
254
+ return templateFiles;
255
+ }
256
+ function validatePlanLikeTemplate(raw, templatePath) {
55
257
  const candidate = raw;
56
- const aliasesValid = Array.isArray(candidate.aliases) && candidate.aliases.every((alias) => typeof alias === "string");
57
- if (typeof candidate.id !== "string" ||
58
- !aliasesValid ||
59
- typeof candidate.planningEnabledStatus !== "string" ||
60
- typeof candidate.planningDisabledStatus !== "string" ||
61
- typeof candidate.planningTask?.title !== "string" ||
62
- typeof candidate.planningTask?.detail !== "string" ||
63
- typeof candidate.activationTask?.title !== "string" ||
64
- typeof candidate.activationTask?.detail !== "string" ||
65
- typeof candidate.activationTask?.goalModeDetail !== "string") {
66
- throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid plan template at ${templatePath}.`, {
258
+ const allowedKeys = new Set([
259
+ "id",
260
+ "configOverride",
261
+ "title",
262
+ "status",
263
+ "goal",
264
+ "requirements",
265
+ "tasks",
266
+ "references",
267
+ "rules",
268
+ "keyDecisions",
269
+ "retrospective",
270
+ ]);
271
+ for (const key of Object.keys(candidate)) {
272
+ if (!allowedKeys.has(key)) {
273
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid plan-like template field "${key}" at ${templatePath}.`, {
274
+ templatePath,
275
+ field: key,
276
+ });
277
+ }
278
+ }
279
+ if (!isTemplateConfigOverride(candidate.configOverride)) {
280
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template configOverride at ${templatePath}.`, {
281
+ templatePath,
282
+ });
283
+ }
284
+ if (typeof candidate.id !== "string" || typeof candidate.status !== "string") {
285
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid plan-like template header at ${templatePath}.`, {
67
286
  templatePath,
68
287
  });
69
288
  }
70
- return candidate;
289
+ if (candidate.title !== undefined && typeof candidate.title !== "string") {
290
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template title at ${templatePath}.`, {
291
+ templatePath,
292
+ });
293
+ }
294
+ if (candidate.goal !== undefined) {
295
+ if (!candidate.goal || typeof candidate.goal !== "object" || Array.isArray(candidate.goal)) {
296
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template goal at ${templatePath}.`, {
297
+ templatePath,
298
+ });
299
+ }
300
+ const goal = candidate.goal;
301
+ if (goal.text !== undefined && typeof goal.text !== "string") {
302
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template goal.text at ${templatePath}.`, {
303
+ templatePath,
304
+ });
305
+ }
306
+ }
307
+ if (candidate.requirements !== undefined && !isPlanRequirements(candidate.requirements)) {
308
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template requirements at ${templatePath}.`, {
309
+ templatePath,
310
+ });
311
+ }
312
+ if (!Array.isArray(candidate.tasks)) {
313
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Template tasks must be an array at ${templatePath}.`, {
314
+ templatePath,
315
+ });
316
+ }
317
+ for (const task of candidate.tasks) {
318
+ if (!isPlanTemplateTask(task)) {
319
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template task at ${templatePath}.`, {
320
+ templatePath,
321
+ });
322
+ }
323
+ }
324
+ if (candidate.references !== undefined && !isPlanReferences(candidate.references)) {
325
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template references at ${templatePath}.`, {
326
+ templatePath,
327
+ });
328
+ }
329
+ if (candidate.rules !== undefined && (!Array.isArray(candidate.rules) || candidate.rules.some((item) => typeof item !== "string"))) {
330
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template rules at ${templatePath}.`, {
331
+ templatePath,
332
+ });
333
+ }
334
+ if (candidate.keyDecisions !== undefined
335
+ && (!Array.isArray(candidate.keyDecisions) || candidate.keyDecisions.some((item) => typeof item !== "string"))) {
336
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template keyDecisions at ${templatePath}.`, {
337
+ templatePath,
338
+ });
339
+ }
340
+ if (candidate.retrospective !== undefined && !isPlanRetrospective(candidate.retrospective)) {
341
+ throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid template retrospective at ${templatePath}.`, {
342
+ templatePath,
343
+ });
344
+ }
345
+ return raw;
346
+ }
347
+ function normalizePlanLikeTemplate(template, meta) {
348
+ return {
349
+ id: template.id,
350
+ configOverride: template.configOverride,
351
+ title: template.title,
352
+ status: template.status,
353
+ goal: template.goal,
354
+ requirements: template.requirements,
355
+ tasks: template.tasks,
356
+ references: template.references,
357
+ rules: template.rules,
358
+ keyDecisions: template.keyDecisions,
359
+ retrospective: template.retrospective,
360
+ source: meta.source,
361
+ templatePath: meta.templatePath,
362
+ };
363
+ }
364
+ export function getTemplateTaskDoneChoices(template, taskId) {
365
+ return getTemplateTaskGuidance(template, taskId)?.onDone?.choices;
366
+ }
367
+ export function getTemplateTaskDoneGuidanceRoute(template, taskId, choiceId) {
368
+ const onDone = getTemplateTaskGuidance(template, taskId)?.onDone;
369
+ if (!onDone) {
370
+ return undefined;
371
+ }
372
+ if (choiceId && onDone.choices && Object.prototype.hasOwnProperty.call(onDone.choices, choiceId)) {
373
+ return onDone.choices[choiceId];
374
+ }
375
+ return onDone.default;
376
+ }
377
+ export function getTemplateTaskGuidance(template, taskId) {
378
+ return template.tasks.find((task) => task.id === taskId)?.guidance;
379
+ }
380
+ function isPlanRequirements(value) {
381
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
382
+ return false;
383
+ }
384
+ const candidate = value;
385
+ return typeof candidate.summary === "string"
386
+ && Array.isArray(candidate.openQuestions)
387
+ && candidate.openQuestions.every((item) => typeof item === "string")
388
+ && Array.isArray(candidate.acceptanceCriteria)
389
+ && candidate.acceptanceCriteria.every((item) => typeof item === "string");
390
+ }
391
+ function isPlanReferences(value) {
392
+ if (!Array.isArray(value)) {
393
+ return false;
394
+ }
395
+ return value.every((reference) => reference
396
+ && typeof reference === "object"
397
+ && !Array.isArray(reference)
398
+ && typeof reference.path === "string"
399
+ && typeof reference.why === "string");
400
+ }
401
+ function isPlanRetrospective(value) {
402
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
403
+ return false;
404
+ }
405
+ const candidate = value;
406
+ if (typeof candidate.summary !== "string") {
407
+ return false;
408
+ }
409
+ for (const key of ["whatWorked", "issues", "followUps", "knowledgeCandidates"]) {
410
+ const field = candidate[key];
411
+ if (field !== undefined && (!Array.isArray(field) || field.some((item) => typeof item !== "string"))) {
412
+ return false;
413
+ }
414
+ }
415
+ return true;
416
+ }
417
+ function isPlanTemplateTask(value) {
418
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
419
+ return false;
420
+ }
421
+ const candidate = value;
422
+ const allowedKeys = new Set([
423
+ "id",
424
+ "title",
425
+ "detail",
426
+ "status",
427
+ "guidance",
428
+ "goalModeDetail",
429
+ "execution",
430
+ "sessionKey",
431
+ ]);
432
+ for (const key of Object.keys(candidate)) {
433
+ if (!allowedKeys.has(key)) {
434
+ return false;
435
+ }
436
+ }
437
+ if (!Number.isInteger(candidate.id)) {
438
+ return false;
439
+ }
440
+ if (typeof candidate.title !== "string") {
441
+ return false;
442
+ }
443
+ if (candidate.detail !== undefined && typeof candidate.detail !== "string") {
444
+ return false;
445
+ }
446
+ if (candidate.status !== "pending"
447
+ && candidate.status !== "in_progress"
448
+ && candidate.status !== "subagent_running"
449
+ && candidate.status !== "done"
450
+ && candidate.status !== "blocked") {
451
+ return false;
452
+ }
453
+ if (!isTemplateTaskGuidance(candidate.guidance)) {
454
+ return false;
455
+ }
456
+ if (candidate.goalModeDetail !== undefined && typeof candidate.goalModeDetail !== "string") {
457
+ return false;
458
+ }
459
+ if (candidate.execution !== undefined) {
460
+ if (!candidate.execution || typeof candidate.execution !== "object" || Array.isArray(candidate.execution)) {
461
+ return false;
462
+ }
463
+ const execution = candidate.execution;
464
+ if (execution.type !== undefined
465
+ && execution.type !== "default"
466
+ && execution.type !== "subagent"
467
+ && execution.type !== "subplan") {
468
+ return false;
469
+ }
470
+ if (execution.subplan !== undefined && typeof execution.subplan !== "string") {
471
+ return false;
472
+ }
473
+ if (execution.planPath !== undefined && typeof execution.planPath !== "string") {
474
+ return false;
475
+ }
476
+ }
477
+ if (candidate.sessionKey !== undefined && typeof candidate.sessionKey !== "string") {
478
+ return false;
479
+ }
480
+ return true;
481
+ }
482
+ function isTemplateConfigOverride(value) {
483
+ if (value === undefined) {
484
+ return true;
485
+ }
486
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
487
+ return false;
488
+ }
489
+ const allowedKeys = new Set([
490
+ "goalMode",
491
+ "truthDispatch",
492
+ "externalPlanningSkill",
493
+ "externalTruthSkill",
494
+ "externalAdrSkill",
495
+ ]);
496
+ const candidate = value;
497
+ for (const key of Object.keys(candidate)) {
498
+ if (!allowedKeys.has(key)) {
499
+ return false;
500
+ }
501
+ }
502
+ if (candidate.goalMode !== undefined && typeof candidate.goalMode !== "boolean") {
503
+ return false;
504
+ }
505
+ if (candidate.truthDispatch !== undefined && candidate.truthDispatch !== "per_task" && candidate.truthDispatch !== "final_only") {
506
+ return false;
507
+ }
508
+ if (candidate.externalPlanningSkill !== undefined && candidate.externalPlanningSkill !== null && typeof candidate.externalPlanningSkill !== "string") {
509
+ return false;
510
+ }
511
+ if (candidate.externalTruthSkill !== undefined && candidate.externalTruthSkill !== null && typeof candidate.externalTruthSkill !== "string") {
512
+ return false;
513
+ }
514
+ if (candidate.externalAdrSkill !== undefined && candidate.externalAdrSkill !== null && typeof candidate.externalAdrSkill !== "string") {
515
+ return false;
516
+ }
517
+ return true;
518
+ }
519
+ function isTemplateTaskGuidance(value) {
520
+ if (value === undefined) {
521
+ return true;
522
+ }
523
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
524
+ return false;
525
+ }
526
+ const candidate = value;
527
+ const allowedKeys = new Set(["onDone"]);
528
+ for (const key of Object.keys(candidate)) {
529
+ if (!allowedKeys.has(key)) {
530
+ return false;
531
+ }
532
+ }
533
+ if (candidate.onDone === undefined) {
534
+ return true;
535
+ }
536
+ if (!candidate.onDone || typeof candidate.onDone !== "object" || Array.isArray(candidate.onDone)) {
537
+ return false;
538
+ }
539
+ const onDone = candidate.onDone;
540
+ const allowedOnDoneKeys = new Set(["default", "choices"]);
541
+ for (const key of Object.keys(onDone)) {
542
+ if (!allowedOnDoneKeys.has(key)) {
543
+ return false;
544
+ }
545
+ }
546
+ if (onDone.default !== undefined && !isTemplateGuidanceRoute(onDone.default)) {
547
+ return false;
548
+ }
549
+ if (onDone.choices !== undefined) {
550
+ if (!onDone.choices || typeof onDone.choices !== "object" || Array.isArray(onDone.choices)) {
551
+ return false;
552
+ }
553
+ for (const [choiceId, route] of Object.entries(onDone.choices)) {
554
+ if (!choiceId.trim() || !isTemplateGuidanceRoute(route)) {
555
+ return false;
556
+ }
557
+ }
558
+ }
559
+ return true;
560
+ }
561
+ function isTemplateGuidanceRoute(value) {
562
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
563
+ return false;
564
+ }
565
+ const candidate = value;
566
+ const allowedKeys = new Set([
567
+ "mergeMode",
568
+ "summary",
569
+ "nextsteps",
570
+ "notes",
571
+ "recommendedCommands",
572
+ "nextTaskId",
573
+ "label",
574
+ "delegateTruth",
575
+ ]);
576
+ for (const key of Object.keys(candidate)) {
577
+ if (!allowedKeys.has(key)) {
578
+ return false;
579
+ }
580
+ }
581
+ if (candidate.mergeMode !== undefined && candidate.mergeMode !== "override" && candidate.mergeMode !== "replace") {
582
+ return false;
583
+ }
584
+ if (candidate.summary !== undefined && typeof candidate.summary !== "string") {
585
+ return false;
586
+ }
587
+ if (candidate.nextsteps !== undefined && (!Array.isArray(candidate.nextsteps) || candidate.nextsteps.some((step) => typeof step !== "string"))) {
588
+ return false;
589
+ }
590
+ if (candidate.notes !== undefined && typeof candidate.notes !== "string") {
591
+ return false;
592
+ }
593
+ if (candidate.recommendedCommands !== undefined &&
594
+ (!Array.isArray(candidate.recommendedCommands) || candidate.recommendedCommands.some((command) => typeof command !== "string"))) {
595
+ return false;
596
+ }
597
+ if (candidate.nextTaskId !== undefined && !Number.isInteger(candidate.nextTaskId)) {
598
+ return false;
599
+ }
600
+ if (candidate.label !== undefined && typeof candidate.label !== "string") {
601
+ return false;
602
+ }
603
+ if (candidate.delegateTruth !== undefined && typeof candidate.delegateTruth !== "boolean") {
604
+ return false;
605
+ }
606
+ if (candidate.mergeMode === "replace") {
607
+ if (typeof candidate.summary !== "string") {
608
+ return false;
609
+ }
610
+ if (!Array.isArray(candidate.nextsteps) || candidate.nextsteps.some((step) => typeof step !== "string")) {
611
+ return false;
612
+ }
613
+ }
614
+ if (candidate.mergeMode !== "replace"
615
+ && candidate.summary === undefined
616
+ && candidate.nextsteps === undefined
617
+ && candidate.notes === undefined
618
+ && candidate.recommendedCommands === undefined
619
+ && candidate.nextTaskId === undefined
620
+ && candidate.label === undefined
621
+ && candidate.delegateTruth === undefined) {
622
+ return false;
623
+ }
624
+ return true;
71
625
  }
72
626
  export function renderSeedTemplateText(template, vars) {
73
627
  return template.replace(/{{\s*planningSkill\s*}}/g, vars.planningSkill);