From aac5faab03b15d6fea7a80f4d9e39084af09ee7b Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 01:59:01 +0900 Subject: [PATCH 1/7] chore : gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From eda1c37d93fa302b5824c21c8671245ee8317e7d Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 01:59:35 +0900 Subject: [PATCH 2/7] refactor : remove .planforge --- packages/cli-js/src/utils/repo-context.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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__/**", From 1cab184e8aff2a7b583e7fefcc4efa8379856e9f Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 02:04:38 +0900 Subject: [PATCH 3/7] refactor : provides cursor rule template --- .cursor/rules/planforge-cursor-agent-terminal.mdc | 11 +++++++++++ .../rules/{workflow.mdc => planforge-workflow.mdc} | 0 packages/cli-js/src/commands/doctor.ts | 4 ++-- packages/cli-py/planforge/commands/doctor.py | 4 ++-- .../cursor/rules/planforge-cursor-agent-terminal.mdc | 11 +++++++++++ .../rules/{workflow.mdc => planforge-workflow.mdc} | 0 6 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 .cursor/rules/planforge-cursor-agent-terminal.mdc rename .cursor/rules/{workflow.mdc => planforge-workflow.mdc} (100%) create mode 100644 templates/cursor/rules/planforge-cursor-agent-terminal.mdc rename templates/cursor/rules/{workflow.mdc => planforge-workflow.mdc} (100%) diff --git a/.cursor/rules/planforge-cursor-agent-terminal.mdc b/.cursor/rules/planforge-cursor-agent-terminal.mdc new file mode 100644 index 0000000..cf778d6 --- /dev/null +++ b/.cursor/rules/planforge-cursor-agent-terminal.mdc @@ -0,0 +1,11 @@ +--- +description: PlanForge — Cursor 에이전트 터미널 실행 시 타임아웃(30초) 우회 +globs: +alwaysApply: true +--- + +# PlanForge: Cursor Agent Terminal (plan/implement) + +- **planforge plan** 및 **planforge implement** 실행 시 Cursor 에이전트 터미널 기본 타임아웃(~30초)에 걸리지 않도록 한다. +- 터미널 도구로 위 명령을 실행할 때 **block_until_ms**(또는 동일 의미의 최대 대기 파라미터)를 **300000**(5분) 이상으로 설정하여 실행한다. plan/implement는 1~5분 걸릴 수 있으므로 30초로 끊기지 않게 한다. +- 해당 파라미터를 지원하지 않는 모델/환경이면, 규칙만으로는 제한을 바꿀 수 없음을 인지하고, 필요 시 사용자에게 "로컬 터미널에서 `planforge plan` / `planforge implement`를 직접 실행하면 스트리밍을 끝까지 볼 수 있다"고 안내한다. 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/packages/cli-js/src/commands/doctor.ts b/packages/cli-js/src/commands/doctor.ts index af85d7f..ce7dc42 100644 --- a/packages/cli-js/src/commands/doctor.ts +++ b/packages/cli-js/src/commands/doctor.ts @@ -328,11 +328,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-py/planforge/commands/doctor.py b/packages/cli-py/planforge/commands/doctor.py index fbacd37..8d01904 100644 --- a/packages/cli-py/planforge/commands/doctor.py +++ b/packages/cli-py/planforge/commands/doctor.py @@ -286,10 +286,10 @@ def run_doctor(args: list[str]) -> None: 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..cf778d6 --- /dev/null +++ b/templates/cursor/rules/planforge-cursor-agent-terminal.mdc @@ -0,0 +1,11 @@ +--- +description: PlanForge — Cursor 에이전트 터미널 실행 시 타임아웃(30초) 우회 +globs: +alwaysApply: true +--- + +# PlanForge: Cursor Agent Terminal (plan/implement) + +- **planforge plan** 및 **planforge implement** 실행 시 Cursor 에이전트 터미널 기본 타임아웃(~30초)에 걸리지 않도록 한다. +- 터미널 도구로 위 명령을 실행할 때 **block_until_ms**(또는 동일 의미의 최대 대기 파라미터)를 **300000**(5분) 이상으로 설정하여 실행한다. plan/implement는 1~5분 걸릴 수 있으므로 30초로 끊기지 않게 한다. +- 해당 파라미터를 지원하지 않는 모델/환경이면, 규칙만으로는 제한을 바꿀 수 없음을 인지하고, 필요 시 사용자에게 "로컬 터미널에서 `planforge plan` / `planforge implement`를 직접 실행하면 스트리밍을 끝까지 볼 수 있다"고 안내한다. 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 From f812099e5399f4becd7ec552f4dccbe2c416dc27 Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 02:06:20 +0900 Subject: [PATCH 4/7] refactor : translate rules language --- .cursor/rules/planforge-cursor-agent-terminal.mdc | 8 ++++---- .../cursor/rules/planforge-cursor-agent-terminal.mdc | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.cursor/rules/planforge-cursor-agent-terminal.mdc b/.cursor/rules/planforge-cursor-agent-terminal.mdc index cf778d6..360e8bc 100644 --- a/.cursor/rules/planforge-cursor-agent-terminal.mdc +++ b/.cursor/rules/planforge-cursor-agent-terminal.mdc @@ -1,11 +1,11 @@ --- -description: PlanForge — Cursor 에이전트 터미널 실행 시 타임아웃(30초) 우회 +description: PlanForge — avoid Cursor agent terminal default timeout (~30s) for plan/implement globs: alwaysApply: true --- # PlanForge: Cursor Agent Terminal (plan/implement) -- **planforge plan** 및 **planforge implement** 실행 시 Cursor 에이전트 터미널 기본 타임아웃(~30초)에 걸리지 않도록 한다. -- 터미널 도구로 위 명령을 실행할 때 **block_until_ms**(또는 동일 의미의 최대 대기 파라미터)를 **300000**(5분) 이상으로 설정하여 실행한다. plan/implement는 1~5분 걸릴 수 있으므로 30초로 끊기지 않게 한다. -- 해당 파라미터를 지원하지 않는 모델/환경이면, 규칙만으로는 제한을 바꿀 수 없음을 인지하고, 필요 시 사용자에게 "로컬 터미널에서 `planforge plan` / `planforge 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/planforge-cursor-agent-terminal.mdc b/templates/cursor/rules/planforge-cursor-agent-terminal.mdc index cf778d6..360e8bc 100644 --- a/templates/cursor/rules/planforge-cursor-agent-terminal.mdc +++ b/templates/cursor/rules/planforge-cursor-agent-terminal.mdc @@ -1,11 +1,11 @@ --- -description: PlanForge — Cursor 에이전트 터미널 실행 시 타임아웃(30초) 우회 +description: PlanForge — avoid Cursor agent terminal default timeout (~30s) for plan/implement globs: alwaysApply: true --- # PlanForge: Cursor Agent Terminal (plan/implement) -- **planforge plan** 및 **planforge implement** 실행 시 Cursor 에이전트 터미널 기본 타임아웃(~30초)에 걸리지 않도록 한다. -- 터미널 도구로 위 명령을 실행할 때 **block_until_ms**(또는 동일 의미의 최대 대기 파라미터)를 **300000**(5분) 이상으로 설정하여 실행한다. plan/implement는 1~5분 걸릴 수 있으므로 30초로 끊기지 않게 한다. -- 해당 파라미터를 지원하지 않는 모델/환경이면, 규칙만으로는 제한을 바꿀 수 없음을 인지하고, 필요 시 사용자에게 "로컬 터미널에서 `planforge plan` / `planforge 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. From 10f7e40df26f31c99d543d4cf9e030130aa8cda4 Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 02:08:31 +0900 Subject: [PATCH 5/7] feat : add rule check in doctor --- packages/cli-js/src/commands/doctor.ts | 12 ++++++++++++ packages/cli-py/planforge/commands/doctor.py | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/cli-js/src/commands/doctor.ts b/packages/cli-js/src/commands/doctor.ts index ce7dc42..e625092 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")); diff --git a/packages/cli-py/planforge/commands/doctor.py b/packages/cli-py/planforge/commands/doctor.py index 8d01904..340a617 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()) From ef53834ebbc231135b1241e3aefe76f57878f253 Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 02:12:30 +0900 Subject: [PATCH 6/7] fix : build script --- packages/cli-js/scripts/check-templates.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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))); From 403ed179a3e696db874a66b8fedc44ac4f49e7a1 Mon Sep 17 00:00:00 2001 From: chucoding Date: Sun, 15 Mar 2026 02:20:41 +0900 Subject: [PATCH 7/7] fix : add static doctor exit code --- packages/cli-js/src/commands/doctor.ts | 1 + packages/cli-py/planforge/commands/doctor.py | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cli-js/src/commands/doctor.ts b/packages/cli-js/src/commands/doctor.ts index e625092..1c9075d 100644 --- a/packages/cli-js/src/commands/doctor.ts +++ b/packages/cli-js/src/commands/doctor.ts @@ -241,6 +241,7 @@ export async function runDoctor(_args: string[]): Promise { if (hasError) { process.exit(1); } + process.exit(0); } export interface DoctorAiModelOption { diff --git a/packages/cli-py/planforge/commands/doctor.py b/packages/cli-py/planforge/commands/doctor.py index 340a617..84250be 100644 --- a/packages/cli-py/planforge/commands/doctor.py +++ b/packages/cli-py/planforge/commands/doctor.py @@ -294,6 +294,7 @@ 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: