takomi 2.1.9 → 2.1.11

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 (47) hide show
  1. package/.pi/agents/architect.md +73 -16
  2. package/.pi/agents/coder.md +70 -15
  3. package/.pi/agents/designer.md +72 -18
  4. package/.pi/agents/orchestrator.md +116 -23
  5. package/.pi/agents/reviewer.md +71 -17
  6. package/.pi/extensions/oauth-router/state.ts +2 -2
  7. package/.pi/extensions/takomi-context-manager/config.ts +6 -0
  8. package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +2 -0
  9. package/.pi/extensions/takomi-context-manager/diagnostics.ts +16 -14
  10. package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +4 -11
  11. package/.pi/extensions/takomi-context-manager/policy-registry.ts +12 -15
  12. package/.pi/extensions/takomi-context-manager/prompt-rewriter.ts +29 -10
  13. package/.pi/extensions/takomi-context-manager/types.ts +7 -0
  14. package/.pi/extensions/takomi-runtime/index.ts +178 -23
  15. package/.pi/extensions/takomi-runtime/routing-policy.ts +145 -105
  16. package/.pi/extensions/takomi-runtime/shared.ts +1 -1
  17. package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +38 -2
  18. package/.pi/extensions/takomi-subagents/native-render.ts +41 -36
  19. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +35 -25
  20. package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +32 -18
  21. package/.pi/prompts/build-prompt.md +259 -199
  22. package/.pi/prompts/design-prompt.md +95 -134
  23. package/.pi/prompts/genesis-prompt.md +140 -133
  24. package/.pi/prompts/orch-prompt.md +11 -139
  25. package/.pi/prompts/prime-prompt.md +110 -80
  26. package/.pi/prompts/takomi-prompt.md +31 -85
  27. package/.pi/prompts/vibe-primeAgent.md +4 -8
  28. package/.pi/prompts/vibe-spawnTask.md +0 -5
  29. package/.pi/prompts/vibe-syncDocs.md +0 -5
  30. package/.pi/settings.json +45 -0
  31. package/.pi/takomi/context-manager/config.json +21 -0
  32. package/.pi/takomi/model-routing.md +3 -0
  33. package/.pi/takomi/policies/subagent-routing.md +13 -0
  34. package/.pi/takomi/policies/takomi-lifecycle-routing.md +12 -0
  35. package/.pi/takomi-profile.json +50 -0
  36. package/README.md +1 -1
  37. package/bin/takomi.js +1 -1
  38. package/package.json +7 -2
  39. package/src/cli.js +5 -1
  40. package/src/doctor.js +3 -1
  41. package/src/pi-harness.js +30 -2
  42. package/src/pi-installer.js +11 -7
  43. package/src/pi-takomi-core/index.ts +1 -0
  44. package/src/pi-takomi-core/orchestration.ts +160 -57
  45. package/src/pi-takomi-core/validation.ts +135 -0
  46. package/src/pi-takomi-core/workflows.ts +6 -260
  47. package/src/utils.js +11 -0
@@ -2,25 +2,40 @@ import type { CandidateContext, ContextManagerConfig, SkillRecord } from "./type
2
2
  import { renderCandidateHint } from "./context-router";
3
3
  import { sortedSkills } from "./skill-registry";
4
4
 
