Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .cursor/rules/planforge-cursor-agent-terminal.mdc
Original file line number Diff line number Diff line change
@@ -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.
File renamed without changes.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/cli-js/scripts/check-templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
17 changes: 15 additions & 2 deletions packages/cli-js/src/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ export async function runDoctor(_args: string[]): Promise<void> {
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"));
Expand Down Expand Up @@ -229,6 +241,7 @@ export async function runDoctor(_args: string[]): Promise<void> {
if (hasError) {
process.exit(1);
}
process.exit(0);
}

export interface DoctorAiModelOption {
Expand Down Expand Up @@ -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");
}
Expand Down
4 changes: 1 addition & 3 deletions packages/cli-js/src/utils/repo-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -65,7 +65,6 @@ function getRipgrepFileList(projectRoot: string, goal: string): string[] {
"!.git/**",
"!node_modules/**",
"!.cursor/**",
"!.planforge/**",
"!dist/**",
"!build/**",
"!__pycache__/**",
Expand Down Expand Up @@ -137,7 +136,6 @@ function getRipgrepContext(projectRoot: string, goal: string): string | undefine
"!.git/**",
"!node_modules/**",
"!.cursor/**",
"!.planforge/**",
"!dist/**",
"!build/**",
"!__pycache__/**",
Expand Down
16 changes: 14 additions & 2 deletions packages/cli-py/planforge/commands/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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(
Expand Down
11 changes: 11 additions & 0 deletions templates/cursor/rules/planforge-cursor-agent-terminal.mdc
Original file line number Diff line number Diff line change
@@ -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.