token-pilot 0.19.2 → 0.22.2
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/.claude-plugin/hooks/hooks.json +21 -0
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +129 -0
- package/README.md +172 -315
- package/dist/agents/tp-commit-writer.md +41 -0
- package/dist/agents/tp-dead-code-finder.md +43 -0
- package/dist/agents/tp-debugger.md +45 -0
- package/dist/agents/tp-impact-analyzer.md +44 -0
- package/dist/agents/tp-migration-scout.md +43 -0
- package/dist/agents/tp-onboard.md +40 -0
- package/dist/agents/tp-pr-reviewer.md +41 -0
- package/dist/agents/tp-refactor-planner.md +42 -0
- package/dist/agents/tp-run.md +48 -0
- package/dist/agents/tp-test-triage.md +40 -0
- package/dist/agents/tp-test-writer.md +46 -0
- package/dist/cli/agent-frontmatter.d.ts +48 -0
- package/dist/cli/agent-frontmatter.js +189 -0
- package/dist/cli/bless-agents.d.ts +65 -0
- package/dist/cli/bless-agents.js +307 -0
- package/dist/cli/claudeignore.d.ts +33 -0
- package/dist/cli/claudeignore.js +88 -0
- package/dist/cli/claudemd-hygiene.d.ts +26 -0
- package/dist/cli/claudemd-hygiene.js +43 -0
- package/dist/cli/doctor-drift.d.ts +31 -0
- package/dist/cli/doctor-drift.js +130 -0
- package/dist/cli/doctor-env-check.d.ts +25 -0
- package/dist/cli/doctor-env-check.js +91 -0
- package/dist/cli/install-agents.d.ts +108 -0
- package/dist/cli/install-agents.js +402 -0
- package/dist/cli/save-doc.d.ts +42 -0
- package/dist/cli/save-doc.js +145 -0
- package/dist/cli/scan-agents.d.ts +46 -0
- package/dist/cli/scan-agents.js +227 -0
- package/dist/cli/stats.d.ts +36 -0
- package/dist/cli/stats.js +131 -0
- package/dist/cli/unbless-agents.d.ts +33 -0
- package/dist/cli/unbless-agents.js +85 -0
- package/dist/cli/uninstall-agents.d.ts +36 -0
- package/dist/cli/uninstall-agents.js +117 -0
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +14 -8
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +105 -11
- package/dist/core/context-registry.d.ts +16 -1
- package/dist/core/context-registry.js +60 -28
- package/dist/core/event-log.d.ts +79 -0
- package/dist/core/event-log.js +190 -0
- package/dist/core/session-registry.d.ts +43 -0
- package/dist/core/session-registry.js +113 -0
- package/dist/core/session-savings.d.ts +19 -0
- package/dist/core/session-savings.js +60 -0
- package/dist/handlers/session-budget.d.ts +32 -0
- package/dist/handlers/session-budget.js +61 -0
- package/dist/handlers/session-snapshot-persist.d.ts +22 -0
- package/dist/handlers/session-snapshot-persist.js +76 -0
- package/dist/hooks/adaptive-threshold.d.ts +27 -0
- package/dist/hooks/adaptive-threshold.js +46 -0
- package/dist/hooks/format-deny-message.d.ts +21 -0
- package/dist/hooks/format-deny-message.js +147 -0
- package/dist/hooks/installer.js +121 -31
- package/dist/hooks/path-safety.d.ts +16 -0
- package/dist/hooks/path-safety.js +34 -0
- package/dist/hooks/post-bash.d.ts +46 -0
- package/dist/hooks/post-bash.js +77 -0
- package/dist/hooks/session-start.d.ts +45 -0
- package/dist/hooks/session-start.js +179 -0
- package/dist/hooks/summary-ast-index.d.ts +28 -0
- package/dist/hooks/summary-ast-index.js +122 -0
- package/dist/hooks/summary-head-tail.d.ts +15 -0
- package/dist/hooks/summary-head-tail.js +78 -0
- package/dist/hooks/summary-pipeline.d.ts +35 -0
- package/dist/hooks/summary-pipeline.js +63 -0
- package/dist/hooks/summary-regex.d.ts +14 -0
- package/dist/hooks/summary-regex.js +130 -0
- package/dist/hooks/summary-types.d.ts +29 -0
- package/dist/hooks/summary-types.js +9 -0
- package/dist/index.d.ts +15 -3
- package/dist/index.js +509 -149
- package/dist/integration/context-mode-detector.d.ts +7 -1
- package/dist/integration/context-mode-detector.js +51 -15
- package/dist/server/tool-definitions.d.ts +149 -0
- package/dist/server/tool-definitions.js +424 -202
- package/dist/server.d.ts +1 -1
- package/dist/server.js +456 -179
- package/dist/templates/agent-builder.d.ts +49 -0
- package/dist/templates/agent-builder.js +104 -0
- package/dist/types.d.ts +38 -4
- package/package.json +4 -2
- package/skills/stats/SKILL.md +13 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-rtg (part 2) — CLAUDE.md hygiene assessment.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code injects the project's `CLAUDE.md` into every message. A
|
|
5
|
+
* 200-line rules file therefore costs thousands of tokens per turn.
|
|
6
|
+
* Community guide B3 recommends keeping it under 60 non-empty lines and
|
|
7
|
+
* loading deeper instructions on demand from `docs/`.
|
|
8
|
+
*
|
|
9
|
+
* This module only *measures*. It does not touch the file. The caller
|
|
10
|
+
* (doctor / init) decides how to surface the tip.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CLAUDE_MD_LINE_THRESHOLD = 60;
|
|
13
|
+
export type ClaudeMdAssessment = {
|
|
14
|
+
kind: "missing";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "ok";
|
|
17
|
+
path: string;
|
|
18
|
+
nonEmptyLines: number;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "bloated";
|
|
21
|
+
path: string;
|
|
22
|
+
nonEmptyLines: number;
|
|
23
|
+
threshold: number;
|
|
24
|
+
};
|
|
25
|
+
export declare function assessClaudeMd(projectRoot: string, threshold?: number): Promise<ClaudeMdAssessment>;
|
|
26
|
+
//# sourceMappingURL=claudemd-hygiene.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-rtg (part 2) — CLAUDE.md hygiene assessment.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code injects the project's `CLAUDE.md` into every message. A
|
|
5
|
+
* 200-line rules file therefore costs thousands of tokens per turn.
|
|
6
|
+
* Community guide B3 recommends keeping it under 60 non-empty lines and
|
|
7
|
+
* loading deeper instructions on demand from `docs/`.
|
|
8
|
+
*
|
|
9
|
+
* This module only *measures*. It does not touch the file. The caller
|
|
10
|
+
* (doctor / init) decides how to surface the tip.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
export const CLAUDE_MD_LINE_THRESHOLD = 60;
|
|
15
|
+
function countNonEmptyLines(content) {
|
|
16
|
+
let n = 0;
|
|
17
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
18
|
+
const line = raw.trim();
|
|
19
|
+
if (line === "")
|
|
20
|
+
continue;
|
|
21
|
+
// Treat markdown horizontal rules as separators, not content.
|
|
22
|
+
if (/^-{3,}$|^_{3,}$|^\*{3,}$/.test(line))
|
|
23
|
+
continue;
|
|
24
|
+
n += 1;
|
|
25
|
+
}
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
export async function assessClaudeMd(projectRoot, threshold = CLAUDE_MD_LINE_THRESHOLD) {
|
|
29
|
+
const path = join(projectRoot, "CLAUDE.md");
|
|
30
|
+
let content;
|
|
31
|
+
try {
|
|
32
|
+
content = await readFile(path, "utf-8");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { kind: "missing" };
|
|
36
|
+
}
|
|
37
|
+
const nonEmptyLines = countNonEmptyLines(content);
|
|
38
|
+
if (nonEmptyLines > threshold) {
|
|
39
|
+
return { kind: "bloated", path, nonEmptyLines, threshold };
|
|
40
|
+
}
|
|
41
|
+
return { kind: "ok", path, nonEmptyLines };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=claudemd-hygiene.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 3 subtask 3.7 — drift detection for blessed agents.
|
|
3
|
+
*
|
|
4
|
+
* A blessed file records `token_pilot.upstream_hash` at bless time. If the
|
|
5
|
+
* upstream (user or plugin scope) agent file changes later, our blessed copy
|
|
6
|
+
* silently ages — users could be running on a definition that no longer
|
|
7
|
+
* matches its upstream description or tool list. This function re-hashes
|
|
8
|
+
* every upstream referenced by a blessed file and reports mismatches.
|
|
9
|
+
*
|
|
10
|
+
* Integration: called from `handleDoctor` after the other doctor checks.
|
|
11
|
+
* Never throws; every error is swallowed and surfaced as a warning. Returns
|
|
12
|
+
* an array so callers can test the findings without stubbing stderr.
|
|
13
|
+
*/
|
|
14
|
+
export interface DriftFinding {
|
|
15
|
+
agentName: string;
|
|
16
|
+
blessedPath: string;
|
|
17
|
+
upstreamScope: "user" | "plugin" | string;
|
|
18
|
+
storedHash: string;
|
|
19
|
+
currentHash: string | null;
|
|
20
|
+
status: "drifted" | "missing-upstream" | "missing-fields";
|
|
21
|
+
}
|
|
22
|
+
export interface DetectDriftOptions {
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
homeDir: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function detectDrift(opts: DetectDriftOptions): Promise<DriftFinding[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Format a single finding as a human-readable stderr line.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatDriftFinding(f: DriftFinding): string;
|
|
31
|
+
//# sourceMappingURL=doctor-drift.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 3 subtask 3.7 — drift detection for blessed agents.
|
|
3
|
+
*
|
|
4
|
+
* A blessed file records `token_pilot.upstream_hash` at bless time. If the
|
|
5
|
+
* upstream (user or plugin scope) agent file changes later, our blessed copy
|
|
6
|
+
* silently ages — users could be running on a definition that no longer
|
|
7
|
+
* matches its upstream description or tool list. This function re-hashes
|
|
8
|
+
* every upstream referenced by a blessed file and reports mismatches.
|
|
9
|
+
*
|
|
10
|
+
* Integration: called from `handleDoctor` after the other doctor checks.
|
|
11
|
+
* Never throws; every error is swallowed and surfaced as a warning. Returns
|
|
12
|
+
* an array so callers can test the findings without stubbing stderr.
|
|
13
|
+
*/
|
|
14
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { parseFrontmatter } from "./agent-frontmatter.js";
|
|
18
|
+
import { scanAgents } from "./scan-agents.js";
|
|
19
|
+
function currentBlessedHash(meta) {
|
|
20
|
+
const tp = meta.token_pilot;
|
|
21
|
+
if (!tp || tp.blessed !== true)
|
|
22
|
+
return null;
|
|
23
|
+
const upstream = typeof tp.upstream === "string" ? tp.upstream : null;
|
|
24
|
+
const hash = typeof tp.upstream_hash === "string" ? tp.upstream_hash : null;
|
|
25
|
+
if (!upstream || !hash)
|
|
26
|
+
return null;
|
|
27
|
+
return { upstream, hash };
|
|
28
|
+
}
|
|
29
|
+
async function sha256File(path) {
|
|
30
|
+
try {
|
|
31
|
+
const content = await readFile(path, "utf-8");
|
|
32
|
+
return createHash("sha256").update(content).digest("hex");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function detectDrift(opts) {
|
|
39
|
+
const findings = [];
|
|
40
|
+
const projectAgentsDir = join(opts.projectRoot, ".claude", "agents");
|
|
41
|
+
let blessedFiles;
|
|
42
|
+
try {
|
|
43
|
+
const entries = await readdir(projectAgentsDir);
|
|
44
|
+
blessedFiles = entries.filter((f) => f.endsWith(".md"));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return findings;
|
|
48
|
+
}
|
|
49
|
+
// Build a single lookup from upstream scan for efficiency.
|
|
50
|
+
let upstreamAgents = [];
|
|
51
|
+
try {
|
|
52
|
+
upstreamAgents = await scanAgents({
|
|
53
|
+
projectRoot: opts.projectRoot,
|
|
54
|
+
homeDir: opts.homeDir,
|
|
55
|
+
pluginCacheGlob: [],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// If scan errors we can still report missing-upstream for each blessed
|
|
60
|
+
// file; keep going with an empty lookup.
|
|
61
|
+
}
|
|
62
|
+
const upstreamByName = new Map(upstreamAgents
|
|
63
|
+
.filter((a) => a.scope !== "project")
|
|
64
|
+
.map((a) => [a.name + "::" + a.scope, a]));
|
|
65
|
+
for (const file of blessedFiles) {
|
|
66
|
+
const fullPath = join(projectAgentsDir, file);
|
|
67
|
+
const name = file.replace(/\.md$/, "");
|
|
68
|
+
let meta;
|
|
69
|
+
try {
|
|
70
|
+
const body = await readFile(fullPath, "utf-8");
|
|
71
|
+
({ meta } = parseFrontmatter(body));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const marker = currentBlessedHash(meta);
|
|
77
|
+
if (!marker)
|
|
78
|
+
continue;
|
|
79
|
+
const upstream = upstreamByName.get(name + "::" + marker.upstream);
|
|
80
|
+
if (!upstream) {
|
|
81
|
+
findings.push({
|
|
82
|
+
agentName: name,
|
|
83
|
+
blessedPath: fullPath,
|
|
84
|
+
upstreamScope: marker.upstream,
|
|
85
|
+
storedHash: marker.hash,
|
|
86
|
+
currentHash: null,
|
|
87
|
+
status: "missing-upstream",
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const currentHash = await sha256File(upstream.path);
|
|
92
|
+
if (!currentHash) {
|
|
93
|
+
findings.push({
|
|
94
|
+
agentName: name,
|
|
95
|
+
blessedPath: fullPath,
|
|
96
|
+
upstreamScope: marker.upstream,
|
|
97
|
+
storedHash: marker.hash,
|
|
98
|
+
currentHash: null,
|
|
99
|
+
status: "missing-upstream",
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (currentHash !== marker.hash) {
|
|
104
|
+
findings.push({
|
|
105
|
+
agentName: name,
|
|
106
|
+
blessedPath: fullPath,
|
|
107
|
+
upstreamScope: marker.upstream,
|
|
108
|
+
storedHash: marker.hash,
|
|
109
|
+
currentHash,
|
|
110
|
+
status: "drifted",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return findings;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Format a single finding as a human-readable stderr line.
|
|
118
|
+
*/
|
|
119
|
+
export function formatDriftFinding(f) {
|
|
120
|
+
if (f.status === "drifted") {
|
|
121
|
+
return (`⚠ tp-${f.agentName}: upstream changed since bless (scope=${f.upstreamScope}). ` +
|
|
122
|
+
`Run: npx token-pilot bless-agents --re ${f.agentName}`);
|
|
123
|
+
}
|
|
124
|
+
if (f.status === "missing-upstream") {
|
|
125
|
+
return (`⚠ tp-${f.agentName}: upstream no longer found (scope=${f.upstreamScope}). ` +
|
|
126
|
+
`The blessed copy is orphaned; consider: npx token-pilot unbless-agents ${f.agentName}`);
|
|
127
|
+
}
|
|
128
|
+
return `⚠ tp-${f.agentName}: blessed frontmatter missing required fields`;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=doctor-drift.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-c08 — detect Claude Code environment knobs that the community
|
|
3
|
+
* usage-limit guide (TP-yk9) calls out as giving 60-80% session savings
|
|
4
|
+
* with zero code change.
|
|
5
|
+
*
|
|
6
|
+
* This is pure advisory: we never modify the user's environment or
|
|
7
|
+
* settings file. `checkClaudeCodeEnv` returns a list of one-line tips,
|
|
8
|
+
* each pointing at a missing or wasteful knob. Empty list == all good.
|
|
9
|
+
*/
|
|
10
|
+
export interface EnvCheckInput {
|
|
11
|
+
/** Snapshot of process.env (pass in for testability). */
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* Parsed contents of `~/.claude/settings.json`. Pass `null` / anything
|
|
15
|
+
* non-object to represent "file missing or unreadable".
|
|
16
|
+
*/
|
|
17
|
+
settings: unknown;
|
|
18
|
+
}
|
|
19
|
+
export declare function checkClaudeCodeEnv(input: EnvCheckInput): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Load `~/.claude/settings.json` and run the pure check. Silent on I/O
|
|
22
|
+
* failures — a missing file simply means "no settings, all tips apply".
|
|
23
|
+
*/
|
|
24
|
+
export declare function runClaudeCodeEnvCheck(homeDirPath?: string, processEnv?: Record<string, string | undefined>): Promise<string[]>;
|
|
25
|
+
//# sourceMappingURL=doctor-env-check.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TP-c08 — detect Claude Code environment knobs that the community
|
|
3
|
+
* usage-limit guide (TP-yk9) calls out as giving 60-80% session savings
|
|
4
|
+
* with zero code change.
|
|
5
|
+
*
|
|
6
|
+
* This is pure advisory: we never modify the user's environment or
|
|
7
|
+
* settings file. `checkClaudeCodeEnv` returns a list of one-line tips,
|
|
8
|
+
* each pointing at a missing or wasteful knob. Empty list == all good.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const MAX_THINKING_TOKENS_CAP = 16000;
|
|
14
|
+
const MAX_AUTOCOMPACT_PCT = 80;
|
|
15
|
+
function asRecord(x) {
|
|
16
|
+
if (x && typeof x === "object" && !Array.isArray(x)) {
|
|
17
|
+
return x;
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pull the effective value of a Claude Code env knob. Process env wins
|
|
23
|
+
* over `settings.env.*` because if Claude Code or the user's shell
|
|
24
|
+
* exported a value, that is what the child process actually sees.
|
|
25
|
+
*/
|
|
26
|
+
function effective(envKey, processEnv, settingsEnv) {
|
|
27
|
+
if (typeof processEnv[envKey] === "string" && processEnv[envKey] !== "") {
|
|
28
|
+
return processEnv[envKey];
|
|
29
|
+
}
|
|
30
|
+
const fromSettings = settingsEnv[envKey];
|
|
31
|
+
if (typeof fromSettings === "string" && fromSettings !== "") {
|
|
32
|
+
return fromSettings;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
export function checkClaudeCodeEnv(input) {
|
|
37
|
+
const tips = [];
|
|
38
|
+
const settings = asRecord(input.settings);
|
|
39
|
+
const settingsEnv = asRecord(settings.env);
|
|
40
|
+
// CLAUDE_CODE_SUBAGENT_MODEL=haiku — biggest single knob for subagent
|
|
41
|
+
// savings (~80% cheaper than Sonnet for the exploration work delegates
|
|
42
|
+
// do). Unset → cost stays at default.
|
|
43
|
+
const subagentModel = effective("CLAUDE_CODE_SUBAGENT_MODEL", input.env, settingsEnv);
|
|
44
|
+
if (!subagentModel || subagentModel === "opus") {
|
|
45
|
+
tips.push(`CLAUDE_CODE_SUBAGENT_MODEL not set to haiku — add \`"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"\` under \`env\` in ~/.claude/settings.json to route subagents to Haiku (~80% cheaper).`);
|
|
46
|
+
}
|
|
47
|
+
// MAX_THINKING_TOKENS — Claude's hidden reasoning tokens default to
|
|
48
|
+
// 32000. Community finds 10000 saves ~70% with minimal quality loss.
|
|
49
|
+
const thinking = effective("MAX_THINKING_TOKENS", input.env, settingsEnv);
|
|
50
|
+
const thinkingNum = thinking ? Number.parseInt(thinking, 10) : null;
|
|
51
|
+
if (!thinking ||
|
|
52
|
+
thinkingNum === null ||
|
|
53
|
+
!Number.isFinite(thinkingNum) ||
|
|
54
|
+
thinkingNum > MAX_THINKING_TOKENS_CAP) {
|
|
55
|
+
tips.push(`MAX_THINKING_TOKENS ${thinking ? `is ${thinking}` : "unset (defaults to 32000)"} — add \`"MAX_THINKING_TOKENS": "10000"\` under \`env\` in ~/.claude/settings.json to cap hidden reasoning tokens (~70% saving).`);
|
|
56
|
+
}
|
|
57
|
+
// CLAUDE_AUTOCOMPACT_PCT_OVERRIDE — compact at 50% instead of the
|
|
58
|
+
// default 95%. Keeps sessions healthier, fewer token cliffs.
|
|
59
|
+
const autocompact = effective("CLAUDE_AUTOCOMPACT_PCT_OVERRIDE", input.env, settingsEnv);
|
|
60
|
+
const autoNum = autocompact ? Number.parseInt(autocompact, 10) : null;
|
|
61
|
+
if (!autocompact ||
|
|
62
|
+
autoNum === null ||
|
|
63
|
+
!Number.isFinite(autoNum) ||
|
|
64
|
+
autoNum > MAX_AUTOCOMPACT_PCT) {
|
|
65
|
+
tips.push(`CLAUDE_AUTOCOMPACT_PCT_OVERRIDE ${autocompact ? `is ${autocompact}` : "unset"} — add \`"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "50"\` under \`env\` in ~/.claude/settings.json to compact context earlier (healthier sessions).`);
|
|
66
|
+
}
|
|
67
|
+
// settings.model — only flag if explicitly set to opus. Absence means
|
|
68
|
+
// Claude Code's own default, which may be the right choice for the
|
|
69
|
+
// user; we don't second-guess.
|
|
70
|
+
const model = settings.model;
|
|
71
|
+
if (typeof model === "string" && model === "opus") {
|
|
72
|
+
tips.push(`"model": "opus" in ~/.claude/settings.json defaults every session to Opus (~5× Sonnet cost). Set \`"model": "sonnet"\` if you don't actively need Opus reasoning on every task.`);
|
|
73
|
+
}
|
|
74
|
+
return tips;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load `~/.claude/settings.json` and run the pure check. Silent on I/O
|
|
78
|
+
* failures — a missing file simply means "no settings, all tips apply".
|
|
79
|
+
*/
|
|
80
|
+
export async function runClaudeCodeEnvCheck(homeDirPath = homedir(), processEnv = process.env) {
|
|
81
|
+
let settings = {};
|
|
82
|
+
try {
|
|
83
|
+
const raw = await readFile(join(homeDirPath, ".claude", "settings.json"), "utf-8");
|
|
84
|
+
settings = JSON.parse(raw);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Missing or malformed — treat as empty settings. Tips will surface.
|
|
88
|
+
}
|
|
89
|
+
return checkClaudeCodeEnv({ env: processEnv, settings });
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=doctor-env-check.js.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 subtasks 5.3 + 5.4 — install tp-* agents into the user's
|
|
3
|
+
* Claude Code agent registry.
|
|
4
|
+
*
|
|
5
|
+
* Copies every `tp-*.md` from `distAgentsDir` into either
|
|
6
|
+
* `<projectRoot>/.claude/agents/` (scope=project) or
|
|
7
|
+
* `<homeDir>/.claude/agents/` (scope=user).
|
|
8
|
+
*
|
|
9
|
+
* Idempotence states (see Phase 5 design):
|
|
10
|
+
*
|
|
11
|
+
* - **unchanged-installed** — stored hash matches template hash → skip
|
|
12
|
+
* (re-write would be a no-op).
|
|
13
|
+
* - **template-upgraded** — stored hash differs from template hash AND
|
|
14
|
+
* the file body still matches the stored hash (user did not edit) →
|
|
15
|
+
* overwrite.
|
|
16
|
+
* - **user-edited** — stored hash does not match the file body hash →
|
|
17
|
+
* skip unless `force: true`.
|
|
18
|
+
* - **no-hash** — file has no `token_pilot_body_hash` in frontmatter
|
|
19
|
+
* → never overwrite. This is always treated as the user's own file,
|
|
20
|
+
* even when `force: true`.
|
|
21
|
+
*
|
|
22
|
+
* Never throws on a per-file failure: the problem is recorded in
|
|
23
|
+
* `skipped` so the CLI can report it without aborting the rest.
|
|
24
|
+
*/
|
|
25
|
+
export type Scope = "user" | "project";
|
|
26
|
+
export interface InstallOptions {
|
|
27
|
+
scope: Scope;
|
|
28
|
+
/** Used when scope === "project". */
|
|
29
|
+
projectRoot: string;
|
|
30
|
+
/** Used when scope === "user". */
|
|
31
|
+
homeDir: string;
|
|
32
|
+
/** Directory holding the rendered dist/agents/tp-*.md files. */
|
|
33
|
+
distAgentsDir: string;
|
|
34
|
+
/** When true, overwrite user-edited files (never no-hash files). */
|
|
35
|
+
force?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface InstallResult {
|
|
38
|
+
/** Names of files actually written during this run. */
|
|
39
|
+
installed: string[];
|
|
40
|
+
/** Entries that were deliberately left alone; reason explains why. */
|
|
41
|
+
skipped: Array<{
|
|
42
|
+
name: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
}>;
|
|
45
|
+
/** Absolute path we wrote (or would have written) into. */
|
|
46
|
+
targetDir: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function installAgents(opts: InstallOptions): Promise<InstallResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the dist/agents directory relative to the running `dist/index.js`
|
|
51
|
+
* entry. Works for both `npm run start` (dist/) and `npm pack`-installed
|
|
52
|
+
* users (node_modules/token-pilot/dist/agents/). Falls back to `templates/
|
|
53
|
+
* agents` only when we are clearly running from source (tests, dev mode).
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveDistAgentsDir(scriptUrl: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Yes/no prompt used by other CLI entry points that want to offer
|
|
58
|
+
* an opt-in step (e.g. `token-pilot init` → "install agents now?").
|
|
59
|
+
* Returns false on non-TTY or ambiguous input; callers can pass a
|
|
60
|
+
* `defaultYes` to accept bare Enter as yes.
|
|
61
|
+
*/
|
|
62
|
+
export declare function promptYesNo(question: string, defaultYes?: boolean): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* Read `agents.scope` from `<projectRoot>/.token-pilot.json`. Returns
|
|
65
|
+
* null if the file is missing, unreadable, not valid JSON, or if the
|
|
66
|
+
* field is absent. Never throws — a bad config should not block install.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readPersistedScope(projectRoot: string): Promise<Scope | null>;
|
|
69
|
+
/**
|
|
70
|
+
* Persist `agents.scope` into `<projectRoot>/.token-pilot.json`, merging
|
|
71
|
+
* with any existing config. Failures are swallowed — persistence is a
|
|
72
|
+
* convenience, not a correctness requirement.
|
|
73
|
+
*/
|
|
74
|
+
export declare function persistScope(projectRoot: string, scope: Scope): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* CLI entry: `token-pilot install-agents [--scope=user|project] [--force]`.
|
|
77
|
+
*
|
|
78
|
+
* Exit codes:
|
|
79
|
+
* 0 — something installed OR everything was deliberately skipped
|
|
80
|
+
* 1 — missing/unreadable dist/agents, or non-TTY without --scope
|
|
81
|
+
*/
|
|
82
|
+
export declare function handleInstallAgents(argv: string[], opts?: {
|
|
83
|
+
distAgentsDir?: string;
|
|
84
|
+
homeDir?: string;
|
|
85
|
+
projectRoot?: string;
|
|
86
|
+
isTTY?: boolean;
|
|
87
|
+
}): Promise<number>;
|
|
88
|
+
export interface StartupReminderOptions {
|
|
89
|
+
projectRoot: string;
|
|
90
|
+
homeDir: string;
|
|
91
|
+
/**
|
|
92
|
+
* If true, the reminder is suppressed. Callers pass
|
|
93
|
+
* `cfg.agents.reminder === false` here.
|
|
94
|
+
*/
|
|
95
|
+
configSuppressed: boolean;
|
|
96
|
+
/** Environment snapshot to consult; defaults to `process.env`. */
|
|
97
|
+
env?: NodeJS.ProcessEnv;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Pure, testable check: should the MCP startup emit the agent-install
|
|
101
|
+
* reminder right now?
|
|
102
|
+
*/
|
|
103
|
+
export declare function shouldEmitStartupReminder(opts: StartupReminderOptions): Promise<boolean>;
|
|
104
|
+
export declare const STARTUP_REMINDER_MESSAGE: string;
|
|
105
|
+
export declare function maybeEmitStartupReminder(opts: StartupReminderOptions): Promise<boolean>;
|
|
106
|
+
/** Test-only: reset the single-fire guard. */
|
|
107
|
+
export declare function _resetStartupReminderForTests(): void;
|
|
108
|
+
//# sourceMappingURL=install-agents.d.ts.map
|