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_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", "safe search query normalization"], 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"], 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"], 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"], 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_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"], 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"], 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", "open issue check", "creator scope check", "project/item/field/value validation", "item targets requested issue"], resultPolicy: "Throws setup, validation, creator-scope refusal, closed-issue refusal, token, repository, REST, GraphQL, and target-mismatch failures before settlement; malformed accepted mutation data returns retry-safe partial_success.", coverage: ["test/projects-tool.test.mjs", "test/tool-integration.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", "abort checks before cache write"], 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.", 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"], 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"], 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_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 validation", "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.", 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_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 open-issue check, repository label existence check, label add/remove/set, issue/comment refresh", localSideEffects: "Refreshes the issue-cache JSON file 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", "closed-issue refusal"], resultPolicy: "Throws validation/setup/token/repository/creator-scope/closed-issue/missing-label/GitHub failures before remote success; returns success for idempotent missing-label removals and 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 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/closed-issue/refusal checks", "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[];