Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions .plan/04-prd-task-bootstrap/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# PRD Task Bootstrap Plan

## Goal
Add a small PR-ready workflow that lets a user select a strategy/PRD markdown file from the current workspace, parse actionable items from it, and populate ordered backlog cards so work can begin immediately.

## Scope
1. In scope
- create-dialog import UX for workspace markdown files
- safe runtime API for reading markdown files from the workspace
- markdown-to-task parsing utilities
- preserving imported task order in backlog batch creation
- focused tests for parsing, runtime API behavior, and ordered creation
2. Out of scope for this pass
- dependency graph generation from PRD sections
- automatic task starting on import without user review
- rich markdown AST parsing or new third-party parser dependencies
- changes to inline card creation outside the main create dialog

## Current Investigation Snapshot
1. `TaskCreateDialog` already supports multi-create by splitting prompt text into list items.
2. `useTaskEditor.handleCreateTasks` is the current multi-create source of truth, but backlog insertion currently reverses input order visually.
3. `TaskPromptComposer` already uses `workspace.searchFiles` through TRPC for workspace-scoped file discovery.
4. There is no dedicated workspace file-read API yet, so markdown import needs a new safe runtime surface.
5. Existing `.plan/<initiative>/plan.md` + `status.md` pairs are the repository convention for tracked initiatives.

## Decision Table
1. Import entry point
- Decision: add import UI only to `web-ui/src/components/task-create-dialog.tsx`
- Rationale: keeps the change tight and reuses the existing create/review flow
2. Supported file types
- Decision: `.md`, `.markdown`, `.mdx`
- Rationale: covers common PRD/strategy files without broad file-reading scope
3. Imported prompt source reference
- Decision: append `@<source-path>` to each imported prompt when not already present
- Rationale: preserves provenance and fits existing file reference conventions
4. Importable markdown content
- Decision: import unchecked checklist items anywhere plus top-level bullet/numbered items under execution-like headings
- Rationale: keeps extraction actionable and avoids pulling in scope/risk prose
5. Batch ordering
- Decision: preserve source order visually in backlog top-to-bottom
- Rationale: imported plans need predictable execution order

## Execution Phases

### Phase 1: Planning docs and acceptance criteria
Deliverables:
1. Add this plan file and matching `status.md`.
2. Freeze parsing and UX decisions for the first pass.

Exit criteria:
1. Decisions above are reflected in implementation and tests.

### Phase 2: Runtime workspace markdown read support
Changes:
1. Add shared request/response contracts for reading a workspace file.
2. Add request validation.
3. Add a safe workspace helper that validates path, extension, workspace containment, and file size.
4. Expose a `workspace.readFile` TRPC query and cover it with tests.

Exit criteria:
1. Web UI can read a selected markdown file through the runtime using workspace scoping.
2. Invalid paths, unsupported extensions, and missing files fail safely.

### Phase 3: Prompt parsing and ordered batch creation
Changes:
1. Move plain list parsing into `web-ui/src/utils/task-prompt.ts`.
2. Add markdown import parsing with tests.
3. Update batch creation in `useTaskEditor` so backlog order matches source order.

Exit criteria:
1. Imported tasks are editable before creation.
2. Creating multiple tasks preserves document order visually.

### Phase 4: Create-dialog import UX
Changes:
1. Add markdown search/load controls to the main create dialog.
2. Let users choose a markdown file, parse prompts, and review/edit them in multi-task mode.
3. Show clear errors for empty parses and read failures.

Exit criteria:
1. A user can import tasks from a workspace PRD/strategy file without leaving the dialog.
2. Existing single-task and prompt-splitting flows continue to work.

### Phase 5: Validation and PR polish
Changes:
1. Run targeted runtime and web tests.
2. Confirm no unnecessary architectural spread beyond the existing task-create flow.

Exit criteria:
1. Relevant tests pass.
2. The diff remains tight and upstream-friendly.

## Risks and Mitigations
1. Parser under-extracts useful tasks
- Mitigation: support explicit checklist items anywhere and common execution headings.
2. Parser over-extracts non-task bullets
- Mitigation: ignore checked items, nested bullets, code fences, and non-execution sections.
3. Search results become stale between search and read
- Mitigation: revalidate the selected path in the server-side read helper.
4. Batch order change surprises existing multi-create users
- Mitigation: keep the change small, test it directly, and call it out in the summary/PR notes.

