takomi 2.1.26 → 2.1.28

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/README.md CHANGED
@@ -45,6 +45,7 @@ Inside Pi, use:
45
45
  - The old standalone commands (`/takomi-genesis`, `/takomi-design`, `/takomi-build`, `/takomi-kickoff`, `/autoorch`, `/orch`, `/architect`, `/code`, `/review`, and the `/takomi-subagent*` variants) are folded into `/takomi` subcommands so slash autocomplete stays small.
46
46
  - prompt shortcuts are suffixed with `-prompt` to avoid collisions with runtime commands, e.g. `/orch-prompt`, `/build-prompt`, `/design-prompt`, `/genesis-prompt`, `/takomi-prompt`, `/prime-prompt`
47
47
  - a project-local theme is available at `.pi/themes/takomi-noir.json`; select `takomi-noir` in `/settings` to use the Takomi UI palette
48
+ - optional Pi feature packs are managed by `takomi setup pi` or `takomi setup pi-features` and refreshed by `takomi refresh`; current defaults install Takomi Interview (`npm:@juicesharp/rpiv-ask-user-question`) while Takomi Todo (`npm:@juicesharp/rpiv-todo`), Browser QA (`npm:pi-chrome`), and Doc Preview (`npm:pi-markdown-preview`) stay opt-in
48
49
  - additional workflow prompts are available as direct slash commands:
49
50
  - `/vibe-primeAgent`
50
51
  - `/vibe-spawnTask`
@@ -128,3 +129,12 @@ So when working on packaging, agents should distinguish between:
128
129
  - rewrite JSON machine state
129
130
  - keep task docs organized in `pending/`, `in-progress/`, `completed/`, and `blocked/`
130
131
  - `takomi_board` intentionally cannot dispatch or redispatch agents. Use `takomi_subagent` for single, parallel, or chain execution, then call `takomi_board update_task` with the result.
