feat(HAI-002): complete React conversion with build fixes and project setup
- Fix Vite alias to resolve @hai/core to types-only module (avoids Node.js deps in browser bundle) - Add tsconfig.app.json for client-side typecheck - Fix server.ts return type annotation - Update imports to use @hai/core (resolved via Vite alias) - Add base project files needed for workspace
This commit is contained in:
20
packages/engine/package.json
Normal file
20
packages/engine/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@hai/engine",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hai/core": "workspace:*",
|
||||
"@mariozechner/pi-coding-agent": "^0.62.0",
|
||||
"@mariozechner/pi-ai": "^0.62.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
226
packages/engine/src/executor.ts
Normal file
226
packages/engine/src/executor.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import type { TaskStore, Task, TaskDetail } from "@hai/core";
|
||||
import { createHaiAgent } from "./pi.js";
|
||||
|
||||
const EXECUTOR_SYSTEM_PROMPT = `You are a task execution agent for "hai", an AI-orchestrated task board.
|
||||
|
||||
You are working in a git worktree isolated from the main branch. Your job is to implement the task described in the PROMPT.md specification you're given.
|
||||
|
||||
## How to work
|
||||
1. Read the PROMPT.md carefully — it contains your mission, steps, file scope, and acceptance criteria
|
||||
2. Work through each step in order
|
||||
3. Write clean, production-quality code
|
||||
4. Test your changes
|
||||
5. Commit at meaningful boundaries (step completion)
|
||||
|
||||
## Git discipline
|
||||
- Commit after completing each major step
|
||||
- Use conventional commit messages prefixed with the task ID
|
||||
- \`feat(HAI-001): implement user profile page\`
|
||||
- \`test(HAI-001): add profile page tests\`
|
||||
- \`fix(HAI-001): handle edge case in validation\`
|
||||
- Do NOT commit broken or half-implemented code
|
||||
|
||||
## Guardrails
|
||||
- Stay within the file scope defined in PROMPT.md
|
||||
- Do not modify files outside the task's scope without good reason
|
||||
- If you discover work that doesn't fit the task, note it but don't do it
|
||||
- If a step is blocked or unclear, document why and move on
|
||||
|
||||
## Completion
|
||||
When all steps are complete and tests pass, create a \`.DONE\` file in the task directory to signal completion.`;
|
||||
|
||||
export interface TaskExecutorOptions {
|
||||
/** Called when task execution starts */
|
||||
onStart?: (task: Task, worktreePath: string) => void;
|
||||
/** Called when task execution completes */
|
||||
onComplete?: (task: Task) => void;
|
||||
/** Called on execution failure */
|
||||
onError?: (task: Task, error: Error) => void;
|
||||
/** Called with agent text output */
|
||||
onAgentText?: (taskId: string, delta: string) => void;
|
||||
/** Called with agent tool usage */
|
||||
onAgentTool?: (taskId: string, toolName: string) => void;
|
||||
}
|
||||
|
||||
export class TaskExecutor {
|
||||
private activeWorktrees = new Map<string, string>();
|
||||
private executing = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private store: TaskStore,
|
||||
private rootDir: string,
|
||||
private options: TaskExecutorOptions = {},
|
||||
) {
|
||||
// Listen for tasks moving to in-progress
|
||||
store.on("task:moved", ({ task, to }) => {
|
||||
if (to === "in-progress") {
|
||||
this.execute(task).catch((err) =>
|
||||
console.error(`[executor] Failed to start ${task.id}:`, err),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a task: create worktree, run pi agent, move to in-review.
|
||||
*/
|
||||
async execute(task: Task): Promise<void> {
|
||||
if (this.executing.has(task.id)) return;
|
||||
this.executing.add(task.id);
|
||||
|
||||
console.log(`[executor] Starting ${task.id}: ${task.title}`);
|
||||
|
||||
try {
|
||||
// Check dependencies
|
||||
const allTasks = await this.store.listTasks();
|
||||
const unmetDeps = task.dependencies.filter((depId) => {
|
||||
const dep = allTasks.find((t) => t.id === depId);
|
||||
return dep && dep.column !== "done" && dep.column !== "in-review";
|
||||
});
|
||||
|
||||
if (unmetDeps.length > 0) {
|
||||
console.log(
|
||||
`[executor] ${task.id} blocked by: ${unmetDeps.join(", ")} — deferring`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create worktree
|
||||
const branchName = `hai/${task.id.toLowerCase()}`;
|
||||
const worktreePath = join(this.rootDir, ".worktrees", task.id);
|
||||
await this.createWorktree(branchName, worktreePath);
|
||||
this.activeWorktrees.set(task.id, worktreePath);
|
||||
|
||||
this.options.onStart?.(task, worktreePath);
|
||||
|
||||
// Read the task's PROMPT.md
|
||||
const detail = await this.store.getTask(task.id);
|
||||
|
||||
// Create a pi agent session in the worktree
|
||||
const { session } = await createHaiAgent({
|
||||
cwd: worktreePath,
|
||||
systemPrompt: EXECUTOR_SYSTEM_PROMPT,
|
||||
tools: "coding",
|
||||
onText: (delta) => this.options.onAgentText?.(task.id, delta),
|
||||
onToolStart: (name) => {
|
||||
this.options.onAgentTool?.(task.id, name);
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const agentPrompt = buildExecutionPrompt(detail);
|
||||
await session.prompt(agentPrompt);
|
||||
|
||||
// Check if the agent signaled completion (.DONE file)
|
||||
const doneFile = join(
|
||||
worktreePath,
|
||||
".hai",
|
||||
"tasks",
|
||||
task.id,
|
||||
".DONE",
|
||||
);
|
||||
const doneCwd = join(worktreePath, ".DONE");
|
||||
|
||||
if (existsSync(doneFile) || existsSync(doneCwd)) {
|
||||
await this.store.moveTask(task.id, "in-review");
|
||||
console.log(`[executor] ✓ ${task.id} completed → in-review`);
|
||||
this.options.onComplete?.(task);
|
||||
} else {
|
||||
// Agent finished but didn't create .DONE — still move to review
|
||||
// so a human can inspect
|
||||
await this.store.moveTask(task.id, "in-review");
|
||||
console.log(
|
||||
`[executor] ⚠ ${task.id} agent finished without .DONE → in-review for inspection`,
|
||||
);
|
||||
this.options.onComplete?.(task);
|
||||
}
|
||||
} finally {
|
||||
session.dispose();
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`[executor] ✗ ${task.id} execution failed:`, err.message);
|
||||
this.options.onError?.(task, err);
|
||||
} finally {
|
||||
this.executing.delete(task.id);
|
||||
}
|
||||
}
|
||||
|
||||
private createWorktree(branch: string, path: string): void {
|
||||
if (existsSync(path)) {
|
||||
console.log(`[executor] Worktree already exists: ${path}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try creating with new branch
|
||||
execSync(`git worktree add -b "${branch}" "${path}"`, {
|
||||
cwd: this.rootDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
} catch {
|
||||
// Branch might already exist — try attaching
|
||||
try {
|
||||
execSync(`git worktree add "${path}" "${branch}"`, {
|
||||
cwd: this.rootDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw new Error(`Failed to create worktree: ${e.message}`);
|
||||
}
|
||||
}
|
||||
console.log(`[executor] Worktree created: ${path}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up worktree after merge (called when task moves to done).
|
||||
*/
|
||||
async cleanup(taskId: string): Promise<void> {
|
||||
const worktreePath = this.activeWorktrees.get(taskId);
|
||||
if (!worktreePath) return;
|
||||
|
||||
try {
|
||||
execSync(`git worktree remove "${worktreePath}" --force`, {
|
||||
cwd: this.rootDir,
|
||||
stdio: "pipe",
|
||||
});
|
||||
this.activeWorktrees.delete(taskId);
|
||||
console.log(`[executor] Cleaned up worktree for ${taskId}`);
|
||||
} catch (err: any) {
|
||||
console.error(
|
||||
`[executor] Failed to clean up worktree for ${taskId}:`,
|
||||
err.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getWorktreePath(taskId: string): string | undefined {
|
||||
return this.activeWorktrees.get(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
function buildExecutionPrompt(task: TaskDetail): string {
|
||||
return `Execute this task. The PROMPT.md specification follows.
|
||||
|
||||
## Task Info
|
||||
- **ID:** ${task.id}
|
||||
- **Title:** ${task.title}
|
||||
${task.dependencies.length > 0 ? `- **Dependencies:** ${task.dependencies.join(", ")}` : ""}
|
||||
|
||||
## PROMPT.md
|
||||
\`\`\`markdown
|
||||
${task.prompt}
|
||||
\`\`\`
|
||||
|
||||
## Instructions
|
||||
1. Read and understand the specification above
|
||||
2. Explore the codebase to understand the current state
|
||||
3. Implement each step in order
|
||||
4. Commit after completing each step using: \`git commit -m "feat(${task.id}): <description>"\`
|
||||
5. When all steps pass, create a \`.DONE\` file: \`echo "done" > .DONE\`
|
||||
|
||||
Begin implementation now.`;
|
||||
}
|
||||
4
packages/engine/src/index.ts
Normal file
4
packages/engine/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { TriageProcessor, type TriageProcessorOptions } from "./triage.js";
|
||||
export { TaskExecutor, type TaskExecutorOptions } from "./executor.js";
|
||||
export { Scheduler, type SchedulerOptions } from "./scheduler.js";
|
||||
export { createHaiAgent, type AgentOptions, type AgentResult } from "./pi.js";
|
||||
83
packages/engine/src/pi.ts
Normal file
83
packages/engine/src/pi.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Shared pi SDK setup for hai engine agents.
|
||||
*
|
||||
* Uses the user's existing pi auth (API keys / OAuth from ~/.pi/agent/auth.json).
|
||||
* Provides factory functions for creating triage and executor agent sessions.
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthStorage,
|
||||
createAgentSession,
|
||||
createCodingTools,
|
||||
createReadOnlyTools,
|
||||
DefaultResourceLoader,
|
||||
ModelRegistry,
|
||||
SessionManager,
|
||||
SettingsManager,
|
||||
type AgentSession,
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export interface AgentResult {
|
||||
session: AgentSession;
|
||||
}
|
||||
|
||||
export interface AgentOptions {
|
||||
cwd: string;
|
||||
systemPrompt: string;
|
||||
tools?: "coding" | "readonly";
|
||||
onText?: (delta: string) => void;
|
||||
onToolStart?: (name: string) => void;
|
||||
onToolEnd?: (name: string, isError: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pi agent session configured for hai.
|
||||
* Reuses the user's existing pi auth and model configuration.
|
||||
*/
|
||||
export async function createHaiAgent(options: AgentOptions): Promise<AgentResult> {
|
||||
const authStorage = AuthStorage.create();
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
const tools =
|
||||
options.tools === "readonly"
|
||||
? createReadOnlyTools(options.cwd)
|
||||
: createCodingTools(options.cwd);
|
||||
|
||||
const settingsManager = SettingsManager.inMemory({
|
||||
compaction: { enabled: true },
|
||||
retry: { enabled: true, maxRetries: 3 },
|
||||
});
|
||||
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
cwd: options.cwd,
|
||||
settingsManager,
|
||||
systemPromptOverride: () => options.systemPrompt,
|
||||
appendSystemPromptOverride: () => [],
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
cwd: options.cwd,
|
||||
authStorage,
|
||||
modelRegistry,
|
||||
resourceLoader,
|
||||
tools,
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
settingsManager,
|
||||
});
|
||||
|
||||
// Wire up event listeners
|
||||
session.subscribe((event) => {
|
||||
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
||||
options.onText?.(event.assistantMessageEvent.delta);
|
||||
}
|
||||
if (event.type === "tool_execution_start") {
|
||||
options.onToolStart?.(event.toolName);
|
||||
}
|
||||
if (event.type === "tool_execution_end") {
|
||||
options.onToolEnd?.(event.toolName, event.isError);
|
||||
}
|
||||
});
|
||||
|
||||
return { session };
|
||||
}
|
||||
99
packages/engine/src/scheduler.ts
Normal file
99
packages/engine/src/scheduler.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { resolveDependencyOrder, type TaskStore, type Task } from "@hai/core";
|
||||
|
||||
export interface SchedulerOptions {
|
||||
/** Max concurrent in-progress tasks. Default: 2 */
|
||||
maxConcurrent?: number;
|
||||
/** Milliseconds between scheduling polls. Default: 15000 */
|
||||
pollIntervalMs?: number;
|
||||
/** Called when scheduler starts a task */
|
||||
onSchedule?: (task: Task) => void;
|
||||
/** Called when a task is blocked by deps */
|
||||
onBlocked?: (task: Task, blockedBy: string[]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler watches the "todo" column and moves tasks to "in-progress"
|
||||
* when their dependencies are satisfied and concurrency allows.
|
||||
*
|
||||
* It respects:
|
||||
* - Dependency ordering (tasks depending on others wait)
|
||||
* - Concurrency limits (max N tasks in-progress at once)
|
||||
*/
|
||||
export class Scheduler {
|
||||
private running = false;
|
||||
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
constructor(
|
||||
private store: TaskStore,
|
||||
private options: SchedulerOptions = {},
|
||||
) {}
|
||||
|
||||
start(): void {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
|
||||
const interval = this.options.pollIntervalMs ?? 15_000;
|
||||
this.pollInterval = setInterval(() => this.schedule(), interval);
|
||||
this.schedule();
|
||||
console.log(
|
||||
`[scheduler] Started (max concurrent: ${this.options.maxConcurrent ?? 2})`,
|
||||
);
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.running = false;
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
console.log("[scheduler] Stopped");
|
||||
}
|
||||
|
||||
/** Run one scheduling pass. */
|
||||
async schedule(): Promise<void> {
|
||||
if (!this.running) return;
|
||||
|
||||
try {
|
||||
const tasks = await this.store.listTasks();
|
||||
const maxConcurrent = this.options.maxConcurrent ?? 2;
|
||||
|
||||
const inProgress = tasks.filter((t) => t.column === "in-progress");
|
||||
const available = maxConcurrent - inProgress.length;
|
||||
if (available <= 0) return;
|
||||
|
||||
const todo = tasks.filter((t) => t.column === "todo");
|
||||
if (todo.length === 0) return;
|
||||
|
||||
// Resolve dependency order among todo tasks
|
||||
const ordered = resolveDependencyOrder(todo);
|
||||
let started = 0;
|
||||
|
||||
for (const taskId of ordered) {
|
||||
if (started >= available) break;
|
||||
|
||||
const task = tasks.find((t) => t.id === taskId)!;
|
||||
|
||||
// Check all deps are satisfied (done or in-review)
|
||||
const unmetDeps = task.dependencies.filter((depId) => {
|
||||
const dep = tasks.find((t) => t.id === depId);
|
||||
return dep && dep.column !== "done" && dep.column !== "in-review";
|
||||
});
|
||||
|
||||
if (unmetDeps.length > 0) {
|
||||
this.options.onBlocked?.(task, unmetDeps);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dependencies met — move to in-progress
|
||||
console.log(
|
||||
`[scheduler] Starting ${task.id}: ${task.title} (deps satisfied)`,
|
||||
);
|
||||
await this.store.moveTask(task.id, "in-progress");
|
||||
this.options.onSchedule?.(task);
|
||||
started++;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[scheduler] Scheduling error:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
packages/engine/src/triage.ts
Normal file
160
packages/engine/src/triage.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import type { TaskStore, Task, TaskDetail } from "@hai/core";
|
||||
import { createHaiAgent } from "./pi.js";
|
||||
|
||||
const TRIAGE_SYSTEM_PROMPT = `You are a task specification agent for "hai", an AI-orchestrated task board.
|
||||
|
||||
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously.
|
||||
|
||||
## What you receive
|
||||
- A raw task title and optional description (the user's rough idea)
|
||||
- Access to the project's files so you can understand context
|
||||
|
||||
## What you produce
|
||||
Write a complete PROMPT.md specification using the write tool. The specification must include:
|
||||
|
||||
1. **Mission** — One paragraph: what to build and why it matters
|
||||
2. **Steps** — Numbered implementation steps, each with:
|
||||
- Specific, verifiable checkbox items
|
||||
- Expected artifacts (files created/modified)
|
||||
3. **File Scope** — Which files/directories will be touched
|
||||
4. **Acceptance Criteria** — How to verify the task is complete
|
||||
5. **Do NOT** — Guardrails to prevent scope creep
|
||||
|
||||
## Guidelines
|
||||
- Read the project structure and relevant source files to understand context before writing the spec
|
||||
- Be specific — name actual files, functions, and patterns from the codebase
|
||||
- Keep steps focused and achievable (2-5 checkboxes per step)
|
||||
- Include a testing step
|
||||
- If the task is vague, make reasonable assumptions and document them
|
||||
- Write the spec directly to the file path you're given — do not ask for clarification
|
||||
|
||||
## Output format
|
||||
Write the PROMPT.md content directly using the write tool. Nothing else.`;
|
||||
|
||||
export interface TriageProcessorOptions {
|
||||
/** Milliseconds between polls. Default: 10000 */
|
||||
pollIntervalMs?: number;
|
||||
/** Called when a task starts being specified */
|
||||
onSpecifyStart?: (task: Task) => void;
|
||||
/** Called when a task is successfully specified */
|
||||
onSpecifyComplete?: (task: Task) => void;
|
||||
/** Called on specification failure */
|
||||
onSpecifyError?: (task: Task, error: Error) => void;
|
||||
/** Called with agent text output */
|
||||
onAgentText?: (taskId: string, delta: string) => void;
|
||||
}
|
||||
|
||||
export class TriageProcessor {
|
||||
private running = false;
|
||||
private pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
private processing = new Set<string>();
|
||||
|
||||
constructor(
|
||||
private store: TaskStore,
|
||||
private rootDir: string,
|
||||
private options: TriageProcessorOptions = {},
|
||||
) {}
|
||||
|
||||
start(): void {
|
||||
if (this.running) return;
|
||||
this.running = true;
|
||||
|
||||
const interval = this.options.pollIntervalMs ?? 10_000;
|
||||
this.pollInterval = setInterval(() => this.poll(), interval);
|
||||
this.poll();
|
||||
console.log("[triage] Processor started");
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.running = false;
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval);
|
||||
this.pollInterval = null;
|
||||
}
|
||||
console.log("[triage] Processor stopped");
|
||||
}
|
||||
|
||||
private async poll(): Promise<void> {
|
||||
if (!this.running) return;
|
||||
|
||||
try {
|
||||
const tasks = await this.store.listTasks();
|
||||
const triageTasks = tasks.filter(
|
||||
(t) => t.column === "triage" && !this.processing.has(t.id),
|
||||
);
|
||||
|
||||
for (const task of triageTasks) {
|
||||
// Process one at a time to avoid overwhelming the API
|
||||
await this.specifyTask(task);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[triage] Poll error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async specifyTask(task: Task): Promise<void> {
|
||||
if (this.processing.has(task.id)) return;
|
||||
this.processing.add(task.id);
|
||||
|
||||
console.log(`[triage] Specifying ${task.id}: ${task.title}`);
|
||||
this.options.onSpecifyStart?.(task);
|
||||
|
||||
try {
|
||||
// Get the full task detail including current prompt
|
||||
const detail = await this.store.getTask(task.id);
|
||||
const promptPath = `.hai/tasks/${task.id}/PROMPT.md`;
|
||||
|
||||
// Create a pi agent session for specification
|
||||
const { session } = await createHaiAgent({
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: TRIAGE_SYSTEM_PROMPT,
|
||||
tools: "coding",
|
||||
onText: (delta) => this.options.onAgentText?.(task.id, delta),
|
||||
onToolStart: (name) =>
|
||||
console.log(`[triage] ${task.id} tool: ${name}`),
|
||||
});
|
||||
|
||||
try {
|
||||
// Build the prompt for the agent
|
||||
const agentPrompt = buildSpecificationPrompt(detail, promptPath);
|
||||
|
||||
// Run the agent
|
||||
await session.prompt(agentPrompt);
|
||||
|
||||
// Move to todo
|
||||
await this.store.moveTask(task.id, "todo");
|
||||
console.log(`[triage] ✓ ${task.id} specified and moved to todo`);
|
||||
this.options.onSpecifyComplete?.(task);
|
||||
} finally {
|
||||
session.dispose();
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`[triage] ✗ ${task.id} specification failed:`, err.message);
|
||||
this.options.onSpecifyError?.(task, err);
|
||||
} finally {
|
||||
this.processing.delete(task.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildSpecificationPrompt(task: TaskDetail, promptPath: string): string {
|
||||
return `Specify this task and write the result to \`${promptPath}\`.
|
||||
|
||||
## Task
|
||||
- **ID:** ${task.id}
|
||||
- **Title:** ${task.title}
|
||||
${task.description ? `- **Description:** ${task.description}` : ""}
|
||||
${task.dependencies.length > 0 ? `- **Dependencies:** ${task.dependencies.join(", ")}` : ""}
|
||||
|
||||
## Current rough prompt
|
||||
\`\`\`
|
||||
${task.prompt}
|
||||
\`\`\`
|
||||
|
||||
## Instructions
|
||||
1. Read the project structure to understand context (look at package.json, source files, etc.)
|
||||
2. Write a complete PROMPT.md specification to \`${promptPath}\`
|
||||
3. The specification must be detailed enough for an autonomous AI agent to implement without asking questions
|
||||
|
||||
Use the write tool to write the specification file.`;
|
||||
}
|
||||
8
packages/engine/tsconfig.json
Normal file
8
packages/engine/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user