import type { IssueMeToolName } from "./tools/inventory.ts"; export type IssueMeExecutionMode = "parallel" | "sequential"; export interface IssueMeCommandContract { command: string; sideEffects: string; trustRequirement: string; modeBehavior: string; resultPolicy: string; coverage: readonly string[]; } export interface IssueMeToolContract { name: IssueMeToolName; readOnly: boolean; trustRequired: true; githubApi: string; localSideEffects: string; executionMode: IssueMeExecutionMode; validationGates: readonly string[]; resultPolicy: string; coverage: readonly string[]; } export const ISSUEME_COMMAND_CONTRACTS = [ { command: "/issueme", sideEffects: "Opens the non-secret config UI in TUI mode; non-TUI contexts send a status/config fallback message only.", trustRequirement: "Requires project trust before reading or saving project-local IssueMe config.", modeBehavior: "ctx.mode === 'tui' and ctx.hasUI are required for custom UI; RPC/JSON/print get safe text output.", resultPolicy: "Untrusted config access is returned as a normal warning message; config save failures throw so Pi reports command failure.", coverage: ["test/command-and-tui.test.mjs", "test/config-tui-renderer.test.mjs", "scripts/smoke-handler-execution.mjs"], }, { command: "/issueme info/help", sideEffects: "Sends a safe help/status message including allowed issue creator; no GitHub calls, no local cache writes, and untrusted projects ignore project-local config, .env, Git config, and cache files.", trustRequirement: "Works in trusted and untrusted projects; trusted mode may read non-secret config/cache metadata.", modeBehavior: "Works in TUI, RPC, JSON, and print-style command contexts through pi.sendMessage.", resultPolicy: "Repository/token/config lookup problems are rendered as safe status fields unless an unexpected filesystem/config error escapes.", coverage: ["test/command-and-tui.test.mjs", "scripts/smoke-handler-execution.mjs"], }, { command: "/issueme start [skill-path]", sideEffects: "Validates a readable project-local skill file and queues a user prompt that asks the agent to read it; does not read the skill contents itself.", trustRequirement: "Requires project trust before resolving project-local skill paths or configured defaultSkillPath.", modeBehavior: "Works outside TUI by sending the workflow prompt; TUI additionally shows a notification.", resultPolicy: "Invalid, missing, unreadable, or escaping skill paths throw IssueMeError; missing path/default returns the info view with usage guidance.", coverage: ["test/command-and-tui.test.mjs"], }, ] as const satisfies readonly IssueMeCommandContract[]; export const ISSUEME_TOOL_CONTRACTS = [ { name: "issueme_sync_issues", readOnly: false, trustRequired: true, githubApi: "REST issues and issue comments", localSideEffects: "Creates, updates, renames, or removes bounded issue-cache JSON files for the current repository and configured creator scope.", executionMode: "sequential", validationGates: ["project trust", "config path safety", "repository resolution", "token resolution", "creator scope filtering", "abort checks before cache writes"], resultPolicy: "Throws setup, token, repository, GitHub, abort, and unexpected cache failures; returns success with invalidFiles diagnostics for corrupt or unsafe cache files left untouched.", coverage: ["test/sync-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_get_overview", readOnly: true, trustRequired: true, githubApi: "REST issues/labels/milestones/assignees plus GraphQL repository-linked Projects v2; at most one page per section", localSideEffects: "None; does not read or write issue-cache files.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "section/limit validation", "creator scope filtering", "single-page request budgets", "abort checks"], resultPolicy: "Throws setup/authentication/rate-limit/boundary/abort/unexpected failures; preserves independent GitHub section failures as partial_success or error when all requested sections fail, always needsSync false. Empty collections are success; omitted/truncated/unavailable sections are explicit.", coverage: ["test/overview-tool.test.mjs", "test/overview-pagination.test.mjs"], }, { name: "issueme_list_issues", readOnly: true, trustRequired: true, githubApi: "REST issues list or search/issues with repo:is:issue constraints", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "at most 25 label filters", "creator scope/conflict validation", "issue type filter validation", "safe search query normalization", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, creator-scope conflict, token, repository, and GitHub failures; returns success for empty result sets and truncation metadata when bounded.", coverage: ["test/list-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_labels", readOnly: true, trustRequired: true, githubApi: "REST repository labels", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "bounded name/query/limit filters", "label collection member identity shape", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, token, repository, GitHub, and malformed label member failures; returns success only for valid empty discovery results.", coverage: ["test/list-labels-tool.test.mjs", "test/tool-integration.test.mjs", "scripts/smoke-handler-execution.mjs"], }, { name: "issueme_list_milestones", readOnly: true, trustRequired: true, githubApi: "REST repository milestones", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "state/sort/direction/limit validation", "milestone collection member identity shape", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, token, repository, GitHub, and malformed milestone member failures; returns success only for valid empty discovery results.", coverage: ["test/list-milestones-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_assignees", readOnly: true, trustRequired: true, githubApi: "REST repository assignees", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "bounded login/query/limit filters", "assignee collection member identity shape", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, token, repository, GitHub, and malformed assignee member failures; returns success only for valid empty discovery results.", coverage: ["test/list-assignees-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_issue_types", readOnly: true, trustRequired: true, githubApi: "REST repository owner type lookup plus the resolved organization's issue-types endpoint", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "owner type resolution", "organization issue-types boundary limited to the resolved owner", "issue type collection member identity shape"], resultPolicy: "Throws setup, token, repository, permission, malformed member, and unexpected GitHub failures; a 404/410 on the organization endpoint throws github_issue_types_unsupported; a user-owned repository returns success with status issue_types_unavailable and no organization request; a valid empty organization list is success.", coverage: ["test/issue-types.test.mjs"], }, { name: "issueme_list_issue_templates", readOnly: true, trustRequired: true, githubApi: "REST repository contents reads of .github/ISSUE_TEMPLATE (or a legacy ISSUE_TEMPLATE.md)", localSideEffects: "None.", executionMode: "parallel", validationGates: ["plain template file name", "after requires filename", "bounded bodyLimit", "project trust", "repository resolution", "token resolution", "contents response shape", "file size limit before decoding", "continuation token bound to repository/path/size"], resultPolicy: "Throws setup/validation/token/repository failures and a rewrapped explicit 403 when Contents read access is missing; an absent directory and legacy files is success with status issue_templates_none; unsupported formats and oversized files are listed without content; organization defaults are reported as unresolved; template text is bounded and marked as repository data.", coverage: ["test/issue-templates-tool.test.mjs"], }, { name: "issueme_list_projects", readOnly: true, trustRequired: true, githubApi: "GraphQL Projects v2 list for repository, organization, or user scopes", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "scope/owner/query/limit validation", "ProjectV2 collection member identity shape", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, token, repository, GraphQL, unsupported-scope, and malformed ProjectV2 member failures; returns success only for valid empty discovery results.", coverage: ["test/projects-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_get_project_fields", readOnly: true, trustRequired: true, githubApi: "GraphQL Projects v2 field discovery", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "project ID or owner/project number validation", "field/option/iteration limits", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, token, repository, GraphQL, and unsupported-field failures; returns success with truncation metadata when bounded.", coverage: ["test/projects-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_add_issue_to_project", readOnly: false, trustRequired: true, githubApi: "REST issue state check plus GraphQL Projects v2 project identity preflight and add item mutation", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project trust", "repository resolution", "token resolution", "open issue check", "creator scope check", "ProjectV2 ID validation", "project owner/scope and open-project preflight"], resultPolicy: "Throws setup, validation, project-owner mismatch, creator-scope refusal, closed-issue refusal, token, repository, REST, and GraphQL failures before the add mutation; returns success for added/already-present items and retry-safe partial_success for malformed accepted mutation data.", coverage: ["test/projects-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_update_project_item", readOnly: false, trustRequired: true, githubApi: "REST issue state check plus GraphQL Projects v2 item validation and field mutation", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project trust", "repository resolution", "token resolution", "issue identity check (open or closed; approved project-only metadata exception)", "creator scope check", "project/item/field/value validation", "item targets requested issue"], resultPolicy: "Throws setup, validation, creator-scope refusal, token, repository, REST, GraphQL, and target-mismatch failures before settlement; closed issues are accepted because only board metadata changes; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/projects-tool.test.mjs", "test/tool-integration.test.mjs", "test/project-item-closed-issue-policy.test.mjs"], }, { name: "issueme_list_project_items", readOnly: true, trustRequired: true, githubApi: "GraphQL Projects v2 items with typed field values", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "project ID or owner/project number validation", "item/value limits", "continuation token bound to repository/collection/filters", "project item member identity shape", "current-repository issue content and creator scope filtering"], resultPolicy: "Throws setup, validation, token, repository, GraphQL, and malformed item failures; returns success with omitted-content counts for pull request, draft, foreign-repository, out-of-scope, and inaccessible items, archive state per item, and truncation/continuation metadata for items and nested field values.", coverage: ["test/project-items-tool.test.mjs"], }, { name: "issueme_get_project_item", readOnly: true, trustRequired: true, githubApi: "GraphQL Projects v2 item by node ID or issue project items with typed field values", localSideEffects: "None.", executionMode: "parallel", validationGates: ["project trust", "repository resolution", "token resolution", "project ID validation", "exactly one of item ID or issue number", "item belongs to project", "issue-backed current-repository content", "creator scope check", "field-value continuation token binding"], resultPolicy: "Throws setup, validation, token, repository, GraphQL, inaccessible/mismatched item, non-issue content, foreign-repository, and creator-scope failures; an issue without an item on the project returns success with status project_item_not_found; nested field-value truncation reports continuation metadata.", coverage: ["test/project-items-tool.test.mjs"], }, { name: "issueme_remove_issue_from_project", readOnly: false, trustRequired: true, githubApi: "REST open-issue check plus GraphQL Projects v2 item validation, issue project-item absence verification, and deleteProjectV2Item mutation", localSideEffects: "None; the issue and its cache file are untouched.", executionMode: "sequential", validationGates: ["confirmRemove true", "project/item/issue ID validation", "project trust", "repository resolution", "token resolution", "issue identity check (open or closed; approved project-only metadata exception)", "creator scope check", "item targets requested project/repository/issue", "verified absence before no-op"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/identity-mismatch/unverifiable-absence/API failures before settlement; closed issues are accepted because only board membership changes; a verified absent item is success no-op; malformed or mismatched accepted deletion data returns retry-safe partial_success.", coverage: ["test/project-item-maintenance-tool.test.mjs"], }, { name: "issueme_clear_project_item_field", readOnly: false, trustRequired: true, githubApi: "REST open-issue check plus GraphQL Projects v2 field validation, item/value validation, and clearProjectV2ItemFieldValue mutation with read-back", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project/item/issue/field ID validation", "project trust", "repository resolution", "token resolution", "issue identity check (open or closed; approved project-only metadata exception)", "creator scope check", "field belongs to project", "project-owned clearable field type", "item targets requested project/repository/issue", "value presence preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/foreign-field/unsupported-type/identity-mismatch/API failures before settlement; closed issues are accepted because only board metadata changes; an already-clear field is success no-op; an accepted mutation whose read-back still shows a value or is malformed returns retry-safe partial_success.", coverage: ["test/project-item-maintenance-tool.test.mjs"], }, { name: "issueme_archive_project_item", readOnly: false, trustRequired: true, githubApi: "REST open-issue check plus GraphQL Projects v2 item validation and archiveProjectV2Item/unarchiveProjectV2Item mutations", localSideEffects: "None.", executionMode: "sequential", validationGates: ["action enum", "project/item/issue ID validation", "project trust", "repository resolution", "token resolution", "issue identity check (open or closed; approved project-only metadata exception)", "creator scope check", "item targets requested project/repository/issue", "archive state preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/identity-mismatch/API failures before settlement; closed issues are accepted because only board metadata changes; an already-matching archive state is success no-op; an accepted mutation returning the wrong or malformed archive state returns retry-safe partial_success.", coverage: ["test/project-item-maintenance-tool.test.mjs"], }, { name: "issueme_move_project_item", readOnly: false, trustRequired: true, githubApi: "REST open-issue check plus GraphQL Projects v2 item and anchor validation and updateProjectV2ItemPosition mutation", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project/item/issue ID validation", "anchor differs from moved item", "project trust", "repository resolution", "token resolution", "open issue check", "creator scope check", "item targets requested project/repository/issue", "anchor is an accessible item on the same board", "returned order shows the item at the top or directly after the anchor"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/identity-mismatch/foreign-or-inaccessible-anchor/API failures before settlement; an accepted mutation whose returned order does not show the expected position (concurrent edit, longer board, or malformed data) returns retry-safe partial_success because repeating the move is safe.", coverage: ["test/project-item-move-tool.test.mjs"], }, { name: "issueme_manage_label", readOnly: false, trustRequired: true, githubApi: "REST repository labels create/update/delete", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project trust", "repository resolution", "token resolution", "action-specific label name/color/description validation", "confirmDelete for delete"], resultPolicy: "Throws setup, validation, token, repository, and unexpected pre-settlement GitHub failures; returns structured result:error for known 404/422 label conflicts, success no-op for an already-absent delete, and retry-safe partial_success for malformed accepted mutation data.", coverage: ["test/manage-label-tool.test.mjs", "test/tool-integration.test.mjs", "test/tool-failure-semantics.test.mjs"], }, { name: "issueme_manage_milestone", readOnly: false, trustRequired: true, githubApi: "REST repository milestones create/update/close/reopen/delete", localSideEffects: "None.", executionMode: "sequential", validationGates: ["project trust", "repository resolution", "token resolution", "action-specific number/title/due date validation", "confirmDelete for delete"], resultPolicy: "Throws setup, validation, token, repository, and unexpected pre-settlement GitHub failures; returns structured result:error for known 404/422 milestone conflicts, success no-op for an already-absent delete, and retry-safe partial_success for malformed accepted mutation data.", coverage: ["test/manage-milestone-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_create_issue", readOnly: false, trustRequired: true, githubApi: "REST issue create plus authenticated-user lookup when creator scope is restricted", localSideEffects: "Writes one local issue-cache JSON file after remote creation.", executionMode: "sequential", validationGates: ["non-empty title", "body whitespace policy", "at most 25 labels and 25 assignees", "project trust", "repository resolution", "token resolution", "creator scope token-user check", "label/assignee normalization", "issue type name validation", "abort checks before cache write", "persisted issue type verification"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/GitHub failures before remote creation; returns retry-safe partial_success if accepted create data is malformed or post-create cache work fails, and partial_success with status create_issue_type_not_applied when GitHub silently dropped a requested issue type.", coverage: ["test/create-update-sanitization.test.mjs", "test/tool-integration.test.mjs", "test/partial-success.test.mjs", "test/tool-failure-semantics.test.mjs"], }, { name: "issueme_create_sub_issue", readOnly: false, trustRequired: true, githubApi: "REST issue create/open-parent check plus authenticated-user lookup when restricted and GraphQL native addSubIssue mutation", localSideEffects: "Writes the created issue and relationship cache files when possible.", executionMode: "sequential", validationGates: ["non-empty title", "body whitespace policy", "at most 25 labels and 25 assignees", "project trust", "repository resolution", "token resolution", "open parent check", "creator scope parent/token-user checks", "label/assignee normalization"], resultPolicy: "Throws validation/setup/token/repository/open-parent/creator-scope/create failures; returns partial_success when the child issue was created but native attachment or cache refresh failed, with retry-safe guidance.", coverage: ["test/sub-issue-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_add_sub_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks plus GraphQL native addSubIssue mutation", localSideEffects: "Refreshes parent/child relationship cache files after remote success.", executionMode: "sequential", validationGates: ["distinct positive parent/child issue numbers", "project trust", "repository resolution", "token resolution", "creator scope parent/child checks", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/abort/unexpected API failures before settlement; returns structured result:error only for documented forbidden/unsupported native refusals and partial_success for malformed accepted data or cache failure after attachment.", coverage: ["test/sub-issue-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_remove_sub_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks plus GraphQL native removeSubIssue mutation", localSideEffects: "Refreshes parent/child relationship cache files after remote success.", executionMode: "sequential", validationGates: ["distinct positive parent/child issue numbers", "project trust", "repository resolution", "token resolution", "creator scope parent/child checks", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/abort/unexpected API failures before settlement; returns structured result:error only for documented forbidden/unsupported native refusals and partial_success for malformed accepted data or cache failure after removal.", coverage: ["test/sub-issue-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_reorder_sub_issues", readOnly: false, trustRequired: true, githubApi: "REST/GraphQL native sub-issue relationship validation plus GraphQL reprioritizeSubIssue mutations", localSideEffects: "Refreshes relationship cache metadata after remote reorder.", executionMode: "sequential", validationGates: ["positive parent issue number", "non-empty unique ordered child list", "project trust", "repository resolution", "token resolution", "creator scope parent/visible-child checks", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/abort/unexpected API failures before settlement; returns structured result:error only for documented forbidden/unsupported native refusals, success for reorder no-op, and partial_success for malformed accepted data or post-reorder cache failure.", coverage: ["test/sub-issue-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_sub_issues", readOnly: true, trustRequired: true, githubApi: "GraphQL native parent/sub-issue relationship inspection", localSideEffects: "None by default; refreshCache true writes relationship cache metadata for related issues.", executionMode: "sequential", validationGates: ["positive issue number", "bounded limit", "project trust", "repository resolution", "token resolution", "creator scope relationship checks", "abort checks before optional cache writes", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, creator-scope, token, repository, and GraphQL inspection failures; with refreshCache true returns partial_success if inspection succeeded but cache refresh failed.", coverage: ["test/sub-issue-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_issue_development_links", readOnly: true, trustRequired: true, githubApi: "REST issue creator check plus GraphQL issue timeline development-link inspection", localSideEffects: "None.", executionMode: "parallel", validationGates: ["positive issue number", "bounded limit", "project trust", "repository resolution", "token resolution", "creator scope check", "continuation token bound to repository/collection/filters"], resultPolicy: "Throws setup, validation, creator-scope, token, repository, and GraphQL failures; returns success with empty developmentLinks when GitHub exposes no linked PR/commit data.", coverage: ["test/development-links-tool.test.mjs"], }, { name: "issueme_list_issue_timeline", readOnly: true, trustRequired: true, githubApi: "REST issue identity/creator check plus REST issue timeline events", localSideEffects: "None.", executionMode: "parallel", validationGates: ["positive issue number", "event type/limit validation", "continuation token bound to repository/collection/filters", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "timeline event member identity shape"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, token, repository, malformed member, and GitHub failures; empty histories are success; unfamiliar event kinds keep only common fields and are counted, comment events carry IDs/URLs but never bodies, and the event list carries continuation metadata.", coverage: ["test/issue-timeline-tool.test.mjs"], }, { name: "issueme_list_issue_dependencies", readOnly: true, trustRequired: true, githubApi: "REST issue identity/creator check plus REST issue dependency blocked_by/blocking collections", localSideEffects: "None.", executionMode: "parallel", validationGates: ["positive issue number", "direction/limit validation", "continuation token bound to repository/collection/filters", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "dependency collection member identity shape"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, token, repository, malformed member, and unexpected REST failures; a 404/410 on the dependency collection after the issue resolved throws github_issue_dependencies_unsupported instead of returning an empty list. Valid empty collections are success; out-of-scope members are omitted and counted.", coverage: ["test/issue-dependencies-tool.test.mjs"], }, { name: "issueme_add_issue_dependency", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks for both endpoints, bounded blocked_by preflight scan, and POST issue dependency by blocking issue database id", localSideEffects: "None; dependencies are not stored in issue cache records.", executionMode: "sequential", validationGates: ["distinct positive issue numbers", "self-dependency refusal", "project trust", "repository resolution", "token resolution", "both issues open", "both issues not pull requests", "creator scope check on both issues", "blocking issue database id resolution", "existing-edge preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/pull-request/abort/unexpected API failures before settlement; already-present edges are success no-ops; documented GitHub 422 refusals and unavailable-feature 404/410 answers return result:error without body-text fallback; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/issue-dependencies-tool.test.mjs"], }, { name: "issueme_remove_issue_dependency", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks for both endpoints, bounded blocked_by preflight scan, and DELETE issue dependency by blocking issue database id", localSideEffects: "None; dependencies are not stored in issue cache records.", executionMode: "sequential", validationGates: ["distinct positive issue numbers", "self-dependency refusal", "project trust", "repository resolution", "token resolution", "both issues open", "both issues not pull requests", "creator scope check on both issues", "blocking issue database id resolution", "existing-edge preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/pull-request/abort/unexpected API failures before settlement; a verified or GitHub-reported absent edge is a success no-op; documented GitHub 422 refusals return result:error; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/issue-dependencies-tool.test.mjs"], }, { name: "issueme_list_related_issues", readOnly: true, trustRequired: true, githubApi: "REST issue identity/creator check plus REST issue relates_to collection", localSideEffects: "None.", executionMode: "parallel", validationGates: ["positive issue number", "limit validation", "continuation token bound to repository/collection/filters", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "related issue collection member identity shape"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, token, repository, malformed member, and unexpected REST failures; a 404/410 on the collection after the issue resolved throws github_related_issues_unsupported instead of returning an empty list. Valid empty collections are success; out-of-scope members are omitted and counted.", coverage: ["test/related-issues-tool.test.mjs"], }, { name: "issueme_add_related_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks for both issues, bounded relates_to preflight scan, and POST related issue by database id", localSideEffects: "None; related issues are not stored in issue cache records.", executionMode: "sequential", validationGates: ["distinct positive issue numbers", "self-relation refusal", "project trust", "repository resolution", "token resolution", "both issues open", "both issues not pull requests", "creator scope check on both issues", "related issue database id resolution", "existing-link preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/pull-request/abort/unexpected API failures before settlement; already-present links are success no-ops; documented GitHub 422 refusals and unavailable-feature 404/410 answers return result:error without body-text fallback; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/related-issues-tool.test.mjs"], }, { name: "issueme_remove_related_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue checks for both issues, bounded relates_to preflight scan, and DELETE related issue by database id", localSideEffects: "None; related issues are not stored in issue cache records.", executionMode: "sequential", validationGates: ["distinct positive issue numbers", "self-relation refusal", "project trust", "repository resolution", "token resolution", "both issues open", "both issues not pull requests", "creator scope check on both issues", "related issue database id resolution", "existing-link preflight"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/pull-request/abort/unexpected API failures before settlement; a verified or GitHub-reported absent link is a success no-op; documented GitHub 422 refusals return result:error; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/related-issues-tool.test.mjs"], }, { name: "issueme_get_issue", readOnly: true, trustRequired: true, githubApi: "Local cache lookup by default; optional REST issue/comment refresh by number", localSideEffects: "None by default; refresh true writes/renames open issue cache files or removes stale closed issue cache files.", executionMode: "sequential", validationGates: ["project trust", "config path safety", "repository resolution", "token resolution for refresh", "unambiguous current-repository cache lookup", "creator scope cache/refresh check", "abort checks before cache writes/removes"], resultPolicy: "Throws setup, validation, ambiguous lookup, creator-scope, token, repository, GitHub, and unexpected cache failures; returns structured not-found status for missing explicit cache-file lookups.", coverage: ["test/get-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_update_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue check, issue update, issue/comment refresh", localSideEffects: "Writes or renames the issue-cache JSON file after remote success.", executionMode: "sequential", validationGates: ["at least one changed field", "title/body/label/assignee/milestone/type validation", "type/clearType exclusivity", "at most 25 labels and 25 assignees", "project trust", "repository resolution", "token resolution", "creator scope check", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/GitHub failures before remote success; returns partial_success when GitHub updated the issue but local cache refresh failed, and partial_success with status update_issue_type_not_applied when a requested type change was silently dropped.", coverage: ["test/create-update-sanitization.test.mjs", "test/tool-integration.test.mjs", "test/tool-detail-consistency.test.mjs", "test/partial-success.test.mjs"], }, { name: "issueme_comment_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue check, comment create, issue/comment refresh", localSideEffects: "Refreshes the issue-cache JSON file after remote comment creation.", executionMode: "sequential", validationGates: ["non-empty comment body", "project trust", "repository resolution", "token resolution", "creator scope check", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/GitHub failures before remote success; returns partial_success when GitHub created the comment but local cache refresh failed.", coverage: ["test/comment-management-tool.test.mjs", "test/tool-integration.test.mjs", "test/tool-detail-consistency.test.mjs"], }, { name: "issueme_update_comment", readOnly: false, trustRequired: true, githubApi: "REST open-issue check, comment ownership check, comment update, issue/comment refresh", localSideEffects: "Refreshes the issue-cache JSON file after remote comment edit.", executionMode: "sequential", validationGates: ["positive issue/comment IDs", "non-empty comment body", "comment belongs to issue", "project trust", "repository resolution", "token resolution", "creator scope check", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/comment-mismatch/GitHub failures before remote success; returns partial_success when GitHub edited the comment but local cache refresh failed.", coverage: ["test/comment-management-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_delete_comment", readOnly: false, trustRequired: true, githubApi: "REST open-issue check, comment ownership check, comment delete, issue/comment refresh", localSideEffects: "Refreshes the issue-cache JSON file after remote comment deletion.", executionMode: "sequential", validationGates: ["positive issue/comment IDs", "comment belongs to issue", "project trust", "repository resolution", "token resolution", "creator scope check", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/comment-mismatch/GitHub failures before remote success; returns partial_success when GitHub deleted the comment but local cache refresh failed.", coverage: ["test/comment-management-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_list_issue_comments", readOnly: true, trustRequired: true, githubApi: "REST issue identity/creator check plus REST issue comments with since filtering", localSideEffects: "None; the cache comment cap is unchanged.", executionMode: "parallel", validationGates: ["positive issue number", "since/limit/bodyLimit validation", "continuation token bound to repository/collection/filters", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "comment collection member identity shape"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, token, repository, malformed member, and GitHub failures; empty threads are success; bodies are windowed per comment with truncation flags and the comment list carries continuation metadata.", coverage: ["test/issue-comments-tool.test.mjs"], }, { name: "issueme_get_comment", readOnly: true, trustRequired: true, githubApi: "REST issue identity/creator check plus REST single issue comment with ownership verification", localSideEffects: "None.", executionMode: "parallel", validationGates: ["positive issue/comment IDs", "bodyLimit validation", "body continuation token bound to issue, comment, and comment version", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "comment belongs to issue"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, comment-mismatch, token, repository, and GitHub failures; returns a bounded body window with continuation metadata, and a token issued for an edited comment version is rejected.", coverage: ["test/issue-comments-tool.test.mjs"], }, { name: "issueme_assign_issue", readOnly: false, trustRequired: true, githubApi: "REST open-issue check, assignee assignability check, assignee add/remove/set, issue/comment refresh", localSideEffects: "Refreshes the issue-cache JSON file after remote success.", executionMode: "sequential", validationGates: ["action validation", "at most 25 assignees", "GitHub username validation", "repository assignability validation for add/set", "project trust", "repository resolution", "token resolution", "creator scope check", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/unassignable-user/GitHub failures before remote success; returns partial_success when assignee mutation succeeded but local cache refresh failed.", coverage: ["test/tool-integration.test.mjs", "test/partial-success.test.mjs"], }, { name: "issueme_label_issue", readOnly: false, trustRequired: true, githubApi: "REST any-state issue check, repository label existence check, label add/remove/set, issue/comment refresh", localSideEffects: "Refreshes open issue cache files or removes stale closed issue cache files after remote success or idempotent remove.", executionMode: "sequential", validationGates: ["action validation", "at most 25 labels", "label list validation", "existing repository label validation for add/set", "project trust", "repository resolution", "token resolution", "creator scope check"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/missing-label/GitHub failures before remote success; accepts open or closed issues, returns success for idempotent missing-label removals, and returns partial_success when label mutation/removal succeeded but local cache refresh failed.", coverage: ["test/label-tool.test.mjs", "test/tool-integration.test.mjs", "test/partial-success.test.mjs"], }, { name: "issueme_reopen_issue", readOnly: false, trustRequired: true, githubApi: "REST issue get/reopen, optional comment create, issue/comment refresh", localSideEffects: "Writes or refreshes the issue-cache JSON file for reopened or already-open issues.", executionMode: "sequential", validationGates: ["positive issue number", "optional comment body validation", "project trust", "repository resolution", "token resolution", "creator scope check"], resultPolicy: "Throws setup, validation, creator-scope, token, repository, and GitHub failures before settlement; returns success no-op for already-open in-scope issues and partial_success for malformed accepted reopen data or post-reopen comment/cache failures.", coverage: ["test/reopen-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_close_issue", readOnly: false, trustRequired: true, githubApi: "REST issue get/close", localSideEffects: "Removes matching stale/open local issue-cache files after close or already-closed detection.", executionMode: "sequential", validationGates: ["positive issue number", "close reason validation", "project trust", "repository resolution", "token resolution", "creator scope check", "abort checks before local removals"], resultPolicy: "Throws setup, validation, creator-scope, token, repository, and GitHub failures before settlement; returns success no-op for already-closed in-scope issues and partial_success for malformed accepted close data or post-close cache removal failure.", coverage: ["test/close-tool.test.mjs", "test/tool-integration.test.mjs"], }, { name: "issueme_delete_issue", readOnly: false, trustRequired: true, githubApi: "REST issue identity/creator preflight plus GraphQL deleteIssue mutation", localSideEffects: "Removes matching local issue-cache files after confirmed permanent deletion.", executionMode: "sequential", validationGates: ["positive issue number", "confirmDelete true", "project trust", "repository resolution", "token resolution", "issue-not-pull-request check", "creator scope check", "GraphQL issue node ID", "abort checks before local removals"], resultPolicy: "Throws setup, validation, creator-scope, pull-request, permission, unsupported-API, token, repository, and pre-settlement GitHub failures; malformed accepted deletion data or post-delete cache removal failure returns retry-safe partial_success.", coverage: ["test/delete-issue-tool.test.mjs", "test/github-client.test.mjs"], }, { name: "issueme_bulk_update_issues", readOnly: false, trustRequired: true, githubApi: "REST issue/label/assignee/milestone/close/reopen APIs and optional GraphQL Projects v2 add item", localSideEffects: "Refreshes or removes cache files for issue actions that change issue state; project-only action does not write cache files.", executionMode: "sequential", validationGates: ["explicit unique issueNumbers only", "action-specific field validation", "at most 25 labels or 25 assignees", "once-per-distinct-value repository preflight per bulk run", "project trust", "repository resolution", "token resolution", "per-issue creator-scope checks", "open-issue checks except add_labels, remove_labels, and reopen", "abort checks between issues"], resultPolicy: "Throws setup/validation and pre-first-mutation abort or unexpected API failures regardless of continueOnError; caches each successful repository label/assignee preflight for the current run; preserves accepted malformed responses and follow-up failures as per-item partial_success, and stops later work on cancellation/unexpected API failure.", coverage: ["test/bulk-issues-tool.test.mjs", "test/tool-integration.test.mjs"], }, ] as const satisfies readonly IssueMeToolContract[];