## Validation Checklist
1. Runtime/API
- `test/runtime/api-validation.test.ts`
- `test/runtime/trpc/workspace-api.test.ts`
- helper test for workspace markdown reads
2. Web UI
- `web-ui/src/utils/task-prompt.test.ts`
- `web-ui/src/hooks/use-task-editor.test.tsx`
- targeted create-dialog coverage if needed
3. Manual flow
- search and import a markdown plan file
- verify imported prompts include `@path`
- create tasks and confirm backlog order matches document order
- confirm back-to-single behaves correctly for prompt split vs markdown import

## Rollback Strategy
1. Keep the runtime file-read path isolated behind `workspace.readFile`.
2. Keep markdown parsing isolated in `task-prompt.ts`.
3. If the feature needs to be reverted, remove the dialog import UI and the additive runtime route without affecting saved board data.

## Progress Tracking Location
Use `.plan/04-prd-task-bootstrap/status.md` as the implementation tracker for this initiative.
58 changes: 58 additions & 0 deletions .plan/04-prd-task-bootstrap/status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# PRD Task Bootstrap Status

## Current State
1. Initiative: `04-prd-task-bootstrap`
2. Overall progress: implemented, pending full local verification
3. Last updated: 2026-03-16

## Decision Tracker
1. Import entry point (`TaskCreateDialog` only)
- Status: decided
2. Supported file types (`.md`, `.markdown`, `.mdx`)
- Status: decided
3. Imported prompt source references (`@path` suffix)
- Status: decided
4. Importable markdown rules (unchecked checklists + execution-section lists)
- Status: decided
5. Preserve batch order visually in backlog
- Status: decided

## Phase Checklist

### Phase 1: Planning and acceptance criteria
- [x] Review existing task-create and workspace file search flows
- [x] Add initiative plan and status tracker
- [x] Freeze first-pass scope decisions

### Phase 2: Runtime workspace markdown read support
- [x] Add shared request/response contracts
- [x] Add validation helper
- [x] Add safe workspace markdown read helper
- [x] Add TRPC route and runtime tests

### Phase 3: Prompt parsing and ordered batch creation
- [x] Move plain list parsing into shared prompt utilities
- [x] Add markdown import parsing tests
- [x] Preserve input order in batch backlog creation

### Phase 4: Create-dialog import UX
- [x] Add markdown import search/load controls
- [x] Parse imported markdown into editable task prompts
- [x] Handle empty parse and read failures cleanly

### Phase 5: Validation and PR polish
- [ ] Run targeted tests (blocked locally: repo dependencies are not installed in this workspace)
- [x] Verify the diff stays tight and upstream-friendly

## Investigation Summary
1. The feature can reuse existing multi-create and create-and-start flows.
2. The main additive backend gap is safe workspace markdown file reading.
3. The main non-additive behavior change is fixing batch creation order to match input order visually.

## Open Risks
1. Some PRD markdown files may not contain importable actionable items.
2. Search index staleness can produce paths that no longer exist at read time.
3. UI complexity can grow if import UX spreads beyond the main create dialog.

## Resume Point
Install workspace dependencies, then run the targeted root and web-ui test/typecheck commands to finish Phase 5 verification.
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,5 @@ Dark theme
- Do NOT use Blueprint, Tailwind's light-mode defaults, or any `dark:` prefix. The theme is always dark.

Misc. tribal knowledge
- Kanban is launched from the user's shell and inherits its environment. For agent detection and task-agent startup, prefer direct PATH checks and direct process launches over spawning an interactive shell. Avoid `zsh -i`, shell fallback command discovery, or "launch shell then type command into it" on hot paths. On setups with heavy shell init like `conda` or `nvm`, doing that per task can freeze the runtime and even make new Terminal.app windows feel hung when several tasks start at once. It's fine to use an actual interactive shell for explicit shell terminals, not for normal agent session work.
- Kanban is launched from the user's shell and inherits its environment. For agent detection and task-agent startup, prefer direct PATH checks and direct process launches over spawning an interactive shell. Avoid `zsh -i`, shell fallback command discovery, or "launch shell then type command into it" on hot paths. On setups with heavy shell init like `conda` or `nvm`, doing that per task can freeze the runtime and even make new Terminal.app windows feel hung when several tasks start at once. It's fine to use an actual interactive shell for explicit shell terminals, not for normal agent session work.
- In `web-ui` Vitest runs, `window.localStorage` may be an incomplete shim. Tests should use the storage helpers or install an explicit `Storage` mock instead of assuming methods like `clear()` or `removeItem()` exist.
13 changes: 13 additions & 0 deletions src/core/api-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ export const runtimeWorkspaceFileSearchResponseSchema = z.object({
});
export type RuntimeWorkspaceFileSearchResponse = z.infer<typeof runtimeWorkspaceFileSearchResponseSchema>;

