Enhance CLI with project workflows, feature flags, and documentation updates#2
Enhance CLI with project workflows, feature flags, and documentation updates#2backslash-ux merged 20 commits intomainfrom
Conversation
…ssing Users can now enter `plane.example.com` during init instead of requiring the full `https://plane.example.com` URL. The normalizeHost helper trims whitespace and prepends https:// when no scheme is detected.
Newer Plane deployments may use `intake_view` instead of `inbox_view`. The CLI now checks both fields via `isProjectIntakeEnabled()` and falls back transparently. Also fixes intake status codes: -2=pending, -1=rejected, 0=snoozed.
…ssues Plane API may return raw issue objects instead of join-table records for cycle-issues and module-issues endpoints. Schemas now accept both formats via union types (CycleIssueSchema, ModuleIssueSchema) so the CLI handles either response shape gracefully.
Extracts payload types and a requestWithFallback helper into src/issue-support.ts. Issue link and worklog commands now try /work-items/ paths before falling back to legacy /issues/ paths, so the CLI works across Plane API versions. Also refreshes the issue after PATCH in update to ensure the displayed state reflects the server response.
When a Plane deployment returns 404 for page endpoints, the CLI now shows a clear message instead of a raw error. Wraps all page API calls with mapPageAvailabilityError to detect and explain the situation.
The command lists workspace members, not project members. Updated the help text to match the actual behavior.
The managed section generated by `plane init --local` now includes CLI preference, @current selector guidance, PLANE_* override clearing, and a small command pattern block. This helps AGENTS.md-aware AI clients discover the CLI without manual documentation.
Adds Known Deployment Compatibility section to AGENTS.md documenting pages, worklogs, feature gating, and missing CLI commands. Updates README and SKILL with matching compatibility notes. Removes misplaced Agent CLI Usage section from AGENTS.md (that guidance now lives in the generated managed section instead).
Add plane labels delete and plane modules delete so cleanup can stay inside the CLI. Both commands resolve targets from discoverable list output using either exact names or UUIDs, keeping the flows friendly for humans and agents. Also adds focused Bun/MSW coverage for the new delete handlers and brings labels command coverage up to the repo threshold.
Update the public docs and contributor guidance to reflect the new plane labels delete and plane modules delete commands. Remove the old compatibility note that told users to fall back to direct API cleanup for those two operations.
…t repository name
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds multi-scope config discovery (env → local → global), introduces project-context snapshots and AGENTS.md, implements per-project feature gating and fallback request helpers, rewrites init/projects/labels/modules/local commands with scoped persistence, updates many CLI commands/schemas/helpers, and adjusts tests and docs for the renamed package identity. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as "plane CLI"
participant ConfigMgr as "Config Manager"
participant LocalFS as "Local Filesystem"
participant API as "Plane API"
User->>CLI: plane init --local
CLI->>ConfigMgr: initHandler(global=false, local=true)
ConfigMgr->>LocalFS: readLocalStoredConfig()
ConfigMgr->>User: prompt(host, workspace, token, default project)
User-->>ConfigMgr: answers
ConfigMgr->>API: GET /projects, /states, /labels, /estimates
API-->>ConfigMgr: project detail + metadata
ConfigMgr->>LocalFS: writeLocalStoredConfig(.plane/config.json)
ConfigMgr->>LocalFS: writeLocalProjectContextSnapshot(.plane/project-context.json)
ConfigMgr->>LocalFS: writeLocalProjectAgentsFile(AGENTS.md)
CLI-->>User: print feature flags and saved locations
sequenceDiagram
participant User
participant CLI as "plane CLI"
participant Resolver as "Config & Feature Resolver"
participant API as "Plane API"
participant Handler as "Command Handler"
User->>CLI: plane cycles list
CLI->>Handler: cyclesListHandler(project="")
Handler->>Resolver: resolveProject("") -> "@current"
Resolver->>Resolver: consult env/local/global config
Resolver-->>Handler: resolved project id
Handler->>Resolver: requireProjectFeature(projectId, "cycle_view")
Resolver->>API: GET /projects/{id}
API-->>Resolver: ProjectDetail with cycle_view flag
alt cycle_view enabled
Resolver-->>Handler: allowed
Handler->>API: GET /projects/{id}/cycles/
API-->>Handler: cycles list
Handler-->>User: formatted cycles output
else cycle_view disabled
Resolver-->>Handler: denied (error)
Handler-->>User: feature-disabled error with hint
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR enhances the Plane CLI’s workflow around project selection and configuration layering (env/global/local), adds project feature-gating with clearer compatibility errors, introduces cleanup and project-management commands, and updates docs/tooling to match the renamed package/repo and new command grammar.
Changes:
- Added global + nearest-local config resolution (
.plane/config.json) with a persisted “current project” (PLANE_PROJECT,plane projects use/current) and richerinitflows (including. initalias + local project context snapshot/AGENTS section). - Added project feature gating (cycles/modules/pages/intake) and API-compatibility fallbacks for evolving endpoints (work-items links/worklogs, raw issue payload variants).
- Added new commands (
labels delete,modules delete) and updated docs/tooling (repo rename references, Husky/Bun reliability, releasing notes).
Reviewed changes
Copilot reviewed 52 out of 53 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/xml-output.test.ts | Updates XML mode tests for new handlers, endpoints, and project-detail mocking. |
| tests/user-config.test.ts | Adds tests for config layering (env/local/global) and host normalization. |
| tests/schemas.test.ts | Adjusts schema imports/order to match config exports. |
| tests/resolve.test.ts | Extends resolve tests for PLANE_PROJECT/@current behaviors. |
| tests/projects.test.ts | Adds coverage for projects use/current/list behaviors and persistence scope. |
| tests/project-features.test.ts | Adds tests for project feature flags and local init context/AGENTS updates. |
| tests/pages.test.ts | Adds project detail mocking and explicit “pages unavailable” error test. |
| tests/output.test.ts | Minor cleanup of output unit test imports. |
| tests/new-schemas2.test.ts | Updates schema tests for cycle-issue payload variants (legacy vs raw). |
| tests/new-schemas.test.ts | Updates schema tests for module-issue payload variants (legacy vs raw). |
| tests/modules.test.ts | Adds tests for modules delete and raw module-issue payload listing. |
| tests/labels.test.ts | Adds tests for labels delete plus list/create coverage. |
| tests/json-output.test.ts | Updates JSON mode tests for new handlers/endpoints and payload variants. |
| tests/issue-links.test.ts | Updates link endpoint tests to new work-items/.../links primary route. |
| tests/issue-comments-worklogs.test.ts | Adds worklogs endpoint fallback coverage (work-items 404 -> issues worklogs). |
| tests/issue-commands.test.ts | Extends issue command tests (blank project uses saved current project, extra GET). |
| tests/issue-activity.test.ts | Minor import/env-var normalization adjustments in activity tests. |
| tests/intake.test.ts | Updates intake payloads/status expectations and mutation endpoint ID usage. |
| tests/helpers/mock-api.ts | Normalizes MSW import ordering. |
| tests/format.test.ts | Minor import ordering cleanup. |
| tests/cycles-extended.test.ts | Adds project detail mocking and legacy cycle-issue join payload coverage. |
| tests/api.test.ts | Updates env-var access style and import ordering. |
| src/user-config.ts | Introduces global+local config storage, discovery, normalization, and source tracking. |
| src/resolve.ts | Adds default-project resolution, project detail caching, feature gating, label/module resolvers. |
| src/project-context.ts | Generates/writes .plane/project-context.json snapshot for states/labels/estimates/features. |
| src/project-agents.ts | Creates/updates managed AGENTS.md section for local project context guidance. |
| src/output.ts | Small XML string-formatting adjustments. |
| src/issue-support.ts | Adds request fallback helper + link/worklog path sets for API compatibility. |
| src/config.ts | Adds ProjectDetail/Estimate schemas and unions for raw vs relation payload variants. |
| src/commands/states.ts | Allows omitting PROJECT (uses saved current project). |
| src/commands/projects.ts | Adds projects current and projects use with local/global persistence behavior. |
| src/commands/pages.ts | Adds feature gating + maps 404s to explicit “pages unavailable” compatibility errors. |
| src/commands/modules.ts | Adds feature gating, modules delete, and raw module-issue payload handling. |
| src/commands/members.ts | Minor import ordering cleanup. |
| src/commands/local.ts | Adds plane . command group for local-only workflows (plane . init). |
| src/commands/labels.ts | Adds labels delete plus refactors list/create into handlers and local-default support. |
| src/commands/issues.ts | Allows omitting PROJECT (uses saved current project) and updates help text. |
| src/commands/issue.ts | Adds endpoint fallbacks for links/worklogs; refreshes issue after update. |
| src/commands/intake.ts | Adds feature gating, resolves mutation ID, and allows omitting PROJECT. |
| src/commands/init.ts | Major init overhaul: scope selection, layering, project selection, local snapshot + AGENTS update. |
| src/commands/cycles.ts | Adds feature gating, omittable project argument, and raw cycle-issue payload handling. |
| src/app.ts | Updates CLI help/docs to reflect new config layering and commands (including plane . init). |
| src/api.ts | Switches config source to user-config and improves missing-config error messaging. |
| SKILL.md | Updates package name/install instructions + expands CLI/config/current-project and compatibility docs. |
| scripts/check-coverage.ts | Uses process.execPath for running bun test --coverage more robustly. |
| README.md | Updates repo/package references and expands setup/config/current-project/compatibility guidance. |
| package.json | Renames published package + updates repository/bugs/homepage URLs. |
| docs/RELEASING.md | Adds maintainer setup and preflight/postflight release guidance. |
| CHANGELOG.md | Updates package identity notes and records new commands/docs validation. |
| AGENTS.md | Adds known deployment compatibility notes and includes a managed local context section. |
| .husky/pre-commit | Improves Bun discovery and runs repo scripts via resolved Bun path. |
| .gitignore | Ignores .plane/ and broadens .vscode/ ignore. |
| .github/ISSUE_TEMPLATE/config.yml | Updates repository links to renamed repo. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const raw = yield* mapPageAvailabilityError( | ||
| api.get(`projects/${id}/pages/${pageId}/`), | ||
| `Project pages are not available for ${key} on this Plane instance or API version.`, | ||
| ); |
There was a problem hiding this comment.
mapPageAvailabilityError converts any HTTP 404 into a "pages are not available" compatibility error. For handlers that target a specific page ID (e.g. get/update/delete), a legitimate "page not found" 404 would be incorrectly reported as an API-availability problem. Consider only mapping 404s to the compatibility message when you can confirm the endpoint is missing (e.g. probe the list endpoint on 404), otherwise preserve a not-found error for the specific page.
| const raw = yield* mapPageAvailabilityError( | |
| api.get(`projects/${id}/pages/${pageId}/`), | |
| `Project pages are not available for ${key} on this Plane instance or API version.`, | |
| ); | |
| const raw = yield* api.get(`projects/${id}/pages/${pageId}/`); |
| yield* requestWithFallback( | ||
| issueLinkPaths(projectId, issue.id).map((path) => `${path}${linkId}/`), | ||
| (path) => api.delete(path), | ||
| `Issue links are not available for ${ref} on this Plane instance or API version.`, | ||
| ); |
There was a problem hiding this comment.
requestWithFallback treats any HTTP 404 as "try the next endpoint" and ultimately returns the generic compatibility error. In issueLinkRemoveHandler, that means a real "link ID not found" 404 (on a supported endpoint) will be reported as "Issue links are not available…". Consider handling delete differently (e.g. determine the correct base links endpoint via list/probe first, then delete once, so a missing link surfaces as a not-found error).
| yield* requestWithFallback( | |
| issueLinkPaths(projectId, issue.id).map((path) => `${path}${linkId}/`), | |
| (path) => api.delete(path), | |
| `Issue links are not available for ${ref} on this Plane instance or API version.`, | |
| ); | |
| // First, resolve the correct base path for issue links via a safe probe. | |
| const basePath = yield* requestWithFallback( | |
| issueLinkPaths(projectId, issue.id), | |
| (path) => api.get(path).pipe(Effect.as(path)), | |
| `Issue links are not available for ${ref} on this Plane instance or API version.`, | |
| ); | |
| // Then, perform the delete once against the resolved base path so a missing | |
| // link ID results in a proper not-found error instead of a fallback message. | |
| yield* api.delete(`${basePath}${linkId}/`); |
| yield* Console.log( | ||
| `\n${scope === "global" ? "Global" : "Local"} config saved to ${savePath}`, | ||
| ); | ||
| yield* Console.log( | ||
| ` Host: ${describeValue(scope, savedHost, normalizedHost)}`, |
There was a problem hiding this comment.
For local init, the displayed host uses the raw savedHost when set, but the persisted value is normalized by writeLocalStoredConfig (via cleanConfig/normalizeHost). This can print a host value that doesn’t match what was actually written (e.g. missing scheme or trailing slash). Consider normalizing savedHost for display (or showing both input and normalized value) so the output reflects the stored config accurately.
| yield* Console.log( | |
| `\n${scope === "global" ? "Global" : "Local"} config saved to ${savePath}`, | |
| ); | |
| yield* Console.log( | |
| ` Host: ${describeValue(scope, savedHost, normalizedHost)}`, | |
| const hostNormalizedForDisplay = | |
| scope === "global" | |
| ? normalizedHost | |
| : savedHost | |
| ? normalizeHost(savedHost) | |
| : normalizedHost; | |
| yield* Console.log( | |
| `\n${scope === "global" ? "Global" : "Local"} config saved to ${savePath}`, | |
| ); | |
| yield* Console.log( | |
| ` Host: ${describeValue(scope, savedHost, hostNormalizedForDisplay)}`, |
| // Intake status codes: -2=pending, -1=rejected, 0=snoozed, 1=accepted, 2=duplicate | ||
| const STATUS_LABELS: Record<number, string> = { | ||
| [-2]: "rejected", | ||
| [-1]: "snoozed", | ||
| [0]: "pending", | ||
| [1]: "accepted", | ||
| [2]: "duplicate", | ||
| [-2]: "pending", | ||
| [-1]: "rejected", | ||
| 0: "snoozed", | ||
| 1: "accepted", | ||
| 2: "duplicate", | ||
| }; |
There was a problem hiding this comment.
With STATUS_LABELS now mapping 0 to snoozed, the existing list formatting logic later in this file that does i.status ?? 0 will label any intake item with an omitted status as snoozed. Consider updating the formatter to treat undefined status as unknown (or handle it separately) rather than defaulting to 0 now that 0 is not a safe default.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
scripts/check-coverage.ts (1)
13-20:⚠️ Potential issue | 🟠 MajorFail closed when coverage output is unparseable.
At line 19, the script exits with code
0if the coverage regex fails to match, allowing the pre-commit hook to silently pass even when coverage metrics cannot be verified. This is inconsistent with the threshold validation at line 41, which correctly exits with code1on failure, and creates a quality gate bypass. The parse failure should fail the hook.Proposed fix
const coverageMatch = output.match(/All files\s*\|\s*([\d.]+)\s*\|\s*([\d.]+)\s*\|/) if (!coverageMatch) { - console.log("\n⚠️ Could not parse coverage output") - process.exit(0) + console.error("\n❌ Could not parse coverage output; failing coverage check") + process.exit(1) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/check-coverage.ts` around lines 13 - 20, The script currently treats an unparseable coverage output as success; change the behavior so the process fails instead. In scripts/check-coverage.ts, after running the test via execSync and when checking coverageMatch (the regex result from output.match(...)), replace the process.exit(0) path with process.exit(1) (and keep or enhance the console.log("\n⚠️ Could not parse coverage output") message) so that an unparsable coverage report causes the hook to fail consistently with the later threshold validation logic.src/commands/pages.ts (1)
180-184:⚠️ Potential issue | 🟡 MinorEarly return missing for validation failure.
When neither
--namenor--descriptionis provided,Effect.failis called but execution continues. The generator will throw, but an explicitreturnmakes intent clearer.🔧 Suggested fix
return Effect.gen(function* () { if (Option.isNone(name) && Option.isNone(description)) { - yield* Effect.fail(new Error("provide at least --name or --description")); + return yield* Effect.fail(new Error("provide at least --name or --description")); } const { key, id } = yield* resolveProject(project);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pages.ts` around lines 180 - 184, The generator started in Effect.gen currently calls Effect.fail when both Option.isNone(name) and Option.isNone(description) but does not explicitly stop execution; update the generator inside Effect.gen so that after yielding Effect.fail(new Error("provide at least --name or --description")) you immediately return (i.e., return yield* Effect.fail(...) or return after the yield) to prevent further execution; locate the failing check using Option.isNone(name), Option.isNone(description), and the surrounding Effect.gen function to apply the change.src/resolve.ts (1)
137-149:⚠️ Potential issue | 🟡 MinorUse the resolved project key in the not-found error.
For
".","@current", and"@default", the current message reports the selector instead of the configured project that actually missed. That makes stale config harder to diagnose.Suggested fix
- `Unknown project: ${identifier}. Known: ${Object.keys(map).join(", ")}`, + `Unknown project: ${key}. Known: ${Object.keys(map).join(", ")}`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/resolve.ts` around lines 137 - 149, The not-found error in resolveProject uses the original selector (identifier) instead of the resolved project key, which hides the actual configured project name; update the Effect.fail error message in resolveProject (where key is computed via getConfiguredProject(identifier).toUpperCase()) to reference key (the resolved project name) and list Object.keys(map) so the error shows the missing configured project and available keys.
🧹 Nitpick comments (6)
.husky/pre-commit (1)
10-16: Addtypecheckto pre-commit quality gates.The hook currently runs file-size and coverage checks but skips TypeScript typechecking. Consider adding
"$BUN" run typecheckbefore coverage.Proposed update
# Check file sizes echo "Checking file sizes..." "$BUN" scripts/check-file-size.ts +# Type safety gate +echo "Checking types..." +"$BUN" run typecheck + # Check test coverage echo "Checking test coverage..." "$BUN" scripts/check-coverage.tsBased on learnings: Run the narrowest relevant tests plus bun run typecheck.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.husky/pre-commit around lines 10 - 16, Add a TypeScript typecheck step to the pre-commit hook by invoking the project's Bun typecheck script before running coverage: insert a call to run the `typecheck` script (e.g., `"$BUN" run typecheck`) in the pre-commit sequence between the file-size check (`"$BUN" scripts/check-file-size.ts`) and the coverage check (`"$BUN" scripts/check-coverage.ts`) so that `typecheck` runs as a quality gate prior to coverage.src/project-context.ts (1)
166-176: RedundantchmodSynccall.The
mode: 0o600option inwriteFileSyncalready sets the file permissions. The subsequentchmodSynccall is unnecessary.♻️ Proposed fix
export function writeLocalProjectContextSnapshot( snapshot: ProjectContextSnapshot, cwd = process.cwd(), ): void { const filePath = getLocalProjectContextFilePath(cwd); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, `${JSON.stringify(snapshot, null, 2)}\n`, { mode: 0o600, }); - fs.chmodSync(filePath, 0o600); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/project-context.ts` around lines 166 - 176, In writeLocalProjectContextSnapshot remove the redundant fs.chmodSync(filePath, 0o600) because fs.writeFileSync already sets permissions via the mode: 0o600 option; update the function by deleting the chmodSync call so permissions are only set via writeFileSync (ensure getLocalProjectContextFilePath and the preceding fs.mkdirSync call remain unchanged).src/issue-support.ts (1)
26-28: Consider making error detection more robust.The 404 detection relies on the exact error message format
^HTTP 404:. This coupling to the API module's error string format could break silently if the message format changes.Consider exporting a typed error class or error code from the API module for more reliable detection.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/issue-support.ts` around lines 26 - 28, The current isNotFoundError(error: Error) uses a fragile regex against error.message; instead export a typed error (e.g., class NotFoundError extends Error) or a stable error code (e.g., error.code === 'NOT_FOUND') from the API module and update isNotFoundError to detect that (prefer using instanceof NotFoundError, falling back to a code check for compatibility). Modify the API to throw or attach the stable type/code, then change the isNotFoundError function to check for that exported symbol (NotFoundError or error.code) rather than matching message text.package.json (1)
2-2: Note: Package name vs repository name inconsistency.The package is named
@backslash-ux/plane-clibut the repository URLs referenceplane-cli-cli. This asymmetry may cause confusion for contributors looking for the source repository. If intentional, consider documenting the naming convention in README.Also applies to: 36-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 2, The package name "@backslash-ux/plane-cli" in package.json is inconsistent with the repository references that use "plane-cli-cli"; update the package.json "name" field to match the canonical repository name (or update the repository fields to match the package name) and add a short note in the README explaining the naming convention if the mismatch is intentional; specifically check and align the "name" value "@backslash-ux/plane-cli" and the repository URL/fields that currently contain "plane-cli-cli" (and any other occurrences) so they are consistent.SKILL.md (1)
274-278: Clarify module-issue removal identifier in docs.The comment mentions "use the identifier returned by
plane modules issues list" which is helpful, but consider making the distinction between issue ref (PROJ-29) and module-issue ID even clearer for agents that might conflate them.📝 Suggested clarification
-plane modules issues remove PROJ <module-id> <module-issue-id> # use the identifier returned by `plane modules issues list` +plane modules issues remove PROJ <module-id> <module-issue-id> # module-issue-id is the join record UUID from `plane modules issues list`, NOT an issue ref like PROJ-29🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@SKILL.md` around lines 274 - 278, The docs currently risk confusing the issue reference (e.g., PROJ-29) with the module-issue identifier returned by the CLI; update the SKILL.md lines around the module commands (specifically the examples for plane modules issues list, add, and remove) to explicitly state that plane modules issues list returns a distinct module-issue ID (not the issue key), show a concrete example of both values (e.g., "issue ref: PROJ-29" vs "module-issue-id: 12345" or the actual format returned), and revise the plane modules issues remove example to use that module-issue-id placeholder and a short clarifying sentence like "use the module-issue-id returned by plane modules issues list (not the issue key like PROJ-29)."README.md (1)
64-71: Add language specifier to fenced code block.Static analysis flagged this code block as missing a language specifier. Since these are environment variables,
bashorshellwould be appropriate.📝 Suggested fix
-``` +```bash PLANE_API_TOKEN=... PLANE_HOST=https://plane.so PLANE_WORKSPACE=myworkspace PLANE_PROJECT=PROJ # optional saved-project override</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@README.mdaround lines 64 - 71, The fenced code block showing environment
variables (e.g., lines with PLANE_API_TOKEN, PLANE_HOST, PLANE_WORKSPACE,
PLANE_PROJECT) in README.md should include a language specifier for static
analysis; update the opening fence to use an appropriate specifier such as
"bash" or "shell" (e.g., ```bash) so the block is marked correctly without
changing the block contents.</details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@src/commands/init.ts:
- Around line 323-327: The readline interface created via
readline.createInterface (rl) can remain open if resolveProjectSelection throws,
so update the code to ensure rl.close() is always called: wrap the interactive
prompts and calls to resolveProjectSelection(...) (and any other logic that may
throw between where rl is created and previous rl.close() calls) inside a
try/finally and call rl.close() in the finally block (or use a try/catch/finally
if you need to handle errors), ensuring rl is closed whether
resolveProjectSelection succeeds or throws.- Around line 470-534: The branch only runs when savedDefaultProject is set, so
when the user inherits a global current project (savedDefaultProject is
undefined) the local helper files are skipped; update the logic in the scope ===
"local" block to pick a selectedProject from either savedDefaultProject or the
inherited current project identifier (e.g., use savedDefaultProject ??
currentProjectIdentifier) before resolving projectsResult, then call
fetchLocalProjectHelperForConfig(...) for that selectedProject and proceed to
run writeLocalProjectContextSnapshot(...), writeLocalProjectAgentsFile(...),
getLocalProjectContextFilePath(), getLocalAgentsFilePath(), and
summarizeProjectFeatures(...) exactly as done now so the helper files are
generated even when the project was inherited rather than re-selected.In
@src/commands/modules.ts:
- Around line 133-141: The list output branch that handles raw issue payloads
(theif ("sequence_id" in mi)branch inside theresults.mapcall) currently
returns only${key}-${seq} ${mi.name}, which omits any removable identifier;
update that branch to append a discoverable identifier so consumers can run
modules issues remove <id>— e.g. return${key}-${seq} ${mi.name} (${mi.id ?? mi.issue_id ?? mi.uuid})or otherwise detect and include the unique id field
present onmi; ensure you only render the parentheses if an identifier exists.In
@src/config.ts:
- Around line 76-94: The current isProjectIntakeEnabled function uses the
nullish coalescing operator (project.inbox_view ?? project.intake_view) which
treats false as a terminal value and prevents intake_view from acting as an
alias; update isProjectIntakeEnabled to return true if either inbox_view or
intake_view is truthy (e.g., use logical OR on booleans) so a payload like {
inbox_view: false, intake_view: true } yields true; modify the implementation of
isProjectIntakeEnabled (and confirm ProjectDetailSchema fields inbox_view and
intake_view remain optional) to evaluate as (Boolean(project.inbox_view) ||
Boolean(project.intake_view)) or equivalent.In
@src/user-config.ts:
- Around line 192-200: The code uses the nullish coalescing operator so empty
environment variables (e.g., PLANE_API_TOKEN or PLANE_HOST captured as
envToken/envHost) incorrectly win over stored configs; change the selection to
treat empty strings as unset by normalizing envToken/envHost to undefined when
they are "" (or only whitespace) before applying precedence for token and host
(the host still passed through normalizeHost); update the logic around token,
host and any similar blocks (also the block referenced at lines 214-242) to use
these normalized env values so stored localConfig/globalConfig are chosen when
env vars are blank.- Around line 187-200: getConfigDetails currently merges token, host, and
workspace independently across env/local/global which can mix scopes; change it
so the connection settings are chosen from a single scope: inspect scopes in
precedence order (env, localConfigFile, globalConfig) to find which scope
supplies the token first and then pick host and workspace only from that same
scope (use DEFAULT_HOST only if that chosen scope lacks host), otherwise if no
token is found fall back to the first scope that supplies host/workspace but do
not combine values from different scopes; update the logic around token, host,
and envWorkspace/envProject resolution in getConfigDetails (and keep using
normalizeHost) to enforce same-scope selection rather than per-field merging.In
@tests/user-config.test.ts:
- Around line 6-18: The test setup deletes PLANЕ_* env vars but never restores
originals, causing state leakage; capture the original values of
PLANЕ_API_TOKEN, PLANЕ_HOST, PLANЕ_WORKSPACE, and PLANЕ_PROJECT at top (similar
to ORIGINAL_HOME), and in afterEach restore process.env.PLANE_API_TOKEN,
process.env.PLANE_HOST, process.env.PLANE_WORKSPACE, and
process.env.PLANE_PROJECT to their saved originals (or delete them if original
was undefined); update the beforeEach/afterEach blocks in
tests/user-config.test.ts to use these saved variables alongside ORIGINAL_HOME
and ORIGINAL_CWD.
Outside diff comments:
In@scripts/check-coverage.ts:
- Around line 13-20: The script currently treats an unparseable coverage output
as success; change the behavior so the process fails instead. In
scripts/check-coverage.ts, after running the test via execSync and when checking
coverageMatch (the regex result from output.match(...)), replace the
process.exit(0) path with process.exit(1) (and keep or enhance the
console.log("\n⚠️ Could not parse coverage output") message) so that an
unparsable coverage report causes the hook to fail consistently with the later
threshold validation logic.In
@src/commands/pages.ts:
- Around line 180-184: The generator started in Effect.gen currently calls
Effect.fail when both Option.isNone(name) and Option.isNone(description) but
does not explicitly stop execution; update the generator inside Effect.gen so
that after yielding Effect.fail(new Error("provide at least --name or
--description")) you immediately return (i.e., return yield* Effect.fail(...) or
return after the yield) to prevent further execution; locate the failing check
using Option.isNone(name), Option.isNone(description), and the surrounding
Effect.gen function to apply the change.In
@src/resolve.ts:
- Around line 137-149: The not-found error in resolveProject uses the original
selector (identifier) instead of the resolved project key, which hides the
actual configured project name; update the Effect.fail error message in
resolveProject (where key is computed via
getConfiguredProject(identifier).toUpperCase()) to reference key (the resolved
project name) and list Object.keys(map) so the error shows the missing
configured project and available keys.
Nitpick comments:
In @.husky/pre-commit:
- Around line 10-16: Add a TypeScript typecheck step to the pre-commit hook by
invoking the project's Bun typecheck script before running coverage: insert a
call to run thetypecheckscript (e.g.,"$BUN" run typecheck) in the
pre-commit sequence between the file-size check ("$BUN" scripts/check-file-size.ts) and the coverage check ("$BUN" scripts/check-coverage.ts) so thattypecheckruns as a quality gate prior to
coverage.In
@package.json:
- Line 2: The package name "@backslash-ux/plane-cli" in package.json is
inconsistent with the repository references that use "plane-cli-cli"; update the
package.json "name" field to match the canonical repository name (or update the
repository fields to match the package name) and add a short note in the README
explaining the naming convention if the mismatch is intentional; specifically
check and align the "name" value "@backslash-ux/plane-cli" and the repository
URL/fields that currently contain "plane-cli-cli" (and any other occurrences) so
they are consistent.In
@README.md:
- Around line 64-71: The fenced code block showing environment variables (e.g.,
lines with PLANE_API_TOKEN, PLANE_HOST, PLANE_WORKSPACE, PLANE_PROJECT) in
README.md should include a language specifier for static analysis; update the
opening fence to use an appropriate specifier such as "bash" or "shell" (e.g.,In `@SKILL.md`: - Around line 274-278: The docs currently risk confusing the issue reference (e.g., PROJ-29) with the module-issue identifier returned by the CLI; update the SKILL.md lines around the module commands (specifically the examples for plane modules issues list, add, and remove) to explicitly state that plane modules issues list returns a distinct module-issue ID (not the issue key), show a concrete example of both values (e.g., "issue ref: PROJ-29" vs "module-issue-id: 12345" or the actual format returned), and revise the plane modules issues remove example to use that module-issue-id placeholder and a short clarifying sentence like "use the module-issue-id returned by plane modules issues list (not the issue key like PROJ-29)." In `@src/issue-support.ts`: - Around line 26-28: The current isNotFoundError(error: Error) uses a fragile regex against error.message; instead export a typed error (e.g., class NotFoundError extends Error) or a stable error code (e.g., error.code === 'NOT_FOUND') from the API module and update isNotFoundError to detect that (prefer using instanceof NotFoundError, falling back to a code check for compatibility). Modify the API to throw or attach the stable type/code, then change the isNotFoundError function to check for that exported symbol (NotFoundError or error.code) rather than matching message text. In `@src/project-context.ts`: - Around line 166-176: In writeLocalProjectContextSnapshot remove the redundant fs.chmodSync(filePath, 0o600) because fs.writeFileSync already sets permissions via the mode: 0o600 option; update the function by deleting the chmodSync call so permissions are only set via writeFileSync (ensure getLocalProjectContextFilePath and the preceding fs.mkdirSync call remain unchanged).🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID:
132d4f35-2bb8-42e6-9be8-f7da1ce4375a📒 Files selected for processing (53)
.github/ISSUE_TEMPLATE/config.yml.gitignore.husky/pre-commitAGENTS.mdCHANGELOG.mdREADME.mdSKILL.mddocs/RELEASING.mdpackage.jsonscripts/check-coverage.tssrc/api.tssrc/app.tssrc/commands/cycles.tssrc/commands/init.tssrc/commands/intake.tssrc/commands/issue.tssrc/commands/issues.tssrc/commands/labels.tssrc/commands/local.tssrc/commands/members.tssrc/commands/modules.tssrc/commands/pages.tssrc/commands/projects.tssrc/commands/states.tssrc/config.tssrc/issue-support.tssrc/output.tssrc/project-agents.tssrc/project-context.tssrc/resolve.tssrc/user-config.tstests/api.test.tstests/cycles-extended.test.tstests/format.test.tstests/helpers/mock-api.tstests/intake.test.tstests/issue-activity.test.tstests/issue-commands.test.tstests/issue-comments-worklogs.test.tstests/issue-links.test.tstests/json-output.test.tstests/labels.test.tstests/modules.test.tstests/new-schemas.test.tstests/new-schemas2.test.tstests/output.test.tstests/pages.test.tstests/project-features.test.tstests/projects.test.tstests/resolve.test.tstests/schemas.test.tstests/user-config.test.tstests/xml-output.test.ts
| const lines = results.map((mi) => { | ||
| if (mi.issue_detail) { | ||
| if ("issue_detail" in mi && mi.issue_detail) { | ||
| const seq = String(mi.issue_detail.sequence_id).padStart(3, " "); | ||
| return `${key}-${seq} ${mi.issue_detail.name} (${mi.id})`; | ||
| } | ||
| if ("sequence_id" in mi) { | ||
| const seq = String(mi.sequence_id).padStart(3, " "); | ||
| return `${key}-${seq} ${mi.name}`; | ||
| } |
There was a problem hiding this comment.
Keep a removable identifier in the raw-issue branch.
When module-issues comes back as raw issue payloads, this branch prints only PROJ-123 Name. That leaves plane modules issues remove ... <module-issue-id> with nothing discoverable to consume, even though the help text says the identifier comes from modules issues list.
💡 Minimal fix
if ("sequence_id" in mi) {
const seq = String(mi.sequence_id).padStart(3, " ");
- return `${key}-${seq} ${mi.name}`;
+ return `${key}-${seq} ${mi.name} (${mi.id})`;
}As per coding guidelines, "Prefer discoverable workflows: if a command depends on UUIDs or join IDs, expose a list or get path to help obtain them."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/modules.ts` around lines 133 - 141, The list output branch that
handles raw issue payloads (the `if ("sequence_id" in mi)` branch inside the
`results.map` call) currently returns only `${key}-${seq} ${mi.name}`, which
omits any removable identifier; update that branch to append a discoverable
identifier so consumers can run `modules issues remove <id>` — e.g. return
`${key}-${seq} ${mi.name} (${mi.id ?? mi.issue_id ?? mi.uuid})` or otherwise
detect and include the unique id field present on `mi`; ensure you only render
the parentheses if an identifier exists.
| export function getConfigDetails(cwd = process.cwd()): PlaneConfigDetails { | ||
| const globalConfig = readGlobalStoredConfig(); | ||
| const localConfigFile = findNearestLocalConfigFilePath(cwd); | ||
| const localConfig = localConfigFile ? readConfigFile(localConfigFile) : {}; | ||
|
|
||
| const envToken = process.env.PLANE_API_TOKEN; | ||
| const envHost = process.env.PLANE_HOST; | ||
| const envWorkspace = process.env.PLANE_WORKSPACE; | ||
| const envProject = process.env.PLANE_PROJECT; | ||
|
|
||
| const token = envToken ?? localConfig.token ?? globalConfig.token ?? ""; | ||
| const host = normalizeHost( | ||
| envHost ?? localConfig.host ?? globalConfig.host ?? DEFAULT_HOST, | ||
| ); |
There was a problem hiding this comment.
Don't compose connection settings across scopes.
This per-field merge lets a repo-local .plane/config.json override host while token falls through from env/global config. Running the CLI in an untrusted repo could then send a real API token to an attacker-controlled Plane URL.
Resolve host, workspace, and token from a single scope, or refuse to use a lower-precedence host unless the token comes from that same scope.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/user-config.ts` around lines 187 - 200, getConfigDetails currently merges
token, host, and workspace independently across env/local/global which can mix
scopes; change it so the connection settings are chosen from a single scope:
inspect scopes in precedence order (env, localConfigFile, globalConfig) to find
which scope supplies the token first and then pick host and workspace only from
that same scope (use DEFAULT_HOST only if that chosen scope lacks host),
otherwise if no token is found fall back to the first scope that supplies
host/workspace but do not combine values from different scopes; update the logic
around token, host, and envWorkspace/envProject resolution in getConfigDetails
(and keep using normalizeHost) to enforce same-scope selection rather than
per-field merging.
…, and streamline environment variable management
… in pages handlers
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/commands/intake.ts (1)
25-40: Consider caching intake issues to avoid redundant API calls.
resolveIntakeMutationIdfetches all intake issues to find one by ID. Foraccept/rejectoperations immediately afterlist, this duplicates the API call. For high-volume projects this could add latency.This is acceptable for now, but if performance becomes a concern, consider short-lived in-memory caching or accepting the mutation ID directly from list output.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/intake.ts` around lines 25 - 40, resolveIntakeMutationId currently issues an API request every time it runs, causing duplicate fetches for back-to-back operations like accept/reject after list; add a short-lived in-memory cache keyed by projectId (or accept an optionally-provided mutationId) so list results can be reused: modify resolveIntakeMutationId to first check a cache map (e.g., a simple Map<string, IntakeIssuesResponse> or Map<string, record>) before calling api.get, populate the cache when list is called (or when resolveIntakeMutationId fetches), and expire entries after a small TTL or when list is explicitly refreshed; reference resolveIntakeMutationId, list, accept, reject to wire the cache usage and ensure behavior falls back to the existing api.get when no cached entry exists.src/user-config.ts (1)
77-87: RedundantchmodSynccall afterwriteFileSync.The
mode: 0o600inwriteFileSyncoptions already sets file permissions. The subsequentchmodSyncis unnecessary.♻️ Suggested fix
function writeConfigFile(filePath: string, config: StoredPlaneConfig): void { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync( filePath, `${JSON.stringify(cleanConfig(config), null, 2)}\n`, { mode: 0o600, }, ); - fs.chmodSync(filePath, 0o600); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/user-config.ts` around lines 77 - 87, In writeConfigFile remove the redundant fs.chmodSync call: rely on fs.writeFileSync(filePath, ..., { mode: 0o600 }) to set permissions and delete the fs.chmodSync(filePath, 0o600) line to avoid the duplicate operation; keep fs.mkdirSync and the writeFileSync call and ensure cleanConfig(config) remains used when serializing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/init.ts`:
- Around line 489-498: The code uses forbidden non-null assertions on
projectsResult (projectsResult!._tag and projectsResult!.right); change the
selectedProject assignment to safely handle a possibly undefined projectsResult
by checking projectsResult?. _tag === "Right" (or using an isRight helper) and
then using projectsResult?.right?.find(...) so you only access .right when
projectsResult is present and Right, ensuring no non-null assertions remain;
update the logic around activeDefaultProject/scope as needed to preserve the
original behavior.
---
Nitpick comments:
In `@src/commands/intake.ts`:
- Around line 25-40: resolveIntakeMutationId currently issues an API request
every time it runs, causing duplicate fetches for back-to-back operations like
accept/reject after list; add a short-lived in-memory cache keyed by projectId
(or accept an optionally-provided mutationId) so list results can be reused:
modify resolveIntakeMutationId to first check a cache map (e.g., a simple
Map<string, IntakeIssuesResponse> or Map<string, record>) before calling
api.get, populate the cache when list is called (or when resolveIntakeMutationId
fetches), and expire entries after a small TTL or when list is explicitly
refreshed; reference resolveIntakeMutationId, list, accept, reject to wire the
cache usage and ensure behavior falls back to the existing api.get when no
cached entry exists.
In `@src/user-config.ts`:
- Around line 77-87: In writeConfigFile remove the redundant fs.chmodSync call:
rely on fs.writeFileSync(filePath, ..., { mode: 0o600 }) to set permissions and
delete the fs.chmodSync(filePath, 0o600) line to avoid the duplicate operation;
keep fs.mkdirSync and the writeFileSync call and ensure cleanConfig(config)
remains used when serializing.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9aac4ba5-3154-48bc-9cd2-b84bf052b0dc
📒 Files selected for processing (8)
src/commands/init.tssrc/commands/intake.tssrc/commands/issue.tssrc/commands/modules.tssrc/commands/pages.tssrc/config.tssrc/user-config.tstests/user-config.test.ts
✅ Files skipped from review due to trivial changes (1)
- tests/user-config.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/commands/issue.ts
- src/commands/pages.ts
- src/config.ts
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/commands/pages.ts (2)
67-68: Extract the repeated availability message string to one helper/constant.The same message template is duplicated; centralizing avoids drift when wording changes later.
♻️ Proposed tidy-up
+function pageUnavailableMessage(projectKey: string): string { + return `Project pages are not available for ${projectKey} on this Plane instance or API version.`; +} + function mapPageAvailabilityError<A>( effect: Effect.Effect<A, Error>, message: string, ): Effect.Effect<A, Error> { @@ const raw = yield* mapPageAvailabilityError( api.get(`projects/${id}/pages/`), - `Project pages are not available for ${key} on this Plane instance or API version.`, + pageUnavailableMessage(key), ); @@ const raw = yield* mapPageAvailabilityError( api.post(`projects/${id}/pages/`, body), - `Project pages are not available for ${key} on this Plane instance or API version.`, + pageUnavailableMessage(key), );Also applies to: 148-149
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pages.ts` around lines 67 - 68, Extract the duplicated message template into a single helper so both call sites reuse it: add a constant like PROJECT_PAGES_UNAVAILABLE = (key: string) => `Project pages are not available for ${key} on this Plane instance or API version.` near the top of the pages command file, then replace the two literal usages (the duplicated template strings in src/commands/pages.ts) with calls to PROJECT_PAGES_UNAVAILABLE(key); update imports/exports if needed so the helper is visible to both locations.
41-57: Use structured HTTP status checks instead of parsing error text.
isNotFoundErrordepends on the exact"HTTP 404:"message format in the error string. Ifsrc/api.tschanges the message format, this silent breaks. Consider adding a structured error type or status field to the API layer (e.g., a customApiErrorclass with astatusproperty) so status checks can match on a concrete field rather than a fragile regex pattern. This same pattern exists insrc/issue-support.ts, so a unified solution across the codebase would improve maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/commands/pages.ts` around lines 41 - 57, The current isNotFoundError implementation relies on parsing error.message which is fragile; introduce a structured error type (e.g., ApiError with a numeric status property) in the API layer and update isNotFoundError to test error as ApiError and check error.status === 404 (instead of regex), then update mapPageAvailabilityError to rely on the new isNotFoundError behavior; apply the same pattern used in src/issue-support.ts so both modules check a concrete ApiError.status rather than error.message.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/commands/init.ts`:
- Around line 478-486: The current check uses (savedDefaultProject ??
effective.defaultProject).trim() which throws if both are undefined; change to
compute a local project variable (e.g., const project = savedDefaultProject ??
effective.defaultProject) and guard using optional chaining or explicit falsy
check (e.g., if (project?.trim()) { ... }) before calling trim; then pass the
original savedDefaultProject and effective.defaultProject into describeValue and
Console.log exactly as before (referencing savedDefaultProject,
effective.defaultProject, scope, describeValue, and Console.log) so you avoid
calling .trim() on undefined.
---
Nitpick comments:
In `@src/commands/pages.ts`:
- Around line 67-68: Extract the duplicated message template into a single
helper so both call sites reuse it: add a constant like
PROJECT_PAGES_UNAVAILABLE = (key: string) => `Project pages are not available
for ${key} on this Plane instance or API version.` near the top of the pages
command file, then replace the two literal usages (the duplicated template
strings in src/commands/pages.ts) with calls to PROJECT_PAGES_UNAVAILABLE(key);
update imports/exports if needed so the helper is visible to both locations.
- Around line 41-57: The current isNotFoundError implementation relies on
parsing error.message which is fragile; introduce a structured error type (e.g.,
ApiError with a numeric status property) in the API layer and update
isNotFoundError to test error as ApiError and check error.status === 404
(instead of regex), then update mapPageAvailabilityError to rely on the new
isNotFoundError behavior; apply the same pattern used in src/issue-support.ts so
both modules check a concrete ApiError.status rather than error.message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0524098b-68f7-42d5-9df8-0758da2c6989
📒 Files selected for processing (2)
src/commands/init.tssrc/commands/pages.ts
| if ((savedDefaultProject ?? effective.defaultProject).trim()) { | ||
| yield* Console.log( | ||
| ` Project: ${describeValue( | ||
| scope, | ||
| savedDefaultProject, | ||
| savedDefaultProject ?? effective.defaultProject, | ||
| )}`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
TypeError when no default project is configured.
If both savedDefaultProject and effective.defaultProject are undefined, calling .trim() on the result of the nullish coalescing operator throws a TypeError. This can happen when running plane init on a fresh setup with no project configured.
🐛 Proposed fix using optional chaining
- if ((savedDefaultProject ?? effective.defaultProject).trim()) {
+ if ((savedDefaultProject ?? effective.defaultProject)?.trim()) {
yield* Console.log(
` Project: ${describeValue(
scope,
savedDefaultProject,
- savedDefaultProject ?? effective.defaultProject,
+ savedDefaultProject ?? effective.defaultProject ?? "",
)}`,
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ((savedDefaultProject ?? effective.defaultProject).trim()) { | |
| yield* Console.log( | |
| ` Project: ${describeValue( | |
| scope, | |
| savedDefaultProject, | |
| savedDefaultProject ?? effective.defaultProject, | |
| )}`, | |
| ); | |
| } | |
| if ((savedDefaultProject ?? effective.defaultProject)?.trim()) { | |
| yield* Console.log( | |
| ` Project: ${describeValue( | |
| scope, | |
| savedDefaultProject, | |
| savedDefaultProject ?? effective.defaultProject ?? "", | |
| )}`, | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/commands/init.ts` around lines 478 - 486, The current check uses
(savedDefaultProject ?? effective.defaultProject).trim() which throws if both
are undefined; change to compute a local project variable (e.g., const project =
savedDefaultProject ?? effective.defaultProject) and guard using optional
chaining or explicit falsy check (e.g., if (project?.trim()) { ... }) before
calling trim; then pass the original savedDefaultProject and
effective.defaultProject into describeValue and Console.log exactly as before
(referencing savedDefaultProject, effective.defaultProject, scope,
describeValue, and Console.log) so you avoid calling .trim() on undefined.
This pull request updates documentation, configuration, and scripts to clarify CLI usage, improve deployment compatibility guidance, and ensure correct references after a repository rename. The most important changes are grouped below.
Repository and Package Name Updates:
plane-clitoplane-cli-cliin URLs, badges, and install instructions to match the new repository name. This includes.github/ISSUE_TEMPLATE/config.yml,README.md, and install commands inSKILL.md. [1] [2] [3] [4]CLI Usage and Configuration Documentation:
README.mdandSKILL.mdto clarify global and local CLI setup, current project selection, configuration precedence, and project context management. Added examples for new commands (plane init --local,plane projects use, etc.) and described how local and environment config is resolved. [1] [2]New and Updated Commands:
plane labels deleteandplane modules delete, and clarified argument expectations for commands likeplane modules issues removeandplane members list. [1] [2] [3] [4] [5]README.mdandSKILL.mdto reflect the latest command grammar and argument conventions, including use of@currentand explicit HTML for descriptions. [1] [2] [3]Deployment Compatibility Guidance:
AGENTS.mdand updated compatibility notes inREADME.mdandSKILL.mdto document how features like pages and worklogs may not be available on all Plane deployments, even if feature flags are set. [1] [2] [3]Developer Tooling Improvements:
.husky/pre-commitscript to reliably locate thebunbinary, ensuring pre-commit hooks work even whenbunis not in the default PATH.These updates ensure the CLI documentation is accurate, user-friendly, and robust against deployment differences, while also aligning all references and scripts with the new repository structure.
Summary by CodeRabbit
New Features
plane init --local,plane . init, top-levelplane .commandplane projects useandplane projects currentplane labels delete,plane modules delete.plane/and updates AGENTS.mdDocumentation
Improvements
Chores
.plane/to ignore list; package branding/metadata updated