takomi 2.1.36 → 2.1.37

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 (28) hide show
  1. package/.pi/README.md +6 -3
  2. package/.pi/extensions/oauth-router/commands.ts +36 -35
  3. package/.pi/extensions/oauth-router/index.ts +8 -8
  4. package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +40 -7
  5. package/.pi/extensions/takomi-context-manager/diagnostics.ts +534 -55
  6. package/.pi/extensions/takomi-context-manager/index.ts +14 -2
  7. package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +16 -4
  8. package/.pi/extensions/takomi-context-manager/policy-tools.ts +7 -0
  9. package/.pi/extensions/takomi-context-manager/prerequisite-gates.ts +2 -0
  10. package/.pi/extensions/takomi-context-manager/session-state.ts +160 -0
  11. package/.pi/extensions/takomi-context-manager/skill-registry.ts +120 -0
  12. package/.pi/extensions/takomi-context-manager/skill-tools.ts +26 -3
  13. package/.pi/extensions/takomi-context-manager/state.ts +11 -1
  14. package/.pi/extensions/takomi-context-manager/types.ts +9 -1
  15. package/.pi/extensions/takomi-runtime/command-text.ts +2 -1
  16. package/.pi/extensions/takomi-runtime/commands.ts +13 -1
  17. package/.pi/extensions/takomi-subagents/index.ts +30 -3
  18. package/.pi/extensions/takomi-subagents/native-render.ts +3 -1
  19. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +18 -3
  20. package/.pi/extensions/takomi-subagents/tool-runner.ts +38 -3
  21. package/README.md +3 -0
  22. package/assets/.agent/skills/exam-creator-skill/SKILL.md +182 -0
  23. package/assets/.agent/skills/exam-creator-skill/references/model-routing-policy.md +48 -0
  24. package/assets/.agent/skills/exam-creator-skill/references/workflow-rulebook.md +115 -0
  25. package/package.json +2 -2
  26. package/src/cli.js +38 -1
  27. package/src/doctor.js +7 -1
  28. package/src/pi-harness.js +96 -0
