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.
- package/.pi/agents/architect.md +73 -16
- package/.pi/agents/coder.md +70 -15
- package/.pi/agents/designer.md +72 -18
- package/.pi/agents/orchestrator.md +116 -23
- package/.pi/agents/reviewer.md +71 -17
- package/.pi/extensions/oauth-router/state.ts +2 -2
- package/.pi/extensions/takomi-context-manager/config.ts +6 -0
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +2 -0
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +16 -14
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +4 -11
- package/.pi/extensions/takomi-context-manager/policy-registry.ts +12 -15
- package/.pi/extensions/takomi-context-manager/prompt-rewriter.ts +29 -10
- package/.pi/extensions/takomi-context-manager/types.ts +7 -0
- package/.pi/extensions/takomi-runtime/index.ts +178 -23
- package/.pi/extensions/takomi-runtime/routing-policy.ts +145 -105
- package/.pi/extensions/takomi-runtime/shared.ts +1 -1
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +38 -2
- package/.pi/extensions/takomi-subagents/native-render.ts +41 -36
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +35 -25
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +32 -18
- package/.pi/prompts/build-prompt.md +259 -199
- package/.pi/prompts/design-prompt.md +95 -134
- package/.pi/prompts/genesis-prompt.md +140 -133
- package/.pi/prompts/orch-prompt.md +11 -139
- package/.pi/prompts/prime-prompt.md +110 -80
- package/.pi/prompts/takomi-prompt.md +31 -85
- package/.pi/prompts/vibe-primeAgent.md +4 -8
- package/.pi/prompts/vibe-spawnTask.md +0 -5
- package/.pi/prompts/vibe-syncDocs.md +0 -5
- package/.pi/settings.json +45 -0
- package/.pi/takomi/context-manager/config.json +21 -0
- package/.pi/takomi/model-routing.md +3 -0
- package/.pi/takomi/policies/subagent-routing.md +13 -0
- package/.pi/takomi/policies/takomi-lifecycle-routing.md +12 -0
- package/.pi/takomi-profile.json +50 -0
- package/README.md +1 -1
- package/bin/takomi.js +1 -1
- package/package.json +7 -2
- package/src/cli.js +5 -1
- package/src/doctor.js +3 -1
- package/src/pi-harness.js +30 -2
- package/src/pi-installer.js +11 -7
- package/src/pi-takomi-core/index.ts +1 -0
- package/src/pi-takomi-core/orchestration.ts +160 -57
- package/src/pi-takomi-core/validation.ts +135 -0
- package/src/pi-takomi-core/workflows.ts +6 -260
- 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
|
|
6
|
-
|
|
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
|
|
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
|
-
"-
|
|
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
|
-
"-
|
|
23
|
-
"- Use policy_load
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
265
|
+
`Active Takomi workflow: ${workflow.title} (${workflow.id}).`,
|
|
174
266
|
workflow.purpose,
|
|
175
267
|
workflow.preferredModelHint ?? "",
|
|
176
|
-
workflow.
|
|
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
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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
|
|
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.`,
|