preflight-dev 3.1.0
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/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/cli.js +11 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +154 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +122 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +34 -0
- package/dist/lib/config.js +118 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/embeddings.d.ts +11 -0
- package/dist/lib/embeddings.js +88 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/files.d.ts +15 -0
- package/dist/lib/files.js +60 -0
- package/dist/lib/files.js.map +1 -0
- package/dist/lib/git-extractor.d.ts +9 -0
- package/dist/lib/git-extractor.js +116 -0
- package/dist/lib/git-extractor.js.map +1 -0
- package/dist/lib/git.d.ts +29 -0
- package/dist/lib/git.js +86 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/session-parser.d.ts +45 -0
- package/dist/lib/session-parser.js +267 -0
- package/dist/lib/session-parser.js.map +1 -0
- package/dist/lib/state.d.ts +21 -0
- package/dist/lib/state.js +86 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/timeline-db.d.ts +67 -0
- package/dist/lib/timeline-db.js +380 -0
- package/dist/lib/timeline-db.js.map +1 -0
- package/dist/lib/triage.d.ts +29 -0
- package/dist/lib/triage.js +193 -0
- package/dist/lib/triage.js.map +1 -0
- package/dist/profiles.d.ts +3 -0
- package/dist/profiles.js +65 -0
- package/dist/profiles.js.map +1 -0
- package/dist/tools/audit-workspace.d.ts +2 -0
- package/dist/tools/audit-workspace.js +86 -0
- package/dist/tools/audit-workspace.js.map +1 -0
- package/dist/tools/checkpoint.d.ts +2 -0
- package/dist/tools/checkpoint.js +108 -0
- package/dist/tools/checkpoint.js.map +1 -0
- package/dist/tools/clarify-intent.d.ts +2 -0
- package/dist/tools/clarify-intent.js +180 -0
- package/dist/tools/clarify-intent.js.map +1 -0
- package/dist/tools/enrich-agent-task.d.ts +2 -0
- package/dist/tools/enrich-agent-task.js +97 -0
- package/dist/tools/enrich-agent-task.js.map +1 -0
- package/dist/tools/generate-scorecard.d.ts +2 -0
- package/dist/tools/generate-scorecard.js +617 -0
- package/dist/tools/generate-scorecard.js.map +1 -0
- package/dist/tools/log-correction.d.ts +2 -0
- package/dist/tools/log-correction.js +76 -0
- package/dist/tools/log-correction.js.map +1 -0
- package/dist/tools/onboard-project.d.ts +2 -0
- package/dist/tools/onboard-project.js +179 -0
- package/dist/tools/onboard-project.js.map +1 -0
- package/dist/tools/preflight-check.d.ts +2 -0
- package/dist/tools/preflight-check.js +229 -0
- package/dist/tools/preflight-check.js.map +1 -0
- package/dist/tools/prompt-score.d.ts +2 -0
- package/dist/tools/prompt-score.js +132 -0
- package/dist/tools/prompt-score.js.map +1 -0
- package/dist/tools/scan-sessions.d.ts +2 -0
- package/dist/tools/scan-sessions.js +182 -0
- package/dist/tools/scan-sessions.js.map +1 -0
- package/dist/tools/scope-work.d.ts +2 -0
- package/dist/tools/scope-work.js +214 -0
- package/dist/tools/scope-work.js.map +1 -0
- package/dist/tools/search-history.d.ts +2 -0
- package/dist/tools/search-history.js +130 -0
- package/dist/tools/search-history.js.map +1 -0
- package/dist/tools/sequence-tasks.d.ts +2 -0
- package/dist/tools/sequence-tasks.js +165 -0
- package/dist/tools/sequence-tasks.js.map +1 -0
- package/dist/tools/session-handoff.d.ts +2 -0
- package/dist/tools/session-handoff.js +113 -0
- package/dist/tools/session-handoff.js.map +1 -0
- package/dist/tools/session-health.d.ts +2 -0
- package/dist/tools/session-health.js +111 -0
- package/dist/tools/session-health.js.map +1 -0
- package/dist/tools/session-stats.d.ts +2 -0
- package/dist/tools/session-stats.js +112 -0
- package/dist/tools/session-stats.js.map +1 -0
- package/dist/tools/sharpen-followup.d.ts +2 -0
- package/dist/tools/sharpen-followup.js +192 -0
- package/dist/tools/sharpen-followup.js.map +1 -0
- package/dist/tools/timeline-view.d.ts +2 -0
- package/dist/tools/timeline-view.js +165 -0
- package/dist/tools/timeline-view.js.map +1 -0
- package/dist/tools/token-audit.d.ts +2 -0
- package/dist/tools/token-audit.js +227 -0
- package/dist/tools/token-audit.js.map +1 -0
- package/dist/tools/verify-completion.d.ts +2 -0
- package/dist/tools/verify-completion.js +154 -0
- package/dist/tools/verify-completion.js.map +1 -0
- package/dist/tools/what-changed.d.ts +2 -0
- package/dist/tools/what-changed.js +40 -0
- package/dist/tools/what-changed.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
- package/src/cli/init.ts +133 -0
- package/src/index.ts +135 -0
- package/src/lib/config.ts +157 -0
- package/src/lib/embeddings.ts +118 -0
- package/src/lib/files.ts +59 -0
- package/src/lib/git-extractor.ts +137 -0
- package/src/lib/git.ts +89 -0
- package/src/lib/session-parser.ts +325 -0
- package/src/lib/state.ts +86 -0
- package/src/lib/timeline-db.ts +490 -0
- package/src/lib/triage.ts +255 -0
- package/src/profiles.ts +70 -0
- package/src/templates/config.yml +23 -0
- package/src/templates/triage.yml +27 -0
- package/src/tools/audit-workspace.ts +97 -0
- package/src/tools/checkpoint.ts +119 -0
- package/src/tools/clarify-intent.ts +191 -0
- package/src/tools/enrich-agent-task.ts +108 -0
- package/src/tools/generate-scorecard.ts +673 -0
- package/src/tools/log-correction.ts +89 -0
- package/src/tools/onboard-project.ts +214 -0
- package/src/tools/preflight-check.ts +263 -0
- package/src/tools/prompt-score.ts +150 -0
- package/src/tools/scan-sessions.ts +209 -0
- package/src/tools/scope-work.ts +238 -0
- package/src/tools/search-history.ts +145 -0
- package/src/tools/sequence-tasks.ts +182 -0
- package/src/tools/session-handoff.ts +125 -0
- package/src/tools/session-health.ts +107 -0
- package/src/tools/session-stats.ts +134 -0
- package/src/tools/sharpen-followup.ts +200 -0
- package/src/tools/timeline-view.ts +181 -0
- package/src/tools/token-audit.ts +259 -0
- package/src/tools/verify-completion.ts +159 -0
- package/src/tools/what-changed.ts +48 -0
- package/src/types.ts +87 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { run, getBranch, getStatus, getRecentCommits, getDiffFiles, getStagedFiles } from "../lib/git.js";
|
|
4
|
+
import { findWorkspaceDocs, PROJECT_DIR } from "../lib/files.js";
|
|
5
|
+
import { searchSemantic } from "../lib/timeline-db.js";
|
|
6
|
+
import { getRelatedProjects } from "../lib/config.js";
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { join, basename } from "path";
|
|
9
|
+
|
|
10
|
+
/** Parse test failures from common report formats without fragile shell pipelines */
|
|
11
|
+
function getTestFailures(): string {
|
|
12
|
+
// Try playwright JSON report
|
|
13
|
+
const reportPath = join(PROJECT_DIR, "playwright-report", "results.json");
|
|
14
|
+
if (existsSync(reportPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse(readFileSync(reportPath, "utf-8"));
|
|
17
|
+
const failures: string[] = [];
|
|
18
|
+
const walk = (suites: any[]) => {
|
|
19
|
+
for (const suite of suites || []) {
|
|
20
|
+
for (const spec of suite.specs || []) {
|
|
21
|
+
if (spec.ok === false) failures.push(spec.title);
|
|
22
|
+
}
|
|
23
|
+
walk(suite.suites);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
walk(data.suites);
|
|
27
|
+
return failures.length ? failures.join("\n") : "all passing";
|
|
28
|
+
} catch {
|
|
29
|
+
return "report exists but could not parse";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Try jest/vitest JSON output
|
|
34
|
+
for (const p of ["test-results.json", "jest-results.json"]) {
|
|
35
|
+
const fp = join(PROJECT_DIR, p);
|
|
36
|
+
if (existsSync(fp)) {
|
|
37
|
+
try {
|
|
38
|
+
const data = JSON.parse(readFileSync(fp, "utf-8"));
|
|
39
|
+
const failed = data.testResults
|
|
40
|
+
?.filter((t: any) => t.status === "failed")
|
|
41
|
+
?.map((t: any) => t.name) || [];
|
|
42
|
+
return failed.length ? failed.join("\n") : "all passing";
|
|
43
|
+
} catch { continue; }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return "no test report found";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Extract intent signals using weighted pattern matching */
|
|
51
|
+
function extractSignals(msg: string, context: { hasTypeErrors: boolean; hasTestFailures: boolean; hasDirtyFiles: boolean }): string[] {
|
|
52
|
+
const signals: string[] = [];
|
|
53
|
+
const lower = msg.toLowerCase();
|
|
54
|
+
|
|
55
|
+
const patterns: [RegExp, string, number][] = [
|
|
56
|
+
[/\b(fix|repair|broken|failing|error|bug|crash|issue)\b/, "FIX", 2],
|
|
57
|
+
[/\b(test|spec|suite|playwright|jest|vitest|e2e)\b/, "TESTS", 2],
|
|
58
|
+
[/\b(commit|push|pr|merge|rebase|cherry.?pick)\b/, "GIT", 2],
|
|
59
|
+
[/\b(add|create|new|build|implement|feature)\b/, "CREATE", 1],
|
|
60
|
+
[/\b(remove|delete|clean|strip|drop|deprecate)\b/, "REMOVE", 1],
|
|
61
|
+
[/\b(check|verify|confirm|status|review|audit)\b/, "VERIFY", 1],
|
|
62
|
+
[/\b(refactor|rename|move|reorganize|extract)\b/, "REFACTOR", 1],
|
|
63
|
+
[/\b(deploy|release|ship|publish)\b/, "DEPLOY", 1],
|
|
64
|
+
[/\b(everything|all|entire|whole)\b/, "⚠️ UNBOUNDED", 3],
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const matched: { label: string; weight: number; hint: string }[] = [];
|
|
68
|
+
for (const [re, label, weight] of patterns) {
|
|
69
|
+
if (re.test(lower)) matched.push({ label, weight, hint: "" });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add contextual hints
|
|
73
|
+
if (matched.some(m => m.label === "FIX")) {
|
|
74
|
+
if (context.hasTypeErrors) signals.push("FIX: Type errors detected — likely the target.");
|
|
75
|
+
if (context.hasTestFailures) signals.push("FIX: Test failures detected — check test output.");
|
|
76
|
+
if (!context.hasTypeErrors && !context.hasTestFailures) signals.push("FIX: No obvious errors — ask what's broken.");
|
|
77
|
+
}
|
|
78
|
+
if (matched.some(m => m.label === "TESTS")) signals.push("TESTS: Check failing tests and test files below.");
|
|
79
|
+
if (matched.some(m => m.label === "GIT")) signals.push("GIT: Check dirty files and branch state.");
|
|
80
|
+
if (matched.some(m => m.label === "CREATE")) signals.push("CREATE: Check workspace priorities for planned work.");
|
|
81
|
+
if (matched.some(m => m.label === "REMOVE")) signals.push("REMOVE: Clarify what 'them/it' refers to before deleting.");
|
|
82
|
+
if (matched.some(m => m.label === "VERIFY")) signals.push("VERIFY: Use git/test state to answer.");
|
|
83
|
+
if (matched.some(m => m.label === "REFACTOR")) signals.push("REFACTOR: Identify scope boundaries before starting.");
|
|
84
|
+
if (matched.some(m => m.label === "DEPLOY")) signals.push("DEPLOY: Verify all checks pass first.");
|
|
85
|
+
if (matched.some(m => m.label === "⚠️ UNBOUNDED")) signals.push("⚠️ UNBOUNDED: Narrow down using workspace priorities.");
|
|
86
|
+
|
|
87
|
+
if (!signals.length) signals.push("UNCLEAR: Ask ONE clarifying question before proceeding.");
|
|
88
|
+
|
|
89
|
+
return signals;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Search for relevant cross-project context */
|
|
93
|
+
async function searchCrossProjectContext(userMessage: string): Promise<string[]> {
|
|
94
|
+
const relatedProjects = getRelatedProjects();
|
|
95
|
+
if (relatedProjects.length === 0) return [];
|
|
96
|
+
|
|
97
|
+
// Generate search queries for schemas, types, contracts, APIs
|
|
98
|
+
const queries = [
|
|
99
|
+
`${userMessage} type interface schema`,
|
|
100
|
+
`${userMessage} API endpoint contract`,
|
|
101
|
+
`${userMessage} enum constant definition`,
|
|
102
|
+
`${userMessage} class function method`,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const contextItems: string[] = [];
|
|
106
|
+
|
|
107
|
+
for (const query of queries) {
|
|
108
|
+
try {
|
|
109
|
+
const results = await searchSemantic(query, {
|
|
110
|
+
project_dirs: relatedProjects,
|
|
111
|
+
type: "commit", // Focus on code changes
|
|
112
|
+
limit: 3,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for (const result of results) {
|
|
116
|
+
const projectName = basename(result.project);
|
|
117
|
+
const content = result.content.slice(0, 200);
|
|
118
|
+
const sourceInfo = result.source_file ? ` at ${result.source_file}` : "";
|
|
119
|
+
contextItems.push(`**From ${projectName}:** ${content}${sourceInfo}`);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Skip if search fails
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return contextItems.slice(0, 5); // Limit to top 5 context items
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function registerClarifyIntent(server: McpServer): void {
|
|
130
|
+
server.tool(
|
|
131
|
+
"clarify_intent",
|
|
132
|
+
`Clarify a vague user instruction by gathering project context. Call BEFORE executing when the user's prompt is missing specific files, actions, scope, or done conditions. Returns git state, test failures, recent changes, and workspace priorities.`,
|
|
133
|
+
{
|
|
134
|
+
user_message: z.string().describe("The user's raw message/instruction to clarify"),
|
|
135
|
+
suspected_area: z.string().optional().describe("Best guess area: 'tests', 'git', 'ui', 'api', 'schema'"),
|
|
136
|
+
},
|
|
137
|
+
async ({ user_message, suspected_area }) => {
|
|
138
|
+
const sections: string[] = [];
|
|
139
|
+
const branch = getBranch();
|
|
140
|
+
const status = getStatus();
|
|
141
|
+
const recentCommits = getRecentCommits(5);
|
|
142
|
+
const recentFiles = getDiffFiles("HEAD~3");
|
|
143
|
+
const staged = getStagedFiles();
|
|
144
|
+
const dirtyCount = status ? status.split("\n").filter(Boolean).length : 0;
|
|
145
|
+
|
|
146
|
+
sections.push(`## Git State\nBranch: ${branch}\nDirty files: ${dirtyCount}\n${status ? `\`\`\`\n${status}\n\`\`\`` : "Working tree clean"}\nStaged: ${staged || "nothing"}\n\nRecent commits:\n\`\`\`\n${recentCommits}\n\`\`\`\n\nRecently changed files:\n\`\`\`\n${recentFiles}\n\`\`\``);
|
|
147
|
+
|
|
148
|
+
// Gather test/type state when relevant
|
|
149
|
+
const area = (suspected_area || "").toLowerCase();
|
|
150
|
+
let hasTypeErrors = false;
|
|
151
|
+
let hasTestFailures = false;
|
|
152
|
+
|
|
153
|
+
if (!area || area.includes("test") || area.includes("fix") || area.includes("ui") || area.includes("api")) {
|
|
154
|
+
const typeErrors = run("pnpm tsc --noEmit 2>&1 | grep -c 'error TS' || echo '0'");
|
|
155
|
+
hasTypeErrors = parseInt(typeErrors, 10) > 0;
|
|
156
|
+
|
|
157
|
+
const testFiles = run("find tests -name '*.spec.ts' -maxdepth 4 2>/dev/null | head -20");
|
|
158
|
+
const failingTests = getTestFailures();
|
|
159
|
+
hasTestFailures = failingTests !== "all passing" && failingTests !== "no test report found";
|
|
160
|
+
|
|
161
|
+
sections.push(`## Test State\nType errors: ${typeErrors}\nFailing tests: ${failingTests}\nTest files:\n\`\`\`\n${testFiles || "none found"}\n\`\`\``);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Workspace priorities
|
|
165
|
+
const workspaceDocs = findWorkspaceDocs();
|
|
166
|
+
const priorityDocs = Object.entries(workspaceDocs)
|
|
167
|
+
.filter(([n]) => /gap|roadmap|current|todo|changelog/i.test(n))
|
|
168
|
+
.slice(0, 3);
|
|
169
|
+
if (priorityDocs.length > 0) {
|
|
170
|
+
sections.push(`## Workspace Priorities\n${priorityDocs.map(([n, d]) => `### .claude/${n}\n\`\`\`\n${d.content}\n\`\`\``).join("\n\n")}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const signals = extractSignals(user_message, {
|
|
174
|
+
hasTypeErrors,
|
|
175
|
+
hasTestFailures,
|
|
176
|
+
hasDirtyFiles: dirtyCount > 0,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Search for cross-project context
|
|
180
|
+
const crossProjectContext = await searchCrossProjectContext(user_message);
|
|
181
|
+
if (crossProjectContext.length > 0) {
|
|
182
|
+
sections.push(`## Related Project Context\n${crossProjectContext.map(c => `- ${c}`).join("\n")}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
sections.push(`## Intent Signals\n${signals.map(s => `- ${s}`).join("\n")}`);
|
|
186
|
+
sections.push(`## Recommendation\n1. **Proceed with specifics** — state what you'll do and why\n2. **Ask ONE question** — if context doesn't disambiguate`);
|
|
187
|
+
|
|
188
|
+
return { content: [{ type: "text" as const, text: sections.join("\n\n") }] };
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { run, getDiffFiles } from "../lib/git.js";
|
|
4
|
+
import { PROJECT_DIR } from "../lib/files.js";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
|
|
8
|
+
/** Sanitize user input for safe use in shell commands */
|
|
9
|
+
function shellEscape(s: string): string {
|
|
10
|
+
return s.replace(/[^a-zA-Z0-9_\-./]/g, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Detect package manager from lockfiles */
|
|
14
|
+
function detectPackageManager(): string {
|
|
15
|
+
if (existsSync(join(PROJECT_DIR, "pnpm-lock.yaml"))) return "pnpm";
|
|
16
|
+
if (existsSync(join(PROJECT_DIR, "yarn.lock"))) return "yarn";
|
|
17
|
+
if (existsSync(join(PROJECT_DIR, "bun.lockb"))) return "bun";
|
|
18
|
+
return "npm";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Find files in a target area using git-tracked files (project-agnostic) */
|
|
22
|
+
function findAreaFiles(area: string): string {
|
|
23
|
+
if (!area) return getDiffFiles("HEAD~3");
|
|
24
|
+
|
|
25
|
+
const safeArea = shellEscape(area);
|
|
26
|
+
|
|
27
|
+
// If area looks like a path, search directly
|
|
28
|
+
if (area.includes("/")) {
|
|
29
|
+
return run(`git ls-files -- '${safeArea}*' 2>/dev/null | head -20`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Search for area keyword in git-tracked file paths
|
|
33
|
+
const files = run(`git ls-files 2>/dev/null | grep -i '${safeArea}' | head -20`);
|
|
34
|
+
if (files && !files.startsWith("[command failed")) return files;
|
|
35
|
+
|
|
36
|
+
// Fallback to recently changed files
|
|
37
|
+
return getDiffFiles("HEAD~3");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Find related test files for an area */
|
|
41
|
+
function findRelatedTests(area: string): string {
|
|
42
|
+
if (!area) return run("git ls-files 2>/dev/null | grep -E '\\.(spec|test)\\.(ts|tsx|js|jsx)$' | head -10");
|
|
43
|
+
|
|
44
|
+
const safeArea = shellEscape(area.split(/\s+/)[0]);
|
|
45
|
+
const tests = run(`git ls-files 2>/dev/null | grep -E '\\.(spec|test)\\.(ts|tsx|js|jsx)$' | grep -i '${safeArea}' | head -10`);
|
|
46
|
+
return tests || run("git ls-files 2>/dev/null | grep -E '\\.(spec|test)\\.(ts|tsx|js|jsx)$' | head -10");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Get an example pattern from the first matching file */
|
|
50
|
+
function getExamplePattern(files: string): string {
|
|
51
|
+
const firstFile = files.split("\n").filter(Boolean)[0];
|
|
52
|
+
if (!firstFile) return "no pattern available";
|
|
53
|
+
return run(`head -30 '${shellEscape(firstFile)}' 2>/dev/null || echo 'could not read file'`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function registerEnrichAgentTask(server: McpServer): void {
|
|
57
|
+
server.tool(
|
|
58
|
+
"enrich_agent_task",
|
|
59
|
+
`Enrich a vague sub-agent task with project context. Call before spawning a Task/sub-agent to add file paths, patterns, scope boundaries, and done conditions.`,
|
|
60
|
+
{
|
|
61
|
+
task_description: z.string().describe("The raw task for the sub-agent"),
|
|
62
|
+
target_area: z.string().optional().describe("Codebase area: directory path, keyword, or description like 'auth tests', 'api routes'"),
|
|
63
|
+
},
|
|
64
|
+
async ({ task_description, target_area }) => {
|
|
65
|
+
const area = target_area || "";
|
|
66
|
+
const pm = detectPackageManager();
|
|
67
|
+
const fileList = findAreaFiles(area);
|
|
68
|
+
const testFiles = findRelatedTests(area);
|
|
69
|
+
const pattern = getExamplePattern(area.includes("test") ? testFiles : fileList);
|
|
70
|
+
|
|
71
|
+
const fileSummary = fileList
|
|
72
|
+
? fileList.split("\n").filter(Boolean).slice(0, 5).join(", ")
|
|
73
|
+
: "Specify exact files";
|
|
74
|
+
const testSummary = testFiles
|
|
75
|
+
? testFiles.split("\n").filter(Boolean).slice(0, 3).join(", ")
|
|
76
|
+
: "Run relevant tests";
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
content: [{
|
|
80
|
+
type: "text" as const,
|
|
81
|
+
text: `## Files in Target Area
|
|
82
|
+
\`\`\`
|
|
83
|
+
${fileList || "none found — specify a more precise area"}
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
## Related Tests
|
|
87
|
+
\`\`\`
|
|
88
|
+
${testFiles || "none"}
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
## Existing Pattern
|
|
92
|
+
\`\`\`typescript
|
|
93
|
+
${pattern}
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
96
|
+
## Enriched Task
|
|
97
|
+
Original: "${task_description}"
|
|
98
|
+
|
|
99
|
+
- **Files**: ${fileSummary}
|
|
100
|
+
- **Pattern**: Follow existing pattern above
|
|
101
|
+
- **Tests**: ${testSummary}
|
|
102
|
+
- **Scope**: Do NOT modify files outside target area
|
|
103
|
+
- **Done when**: All relevant tests pass + \`${pm} tsc --noEmit\` clean`,
|
|
104
|
+
}],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|