132
+
133
+ ## Attribution Notes
134
+
135
+ - Pi-native Takomi runs on [Pi](https://github.com/earendil-works/pi) / `@earendil-works/pi-coding-agent`, authored by Mario Zechner and Earendil Works.
136
+ - Takomi subagent execution builds on [`pi-subagents`](https://github.com/nicobailon/pi-subagents) by Nico Bailon, with Takomi adding lifecycle orchestration, board/state artifacts, model-routing policy, and review-loop conventions.
137
+ - Optional Takomi Interview and Todo feature packs integrate [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question) and [`@juicesharp/rpiv-todo`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo) by juicesharp.
138
+ - Optional Browser QA can use [`pi-chrome`](https://github.com/tianrendong/pi-chrome) by tianrendong.
139
+ - Optional Doc Preview can use [`pi-markdown-preview`](https://github.com/omaclaren/pi-markdown-preview) by omaclaren.
140
+ - [`context-mode`](https://github.com/mksglu/context-mode) by Mert Koseoğlu is credited as external research/power-user context tooling; it is not a Takomi default bundle.
@@ -2,7 +2,6 @@
2
2
  name: architect
3
3
  description: Gather requirements, design architecture, and create implementation-ready plans.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4
6
5
  ---
7
6
  You are the Takomi Architect.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: coder
3
3
  description: Implement, fix, and refactor code with verification and controlled scope.
4
4
  tools: read,bash,edit,write,grep,find,ls
5
- model: gpt-5.4-mini
6
5
  ---
7
6
  You are the Takomi Code Specialist.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: designer
3
3
  description: Translate requirements into build-ready UI, UX, visual systems, and interaction direction.
4
4
  tools: read,bash,grep,find,ls
5
- model: gemini-3.1-pro-preview
6
5
  ---
7
6
  You are the Takomi Design Specialist.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: orchestrator
3
3
  description: Coordinate complex projects by decomposing, sequencing, delegating, and synthesizing specialist work.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4
6
5
  ---
7
6
  You are the Takomi Orchestrator.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: reviewer
3
3
  description: Review changes for correctness, risk, security, maintainability, and spec compliance.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4-mini
6
5
  ---
7
6
  You are the Takomi Review Specialist.
8
7
 
@@ -41,7 +41,7 @@ export function renderReport(state: ContextManagerState, verbose = false): strin
41
41
  lines.push("- Candidates: none");
42
42
  }
43
43
  if (report.duplicateExtensionWarnings.length > 0 || verbose) {
44
- lines.push("", "Extension Conflict Diagnostics:", ...renderDuplicateExtensionGuidance(report.duplicateExtensionWarnings));
44
+ lines.push("", "Extension Conflict Diagnostics:", ...renderDuplicateExtensionGuidance(report.duplicateExtensionWarnings, report.cwd));
45
45
  }
46
46
  if (verbose) {
47
47
  lines.push("", "Skill Index:", ...sortedSkills(state.skills).map((skill) => `- ${skill.name}`));
@@ -1,7 +1,7 @@
1
1
  import { access } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
2
3
  import os from "node:os";
3
4
  import path from "node:path";
4
- import type { ContextManagerState } from "./state";
5
5
 
6
6
  type KnownTakomiExtension = {
7
7
  toolName: string;
@@ -43,9 +43,20 @@ export async function detectDuplicateTakomiExtensions(cwd: string): Promise<Arra
43
43
  return warnings;
44
44
  }
45
45
 
46
- export function renderDuplicateExtensionGuidance(warnings: Array<{ toolName: string; paths: string[] }>): string[] {
46
+ function isTakomiSourceRepo(cwd: string): boolean {
47
+ return [
48
+ path.join(cwd, "package.json"),
49
+ path.join(cwd, "scripts", "pi-dev.ps1"),
50
+ path.join(cwd, ".pi", "extensions", "takomi-runtime", "index.ts"),
51
+ ].every((filePath) => existsSync(filePath));
52
+ }
53
+
54
+ export function renderDuplicateExtensionGuidance(warnings: Array<{ toolName: string; paths: string[] }>, cwd?: string): string[] {
47
55
  if (warnings.length === 0) return ["- Duplicate Takomi extensions: none detected"];
48
- const lines = ["- Duplicate Takomi extensions detected:"];
56
+ const sourceRepo = cwd ? isTakomiSourceRepo(cwd) : false;
57
+ const lines = [sourceRepo
58
+ ? "- Duplicate Takomi extensions detected (expected when running normal Pi inside the Takomi source repo):"
59
+ : "- Duplicate Takomi extensions detected:"];
49
60
  for (const warning of warnings) {
50
61
  lines.push(` - ${warning.toolName}`);
51
62
  for (const filePath of warning.paths) lines.push(` - ${filePath}`);
@@ -11,6 +11,7 @@ import { registerDiagnostics } from "./diagnostics-tools";
11
11
  import { installPrerequisiteGates } from "./prerequisite-gates";
12
12
  import { installModelPolicyGate } from "./model-policy-gate";
13
13
  import { detectDuplicateTakomiExtensions } from "./extension-conflicts";
14
+ import { loadTakomiModelRoutingSnapshot, renderCompactTakomiModelRoutingSummary } from "../takomi-runtime/model-routing-defaults";
14
15
  import type { ContextManagerConfig } from "./types";
15
16
 
16
17
  export default function takomiContextManager(pi: ExtensionAPI) {
@@ -33,6 +34,8 @@ export default function takomiContextManager(pi: ExtensionAPI) {
33
34
 
34
35
  const candidates = findCandidates(event.prompt, state.skills, config);
35
36
  const rewrite = rewritePrompt(event.systemPrompt, state.skills, candidates, config);
37
+ const routingSummary = renderCompactTakomiModelRoutingSummary(await loadTakomiModelRoutingSnapshot(ctx.cwd));
38
+ const rewrittenPrompt = routingSummary ? `${rewrite.prompt}\n\n${routingSummary}` : rewrite.prompt;
36
39
  state.report = {
37
40
  ...state.report,
38
41
  timestamp: new Date().toISOString(),
@@ -43,14 +46,14 @@ export default function takomiContextManager(pi: ExtensionAPI) {
43
46
  duplicateExtensionWarnings,
44
47
  promptRewrite: {
45
48
  attempted: true,
46
- changed: rewrite.changed,
49
+ changed: rewrite.changed || Boolean(routingSummary),
47
50
  originalLength: event.systemPrompt.length,
48
- rewrittenLength: rewrite.prompt.length,
51
+ rewrittenLength: rewrittenPrompt.length,
49
52
  removedSections: rewrite.removedSections,
50
53
  warnings: rewrite.warnings,
51
54
  },
52
55
  };
53
56
 
54
- return { systemPrompt: rewrite.prompt };
57
+ return { systemPrompt: rewrittenPrompt };
55
58
  });
56
59
  }
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
5
  import type { ContextManagerState } from "./state";
6
6
  import { recordBlocked } from "./state";
7
7
  import { resolveTakomiRoutingPolicy } from "../takomi-runtime/routing-policy";
8
+ import { approvedModelEquivalent, isTakomiModelApproved } from "../takomi-runtime/model-routing-defaults";
8
9
 
9
10
  type Settings = {
10
11
  takomi?: { modelRoutingPolicyFile?: string };
@@ -79,12 +80,21 @@ function isModelLike(value: string): boolean {
79
80
  || lower.includes("lmstudio/");
80
81
  }
81
82
 
83
+ function extractPreferredProvider(text: string): string | undefined {
84
+ const match = text.match(/(?:preferred|default)\s+(?:provider|router)(?:\s*\/\s*(?:provider|router))?\s*:\s*([a-z0-9-]+)/i)
85
+ ?? text.match(/use\s+([a-z0-9-]+)\s+as\s+(?:the\s+)?(?:provider|router)/i);
86
+ return match?.[1];
87
+ }
88
+
82
89
  function collectModelsFromPolicy(text: string): string[] {
90
+ // Providerless names such as "GPT-5.5" are intent labels unless the policy
91
+ // declares a preferred provider/router header.
83
92
  const explicit = (text.match(/[a-z0-9-]+\/[a-z0-9._-]+/gi) ?? []).filter(isModelLike);
93
+ const preferredProvider = extractPreferredProvider(text);
84
94
  const inferred: string[] = [];
85
- if (/gpt[- ]?5\.5/i.test(text)) inferred.push("oauth-router/gpt-5.5");
86
- if (/gpt[- ]?5\.4(?!\s*mini)/i.test(text)) inferred.push("oauth-router/gpt-5.4");
87
- if (/gpt[- ]?5\.4\s*mini/i.test(text)) inferred.push("oauth-router/gpt-5.4-mini");
95
+ if (preferredProvider && /gpt[- ]?5\.5/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.5`);
96
+ if (preferredProvider && /gpt[- ]?5\.4(?!\s*mini)/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4`);
97
+ if (preferredProvider && /gpt[- ]?5\.4\s*mini/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4-mini`);
88
98
  return unique([...explicit, ...inferred]);
89
99
  }
90
100
 
@@ -98,8 +108,8 @@ async function loadSnapshot(cwd: string): Promise<ModelPolicySnapshot> {
98
108
  return { approvedModels, preferredModels: settingsModels.length ? unique(settingsModels) : approvedModels, sourceFiles };
99
109
  }
100
110
 
101
- function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<string, unknown>; key: string; value: string }> {
102
- const refs: Array<{ holder: Record<string, unknown>; key: string; value: string }> = [];
111
+ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<string, unknown>; key: string; value: string; index?: number }> {
112
+ const refs: Array<{ holder: Record<string, unknown>; key: string; value: string; index?: number }> = [];
103
113
  function visit(value: unknown): void {
104
114
  if (!value || typeof value !== "object") return;
105
115
  if (Array.isArray(value)) {
@@ -111,7 +121,9 @@ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<strin
111
121
  if (typeof record[key] === "string") refs.push({ holder: record, key, value: record[key] });
112
122
  }
113
123
  if (Array.isArray(record.fallbackModels)) {
114
- record.fallbackModels = record.fallbackModels.filter((item) => typeof item === "string");
124
+ const fallbackModels = record.fallbackModels.filter((item): item is string => typeof item === "string");
125
+ record.fallbackModels = fallbackModels;
126
+ fallbackModels.forEach((value: string, index: number) => refs.push({ holder: record, key: "fallbackModels", value, index }));
115
127
  }
116
128
  for (const key of ["tasks", "chain"]) visit(record[key]);
117
129
  }
@@ -119,9 +131,12 @@ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<strin
119
131
  return refs;
120
132
  }
121
133
 
122
- function approvedEquivalent(requested: string, approved: string[]): string | undefined {
123
- const requestedFamily = modelFamily(requested);
124
- return approved.find((candidate) => modelFamily(candidate) === requestedFamily);
134
+ function setModelRef(ref: { holder: Record<string, unknown>; key: string; value: string; index?: number }, value: string): void {
135
+ if (ref.key === "fallbackModels" && typeof ref.index === "number" && Array.isArray(ref.holder.fallbackModels)) {
136
+ ref.holder.fallbackModels[ref.index] = value;
137
+ return;
138
+ }
139
+ ref.holder[ref.key] = value;
125
140
  }
126
141
 
127
142
  function isModelFailure(text: string): boolean {
@@ -170,16 +185,16 @@ export function installModelPolicyGate(pi: ExtensionAPI, state: ContextManagerSt
170
185
  const refs = collectRequestedModelRefs(event.input);
171
186
  const corrections: string[] = [];
172
187
  for (const ref of refs) {
173
- if (approved.includes(ref.value)) continue;
174
- const equivalent = approvedEquivalent(ref.value, approved);
188
+ if (isTakomiModelApproved(ref.value, approved)) continue;
189
+ const equivalent = approvedModelEquivalent(ref.value, approved);
175
190
  if (equivalent) {
176
- ref.holder[ref.key] = equivalent;
191
+ setModelRef(ref, equivalent);
177
192
  corrections.push(`${ref.value} -> ${equivalent}`);
178
193
  continue;
179
194
  }
180
195
  const recovery = await askForInvalidModelRecovery(ctx, ref.value, approved);
181
196
  if (recovery.action === "retry") {
182
- ref.holder[ref.key] = recovery.model;
197
+ setModelRef(ref, recovery.model);
183
198
  corrections.push(`${ref.value} -> ${recovery.model} (user selected recovery)`);
184
199
  continue;
185
200
  }
@@ -7,7 +7,7 @@ import type {
7
7
  } from "../../../src/pi-takomi-core";
8
8
  import { commandHelp, completions, statusText, workflowPrompt } from "./command-text";
9
9
  import type { TakomiSubagentController } from "./subagent-types";
10
- import { installTakomiRoutingPolicy, resolveTakomiRoutingPolicy, type RoutingPolicyInstallScope } from "./routing-policy";
10
+ import { previewTakomiRoutingPolicy, renderRoutingPolicyPreview, resolveTakomiRoutingPolicy, type RoutingPolicyInstallScope } from "./routing-policy";
11
11
  import { collectTakomiStats, renderTakomiStats } from "./takomi-stats.js";
12
12
 
13
13
  export type TakomiRuntimeCommandState = {
@@ -158,12 +158,29 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
158
158
  const policyText = scopeMatch?.[2] ?? trimmed.replace(/^set\s+/i, "");
159
159
 
160
160
  try {
161
- const result = await installTakomiRoutingPolicy(ctx.cwd, policyText, { scope });
162
- const detected = result.detectedDefaults.length ? `\n\nDetected defaults:\n- ${result.detectedDefaults.join("\n- ")}` : "\n\nNo model names were auto-detected; saved policy only.";
163
- const overrideNote = scope === "global"
164
- ? "\n\nThis will be used by every project unless that project has its own .pi/takomi/model-routing.md override."
165
- : "\n\nThis project-local policy overrides the global policy for the current project.";
166
- ctx.ui.notify(`Takomi routing policy updated (${scope}).\n\nPolicy: ${result.policyPath}\nSettings: ${result.settingsPath}${detected}${overrideNote}`, "info");
161
+ const preview = previewTakomiRoutingPolicy(ctx.cwd, policyText, { scope });
162
+ const reviewPrompt = [
163
+ "Review this Takomi routing policy extraction before it is saved.",
164
+ "",
165
+ "Rules:",
166
+ "- Do not invent providers or model IDs not grounded in the policy.",
167
+ "- Providerless names like GPT-5.5 are routing intent unless a preferred provider/router is declared.",
168
+ "- Valid Takomi roles are: general, orchestrator, architect, designer, coder, reviewer.",
169
+ "- If the extraction is correct and safe, call takomi_apply_routing_policy with the exact policyText and scope below.",
170
+ "- If it is ambiguous or wrong, explain what the user should clarify and do not call the tool.",
171
+ "",
172
+ "Deterministic extraction:",
173
+ renderRoutingPolicyPreview(preview),
174
+ "",
175
+ "Original policy text:",
176
+ "```",
177
+ preview.policy,
178
+ "```",
179
+ "",
180
+ `Tool call to apply if safe: takomi_apply_routing_policy({ scope: ${JSON.stringify(scope)}, policyText: <original policy text> })`,
181
+ ].join("\n");
182
+ ctx.ui.notify("Takomi routing extraction prepared. Sending it to the active model for review before saving.", "info");
183
+ pi.sendUserMessage(reviewPrompt);
167
184
  } catch (error) {
168
185
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
169
186
  }