@@ -1,55 +1,534 @@
1
- import type { ContextManagerState } from "./state";
2
- import { sortedSkills } from "./skill-registry";
3
- import { syncReportLedger } from "./state";
4
- import { renderDuplicateExtensionGuidance } from "./extension-conflicts";
5
-
6
- export function renderReport(state: ContextManagerState, verbose = false): string {
7
- syncReportLedger(state);
8
- const report = state.report;
9
- const reduction = report.promptRewrite.originalLength > 0
10
- ? Math.round((1 - report.promptRewrite.rewrittenLength / report.promptRewrite.originalLength) * 1000) / 10
11
- : 0;
12
- const lines = [
13
- "Takomi Context Manager Report",
14
- `- Timestamp: ${report.timestamp}`,
15
- `- CWD: ${report.cwd || "(unknown)"}`,
16
- "- Scope: latest context-manager prompt rewrite and context-manager tool state only; this is not a full Pi session transcript.",
17
- `- Skill count discovered: ${report.skillCount}`,
18
- `- Prompt rewrite attempted: ${report.promptRewrite.attempted ? "yes" : "no"}`,
19
- `- Prompt changed: ${report.promptRewrite.changed ? "yes" : "no"}`,
20
- `- Original prompt length observed by context manager: ${report.promptRewrite.originalLength} chars`,
21
- `- Rewritten prompt length returned by context manager: ${report.promptRewrite.rewrittenLength} chars`,
22
- `- Context-manager prompt reduction: ${reduction}%`,
23
- `- Sections compressed/removed by context manager: ${report.promptRewrite.removedSections.join(", ") || "none"}`,
24
- `- Skills loaded through skill_load: ${report.loadedByTool.join(", ") || "none"}`,
25
- `- Policies loaded through policy_load/gates: ${report.loadedPolicies.join(", ") || "none"}`,
26
- `- Context-manager tracked read files: ${report.readFiles.length}`,
27
- `- Context-manager tracked edited files: ${report.editedFiles.length}`,
28
- `- Context-manager tracked written files: ${report.writtenFiles.length}`,
29
- `- Blocked context-manager gated actions: ${report.blockedActions.length}`,
30
- `- Duplicate Takomi extension tool-name warnings: ${report.duplicateExtensionWarnings.length}`,
31
- `- Context-manager tool calls: skill_index=${report.toolCalls.skillIndex}, skill_manifest=${report.toolCalls.skillManifest}, skill_load=${report.toolCalls.skillLoad}, policy_manifest=${report.toolCalls.policyManifest}, policy_load=${report.toolCalls.policyLoad}, context_report=${report.toolCalls.contextReport}`,
32
- `- Rewrite warnings: ${report.promptRewrite.warnings.join("; ") || "none"}`,
33
- "- Note: native Pi read/edit/write/bash calls are not counted here unless a context-manager tool explicitly records them.",
34
- ];
35
- if (report.candidates.length > 0) {
36
- lines.push("- Candidates:");
37
- for (const candidate of report.candidates) {
38
- lines.push(` - ${candidate.name} (${candidate.confidence}, ${candidate.score}): ${candidate.reasons.join("; ")}`);
39
- }
40
- } else {
41
- lines.push("- Candidates: none");
42
- }
43
- if (report.duplicateExtensionWarnings.length > 0 || verbose) {
44
- lines.push("", "Extension Conflict Diagnostics:", ...renderDuplicateExtensionGuidance(report.duplicateExtensionWarnings, report.cwd));
45
- }
46
- if (verbose) {
47
- lines.push("", "Skill Index:", ...sortedSkills(state.skills).map((skill) => `- ${skill.name}`));
48
- lines.push("", "Policy Packs:", ...[...state.policies.values()].map((policy) => `- ${policy.name}: ${policy.description}`));
49
- if (report.blockedActions.length > 0) {
50
- lines.push("", "Blocked Actions:");
51
- for (const action of report.blockedActions.slice(-10)) lines.push(`- ${action.timestamp} ${action.toolName}: ${action.reason}`);
52
- }
53
- }
54
- return lines.join("\n");
55
- }
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import type { ContextManagerState } from "./state";
4
+ import { sortedSkills } from "./skill-registry";
5
+ import { syncReportLedger } from "./state";
6
+ import { renderDuplicateExtensionGuidance } from "./extension-conflicts";
7
+
8
+ export type ContextReportMode = "summary" | "verbose" | "problems";
9
+
10
+ type IssueSeverity = "info" | "attention" | "blocked" | "failed";
11
+
12
+ type ReportIssue = {
13
+ severity: IssueSeverity;
14
+ title: string;
15
+ impact: string;
16
+ fix: string;
17
+ details?: string[];
18
+ timestamp?: string;
19
+ };
20
+
21
+ function normalizeMode(modeOrVerbose: ContextReportMode | boolean = "summary"): ContextReportMode {
22
+ if (modeOrVerbose === true) return "verbose";
23
+ if (modeOrVerbose === false) return "summary";
24
+ return modeOrVerbose;
25
+ }
26
+
27
+ function projectName(cwd: string): string {
28
+ return cwd ? path.basename(cwd) || cwd : "unknown";
29
+ }
30
+
31
+ function fmtNumber(value: number): string {
32
+ return value.toLocaleString("en-US");
33
+ }
34
+
35
+ function reductionPercent(originalLength: number, rewrittenLength: number): number {
36
+ if (originalLength <= 0) return 0;
37
+ return Math.round((1 - rewrittenLength / originalLength) * 1000) / 10;
38
+ }
39
+
40
+ function statusIcon(severity: IssueSeverity): string {
41
+ if (severity === "failed") return "❌";
42
+ if (severity === "blocked") return "⛔";
43
+ if (severity === "attention") return "⚠";
44
+ return "";
45
+ }
46
+
47
+ function issueRank(severity: IssueSeverity): number {
48
+ if (severity === "failed") return 0;
49
+ if (severity === "blocked") return 1;
50
+ if (severity === "attention") return 2;
51
+ return 3;
52
+ }
53
+
54
+ function collectIssues(state: ContextManagerState): ReportIssue[] {
55
+ const report = state.report;
56
+ const issues: ReportIssue[] = [];
57
+
58
+ if (report.promptRewrite.attempted && report.promptRewrite.rewrittenLength === 0) {
59
+ issues.push({
60
+ severity: "failed",
61
+ title: "Prompt rewrite returned an empty prompt",
62
+ impact: "The context manager may not have returned usable prompt context.",
63
+ fix: "Reload Pi and retry. If it repeats, inspect prompt-rewriter warnings and recent extension changes.",
64
+ });
65
+ }
66
+
67
+ for (const warning of report.promptRewrite.warnings) {
68
+ issues.push({
69
+ severity: "attention",
70
+ title: "Prompt rewrite warning",
71
+ impact: warning,
72
+ fix: "Review prompt rewrite configuration or run context_report({ mode: \"verbose\" }) for details.",
73
+ });
74
+ }
75
+
76
+ for (const action of report.blockedActions.slice(-5)) {
77
+ issues.push({
78
+ severity: "blocked",
79
+ title: `${action.toolName} was blocked by a context-manager gate`,
80
+ impact: "A required prerequisite prevented the operation from running at that point in the session.",
81
+ fix: action.reason.includes("Retry") || action.reason.includes("retry")
82
+ ? "Follow the gate guidance and retry the original operation if it is still needed."
83
+ : "Review the blocked action details in verbose mode before retrying.",
84
+ details: [action.reason.split(/\r?\n/)[0]].filter(Boolean),
85
+ timestamp: action.timestamp,
86
+ });
87
+ }
88
+
89
+ for (const correction of report.modelRoutingCorrections ?? []) {
90
+ issues.push({
91
+ severity: "attention",
92
+ title: "Model routing was corrected",
93
+ impact: `${correction.from} was replaced with ${correction.to}.`,
94
+ fix: "Use the approved provider-qualified model ID in future takomi_subagent calls.",
95
+ timestamp: correction.timestamp,
96
+ });
97
+ }
98
+
99
+ if (report.duplicateExtensionWarnings.length > 0) {
100
+ const tools = report.duplicateExtensionWarnings.map((warning) => warning.toolName);
101
+ const paths = [...new Set(report.duplicateExtensionWarnings.flatMap((warning) => warning.paths))];
102
+ issues.push({
103
+ severity: "attention",
104
+ title: "Duplicate Takomi extension registrations",
105
+ impact: `${tools.length} Takomi tool${tools.length === 1 ? "" : "s"} are registered from both global and project extension paths; behavior may depend on load order.`,
106
+ fix: "Use scripts/pi-dev.ps1 while developing Takomi, or remove/sync the stale duplicate extension source for normal Pi sessions.",
107
+ details: [`Tools: ${tools.join(", ")}`, ...paths],
108
+ });
109
+ }
110
+
111
+ if (report.skillCount === 0) {
112
+ issues.push({
113
+ severity: "info",
114
+ title: "No skills discovered by context-manager",
115
+ impact: "This only describes context-manager discovery, not necessarily all Pi skill state.",
116
+ fix: "Run `/reload` after skill or extension changes, then retry context_report.",
117
+ });
118
+ }
119
+
120
+ if (report.sessionRestore.attempted && !report.sessionRestore.restored && report.toolCalls.contextReport > 1) {
121
+ issues.push({
122
+ severity: "info",
123
+ title: "No previous context-manager history restored",
124
+ impact: "The report is based on current in-memory state and discovery only.",
125
+ fix: "No action is needed unless you expected earlier context-manager tool calls in this Pi session.",
126
+ });
127
+ }
128
+
129
+ return issues.sort((a, b) => issueRank(a.severity) - issueRank(b.severity) || (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
130
+ }
131
+
132
+ function overallStatus(issues: ReportIssue[]): { label: string; next: string; warningCount: number } {
133
+ const actionable = issues.filter((issue) => issue.severity !== "info");
134
+ const highest = issues[0]?.severity;
135
+ if (!issues.length) return { label: "✅ Healthy", next: "No action needed.", warningCount: 0 };
136
+ if (!actionable.length) return { label: "ℹ Informational", next: issues[0].fix, warningCount: 0 };
137
+ if (highest === "failed") return { label: "❌ Failed", next: actionable[0].fix, warningCount: actionable.length };
138
+ if (highest === "blocked") return { label: "⛔ Blocked", next: actionable[0].fix, warningCount: actionable.length };
139
+ return { label: "⚠ Attention Required", next: actionable[0].fix, warningCount: actionable.length };
140
+ }
141
+
142
+ function promptRewriteState(state: ContextManagerState): string {
143
+ const rewrite = state.report.promptRewrite;
144
+ if (!rewrite.attempted) return "Not run yet";
145
+ if (rewrite.rewrittenLength === 0) return "Failed";
146
+ if (!rewrite.changed) return "Checked · no change";
147
+ return `Applied · ${reductionPercent(rewrite.originalLength, rewrite.rewrittenLength)}% reduction`;
148
+ }
149
+
150
+ function restoreState(state: ContextManagerState): string {
151
+ const restore = state.report.sessionRestore;
152
+ if (!restore.attempted) return "Not checked yet";
153
+ if (!restore.restored) return "No prior history found";
154
+ const sources: string[] = [];
155
+ if (restore.snapshotCount > 0) sources.push("snapshots");
156
+ if (restore.toolResultCount > 0) sources.push("tool history");
157
+ return `Restored from ${sources.join(" and ") || "session history"}`;
158
+ }
159
+
160
+ function policyState(state: ContextManagerState): string {
161
+ const available = state.policies.size;
162
+ const loaded = state.report.loadedPolicies.length;
163
+ if (available === 0 && loaded === 0) return "None discovered";
164
+ if (loaded === 0) return `${available} available · none loaded`;
165
+ return `${loaded} loaded`;
166
+ }
167
+
168
+ function contextToolCalls(state: ContextManagerState): number {
169
+ const calls = state.report.toolCalls;
170
+ return calls.skillIndex + calls.skillManifest + calls.skillLoad + calls.policyManifest + calls.policyLoad + calls.contextReport;
171
+ }
172
+
173
+ function renderHeader(state: ContextManagerState, status: ReturnType<typeof overallStatus>, title = "Takomi Context Report"): string[] {
174
+ const report = state.report;
175
+ return [
176
+ title,
177
+ "─".repeat(title.length),
178
+ `Status : ${status.label}`,
179
+ `Project: ${projectName(report.cwd)}`,
180
+ `Updated: ${report.timestamp}`,
181
+ ];
182
+ }
183
+
184
+ function renderSummaryLine(label: string, value: string): string {
185
+ return ` ${label.padEnd(16)} ${value}`;
186
+ }
187
+
188
+ function renderSummary(state: ContextManagerState): string {
189
+ syncReportLedger(state);
190
+ const issues = collectIssues(state);
191
+ const status = overallStatus(issues);
192
+ const report = state.report;
193
+ const lines = [
194
+ ...renderHeader(state, status),
195
+ "",
196
+ "Overview",
197
+ renderSummaryLine("Session restore", restoreState(state)),
198
+ renderSummaryLine("Skills", `${fmtNumber(report.skillCount)} discovered, ${report.loadedByTool.length} loaded`),
199
+ renderSummaryLine("Policies", policyState(state)),
200
+ renderSummaryLine("Prompt rewrite", promptRewriteState(state)),
201
+ renderSummaryLine("Warnings", status.warningCount === 0 ? "None" : String(status.warningCount)),
202
+ ];
203
+
204
+ const visibleProblems = issues.filter((issue) => issue.severity !== "info").slice(0, 3);
205
+ if (visibleProblems.length > 0) {
206
+ lines.push("", "Needs attention");
207
+ visibleProblems.forEach((issue, index) => {
208
+ lines.push(` ${index + 1}. ${statusIcon(issue.severity)} ${issue.title}`);
209
+ lines.push(` ${issue.fix.replace(/`/g, "")}`);
210
+ if (issue.details?.[0]) lines.push(` ${issue.details[0].replace(/`/g, "")}`);
211
+ });
212
+ const remaining = issues.filter((issue) => issue.severity !== "info").length - visibleProblems.length;
213
+ if (remaining > 0) lines.push(` …and ${remaining} more. Run context_report({ mode: "problems" }).`);
214
+ }
215
+
216
+ lines.push("", `Next: ${status.next.replace(/`/g, "")}`);
217
+ return lines.join("\n");
218
+ }
219
+
220
+ function renderSessionRestore(state: ContextManagerState): string[] {
221
+ const restore = state.report.sessionRestore;
222
+ return [
223
+ "## Session Restore",
224
+ "",
225
+ `**Result:** ${restore.restored ? "✅ Restored" : restore.attempted ? "ℹ No prior history found" : "ℹ Not checked yet"}`,
226
+ "",
227
+ "| Source | Result |",
228
+ "|---|---:|",
229
+ "| Current in-memory state | Active |",
230
+ `| Context-manager snapshots | ${restore.snapshotCount} restored |`,
231
+ `| Pi tool results | ${restore.toolResultCount} scanned |`,
232
+ `| Filesystem skill discovery | ${state.report.skillCount > 0 ? "Used" : "No skills found"} |`,
233
+ "",
234
+ restore.note,
235
+ ];
236
+ }
237
+
238
+ function renderSkills(state: ContextManagerState): string[] {
239
+ const report = state.report;
240
+ const sourceCounts = sortedSkills(state.skills).reduce((counts, skill) => {
241
+ counts.set(skill.source, (counts.get(skill.source) ?? 0) + 1);
242
+ return counts;
243
+ }, new Map<string, number>());
244
+ const loaded = report.loadedByTool.length ? report.loadedByTool.map((skill) => `- \`${skill}\``) : ["No skills have been loaded through `skill_load` in restored/current context-manager state."];
245
+ const candidateRows = report.candidates.length
246
+ ? report.candidates.map((candidate) => `| \`${candidate.name}\` | ${candidate.confidence} | ${report.loadedByTool.includes(candidate.name) ? "Loaded" : "Not loaded"} |`)
247
+ : ["No candidate skills recorded for the latest prompt rewrite."];
248
+ const sourceRows = [...sourceCounts.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([source, count]) => `| ${source} | ${count} |`);
249
+
250
+ return [
251
+ "## Skills",
252
+ "",
253
+ `**Skills discovered:** ${fmtNumber(report.skillCount)} `,
254
+ `**Skills loaded this session:** ${report.loadedByTool.length}`,
255
+ "",
256
+ "### Loaded Skills",
257
+ "",
258
+ ...loaded,
259
+ "",
260
+ "### Candidate Skills",
261
+ "",
262
+ ...(report.candidates.length ? ["| Skill | Confidence | State |", "|---|---:|---|", ...candidateRows] : candidateRows),
263
+ "",
264
+ "### Discovery Sources",
265
+ "",
266
+ ...(sourceRows.length ? ["| Source | Skills Found |", "|---|---:|", ...sourceRows] : ["No discovery sources recorded."]),
267
+ ];
268
+ }
269
+
270
+ function renderPoliciesSection(state: ContextManagerState): string[] {
271
+ const policies = [...state.policies.values()].sort((a, b) => a.name.localeCompare(b.name));
272
+ const loaded = state.report.loadedPolicies.length ? state.report.loadedPolicies.map((policy) => `- \`${policy}\``) : ["No policies loaded in restored/current context-manager state."];
273
+ const availableRows = policies.map((policy) => `| \`${policy.name}\` | ${policy.description.replace(/\|/g, "\\|")} | ${policy.path ? `\`${policy.path}\`` : "generated/default"} |`);
274
+ const modelRouting = policies.find((policy) => policy.name === "model-routing");
275
+
276
+ return [
277
+ "## Policies",
278
+ "",
279
+ "### Loaded Policies",
280
+ "",
281
+ ...loaded,
282
+ "",
283
+ "### Available Policies",
284
+ "",
285
+ ...(availableRows.length ? ["| Policy | Purpose | Source |", "|---|---|---|", ...availableRows] : ["No policy packs discovered."]),
286
+ "",
287
+ "### Gated Tools",
288
+ "",
289
+ ...(modelRouting
290
+ ? [
291
+ "| Tool | Required Policy | State |",
292
+ "|---|---|---|",
293
+ "| `takomi_subagent` | `model-routing` | " + (state.report.loadedPolicies.includes("model-routing") ? "Ready" : "Loads on demand / gated") + " |",
294
+ ]
295
+ : ["No gated tool policy mapping discovered."]),
296
+ ];
297
+ }
298
+
299
+ function renderPromptRewrite(state: ContextManagerState): string[] {
300
+ const rewrite = state.report.promptRewrite;
301
+ const reduction = reductionPercent(rewrite.originalLength, rewrite.rewrittenLength);
302
+ return [
303
+ "## Prompt Rewrite",
304
+ "",
305
+ `**Result:** ${rewrite.attempted ? rewrite.rewrittenLength === 0 ? "❌ Failed" : rewrite.changed ? "✅ Applied" : "ℹ Checked, no change" : "ℹ Not run yet"}`,
306
+ "",
307
+ "| Metric | Value |",
308
+ "|---|---:|",
309
+ `| Original length | ${fmtNumber(rewrite.originalLength)} characters |`,
310
+ `| Rewritten length | ${fmtNumber(rewrite.rewrittenLength)} characters |`,
311
+ `| Reduction | ${reduction}% |`,
312
+ "",
313
+ rewrite.removedSections.length ? "Compressed areas:" : "No compressed areas recorded.",
314
+ ...rewrite.removedSections.map((section) => `- ${section}`),
315
+ "",
316
+ rewrite.warnings.length ? "Rewrite warnings:" : "No rewrite warnings were recorded.",
317
+ ...rewrite.warnings.map((warning) => `- ${warning}`),
318
+ ];
319
+ }
320
+
321
+ function renderToolUsage(state: ContextManagerState): string[] {
322
+ const calls = state.report.toolCalls;
323
+ return [
324
+ "## Context-Manager Tool Usage",
325
+ "",
326
+ "| Tool | Calls |",
327
+ "|---|---:|",
328
+ `| \`skill_index\` | ${calls.skillIndex} |`,
329
+ `| \`skill_manifest\` | ${calls.skillManifest} |`,
330
+ `| \`skill_load\` | ${calls.skillLoad} |`,
331
+ `| \`policy_manifest\` | ${calls.policyManifest} |`,
332
+ `| \`policy_load\` | ${calls.policyLoad} |`,
333
+ `| \`context_report\` | ${calls.contextReport} |`,
334
+ "",
335
+ `Total context-manager tool calls observed: ${contextToolCalls(state)}.` ,
336
+ ];
337
+ }
338
+
339
+ function renderGatesAndCorrections(state: ContextManagerState): string[] {
340
+ const report = state.report;
341
+ const lines = [
342
+ "## Gates and Corrections",
343
+ "",
344
+ `**Blocked actions:** ${report.blockedActions.length} `,
345
+ `**Model-routing corrections:** ${(report.modelRoutingCorrections ?? []).length}`,
346
+ ];
347
+ if (report.blockedActions.length === 0 && !(report.modelRoutingCorrections ?? []).length) {
348
+ lines.push("", "No blocked actions or model-routing corrections recorded.");
349
+ return lines;
350
+ }
351
+ for (const action of report.blockedActions.slice(-5)) {
352
+ lines.push("", `### ${action.toolName}`, "", `- **Time:** ${action.timestamp}`, `- **Cause:** ${action.reason.split(/\r?\n/)[0] || "Context-manager gate blocked the action."}`, "- **Recovery:** Follow the gate guidance; retry may be required.");
353
+ }
354
+ for (const correction of (report.modelRoutingCorrections ?? []).slice(-5)) {
355
+ lines.push("", `### Model correction`, "", `- **Time:** ${correction.timestamp}`, `- **Change:** \`${correction.from}\` → \`${correction.to}\``, `- **Tool:** ${correction.toolName}`);
356
+ }
357
+ return lines;
358
+ }
359
+
360
+ function renderExtensionDiagnostics(state: ContextManagerState): string[] {
361
+ return [
362
+ "## Extension Diagnostics",
363
+ "",
364
+ ...(state.report.duplicateExtensionWarnings.length
365
+ ? renderDuplicateExtensionGuidance(state.report.duplicateExtensionWarnings, state.report.cwd)
366
+ : ["✅ No duplicate extension registrations detected."]),
367
+ ];
368
+ }
369
+
370
+ function renderFileLedger(state: ContextManagerState): string[] {
371
+ const report = state.report;
372
+ return [
373
+ "## Context-Manager File Ledger",
374
+ "",
375
+ "| Activity | Files |",
376
+ "|---|---:|",
377
+ `| Read | ${report.readFiles.length} |`,
378
+ `| Edited | ${report.editedFiles.length} |`,
379
+ `| Written | ${report.writtenFiles.length} |`,
380
+ "",
381
+ "This ledger only includes activity explicitly tracked by the context manager.",
382
+ ];
383
+ }
384
+
385
+ function renderScope(): string[] {
386
+ return [
387
+ "## Scope",
388
+ "",
389
+ "- This report covers Takomi context-manager state.",
390
+ "- It is not a complete Pi transcript or activity audit.",
391
+ "- Pi Alt-C remains the source of truth for token and context-window statistics.",
392
+ "- Native Pi tools may not appear unless their activity is mirrored into context-manager state.",
393
+ "- Run `/reload` after installing or updating extensions.",
394
+ ];
395
+ }
396
+
397
+ function displayPath(filePath: string | undefined, cwd: string): string {
398
+ if (!filePath) return "generated/default";
399
+ const relative = path.relative(cwd, filePath);
400
+ if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) return relative;
401
+ const home = os.homedir();
402
+ if (filePath.startsWith(home)) return `~${filePath.slice(home.length)}`;
403
+ return filePath;
404
+ }
405
+
406
+ function clip(value: string, max = 110): string {
407
+ const compact = value.replace(/\s+/g, " ").trim();
408
+ return compact.length > max ? `${compact.slice(0, max - 1)}…` : compact;
409
+ }
410
+
411
+ function renderVerbose(state: ContextManagerState): string {
412
+ syncReportLedger(state);
413
+ const issues = collectIssues(state);
414
+ const status = overallStatus(issues);
415
+ const report = state.report;
416
+ const rewrite = report.promptRewrite;
417
+ const restore = report.sessionRestore;
418
+ const sourceCounts = sortedSkills(state.skills).reduce((counts, skill) => {
419
+ counts.set(skill.source, (counts.get(skill.source) ?? 0) + 1);
420
+ return counts;
421
+ }, new Map<string, number>());
422
+ const calls = report.toolCalls;
423
+ const lines = [
424
+ ...renderHeader(state, status, "Takomi Context Report — Verbose"),
425
+ renderSummaryLine("Warnings", status.warningCount === 0 ? "None" : String(status.warningCount)),
426
+ "",
427
+ "Overview",
428
+ renderSummaryLine("Session restore", restoreState(state)),
429
+ renderSummaryLine("Skills", `${fmtNumber(report.skillCount)} discovered, ${report.loadedByTool.length} loaded`),
430
+ renderSummaryLine("Policies", `${state.policies.size} available, ${report.loadedPolicies.length} loaded`),
431
+ renderSummaryLine("Prompt rewrite", promptRewriteState(state)),
432
+ renderSummaryLine("Gated actions", report.blockedActions.length ? `${report.blockedActions.length} recorded` : "None"),
433
+ renderSummaryLine("Extensions", report.duplicateExtensionWarnings.length ? `${report.duplicateExtensionWarnings.length} duplicate tool registrations` : "No conflicts"),
434
+ "",
435
+ "Session restore",
436
+ renderSummaryLine("Result", restore.restored ? "Restored" : restore.attempted ? "No prior history found" : "Not checked yet"),
437
+ renderSummaryLine("Snapshots", `${restore.snapshotCount} restored`),
438
+ renderSummaryLine("Tool results", `${restore.toolResultCount} scanned`),
439
+ renderSummaryLine("Skill discovery", report.skillCount > 0 ? "Filesystem scan used" : "No skills found"),
440
+ ` Note ${restore.note}`,
441
+ "",
442
+ "Skills",
443
+ renderSummaryLine("Discovered", fmtNumber(report.skillCount)),
444
+ renderSummaryLine("Loaded", String(report.loadedByTool.length)),
445
+ ...(report.loadedByTool.length ? [" Loaded skills", ...report.loadedByTool.map((skill) => ` - ${skill}`)] : [" Loaded skills None recorded"]),
446
+ ...(report.candidates.length ? [" Candidate skills", ...report.candidates.map((candidate) => ` - ${candidate.name} (${candidate.confidence}, ${report.loadedByTool.includes(candidate.name) ? "loaded" : "not loaded"})`)] : [" Candidate skills None recorded"]),
447
+ ...(sourceCounts.size ? [" Discovery sources", ...[...sourceCounts.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([source, count]) => ` - ${source}: ${count}`)] : [" Discovery sources None recorded"]),
448
+ "",
449
+ "Policies",
450
+ renderSummaryLine("Available", String(state.policies.size)),
451
+ renderSummaryLine("Loaded", report.loadedPolicies.length ? report.loadedPolicies.join(", ") : "None"),
452
+ ...(state.policies.size ? [" Available policies", ...[...state.policies.values()].sort((a, b) => a.name.localeCompare(b.name)).map((policy) => ` - ${policy.name}: ${clip(policy.description)}\n source: ${displayPath(policy.path, report.cwd)}`)] : [" Available policies None discovered"]),
453
+ " Gated tools",
454
+ state.policies.has("model-routing")
455
+ ? ` - takomi_subagent requires model-routing (${report.loadedPolicies.includes("model-routing") ? "ready" : "loads on demand / gated"})`
456
+ : " - No gated tool policy mapping discovered",
457
+ "",
458
+ "Prompt rewrite",
459
+ renderSummaryLine("Result", rewrite.attempted ? rewrite.rewrittenLength === 0 ? "Failed" : rewrite.changed ? "Applied" : "Checked, no change" : "Not run yet"),
460
+ renderSummaryLine("Original", `${fmtNumber(rewrite.originalLength)} chars`),
461
+ renderSummaryLine("Rewritten", `${fmtNumber(rewrite.rewrittenLength)} chars`),
462
+ renderSummaryLine("Reduction", `${reductionPercent(rewrite.originalLength, rewrite.rewrittenLength)}%`),
463
+ ...(rewrite.removedSections.length ? [" Compressed areas", ...rewrite.removedSections.map((section) => ` - ${section}`)] : [" Compressed areas None recorded"]),
464
+ ...(rewrite.warnings.length ? [" Rewrite warnings", ...rewrite.warnings.map((warning) => ` - ${warning}`)] : [" Rewrite warnings None"]),
465
+ "",
466
+ "Context-manager tool usage",
467
+ renderSummaryLine("skill_index", String(calls.skillIndex)),
468
+ renderSummaryLine("skill_manifest", String(calls.skillManifest)),
469
+ renderSummaryLine("skill_load", String(calls.skillLoad)),
470
+ renderSummaryLine("policy_manifest", String(calls.policyManifest)),
471
+ renderSummaryLine("policy_load", String(calls.policyLoad)),
472
+ renderSummaryLine("context_report", String(calls.contextReport)),
473
+ renderSummaryLine("Total", String(contextToolCalls(state))),
474
+ "",
475
+ "Gates and corrections",
476
+ renderSummaryLine("Blocked", String(report.blockedActions.length)),
477
+ renderSummaryLine("Corrections", String((report.modelRoutingCorrections ?? []).length)),
478
+ ...(report.blockedActions.length ? report.blockedActions.slice(-5).flatMap((action) => [
479
+ ` - ${action.toolName} at ${action.timestamp}`,
480
+ ` cause: ${action.reason.split(/\r?\n/)[0] || "Context-manager gate blocked the action."}`,
481
+ ]) : [" No blocked actions recorded"]),
482
+ ...((report.modelRoutingCorrections ?? []).length ? (report.modelRoutingCorrections ?? []).slice(-5).map((correction) => ` - ${correction.from} -> ${correction.to} (${correction.toolName}, ${correction.timestamp})`) : [" No model-routing corrections recorded"]),
483
+ "",
484
+ "Extension diagnostics",
485
+ ...(report.duplicateExtensionWarnings.length ? [
486
+ ` Duplicate registrations: ${report.duplicateExtensionWarnings.length} tool(s)`,
487
+ ...report.duplicateExtensionWarnings.map((warning) => ` - ${warning.toolName}\n global : ${displayPath(warning.paths[0], report.cwd)}\n project: ${displayPath(warning.paths[1], report.cwd)}`),
488
+ " Fix: use scripts/pi-dev.ps1 while developing Takomi, or remove/sync stale duplicates for normal Pi sessions.",
489
+ ] : [" No duplicate extension registrations detected"]),
490
+ "",
491
+ "Context-manager file ledger",
492
+ renderSummaryLine("Read", String(report.readFiles.length)),
493
+ renderSummaryLine("Edited", String(report.editedFiles.length)),
494
+ renderSummaryLine("Written", String(report.writtenFiles.length)),
495
+ " This ledger only includes activity explicitly tracked by the context manager.",
496
+ "",
497
+ "Scope",
498
+ " - Covers Takomi context-manager state only.",
499
+ " - Not a complete Pi transcript or activity audit.",
500
+ " - Pi Alt-C remains the source of truth for token/context-window statistics.",
501
+ " - Native Pi tools may not appear unless mirrored into context-manager state.",
502
+ " - Run /reload after installing or updating extensions.",
503
+ ];
504
+ return lines.join("\n");
505
+ }
506
+
507
+ function renderProblems(state: ContextManagerState): string {
508
+ syncReportLedger(state);
509
+ const issues = collectIssues(state).filter((issue) => issue.severity !== "info");
510
+ if (issues.length === 0) return ["Takomi Context Problems", "───────────────────────", "✅ No problems detected."].join("\n");
511
+ const status = overallStatus(issues);
512
+ const lines = [
513
+ "Takomi Context Problems",
514
+ "───────────────────────",
515
+ `Status: ${status.label}`,
516
+ `Issues: ${issues.length}`,
517
+ ];
518
+ for (const [index, issue] of issues.entries()) {
519
+ lines.push("", `${index + 1}. ${statusIcon(issue.severity)} ${issue.title}`, ` Impact: ${issue.impact.replace(/`/g, "")}`, ` Fix : ${issue.fix.replace(/`/g, "")}`);
520
+ if (issue.timestamp) lines.push(` Time : ${issue.timestamp}`);
521
+ if (issue.details?.length) {
522
+ lines.push(" Details:");
523
+ for (const detail of issue.details) lines.push(` - ${detail.replace(/`/g, "")}`);
524
+ }
525
+ }
526
+ return lines.join("\n");
527
+ }
528
+
529
+ export function renderReport(state: ContextManagerState, modeOrVerbose: ContextReportMode | boolean = "summary"): string {
530
+ const mode = normalizeMode(modeOrVerbose);
531
+ if (mode === "verbose") return renderVerbose(state);
532
+ if (mode === "problems") return renderProblems(state);
533
+ return renderSummary(state);
534
+ }
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { loadConfig, DEFAULT_CONFIG } from "./config";
3
3
  import { createState } from "./state";
4
- import { collectSkillsFromOptions, collectSkillsFromXml, mergeSkills } from "./skill-registry";
4
+ import { collectSkillsFromOptions, collectSkillsFromXml, discoverSkillsFromFilesystem, mergeSkills } from "./skill-registry";
5
5
  import { discoverPolicies } from "./policy-registry";
6
6
  import { findCandidates } from "./context-router";
7
7
  import { rewritePrompt } from "./prompt-rewriter";
@@ -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 { persistReportSnapshot, restoreReportFromSession } from "./session-state";
14
15
  import { loadTakomiModelRoutingSnapshot, renderCompactTakomiModelRoutingSummary } from "../takomi-runtime/model-routing-defaults";
15
16
  import type { ContextManagerConfig } from "./types";
16
17
 
@@ -24,13 +25,23 @@ export default function takomiContextManager(pi: ExtensionAPI) {
24
25
  installPrerequisiteGates(pi, state, () => config);
25
26
  installModelPolicyGate(pi, state);
26
27
 
28
+ pi.on("session_start", async (_event, ctx) => {
29
+ config = await loadConfig(ctx.cwd);
30
+ state.policies = await discoverPolicies(ctx.cwd, config);
31
+ state.skills = mergeSkills(await discoverSkillsFromFilesystem(ctx.cwd));
32
+ state.report.cwd = ctx.cwd;
33
+ state.report.skillCount = state.skills.size;
34
+ restoreReportFromSession(state, ctx);
35
+ });
36
+
27
37
  pi.on("before_agent_start", async (event, ctx) => {
28
38
  config = await loadConfig(ctx.cwd);
29
39
  state.policies = await discoverPolicies(ctx.cwd, config);
30
40
  const duplicateExtensionWarnings = await detectDuplicateTakomiExtensions(ctx.cwd);
31
41
  const optionSkills = collectSkillsFromOptions(event.systemPromptOptions);
32
42
  const xmlSkills = collectSkillsFromXml(event.systemPrompt);
33
- state.skills = mergeSkills([...optionSkills, ...xmlSkills]);
43
+ const filesystemSkills = await discoverSkillsFromFilesystem(ctx.cwd);
44
+ state.skills = mergeSkills([...filesystemSkills, ...optionSkills, ...xmlSkills]);
34
45
 
35
46
  const candidates = findCandidates(event.prompt, state.skills, config);
36
47
  const rewrite = rewritePrompt(event.systemPrompt, state.skills, candidates, config);
@@ -53,6 +64,7 @@ export default function takomiContextManager(pi: ExtensionAPI) {
53
64
  warnings: rewrite.warnings,
54
65
  },
55
66
  };
67
+ persistReportSnapshot(pi, state, "before_agent_start");
56
68
 
57
69
  return { systemPrompt: rewrittenPrompt };
58
70
  });