diff --git a/.cursor/rules/planforge-cursor-agent-terminal.mdc b/.cursor/rules/planforge-cursor-agent-terminal.mdc new file mode 100644 index 0000000..360e8bc --- /dev/null +++ b/.cursor/rules/planforge-cursor-agent-terminal.mdc @@ -0,0 +1,11 @@ +--- +description: PlanForge — avoid Cursor agent terminal default timeout (~30s) for plan/implement +globs: +alwaysApply: true +--- + +# PlanForge: Cursor Agent Terminal (plan/implement) + +- When running **planforge plan** or **planforge implement**, avoid hitting the Cursor agent terminal default timeout (~30s). +- Run those commands with **block_until_ms** (or equivalent max-wait parameter) set to **300000** (5 minutes) or more, since plan/implement may take 1–5 minutes. +- If the environment does not support that parameter, note that the rule cannot change the limit; when needed, tell the user they can run `planforge plan` / `planforge implement` in a local terminal to see streaming to completion. diff --git a/.cursor/rules/workflow.mdc b/.cursor/rules/planforge-workflow.mdc similarity index 100% rename from .cursor/rules/workflow.mdc rename to .cursor/rules/planforge-workflow.mdc diff --git a/.gitignore b/.gitignore index 24eab3b..5961dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,8 @@ __pycache__ # cursor / planforge planforge.json -.planforge/plans -.planforge/contexts +.cursor/plans +.cursor/contexts .cursor/rules/code-comments.mdc .cursor/rules/notion.mdc .cursor/rules/pr-writing.mdc diff --git a/packages/cli-js/scripts/check-templates.js b/packages/cli-js/scripts/check-templates.js index fc3205d..5b5c3de 100644 --- a/packages/cli-js/scripts/check-templates.js +++ b/packages/cli-js/scripts/check-templates.js @@ -15,7 +15,8 @@ const required = [ "config/default-claude-only.json", "config/default-codex-only.json", "doctor/prompts.json", - "cursor/rules/workflow.mdc", + "cursor/rules/planforge-workflow.mdc", + "cursor/rules/planforge-cursor-agent-terminal.mdc", ]; const missing = required.filter((rel) => !existsSync(resolve(templatesDir, rel))); diff --git a/packages/cli-js/src/commands/doctor.ts b/packages/cli-js/src/commands/doctor.ts index af85d7f..1c9075d 100644 --- a/packages/cli-js/src/commands/doctor.ts +++ b/packages/cli-js/src/commands/doctor.ts @@ -170,6 +170,18 @@ export async function runDoctor(_args: string[]): Promise { message: hasContextDir ? "exists" : "missing (run planforge init)", }); + const rulesDir = resolve(projectRoot, ".cursor", "rules"); + const planforgeRuleFiles = ["planforge-workflow.mdc", "planforge-cursor-agent-terminal.mdc"]; + for (const ruleFile of planforgeRuleFiles) { + const rulePath = resolve(rulesDir, ruleFile); + const hasRule = await fs.pathExists(rulePath); + checks.push({ + name: `.cursor/rules/${ruleFile}`, + status: hasRule ? "ok" : "warn", + message: hasRule ? "exists" : "missing (run planforge install)", + }); + } + if (hasPlansDir) { const entries = await fs.readdir(plansDir, { withFileTypes: true }); const hasLegacyFlatPlans = entries.some((entry) => entry.isFile() && entry.name.endsWith(".plan.md")); @@ -229,6 +241,7 @@ export async function runDoctor(_args: string[]): Promise { if (hasError) { process.exit(1); } + process.exit(0); } export interface DoctorAiModelOption { @@ -328,11 +341,11 @@ async function runStreamingDoctorTc( } function loadWorkflowMdc(projectRoot: string): string { - const installed = resolve(projectRoot, ".cursor", "rules", "workflow.mdc"); + const installed = resolve(projectRoot, ".cursor", "rules", "planforge-workflow.mdc"); if (fs.existsSync(installed)) { return fs.readFileSync(installed, "utf-8"); } - const templatesPath = resolve(getTemplatesRoot(), "cursor", "rules", "workflow.mdc"); + const templatesPath = resolve(getTemplatesRoot(), "cursor", "rules", "planforge-workflow.mdc"); if (fs.existsSync(templatesPath)) { return fs.readFileSync(templatesPath, "utf-8"); } diff --git a/packages/cli-js/src/utils/repo-context.ts b/packages/cli-js/src/utils/repo-context.ts index 32b21ec..751d0d1 100644 --- a/packages/cli-js/src/utils/repo-context.ts +++ b/packages/cli-js/src/utils/repo-context.ts @@ -13,7 +13,7 @@ const MAX_RIPGREP_FILES = 15; const MAX_RECENT_COMMITS_CHARS = 500; const MAX_GOAL_FILES_LOG_CHARS = 600; const MAX_GOAL_FILES_LOG_FILES = 5; -const SKIP_DIRS = new Set([".git", "node_modules", ".cursor", ".planforge", "dist", "build", "__pycache__", ".venv", "venv"]); +const SKIP_DIRS = new Set([".git", "node_modules", ".cursor", "dist", "build", "__pycache__", ".venv", "venv"]); function runGit(cwd: string, args: string[]): string | null { try { @@ -65,7 +65,6 @@ function getRipgrepFileList(projectRoot: string, goal: string): string[] { "!.git/**", "!node_modules/**", "!.cursor/**", - "!.planforge/**", "!dist/**", "!build/**", "!__pycache__/**", @@ -137,7 +136,6 @@ function getRipgrepContext(projectRoot: string, goal: string): string | undefine "!.git/**", "!node_modules/**", "!.cursor/**", - "!.planforge/**", "!dist/**", "!build/**", "!__pycache__/**", diff --git a/packages/cli-py/planforge/commands/doctor.py b/packages/cli-py/planforge/commands/doctor.py index fbacd37..84250be 100644 --- a/packages/cli-py/planforge/commands/doctor.py +++ b/packages/cli-py/planforge/commands/doctor.py @@ -236,6 +236,17 @@ def run_doctor(args: list[str]) -> None: "exists" if has_context_dir else "missing (run planforge init)", )) + rules_dir = Path(project_root) / ".cursor" / "rules" + planforge_rule_files = ["planforge-workflow.mdc", "planforge-cursor-agent-terminal.mdc"] + for rule_file in planforge_rule_files: + rule_path = rules_dir / rule_file + has_rule = rule_path.exists() + checks.append(( + f".cursor/rules/{rule_file}", + "ok" if has_rule else "warn", + "exists" if has_rule else "missing (run planforge install)", + )) + if has_plans_dir: plan_root = Path(plans_dir) root_entries = list(plan_root.iterdir()) @@ -283,13 +294,14 @@ def run_doctor(args: list[str]) -> None: if any(status == "error" for _, status, _ in checks): raise SystemExit(1) + sys.exit(0) def _load_workflow_mdc(project_root: str) -> str: - installed = Path(project_root) / ".cursor" / "rules" / "workflow.mdc" + installed = Path(project_root) / ".cursor" / "rules" / "planforge-workflow.mdc" if installed.exists(): return installed.read_text(encoding="utf-8") - templates_path = Path(get_templates_root()) / "cursor" / "rules" / "workflow.mdc" + templates_path = Path(get_templates_root()) / "cursor" / "rules" / "planforge-workflow.mdc" if templates_path.exists(): return templates_path.read_text(encoding="utf-8") raise FileNotFoundError( diff --git a/templates/cursor/rules/planforge-cursor-agent-terminal.mdc b/templates/cursor/rules/planforge-cursor-agent-terminal.mdc new file mode 100644 index 0000000..360e8bc --- /dev/null +++ b/templates/cursor/rules/planforge-cursor-agent-terminal.mdc @@ -0,0 +1,11 @@ +--- +description: PlanForge — avoid Cursor agent terminal default timeout (~30s) for plan/implement +globs: +alwaysApply: true +--- + +# PlanForge: Cursor Agent Terminal (plan/implement) + +- When running **planforge plan** or **planforge implement**, avoid hitting the Cursor agent terminal default timeout (~30s). +- Run those commands with **block_until_ms** (or equivalent max-wait parameter) set to **300000** (5 minutes) or more, since plan/implement may take 1–5 minutes. +- If the environment does not support that parameter, note that the rule cannot change the limit; when needed, tell the user they can run `planforge plan` / `planforge implement` in a local terminal to see streaming to completion. diff --git a/templates/cursor/rules/workflow.mdc b/templates/cursor/rules/planforge-workflow.mdc similarity index 100% rename from templates/cursor/rules/workflow.mdc rename to templates/cursor/rules/planforge-workflow.mdc