5
- function renderSkillIndex(skills: SkillRecord[]): string {
6
- if (skills.length === 0) return "Skills: none discovered.";
7
- return `Skills: ${skills.length} discovered. Use skill_index only when the task may need a skill.`;
5
+ function renderSkillNames(skills: SkillRecord[]): string {
6
+ return ["Available skills (names only):", ...skills.map((skill) => `- ${skill.name}`)].join("\n");
8
7
  }
9
8
 
10
- function renderProgressiveRule(): string {
9
+ function renderSkillDisplay(skills: SkillRecord[], candidates: CandidateContext[], config: ContextManagerConfig): string {
10
+ const mode = config.skillDisplay.mode;
11
+ const countLine = skills.length === 0 ? "Skills: none discovered." : `Skills: ${skills.length} available.`;
12
+
13
+ if (mode === "hidden") return `${countLine} Use skill_index if this task may need a skill.`;
14
+ if (mode === "all-names") return skills.length === 0 ? countLine : renderSkillNames(skills);
15
+ if (mode === "auto" && skills.length <= config.skillDisplay.maxVisibleSkillNames) {
16
+ return skills.length === 0 ? countLine : renderSkillNames(skills);
17
+ }
18
+
19
+ const candidateHint = renderCandidateHint(candidates);
20
+ return [countLine, candidateHint || "Use skill_index if a specialized skill may help."].join("\n");
21
+ }
22
+
23
+ function renderProgressiveRule(config: ContextManagerConfig): string {
24
+ if (!config.skillDisplay.alwaysShowToolInstructions) return "";
11
25
  return [
12
26
  "Skill loading:",
13
27
  "- Skills are optional capability packs that give you special instructions/tools for specialized, repetitive tasks.",
14
28
  "- Do not preload skill descriptions into the prompt.",
15
- "- When doing specialized work, you may check whether a suited skill exists with skill_index.",
29
+ "- Use skill_index to view all skill names when needed.",
16
30
  "- For uncertain matches, request skill_manifest for likely skills; manifests include descriptions and locations.",
17
31
  "- If a skill is clearly relevant or the user names it directly, use skill_load without requesting a manifest first.",
18
32
  "- Load full skill instructions only for skills you will actually use.",
19
33
  "",
20
34
  "Policy loading:",
21
35
  "- Model/subagent/lifecycle policies are lazy-loaded policy packs.",
22
- "- Use policy_manifest to inspect available policies.",
23
- "- Use policy_load before sensitive tools such as takomi_subagent.",
36
+ "- The active routing policy may come from the project or the bundled Takomi harness default.",
37
+ "- Use policy_manifest or policy_load when you need to inspect or quote a policy explicitly.",
38
+ "- If takomi_subagent is blocked for missing policy context, the gate has already loaded the required policy for this session; retry the tool call and follow it.",
24
39
  ].join("\n");
25
40
  }
26
41
 
@@ -28,7 +43,7 @@ function compactHeavyPolicyBlocks(prompt: string, config: ContextManagerConfig):
28
43
  let next = prompt;
29
44
  const removedSections: string[] = [];
30
45
  if (config.promptCompaction.compactModelRouting) {
31
- const modelRoutingRegex = /Project Takomi model routing policy is active\. Apply it when choosing parent\/subagent models and escalation levels:\s*\n\n# Takomi Model Routing Policy[\s\S]*?(?=\nAvailable model context from Pi registry:)/;
46
+ const modelRoutingRegex = /(Project|Bundled) Takomi model routing policy is active\. Apply it when choosing parent\/subagent models and escalation levels:\s*\n\n# Takomi Model Routing Policy[\s\S]*?(?=\nAvailable model context from Pi registry:)/;
32
47
  if (modelRoutingRegex.test(next)) {
33
48
  next = next.replace(modelRoutingRegex, [
34
49
  "Project Takomi model routing policy is available as a lazy-loaded policy pack.",
@@ -60,7 +75,11 @@ export function rewritePrompt(systemPrompt: string, skills: Map<string, SkillRec
60
75
  let changed = false;
61
76
 
62
77
  if (config.promptCompaction.compactSkillDescriptions) {
63
- const replacement = [renderSkillIndex(sortedSkills(skills)), renderProgressiveRule(), renderCandidateHint(candidates)].filter(Boolean).join("\n\n");
78
+ const sorted = sortedSkills(skills);
79
+ const replacement = [
80
+ renderSkillDisplay(sorted, candidates, config),
81
+ renderProgressiveRule(config),
82
+ ].filter(Boolean).join("\n\n");
64
83
  const skillBlockRegex = /<available_skills>[\s\S]*?<\/available_skills>/i;
65
84
  if (!skillBlockRegex.test(next)) {
66
85
  warnings.push("No <available_skills> block found; appended progressive skill guidance instead.");
@@ -69,7 +88,7 @@ export function rewritePrompt(systemPrompt: string, skills: Map<string, SkillRec
69
88
  } else {
70
89
  next = next.replace(skillBlockRegex, replacement);
71
90
  changed = true;
72
- removedSections.push("available_skills descriptions");
91
+ removedSections.push(`available_skills descriptions (${config.skillDisplay.mode} display)`);
73
92
  }
74
93
  }
75
94
 
@@ -22,7 +22,14 @@ export type PolicyPack = {
22
22
 
23
23
  export type Prerequisite = { type: "policies"; policies: string[] };
24
24
 
25
+ export type SkillIndexDisplayMode = "hidden" | "candidates" | "all-names" | "auto";
26
+
25
27
  export type ContextManagerConfig = {
28
+ skillDisplay: {
29
+ mode: SkillIndexDisplayMode;
30
+ maxVisibleSkillNames: number;
31
+ alwaysShowToolInstructions: boolean;
32
+ };
26
33
  candidateRouter: {
27
34
  maxCandidates: number;
28
35
  highConfidence: number;
@@ -1,5 +1,6 @@
1
1
  import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
3
4
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
4
5
  import { StringEnum } from "@mariozechner/pi-ai";
5
6
  import { Type } from "typebox";
@@ -17,8 +18,10 @@ import {
17
18
  normalizeSessionState,
18
19
  renderMasterPlan,
19
20
  renderTaskFile,
21
+ renderValidationReport,
20
22
  serializeSessionState,
21
23
  slugifyTaskTitle,
24
+ validateSessionState,
22
25
  type OrchestratorTask,
23
26
  type OrchestratorSessionState,
24
27
  type OrchestratorTaskStatus,
@@ -57,7 +60,7 @@ import {
57
60
  getProfileDefaults,
58
61
  loadTakomiProfile,
59
62
  } from "./profile";
60
- import { installTakomiRoutingPolicy, loadTakomiRoutingPolicy } from "./routing-policy";
63
+ import { installTakomiRoutingPolicy, resolveTakomiRoutingPolicy } from "./routing-policy";
61
64
 
62
65
  type TakomiState = {
63
66
  enabled: boolean;
@@ -69,6 +72,7 @@ type TakomiState = {
69
72
  workflow?: TakomiWorkflowId;
70
73
  activeSessionId?: string;
71
74
  subagentsEnabled: boolean;
75
+ lastFullPromptKey?: string;
72
76
  };
73
77
 
74
78
  const DEFAULT_STATE: TakomiState = {
@@ -121,7 +125,7 @@ function setStageAndWorkflow(state: TakomiState, stage: VibeLifecycleStage, opti
121
125
  state.enabled = true;
122
126
  }
123
127
 
124
- function rolePrompt(role: TakomiRole): string {
128
+ function fallbackRolePrompt(role: TakomiRole): string {
125
129
  switch (role) {
126
130
  case "orchestrator":
127
131
  return [
@@ -138,7 +142,6 @@ function rolePrompt(role: TakomiRole): string {
138
142
  return [
139
143
  "You are operating in Takomi design mode.",
140
144
  "Translate genesis context into build-ready UX and visual direction.",
141
- "Prefer Gemini or a similarly strong design-oriented model if available.",
142
145
  ].join("\n");
143
146
  case "code":
144
147
  return [
@@ -158,6 +161,43 @@ function rolePrompt(role: TakomiRole): string {
158
161
  }
159
162
  }
160
163
 
164
+ function agentFileNameForRole(role: TakomiRole): string | undefined {
165
+ switch (role) {
166
+ case "orchestrator": return "orchestrator.md";
167
+ case "architect": return "architect.md";
168
+ case "design": return "designer.md";
169
+ case "code": return "coder.md";
170
+ case "review": return "reviewer.md";
171
+ default: return undefined;
172
+ }
173
+ }
174
+
175
+ async function loadRolePrompt(cwd: string, role: TakomiRole): Promise<string> {
176
+ const fileName = agentFileNameForRole(role);
177
+ if (!fileName) return fallbackRolePrompt(role);
178
+
179
+ const candidates = [
180
+ path.join(cwd, ".pi", "agents", fileName),
181
+ path.join(installedAssetRoot("agents"), fileName),
182
+ ];
183
+
184
+ for (const candidate of candidates) {
185
+ try {
186
+ const raw = await readFile(candidate, "utf8");
187
+ const cleaned = stripPromptFrontmatter(raw);
188
+ if (cleaned) {
189
+ return [
190
+ fallbackRolePrompt(role),
191
+ `Canonical Takomi role mirror loaded from ${candidate}:`,
192
+ cleaned,
193
+ ].join("\n\n");
194
+ }
195
+ } catch {}
196
+ }
197
+
198
+ return fallbackRolePrompt(role);
199
+ }
200
+
161
201
  function planPrompt(): string {
162
202
  return [
163
203
  "Takomi planning mode is active.",
@@ -166,14 +206,66 @@ function planPrompt(): string {
166
206
  ].join("\n");
167
207
  }
168
208
 
169
- function getInjectedPlaybook(state: TakomiState): string | undefined {
209
+ function stripPromptFrontmatter(content: string): string {
210
+ return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
211
+ }
212
+
213
+ function stripTemplateOnlyRequestPlaceholder(content: string): string {
214
+ return content
215
+ .replace(/\n?---\s*\r?\n\s*## Current User Request\s*\r?\n\s*(?:\$@|\$ARGUMENTS)\s*$/i, "")
216
+ .replace(/\n?## Current User Request\s*\r?\n\s*(?:\$@|\$ARGUMENTS)\s*$/i, "")
217
+ .trim();
218
+ }
219
+
220
+ function installedAssetRoot(kind: "agents" | "prompts"): string {
221
+ return path.resolve(
222
+ path.dirname(fileURLToPath(import.meta.url)),
223
+ "..",
224
+ "..",
225
+ kind,
226
+ );
227
+ }
228
+
229
+ async function loadWorkflowPrompt(cwd: string, workflow: TakomiWorkflowId): Promise<string | undefined> {
230
+ const fileName = workflow === "vibe-genesis"
231
+ ? "genesis-prompt.md"
232
+ : workflow === "vibe-design"
233
+ ? "design-prompt.md"
234
+ : "build-prompt.md";
235
+ const candidates = [
236
+ path.join(cwd, ".pi", "prompts", fileName),
237
+ path.join(installedAssetRoot("prompts"), fileName),
238
+ ];
239
+
240
+ for (const candidate of candidates) {
241
+ try {
242
+ const raw = await readFile(candidate, "utf8");
243
+ const cleaned = stripTemplateOnlyRequestPlaceholder(stripPromptFrontmatter(raw));
244
+ if (cleaned) return cleaned;
245
+ } catch {}
246
+ }
247
+
248
+ return undefined;
249
+ }
250
+
251
+ async function getInjectedPlaybook(cwd: string, state: TakomiState, includeFullWorkflow: boolean): Promise<string | undefined> {
170
252
  if (!state.workflow) return undefined;
171
253
  const workflow = getWorkflowDefinition(state.workflow);
254
+ const prompt = includeFullWorkflow ? await loadWorkflowPrompt(cwd, state.workflow) : undefined;
255
+
256
+ if (includeFullWorkflow) {
257
+ return [
258
+ `Active Takomi workflow: ${workflow.title} (${workflow.id}).`,
259
+ prompt ?? workflow.playbook,
260
+ workflow.nextStage ? `After this stage, recommend ${workflow.nextStage}.` : "",
261
+ ].filter(Boolean).join("\n\n");
262
+ }
263
+
172
264
  return [
173
- `${workflow.title} is the active Takomi workflow.`,
265
+ `Active Takomi workflow: ${workflow.title} (${workflow.id}).`,
174
266
  workflow.purpose,
175
267
  workflow.preferredModelHint ?? "",
176
- workflow.playbook,
268
+ `Compact reminder: follow the ${workflow.id} stage. Full workflow was injected when this role/workflow became active; reload the markdown prompt only if behavior degrades or the task is complex.`,
177
269
  workflow.nextStage ? `After this stage, recommend ${workflow.nextStage}.` : "",
178
270
  ].filter(Boolean).join("\n\n");
179
271
  }
@@ -302,6 +394,37 @@ async function loadSessionState(cwd: string, sessionId: string): Promise<{ state
302
394
  return { state, paths };
303
395
  }
304
396
 
397
+ function repairTaskMarkdown(content: string): string {
398
+ return content
399
+ .replace(/### Required Skills/g, "### Optional Skill / Context Overlays")
400
+ .replace(/Required Skills/g, "Optional Skill / Context Overlays")
401
+ .replace(/Load ALL required skills/g, "Use relevant optional skill/context overlays only when available and genuinely helpful")
402
+ .replace(/Required skills/g, "Optional skill/context overlays")
403
+ .replace(/required skills/g, "optional skill/context overlays");
404
+ }
405
+
406
+ async function findExistingTaskFile(paths: ReturnType<typeof getSessionPaths>, task: OrchestratorTask): Promise<string | undefined> {
407
+ for (const folder of [paths.pending, paths.inProgress, paths.completed, paths.blocked]) {
408
+ const entries = await readdir(folder).catch(() => [] as string[]);
409
+ const match = entries.find((entry) => entry.endsWith(".task.md") && entry.startsWith(`${task.id}_`));
410
+ if (match) return path.join(folder, match);
411
+ }
412
+ return undefined;
413
+ }
414
+
415
+ async function writeTaskArtifact(paths: ReturnType<typeof getSessionPaths>, state: OrchestratorSessionState, task: OrchestratorTask) {
416
+ const targetPath = path.join(getTaskFolder(paths, task.status), getTaskFileName(task));
417
+ const existingPath = await findExistingTaskFile(paths, task);
418
+ if (!existingPath) {
419
+ await writeFile(targetPath, renderTaskFile(task, `Parent session: ${state.sessionId}\n\nTask title: ${task.title}`), "utf8");
420
+ return;
421
+ }
422
+
423
+ const existing = repairTaskMarkdown(await readFile(existingPath, "utf8"));
424
+ await writeFile(targetPath, existing, "utf8");
425
+ if (existingPath !== targetPath) await rm(existingPath, { force: true });
426
+ }
427
+
305
428
  async function syncTaskArtifacts(cwd: string, session: OrchestratorSessionState) {
306
429
  const normalizedState = normalizeSessionState(session);
307
430
  const paths = getSessionPaths(cwd, normalizedState.sessionId);
@@ -310,7 +433,11 @@ async function syncTaskArtifacts(cwd: string, session: OrchestratorSessionState)
310
433
  await mkdir(paths.completed, { recursive: true });
311
434
  await mkdir(paths.blocked, { recursive: true });
312
435
  await mkdir(paths.stateDir, { recursive: true });
313
- await writeFile(paths.masterPlan, renderMasterPlan(normalizedState), "utf8");
436
+ const existingMasterPlan = await readFile(paths.masterPlan, "utf8").catch(() => "");
437
+ if (!existingMasterPlan || existingMasterPlan.includes("takomi-generated-master-plan")) {
438
+ await writeFile(paths.masterPlan, renderMasterPlan(normalizedState), "utf8");
439
+ }
440
+ const validation = validateSessionState(normalizedState);
314
441
  await writeFile(paths.summary, [
315
442
  `# Orchestrator Summary: ${normalizedState.title}`,
316
443
  "",
@@ -319,21 +446,16 @@ async function syncTaskArtifacts(cwd: string, session: OrchestratorSessionState)
319
446
  `- Machine state: ${paths.stateFile}`,
320
447
  `- Runtime mode: ${normalizedState.mode}`,
321
448
  `- Session intent: ${normalizedState.sessionIntent ?? "full-project"}`,
449
+ `- Validation: ${validation.ok ? "PASS" : "ERRORS"} (${validation.errors.length} errors, ${validation.warnings.length} warnings)`,
450
+ "",
451
+ "## Validation",
452
+ "",
453
+ renderValidationReport(validation),
322
454
  ].join("\n"), "utf8");
323
455
  await writeFile(paths.stateFile, serializeSessionState(normalizedState), "utf8");
324
456
 
325
- for (const folder of [paths.pending, paths.inProgress, paths.completed, paths.blocked]) {
326
- const entries = await readdir(folder).catch(() => [] as string[]);
327
- for (const entry of entries) {
328
- if (entry.endsWith(".task.md")) {
329
- await rm(path.join(folder, entry), { force: true });
330
- }
331
- }
332
- }
333
-
334
457
  for (const task of normalizedState.tasks) {
335
- const filePath = path.join(getTaskFolder(paths, task.status), getTaskFileName(task));
336
- await writeFile(filePath, renderTaskFile(task, `Parent session: ${normalizedState.sessionId}\n\nTask title: ${task.title}`), "utf8");
458
+ await writeTaskArtifact(paths, normalizedState, task);
337
459
  }
338
460
 
339
461
  return paths;
@@ -346,6 +468,7 @@ async function writeOrchestratorSession(cwd: string, session: OrchestratorSessio
346
468
  type IncomingTask = {
347
469
  id?: string;
348
470
  title: string;
471
+ taskMarkdown?: string;
349
472
  role: TakomiRole;
350
473
  stage?: VibeLifecycleStage;
351
474
  workflow?: TakomiWorkflowId;
@@ -657,9 +780,11 @@ export default function takomiRuntime(pi: ExtensionAPI) {
657
780
  name: "takomi_board",
658
781
  label: "Takomi Board",
659
782
  description: "Create and manage lifecycle-aware Takomi orchestration session artifacts.",
660
- promptSnippet: "Create or expand a Genesis -> Design -> Build orchestration session only when the work is large enough to merit it.",
783
+ promptSnippet: "Track/delegate a Genesis -> Design -> Build orchestration session after the core plan has been authored in markdown.",
661
784
  promptGuidelines: [
662
785
  "Use this when you need a concrete orchestrator session directory and task artifacts on disk.",
786
+ "Do not use takomi_board as a substitute for authoring PRD, issue, design, build, master-plan, or task markdown. Write the human-facing markdown first, then use this tool to register/preserve it for state tracking and dispatch.",
787
+ "For high-quality orchestration sessions, provide masterPlanMarkdown and taskMarkdown values that contain the authored plan/task packets; JSON fields should carry IDs/status/roles/workflow/dependencies/checklists for tracking, not replace expressive markdown.",
663
788
  "A new session should normally begin Genesis-first, then expand Design and Build into as many tasks as the scope actually needs.",
664
789
  "If the request is small enough, do not force orchestration just because the tool exists.",
665
790
  "If a reviewed task needs more work, keep or reuse its conversationId so the same subagent can continue it.",
@@ -692,9 +817,11 @@ export default function takomiRuntime(pi: ExtensionAPI) {
692
817
  index: Type.Optional(Type.Number()),
693
818
  done: Type.Optional(Type.Boolean()),
694
819
  }))),
820
+ masterPlanMarkdown: Type.Optional(Type.String()),
695
821
  tasks: Type.Optional(Type.Array(Type.Object({
696
822
  id: Type.Optional(Type.String()),
697
823
  title: Type.String(),
824
+ taskMarkdown: Type.Optional(Type.String()),
698
825
  role: StringEnum(["general", "orchestrator", "architect", "design", "code", "review"] as const),
699
826
  stage: Type.Optional(StringEnum(["genesis", "design", "build"] as const)),
700
827
  workflow: Type.Optional(StringEnum(["vibe-genesis", "vibe-design", "vibe-build"] as const)),
@@ -1059,6 +1186,15 @@ ${stateJson}` }],
1059
1186
  );
1060
1187
  nextState = markStageExpanded(nextState, params.stage, params.notes);
1061
1188
  const paths = await writeOrchestratorSession(ctx.cwd, nextState);
1189
+ if (params.masterPlanMarkdown?.trim()) {
1190
+ await writeFile(paths.masterPlan, params.masterPlanMarkdown.trimEnd() + "\n", "utf8");
1191
+ }
1192
+ for (const task of nextState.tasks) {
1193
+ const authored = (params.tasks as IncomingTask[] | undefined)?.find((input) => (input.id ?? task.id) === task.id)?.taskMarkdown;
1194
+ if (authored?.trim()) {
1195
+ await writeFile(path.join(getTaskFolder(paths, task.status), getTaskFileName(task)), authored.trimEnd() + "\n", "utf8");
1196
+ }
1197
+ }
1062
1198
  state.activeSessionId = nextState.sessionId;
1063
1199
  persistState();
1064
1200
  syncContextPanelState();
@@ -1088,6 +1224,15 @@ ${stateJson}` }],
1088
1224
  },
1089
1225
  );
1090
1226
  const paths = await writeOrchestratorSession(ctx.cwd, nextState);
1227
+ if (params.masterPlanMarkdown?.trim()) {
1228
+ await writeFile(paths.masterPlan, params.masterPlanMarkdown.trimEnd() + "\n", "utf8");
1229
+ }
1230
+ for (const task of nextState.tasks) {
1231
+ const authored = (params.tasks as IncomingTask[] | undefined)?.find((input) => (input.id ?? task.id) === task.id)?.taskMarkdown;
1232
+ if (authored?.trim()) {
1233
+ await writeFile(path.join(getTaskFolder(paths, task.status), getTaskFileName(task)), authored.trimEnd() + "\n", "utf8");
1234
+ }
1235
+ }
1091
1236
  state.activeSessionId = nextState.sessionId;
1092
1237
  state.role = "orchestrator";
1093
1238
  state.stage = nextState.lifecycle.genesis.status === "completed" ? "build" : "genesis";
@@ -1181,7 +1326,15 @@ ${stateJson}` }],
1181
1326
  routingNote = "Blank project detected; orchestrator remains in control and must honor Genesis → Design → Build.";
1182
1327
  }
1183
1328
 
1184
- const routingPolicy = await loadTakomiRoutingPolicy(runtimeCwd);
1329
+ const promptKey = `${effectiveState.role}:${effectiveState.workflow ?? "none"}`;
1330
+ const includeFullWorkflow = Boolean(effectiveState.workflow && effectiveState.lastFullPromptKey !== promptKey);
1331
+ if (includeFullWorkflow) {
1332
+ state.lastFullPromptKey = promptKey;
1333
+ persistState();
1334
+ syncContextPanelState();
1335
+ }
1336
+
1337
+ const routingPolicy = await resolveTakomiRoutingPolicy(runtimeCwd);
1185
1338
  const modelPreflightContext = (() => {
1186
1339
  try {
1187
1340
  const available = typeof (runtimeCtx as { modelRegistry?: { getAvailable?: () => Array<{ provider?: string; id?: string; name?: string }> } } | undefined)?.modelRegistry?.getAvailable === "function"
@@ -1196,11 +1349,13 @@ ${stateJson}` }],
1196
1349
 
1197
1350
  const parts = [
1198
1351
  "Takomi runtime is active for this turn.",
1199
- rolePrompt(effectiveState.role),
1352
+ await loadRolePrompt(runtimeCwd, effectiveState.role),
1200
1353
  effectiveState.planMode ? planPrompt() : "",
1201
- getInjectedPlaybook(effectiveState),
1354
+ await getInjectedPlaybook(runtimeCwd, effectiveState, includeFullWorkflow),
1202
1355
  `Routing note: ${routingNote}`,
1203
- routingPolicy ? `Project Takomi model routing policy is active. Apply it when choosing parent/subagent models and escalation levels:\n\n${routingPolicy}` : "No project Takomi model routing policy file was found. Users can install one with `/takomi routing <policy>` or by saying `Update Takomi routing logic: \"\"\"...\"\"\"`.",
1356
+ routingPolicy.text
1357
+ ? `${routingPolicy.source === "bundled" ? "Bundled" : "Project"} Takomi model routing policy is active. Apply it when choosing parent/subagent models and escalation levels:\n\n${routingPolicy.text}`
1358
+ : "No Takomi routing policy file was found. Users can install one with `/takomi routing <policy>` or by saying `Update Takomi routing logic: \"\"\"...\"\"\"`.",
1204
1359
  modelPreflightContext,
1205
1360
  `Execution mode: ${route.executionMode}. Session recommendation: ${route.sessionRecommendation}.`,
1206
1361
  `Takomi execution gate: ${effectiveState.launchMode === "manual" ? "review" : "auto"}. In review gate mode, show the delegation plan before launching and return to the user after each task with results, verification guidance, and the recommended next step.`,