export const runtimeWorkspaceFileReadRequestSchema = z.object({
path: z.string(),
});
export type RuntimeWorkspaceFileReadRequest = z.infer<typeof runtimeWorkspaceFileReadRequestSchema>;

export const runtimeWorkspaceFileReadResponseSchema = z.object({
path: z.string(),
content: z.string(),
});
export type RuntimeWorkspaceFileReadResponse = z.infer<typeof runtimeWorkspaceFileReadResponseSchema>;

export const runtimeAgentIdSchema = z.enum(["claude", "codex", "gemini", "opencode", "droid", "cline"]);
export type RuntimeAgentId = z.infer<typeof runtimeAgentIdSchema>;

Expand All @@ -73,6 +84,7 @@ export const runtimeBoardCardSchema = z.object({
startInPlanMode: z.boolean(),
autoReviewEnabled: z.boolean().optional(),
autoReviewMode: runtimeTaskAutoReviewModeSchema.optional(),
agentId: runtimeAgentIdSchema.optional(),
baseRef: z.string(),
createdAt: z.number(),
updatedAt: z.number(),
Expand Down Expand Up @@ -492,6 +504,7 @@ export const runtimeTaskSessionStartRequestSchema = z.object({
prompt: z.string(),
startInPlanMode: z.boolean().optional(),
resumeFromTrash: z.boolean().optional(),
agentId: runtimeAgentIdSchema.optional(),
baseRef: z.string(),
cols: z.number().int().positive().optional(),
rows: z.number().int().positive().optional(),
Expand Down
13 changes: 13 additions & 0 deletions src/core/api-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type RuntimeTaskWorkspaceInfoRequest,
type RuntimeTerminalWsClientMessage,
type RuntimeWorkspaceChangesRequest,
type RuntimeWorkspaceFileReadRequest,
type RuntimeWorkspaceFileSearchRequest,
type RuntimeWorkspaceStateSaveRequest,
type RuntimeWorktreeDeleteRequest,
Expand All @@ -31,6 +32,7 @@ import {
runtimeTaskWorkspaceInfoRequestSchema,
runtimeTerminalWsClientMessageSchema,
runtimeWorkspaceChangesRequestSchema,
runtimeWorkspaceFileReadRequestSchema,
runtimeWorkspaceFileSearchRequestSchema,
runtimeWorkspaceStateSaveRequestSchema,
runtimeWorktreeDeleteRequestSchema,
Expand Down Expand Up @@ -106,6 +108,17 @@ export function parseWorkspaceFileSearchRequest(query: URLSearchParams): Runtime
});
}

export function parseWorkspaceFileReadRequest(value: unknown): RuntimeWorkspaceFileReadRequest {
const parsed = parseWithSchema(runtimeWorkspaceFileReadRequestSchema, value);
const path = parsed.path.trim();
if (!path) {
throw new Error("File path cannot be empty.");
}
return {
path,
};
}

export function parseGitCheckoutRequest(value: unknown): RuntimeGitCheckoutRequest {
const parsed = parseWithSchema(runtimeGitCheckoutRequestSchema, value);
const branch = parsed.branch.trim();
Expand Down
5 changes: 5 additions & 0 deletions src/core/task-board-mutations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
RuntimeAgentId,
RuntimeBoardCard,
RuntimeBoardColumnId,
RuntimeBoardData,
Expand All @@ -12,6 +13,7 @@ export interface RuntimeCreateTaskInput {
startInPlanMode?: boolean;
autoReviewEnabled?: boolean;
autoReviewMode?: RuntimeTaskAutoReviewMode;
agentId?: RuntimeAgentId;
baseRef: string;
}

Expand All @@ -20,6 +22,7 @@ export interface RuntimeUpdateTaskInput {
startInPlanMode?: boolean;
autoReviewEnabled?: boolean;
autoReviewMode?: RuntimeTaskAutoReviewMode;
agentId?: RuntimeAgentId;
baseRef: string;
}

Expand Down Expand Up @@ -266,6 +269,7 @@ export function addTaskToColumn(
startInPlanMode: Boolean(input.startInPlanMode),
autoReviewEnabled: Boolean(input.autoReviewEnabled),
autoReviewMode: normalizeTaskAutoReviewMode(input.autoReviewMode),
...(input.agentId ? { agentId: input.agentId } : {}),
baseRef,
createdAt: now,
updatedAt: now,
Expand Down Expand Up @@ -530,6 +534,7 @@ export function updateTask(
startInPlanMode: Boolean(input.startInPlanMode),
autoReviewEnabled: Boolean(input.autoReviewEnabled),
autoReviewMode: normalizeTaskAutoReviewMode(input.autoReviewMode),
agentId: input.agentId ?? card.agentId,
baseRef,
updatedAt: now,
};
Expand Down
8 changes: 6 additions & 2 deletions src/terminal/agent-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,12 @@ function getCuratedDefinitions(runtimeConfig: RuntimeConfigState, detected: stri
});
}

export function resolveAgentCommand(runtimeConfig: RuntimeConfigState): ResolvedAgentCommand | null {
const selected = RUNTIME_AGENT_CATALOG.find((entry) => entry.id === runtimeConfig.selectedAgentId);
export function resolveAgentCommand(
runtimeConfig: RuntimeConfigState,
agentIdOverride?: RuntimeAgentId | null,
): ResolvedAgentCommand | null {
const effectiveAgentId = agentIdOverride ?? runtimeConfig.selectedAgentId;
const selected = RUNTIME_AGENT_CATALOG.find((entry) => entry.id === effectiveAgentId);
if (!selected) {
return null;
}
Expand Down
14 changes: 14 additions & 0 deletions src/trpc/app-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import type {
RuntimeTaskWorkspaceInfoResponse,
RuntimeWorkspaceChangesRequest,
RuntimeWorkspaceChangesResponse,
RuntimeWorkspaceFileReadRequest,
RuntimeWorkspaceFileReadResponse,
RuntimeWorkspaceFileSearchRequest,
RuntimeWorkspaceFileSearchResponse,
RuntimeWorkspaceStateResponse,
Expand Down Expand Up @@ -83,6 +85,8 @@ import {
runtimeTaskWorkspaceInfoResponseSchema,
runtimeWorkspaceChangesRequestSchema,
runtimeWorkspaceChangesResponseSchema,
runtimeWorkspaceFileReadRequestSchema,
runtimeWorkspaceFileReadResponseSchema,
runtimeWorkspaceFileSearchRequestSchema,
runtimeWorkspaceFileSearchResponseSchema,
runtimeWorkspaceStateResponseSchema,
Expand Down Expand Up @@ -162,6 +166,10 @@ export interface RuntimeTrpcContext {
scope: RuntimeTrpcWorkspaceScope,
input: RuntimeWorkspaceFileSearchRequest,
) => Promise<RuntimeWorkspaceFileSearchResponse>;
readFile: (
scope: RuntimeTrpcWorkspaceScope,
input: RuntimeWorkspaceFileReadRequest,
) => Promise<RuntimeWorkspaceFileReadResponse>;
loadState: (scope: RuntimeTrpcWorkspaceScope) => Promise<RuntimeWorkspaceStateResponse>;
saveState: (
scope: RuntimeTrpcWorkspaceScope,
Expand Down Expand Up @@ -346,6 +354,12 @@ export const runtimeAppRouter = t.router({
.query(async ({ ctx, input }) => {
return await ctx.workspaceApi.searchFiles(ctx.workspaceScope, input);
}),
readFile: workspaceProcedure
.input(runtimeWorkspaceFileReadRequestSchema)
.output(runtimeWorkspaceFileReadResponseSchema)
.query(async ({ ctx, input }) => {
return await ctx.workspaceApi.readFile(ctx.workspaceScope, input);
}),
getState: workspaceProcedure.output(runtimeWorkspaceStateResponseSchema).query(async ({ ctx }) => {
return await ctx.workspaceApi.loadState(ctx.workspaceScope);
}),
Expand Down
6 changes: 4 additions & 2 deletions src/trpc/runtime-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ export function createRuntimeApi(deps: CreateRuntimeApiDependencies): RuntimeTrp
try {
const body = parseTaskSessionStartRequest(input);
const scopedRuntimeConfig = await deps.loadScopedRuntimeConfig(workspaceScope);
const resolved = resolveAgentCommand(scopedRuntimeConfig);
const resolved = resolveAgentCommand(scopedRuntimeConfig, body.agentId);
if (!resolved) {
return {
ok: false,
summary: null,
error: "No runnable agent command is configured. Open Settings, install a supported CLI, and select it.",
error: body.agentId
? "The runtime assigned to this task is not runnable. Install it or choose a different runtime for the task."
: "No runnable agent command is configured. Open Settings, install a supported CLI, and select it.",
};
}
const taskCwd = await resolveExistingTaskCwdOrEnsure({
Expand Down
Loading