gsd-pi 2.10.12 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundled-extension-paths.d.ts +2 -0
- package/dist/bundled-extension-paths.js +10 -0
- package/dist/loader.js +45 -28
- package/dist/onboarding.js +100 -0
- package/dist/resource-loader.js +12 -3
- package/dist/resources/GSD-WORKFLOW.md +661 -0
- package/dist/resources/agents/researcher.md +29 -0
- package/dist/resources/agents/scout.md +56 -0
- package/dist/resources/agents/worker.md +31 -0
- package/dist/resources/extensions/ask-user-questions.ts +289 -0
- package/dist/resources/extensions/async-jobs/async-bash-tool.ts +211 -0
- package/dist/resources/extensions/async-jobs/await-tool.ts +101 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.ts +34 -0
- package/dist/resources/extensions/async-jobs/index.ts +133 -0
- package/dist/resources/extensions/async-jobs/job-manager.ts +211 -0
- package/dist/resources/extensions/bg-shell/index.ts +3179 -0
- package/dist/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/dist/resources/extensions/browser-tools/capture.ts +165 -0
- package/dist/resources/extensions/browser-tools/core.d.ts +205 -0
- package/dist/resources/extensions/browser-tools/core.js +1057 -0
- package/dist/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
- package/dist/resources/extensions/browser-tools/index.ts +51 -0
- package/dist/resources/extensions/browser-tools/lifecycle.ts +270 -0
- package/dist/resources/extensions/browser-tools/package.json +24 -0
- package/dist/resources/extensions/browser-tools/refs.ts +264 -0
- package/dist/resources/extensions/browser-tools/settle.ts +197 -0
- package/dist/resources/extensions/browser-tools/state.ts +408 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
- package/dist/resources/extensions/browser-tools/tools/assertions.ts +342 -0
- package/dist/resources/extensions/browser-tools/tools/forms.ts +801 -0
- package/dist/resources/extensions/browser-tools/tools/inspection.ts +492 -0
- package/dist/resources/extensions/browser-tools/tools/intent.ts +614 -0
- package/dist/resources/extensions/browser-tools/tools/interaction.ts +865 -0
- package/dist/resources/extensions/browser-tools/tools/navigation.ts +232 -0
- package/dist/resources/extensions/browser-tools/tools/pages.ts +303 -0
- package/dist/resources/extensions/browser-tools/tools/refs.ts +541 -0
- package/dist/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
- package/dist/resources/extensions/browser-tools/tools/session.ts +400 -0
- package/dist/resources/extensions/browser-tools/tools/wait.ts +247 -0
- package/dist/resources/extensions/browser-tools/utils.ts +660 -0
- package/dist/resources/extensions/context7/index.ts +428 -0
- package/dist/resources/extensions/context7/package.json +11 -0
- package/dist/resources/extensions/get-secrets-from-user.ts +625 -0
- package/dist/resources/extensions/google-search/index.ts +336 -0
- package/dist/resources/extensions/google-search/package.json +9 -0
- package/dist/resources/extensions/gsd/activity-log.ts +116 -0
- package/dist/resources/extensions/gsd/auto.ts +3732 -0
- package/dist/resources/extensions/gsd/commands.ts +544 -0
- package/dist/resources/extensions/gsd/crash-recovery.ts +105 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +564 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +78 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +278 -0
- package/dist/resources/extensions/gsd/doctor.ts +766 -0
- package/dist/resources/extensions/gsd/exit-command.ts +18 -0
- package/dist/resources/extensions/gsd/files.ts +857 -0
- package/dist/resources/extensions/gsd/git-service.ts +904 -0
- package/dist/resources/extensions/gsd/gitignore.ts +200 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +1054 -0
- package/dist/resources/extensions/gsd/index.ts +735 -0
- package/dist/resources/extensions/gsd/metrics.ts +374 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +218 -0
- package/dist/resources/extensions/gsd/migrate/index.ts +42 -0
- package/dist/resources/extensions/gsd/migrate/parser.ts +323 -0
- package/dist/resources/extensions/gsd/migrate/parsers.ts +624 -0
- package/dist/resources/extensions/gsd/migrate/preview.ts +48 -0
- package/dist/resources/extensions/gsd/migrate/transformer.ts +346 -0
- package/dist/resources/extensions/gsd/migrate/types.ts +370 -0
- package/dist/resources/extensions/gsd/migrate/validator.ts +55 -0
- package/dist/resources/extensions/gsd/migrate/writer.ts +539 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +181 -0
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +135 -0
- package/dist/resources/extensions/gsd/observability-validator.ts +408 -0
- package/dist/resources/extensions/gsd/package.json +11 -0
- package/dist/resources/extensions/gsd/paths.ts +339 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/dist/resources/extensions/gsd/preferences.ts +988 -0
- package/dist/resources/extensions/gsd/prompt-loader.ts +92 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +27 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +237 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +63 -0
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +61 -0
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +29 -0
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +13 -0
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +78 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +55 -0
- package/dist/resources/extensions/gsd/prompts/queue.md +100 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +39 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +37 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +28 -0
- package/dist/resources/extensions/gsd/prompts/review-migration.md +66 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +109 -0
- package/dist/resources/extensions/gsd/prompts/system.md +190 -0
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
- package/dist/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/dist/resources/extensions/gsd/session-forensics.ts +500 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +137 -0
- package/dist/resources/extensions/gsd/state.ts +569 -0
- package/dist/resources/extensions/gsd/templates/context.md +76 -0
- package/dist/resources/extensions/gsd/templates/decisions.md +8 -0
- package/dist/resources/extensions/gsd/templates/milestone-summary.md +73 -0
- package/dist/resources/extensions/gsd/templates/plan.md +131 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +23 -0
- package/dist/resources/extensions/gsd/templates/project.md +31 -0
- package/dist/resources/extensions/gsd/templates/reassessment.md +28 -0
- package/dist/resources/extensions/gsd/templates/requirements.md +81 -0
- package/dist/resources/extensions/gsd/templates/research.md +46 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +118 -0
- package/dist/resources/extensions/gsd/templates/secrets-manifest.md +22 -0
- package/dist/resources/extensions/gsd/templates/slice-context.md +58 -0
- package/dist/resources/extensions/gsd/templates/slice-summary.md +99 -0
- package/dist/resources/extensions/gsd/templates/state.md +19 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +52 -0
- package/dist/resources/extensions/gsd/templates/task-summary.md +57 -0
- package/dist/resources/extensions/gsd/templates/uat.md +54 -0
- package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
- package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +109 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +45 -0
- package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +196 -0
- package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
- package/dist/resources/extensions/gsd/tests/collect-from-manifest.test.ts +469 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +200 -0
- package/dist/resources/extensions/gsd/tests/cost-projection.test.ts +134 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +312 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +299 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +660 -0
- package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +27 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +91 -0
- package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +170 -0
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +594 -0
- package/dist/resources/extensions/gsd/tests/draft-promotion.test.ts +169 -0
- package/dist/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +1788 -0
- package/dist/resources/extensions/gsd/tests/idle-recovery.test.ts +577 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +580 -0
- package/dist/resources/extensions/gsd/tests/manifest-status.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +176 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +188 -0
- package/dist/resources/extensions/gsd/tests/migrate-command.test.ts +369 -0
- package/dist/resources/extensions/gsd/tests/migrate-parser.test.ts +757 -0
- package/dist/resources/extensions/gsd/tests/migrate-transformer.test.ts +635 -0
- package/dist/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +414 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer.test.ts +398 -0
- package/dist/resources/extensions/gsd/tests/must-have-parser.test.ts +291 -0
- package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +66 -0
- package/dist/resources/extensions/gsd/tests/orphaned-branch.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +1664 -0
- package/dist/resources/extensions/gsd/tests/plan-milestone.test.ts +133 -0
- package/dist/resources/extensions/gsd/tests/plan-quality-validator.test.ts +363 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/dist/resources/extensions/gsd/tests/queue-draft-detection.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/reassess-prompt.test.ts +142 -0
- package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
- package/dist/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +493 -0
- package/dist/resources/extensions/gsd/tests/requirements.test.ts +106 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +35 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +41 -0
- package/dist/resources/extensions/gsd/tests/run-uat.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/secure-env-collect.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/test-helpers.ts +61 -0
- package/dist/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +219 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +234 -0
- package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +75 -0
- package/dist/resources/extensions/gsd/tests/worktree-integration.test.ts +234 -0
- package/dist/resources/extensions/gsd/tests/worktree-manager.test.ts +141 -0
- package/dist/resources/extensions/gsd/tests/worktree.test.ts +597 -0
- package/dist/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/dist/resources/extensions/gsd/types.ts +296 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +184 -0
- package/dist/resources/extensions/gsd/workspace-index.ts +189 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +845 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +459 -0
- package/dist/resources/extensions/gsd/{worktree.js → worktree.ts} +92 -61
- package/dist/resources/extensions/mac-tools/index.ts +852 -0
- package/dist/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/dist/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/dist/resources/extensions/mcporter/index.ts +429 -0
- package/dist/resources/extensions/remote-questions/config.ts +81 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +128 -0
- package/dist/resources/extensions/remote-questions/format.ts +163 -0
- package/dist/resources/extensions/remote-questions/manager.ts +192 -0
- package/dist/resources/extensions/remote-questions/remote-command.ts +361 -0
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +92 -0
- package/dist/resources/extensions/remote-questions/status.ts +31 -0
- package/dist/resources/extensions/remote-questions/store.ts +77 -0
- package/dist/resources/extensions/remote-questions/types.ts +75 -0
- package/dist/resources/extensions/search-the-web/cache.ts +78 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.ts +99 -0
- package/dist/resources/extensions/search-the-web/format.ts +258 -0
- package/dist/resources/extensions/search-the-web/http.ts +238 -0
- package/dist/resources/extensions/search-the-web/index.ts +65 -0
- package/dist/resources/extensions/search-the-web/native-search.ts +165 -0
- package/dist/resources/extensions/search-the-web/provider.ts +136 -0
- package/dist/resources/extensions/search-the-web/tavily.ts +116 -0
- package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +581 -0
- package/dist/resources/extensions/search-the-web/tool-llm-context.ts +620 -0
- package/dist/resources/extensions/search-the-web/tool-search.ts +635 -0
- package/dist/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/dist/resources/extensions/shared/bundled-extension-paths.ts +11 -0
- package/dist/resources/extensions/shared/confirm-ui.ts +126 -0
- package/dist/resources/extensions/shared/interview-ui.ts +613 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +197 -0
- package/dist/resources/extensions/shared/progress-widget.ts +282 -0
- package/dist/resources/extensions/shared/terminal.ts +23 -0
- package/dist/resources/extensions/shared/thinking-widget.ts +107 -0
- package/dist/resources/extensions/shared/ui.ts +400 -0
- package/dist/resources/extensions/shared/wizard-ui.ts +551 -0
- package/dist/resources/extensions/slash-commands/audit.ts +88 -0
- package/dist/resources/extensions/slash-commands/clear.ts +10 -0
- package/dist/resources/extensions/slash-commands/create-extension.ts +297 -0
- package/dist/resources/extensions/slash-commands/create-slash-command.ts +234 -0
- package/dist/resources/extensions/slash-commands/index.ts +12 -0
- package/dist/resources/extensions/subagent/agents.ts +126 -0
- package/dist/resources/extensions/subagent/index.ts +1072 -0
- package/dist/resources/extensions/subagent/isolation.ts +498 -0
- package/dist/resources/extensions/ttsr/index.ts +163 -0
- package/dist/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/dist/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +430 -0
- package/dist/resources/extensions/universal-config/discovery.ts +78 -0
- package/dist/resources/extensions/universal-config/format.ts +160 -0
- package/dist/resources/extensions/universal-config/index.ts +118 -0
- package/dist/resources/extensions/universal-config/package.json +11 -0
- package/dist/resources/extensions/universal-config/scanners.ts +579 -0
- package/dist/resources/extensions/universal-config/tests/discovery.test.ts +111 -0
- package/dist/resources/extensions/universal-config/tests/format.test.ts +111 -0
- package/dist/resources/extensions/universal-config/tests/scanners.test.ts +438 -0
- package/dist/resources/extensions/universal-config/tools.ts +60 -0
- package/dist/resources/extensions/universal-config/types.ts +116 -0
- package/dist/resources/extensions/voice/index.ts +271 -0
- package/dist/resources/extensions/voice/speech-recognizer.py +504 -0
- package/dist/resources/extensions/voice/speech-recognizer.swift +154 -0
- package/dist/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/dist/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/dist/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/dist/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/dist/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/dist/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/dist/resources/skills/frontend-design/SKILL.md +45 -0
- package/dist/resources/skills/github-workflows/SKILL.md +92 -0
- package/dist/resources/skills/github-workflows/references/gh/SKILL.md +255 -0
- package/dist/resources/skills/github-workflows/references/gh/references/issue-stories.md +204 -0
- package/dist/resources/skills/github-workflows/references/gh/references/labels.md +170 -0
- package/dist/resources/skills/github-workflows/references/gh/references/milestones.md +158 -0
- package/dist/resources/skills/github-workflows/references/gh/references/projects-v2.md +177 -0
- package/dist/resources/skills/github-workflows/references/gh/scripts/experiment_cleanup.py +191 -0
- package/dist/resources/skills/github-workflows/references/gh/scripts/github_project_setup.py +799 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
- package/dist/resources/skills/swiftui/SKILL.md +208 -0
- package/dist/resources/skills/swiftui/references/animations.md +921 -0
- package/dist/resources/skills/swiftui/references/architecture.md +1561 -0
- package/dist/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/dist/resources/skills/swiftui/references/navigation.md +1492 -0
- package/dist/resources/skills/swiftui/references/networking-async.md +214 -0
- package/dist/resources/skills/swiftui/references/performance.md +1706 -0
- package/dist/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/dist/resources/skills/swiftui/references/state-management.md +1443 -0
- package/dist/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/dist/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/dist/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/dist/resources/skills/swiftui/workflows/ship-app.md +203 -0
- package/dist/resources/skills/swiftui/workflows/write-tests.md +235 -0
- package/dist/wizard.js +1 -0
- package/package.json +8 -6
- package/packages/native/dist/fd/index.d.ts +1 -0
- package/packages/native/dist/fd/index.js +1 -0
- package/packages/native/dist/grep/index.d.ts +2 -1
- package/packages/native/dist/grep/index.js +1 -0
- package/packages/native/src/__tests__/fd.test.mjs +35 -0
- package/packages/native/src/__tests__/grep.test.mjs +26 -9
- package/packages/native/src/fd/index.ts +1 -0
- package/packages/native/src/grep/index.ts +3 -2
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +169 -55
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +13 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +91 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +273 -63
- package/packages/pi-agent-core/src/agent.ts +24 -0
- package/packages/pi-agent-core/src/types.ts +98 -0
- package/packages/pi-ai/dist/env-api-keys.js +2 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +314 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +236 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +6 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/env-api-keys.ts +2 -0
- package/packages/pi-ai/src/models.generated.ts +236 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +8 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +3 -0
- package/packages/pi-ai/src/types.ts +2 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +1 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +137 -22
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +43 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts +51 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js +119 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +183 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +12 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +37 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +61 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +47 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.js +1 -0
- package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +6 -15
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +18 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +46 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +167 -22
- package/packages/pi-coding-agent/src/core/auth-storage.ts +45 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +229 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.ts +165 -0
- package/packages/pi-coding-agent/src/core/index.ts +1 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +51 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +66 -0
- package/packages/pi-coding-agent/src/core/tools/find.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +6 -16
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +49 -4
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +28 -2
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +3 -4
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +7 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +57 -50
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +3 -5
- package/packages/pi-tui/src/components/editor.ts +70 -49
- package/pkg/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
- package/pkg/dist/core/export-html/ansi-to-html.js +249 -0
- package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -0
- package/pkg/dist/core/export-html/index.d.ts +37 -0
- package/pkg/dist/core/export-html/index.d.ts.map +1 -0
- package/pkg/dist/core/export-html/index.js +223 -0
- package/pkg/dist/core/export-html/index.js.map +1 -0
- package/pkg/dist/core/export-html/template.css +971 -0
- package/pkg/dist/core/export-html/template.html +54 -0
- package/pkg/dist/core/export-html/template.js +1583 -0
- package/pkg/dist/core/export-html/tool-renderer.d.ts +38 -0
- package/pkg/dist/core/export-html/tool-renderer.d.ts.map +1 -0
- package/pkg/dist/core/export-html/tool-renderer.js +70 -0
- package/pkg/dist/core/export-html/tool-renderer.js.map +1 -0
- package/pkg/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/pkg/dist/core/export-html/vendor/marked.min.js +6 -0
- package/scripts/postinstall.js +30 -17
- package/src/resources/extensions/bg-shell/index.ts +51 -7
- package/src/resources/extensions/gsd/activity-log.ts +61 -14
- package/src/resources/extensions/gsd/auto.ts +288 -19
- package/src/resources/extensions/gsd/commands.ts +9 -3
- package/src/resources/extensions/gsd/dashboard-overlay.ts +55 -12
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor.ts +60 -3
- package/src/resources/extensions/gsd/files.ts +45 -1
- package/src/resources/extensions/gsd/git-service.ts +46 -41
- package/src/resources/extensions/gsd/guided-flow.ts +81 -9
- package/src/resources/extensions/gsd/index.ts +62 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +181 -0
- package/src/resources/extensions/gsd/paths.ts +35 -4
- package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/src/resources/extensions/gsd/preferences.ts +232 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/queue.md +3 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/state.ts +130 -18
- package/src/resources/extensions/gsd/templates/context.md +1 -1
- package/src/resources/extensions/gsd/templates/state.md +3 -3
- package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/git-service.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +8 -8
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/orphaned-branch.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree.test.ts +19 -19
- package/src/resources/extensions/gsd/types.ts +109 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +72 -13
- package/src/resources/extensions/gsd/worktree.ts +5 -4
- package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
- package/src/resources/extensions/search-the-web/native-search.ts +32 -23
- package/src/resources/extensions/search-the-web/provider.ts +19 -2
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
- package/src/resources/extensions/search-the-web/tool-search.ts +62 -3
- package/src/resources/extensions/shared/bundled-extension-paths.ts +11 -0
- package/src/resources/extensions/subagent/index.ts +2 -1
- package/src/resources/skills/github-workflows/SKILL.md +5 -0
- package/dist/resources/extensions/gsd/git-service.d.ts +0 -225
- package/dist/resources/extensions/gsd/git-service.js +0 -756
- package/dist/resources/extensions/gsd/preferences.d.ts +0 -122
- package/dist/resources/extensions/gsd/preferences.js +0 -616
- package/dist/resources/extensions/gsd/worktree.d.ts +0 -106
- package/dist/resources/extensions/remote-questions/config.d.ts +0 -14
- package/dist/resources/extensions/remote-questions/config.js +0 -68
- package/dist/resources/extensions/remote-questions/discord-adapter.d.ts +0 -17
- package/dist/resources/extensions/remote-questions/discord-adapter.js +0 -118
- package/dist/resources/extensions/remote-questions/format.d.ts +0 -38
- package/dist/resources/extensions/remote-questions/format.js +0 -119
- package/dist/resources/extensions/remote-questions/manager.d.ts +0 -23
- package/dist/resources/extensions/remote-questions/manager.js +0 -161
- package/dist/resources/extensions/remote-questions/remote-command.d.ts +0 -6
- package/dist/resources/extensions/remote-questions/remote-command.js +0 -358
- package/dist/resources/extensions/remote-questions/slack-adapter.d.ts +0 -15
- package/dist/resources/extensions/remote-questions/slack-adapter.js +0 -82
- package/dist/resources/extensions/remote-questions/status.d.ts +0 -9
- package/dist/resources/extensions/remote-questions/status.js +0 -25
- package/dist/resources/extensions/remote-questions/store.d.ts +0 -11
- package/dist/resources/extensions/remote-questions/store.js +0 -66
- package/dist/resources/extensions/remote-questions/types.d.ts +0 -68
- package/dist/resources/extensions/remote-questions/types.js +0 -4
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv --quiet run --active --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = [
|
|
5
|
+
# "typer>=0.21.0",
|
|
6
|
+
# "PyGithub>=2.1.1",
|
|
7
|
+
# ]
|
|
8
|
+
# ///
|
|
9
|
+
"""GitHub Project Setup — multi-step project management automation.
|
|
10
|
+
|
|
11
|
+
Orchestrates: label creation, milestone management, project setup,
|
|
12
|
+
Projects V2 status updates, and backlog item issue import using the
|
|
13
|
+
PyGithub native library. Projects V2 GraphQL mutations use the gh CLI.
|
|
14
|
+
|
|
15
|
+
Authentication: reads GITHUB_TOKEN from environment.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
github_project_setup.py setup --repo OWNER/REPO [--project-title TITLE]
|
|
19
|
+
github_project_setup.py labels --repo OWNER/REPO [--force]
|
|
20
|
+
github_project_setup.py milestone create --repo OWNER/REPO --title TITLE [--due YYYY-MM-DD]
|
|
21
|
+
github_project_setup.py milestone list --repo OWNER/REPO
|
|
22
|
+
github_project_setup.py milestone start --repo OWNER/REPO --number N [--dry-run]
|
|
23
|
+
github_project_setup.py milestone close --repo OWNER/REPO --number N [--dry-run]
|
|
24
|
+
github_project_setup.py issue create --repo OWNER/REPO --title TITLE [options]
|
|
25
|
+
github_project_setup.py issue list --repo OWNER/REPO [--priority p1] [--state open]
|
|
26
|
+
github_project_setup.py project update-status --project-number N --issue-number N --status STATUS
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import shutil
|
|
34
|
+
import subprocess
|
|
35
|
+
from datetime import UTC, datetime
|
|
36
|
+
from typing import TYPE_CHECKING, Annotated
|
|
37
|
+
|
|
38
|
+
import typer
|
|
39
|
+
from github import Auth, Github, GithubException
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from github.Issue import Issue
|
|
43
|
+
from github.Label import Label
|
|
44
|
+
from github.Milestone import Milestone
|
|
45
|
+
from github.Repository import Repository
|
|
46
|
+
|
|
47
|
+
app = typer.Typer(help="GitHub Project management automation via PyGithub")
|
|
48
|
+
milestone_app = typer.Typer(help="Milestone operations")
|
|
49
|
+
issue_app = typer.Typer(help="Issue operations")
|
|
50
|
+
project_app = typer.Typer(help="GitHub Projects V2 operations")
|
|
51
|
+
app.add_typer(milestone_app, name="milestone")
|
|
52
|
+
app.add_typer(issue_app, name="issue")
|
|
53
|
+
app.add_typer(project_app, name="project")
|
|
54
|
+
|
|
55
|
+
DEFAULT_REPO = "Jamie-BitFlight/claude_skills"
|
|
56
|
+
|
|
57
|
+
# Standard label taxonomy
|
|
58
|
+
LABELS: list[dict[str, str]] = [
|
|
59
|
+
# Priority
|
|
60
|
+
{"name": "priority:p0", "color": "D73A4A", "description": "Critical — blocks work or production"},
|
|
61
|
+
{"name": "priority:p1", "color": "E99695", "description": "High — should be done next"},
|
|
62
|
+
{"name": "priority:p2", "color": "F9D0C4", "description": "Medium — do when P0/P1 are clear"},
|
|
63
|
+
{"name": "priority:idea", "color": "BFD4F2", "description": "Unscoped — future consideration"},
|
|
64
|
+
# Type
|
|
65
|
+
{"name": "type:feature", "color": "0E8A16", "description": "New capability or skill"},
|
|
66
|
+
{"name": "type:bug", "color": "B60205", "description": "Something is broken"},
|
|
67
|
+
{"name": "type:refactor", "color": "5319E7", "description": "Internal improvement, no behavior change"},
|
|
68
|
+
{"name": "type:docs", "color": "0075CA", "description": "Documentation only"},
|
|
69
|
+
{"name": "type:chore", "color": "EDEDED", "description": "Maintenance, tooling, CI"},
|
|
70
|
+
# Status (all 8 state-machine states + legacy needs-review)
|
|
71
|
+
{"name": "status:needs-grooming", "color": "FEF2C0", "description": "Captured but not yet groomed"},
|
|
72
|
+
{"name": "status:groomed", "color": "C2E0C6", "description": "Grooming complete, RT-ICA APPROVED"},
|
|
73
|
+
{"name": "status:blocked", "color": "B60205", "description": "RT-ICA BLOCKED or AC verification FAIL"},
|
|
74
|
+
{"name": "status:in-milestone", "color": "BFD4F2", "description": "Assigned to an active milestone"},
|
|
75
|
+
{"name": "status:in-progress", "color": "1D76DB", "description": "Actively being worked on"},
|
|
76
|
+
{"name": "status:done", "color": "0E8A16", "description": "Implementation complete, AC verified PASS"},
|
|
77
|
+
{"name": "status:resolved", "color": "6B737B", "description": "Closed without full implementation"},
|
|
78
|
+
{"name": "status:closed", "color": "EDEDED", "description": "Terminal — milestone archived"},
|
|
79
|
+
# Legacy label retained for backwards compatibility — not part of state machine
|
|
80
|
+
{"name": "status:needs-review", "color": "D876E3", "description": "Implementation done, needs review"},
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
PRIORITY_LABEL_MAP = {"P0": "priority:p0", "P1": "priority:p1", "P2": "priority:p2", "IDEAS": "priority:idea"}
|
|
84
|
+
|
|
85
|
+
VALID_STATUSES = ("Backlog", "Grooming", "In Progress", "In Review", "Done")
|
|
86
|
+
|
|
87
|
+
# Label-to-status mapping for milestone transitions
|
|
88
|
+
_LABEL_TO_PROJECT_STATUS = {
|
|
89
|
+
"status:in-progress": "In Progress",
|
|
90
|
+
"status:done": "Done",
|
|
91
|
+
"status:needs-grooming": "Grooming",
|
|
92
|
+
"status:needs-review": "In Review",
|
|
93
|
+
"status:blocked": "Backlog",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_github() -> Github:
|
|
98
|
+
"""Return an authenticated Github client from GITHUB_TOKEN."""
|
|
99
|
+
token = os.environ.get("GITHUB_TOKEN")
|
|
100
|
+
if not token:
|
|
101
|
+
typer.echo("ERROR: GITHUB_TOKEN environment variable not set", err=True)
|
|
102
|
+
raise typer.Exit(1)
|
|
103
|
+
return Github(auth=Auth.Token(token))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_repo(gh: Github, repo_slug: str) -> Repository:
|
|
107
|
+
"""Return a Repository object, exit on failure.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
gh: Authenticated Github client.
|
|
111
|
+
repo_slug: Repository identifier in ``owner/repo`` format.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Repository object for the given slug.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
return gh.get_repo(repo_slug)
|
|
118
|
+
except GithubException as exc:
|
|
119
|
+
typer.echo(f"ERROR: Cannot access repo '{repo_slug}': {exc}", err=True)
|
|
120
|
+
raise typer.Exit(1) from exc
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def labels(
|
|
125
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
126
|
+
force: Annotated[bool, typer.Option("--force")] = False,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Create standard label taxonomy. Skips labels that already exist unless --force."""
|
|
129
|
+
gh = get_github()
|
|
130
|
+
repository = get_repo(gh, repo)
|
|
131
|
+
|
|
132
|
+
existing = {lbl.name: lbl for lbl in repository.get_labels()}
|
|
133
|
+
created = updated = skipped = 0
|
|
134
|
+
|
|
135
|
+
for spec in LABELS:
|
|
136
|
+
name = spec["name"]
|
|
137
|
+
if name in existing:
|
|
138
|
+
if force:
|
|
139
|
+
existing[name].edit(name=name, color=spec["color"], description=spec["description"])
|
|
140
|
+
typer.echo(f" updated: {name}")
|
|
141
|
+
updated += 1
|
|
142
|
+
else:
|
|
143
|
+
typer.echo(f" exists: {name} (--force to update)")
|
|
144
|
+
skipped += 1
|
|
145
|
+
else:
|
|
146
|
+
repository.create_label(name=name, color=spec["color"], description=spec["description"])
|
|
147
|
+
typer.echo(f" created: {name}")
|
|
148
|
+
created += 1
|
|
149
|
+
|
|
150
|
+
typer.echo(f"\nLabels: {created} created, {updated} updated, {skipped} skipped")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@milestone_app.command("create")
|
|
154
|
+
def milestone_create(
|
|
155
|
+
title: Annotated[str, typer.Option("--title")],
|
|
156
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
157
|
+
description: Annotated[str, typer.Option("--description")] = "",
|
|
158
|
+
due: Annotated[str | None, typer.Option("--due", help="Due date YYYY-MM-DD")] = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Create a milestone."""
|
|
161
|
+
gh = get_github()
|
|
162
|
+
repository = get_repo(gh, repo)
|
|
163
|
+
|
|
164
|
+
due_dt = datetime.strptime(due, "%Y-%m-%d").replace(tzinfo=UTC) if due else None
|
|
165
|
+
if due_dt is not None and description:
|
|
166
|
+
milestone = repository.create_milestone(title=title, description=description, due_on=due_dt)
|
|
167
|
+
elif due_dt is not None:
|
|
168
|
+
milestone = repository.create_milestone(title=title, due_on=due_dt)
|
|
169
|
+
elif description:
|
|
170
|
+
milestone = repository.create_milestone(title=title, description=description)
|
|
171
|
+
else:
|
|
172
|
+
milestone = repository.create_milestone(title=title)
|
|
173
|
+
typer.echo(f"Created milestone #{milestone.number}: {milestone.title}")
|
|
174
|
+
typer.echo(f" URL: {milestone.html_url}")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@milestone_app.command("list")
|
|
178
|
+
def milestone_list(repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO) -> None:
|
|
179
|
+
"""List all open milestones."""
|
|
180
|
+
gh = get_github()
|
|
181
|
+
repository = get_repo(gh, repo)
|
|
182
|
+
|
|
183
|
+
milestones = list(repository.get_milestones(state="all"))
|
|
184
|
+
if not milestones:
|
|
185
|
+
typer.echo("No milestones.")
|
|
186
|
+
return
|
|
187
|
+
for m in milestones:
|
|
188
|
+
due = m.due_on.strftime("%Y-%m-%d") if m.due_on else "no due date"
|
|
189
|
+
typer.echo(
|
|
190
|
+
f" #{m.number:3d} [{m.state}] {m.title} ({m.open_issues} open, {m.closed_issues} closed) due: {due}"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@milestone_app.command("start")
|
|
195
|
+
def milestone_start(
|
|
196
|
+
number: Annotated[int, typer.Option("--number", "-n", help="Milestone number")],
|
|
197
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
198
|
+
dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
|
|
199
|
+
project_number: Annotated[int, typer.Option("--project-number", "-p", help="Projects V2 number")] = 0,
|
|
200
|
+
owner: Annotated[str, typer.Option("--owner", help="GitHub owner for Projects V2")] = "Jamie-BitFlight",
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Transition open milestone issues from status:needs-grooming to status:in-progress.
|
|
203
|
+
|
|
204
|
+
When --project-number is set, also updates the Projects V2 Status field
|
|
205
|
+
to "In Progress" for each transitioned issue.
|
|
206
|
+
"""
|
|
207
|
+
gh = get_github()
|
|
208
|
+
repository = get_repo(gh, repo)
|
|
209
|
+
milestone = _get_open_milestone(repository, number)
|
|
210
|
+
|
|
211
|
+
if milestone.open_issues == 0:
|
|
212
|
+
typer.echo(
|
|
213
|
+
f"WARNING: Milestone #{number} '{milestone.title}' has no open issues. "
|
|
214
|
+
"Add items first with /group-items-to-milestone."
|
|
215
|
+
)
|
|
216
|
+
raise typer.Exit(0)
|
|
217
|
+
|
|
218
|
+
open_issues = list(repository.get_issues(milestone=milestone, state="open"))
|
|
219
|
+
typer.echo(f"Milestone #{milestone.number}: {milestone.title}")
|
|
220
|
+
typer.echo(f" {milestone.open_issues} open issue(s) — transitioning labels:\n")
|
|
221
|
+
|
|
222
|
+
for issue in open_issues:
|
|
223
|
+
label_names = [lbl.name for lbl in issue.labels]
|
|
224
|
+
typer.echo(f" #{issue.number:4d} {issue.title[:60]:<60} [{', '.join(label_names)}]")
|
|
225
|
+
|
|
226
|
+
if dry_run:
|
|
227
|
+
typer.echo("\n[dry-run] No changes made.")
|
|
228
|
+
if project_number:
|
|
229
|
+
typer.echo("\nProjects V2 status updates (dry-run):")
|
|
230
|
+
_bulk_update_project_status(owner, project_number, open_issues, "In Progress", dry_run=True)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
in_progress_label = _ensure_label(repository, "status:in-progress", "1D76DB", "Actively being worked on")
|
|
234
|
+
succeeded, skipped, failed = _transition_issues(open_issues, in_progress_label)
|
|
235
|
+
|
|
236
|
+
# Update Projects V2 Status if project specified
|
|
237
|
+
v2_succeeded = v2_failed = 0
|
|
238
|
+
if project_number:
|
|
239
|
+
typer.echo("\nUpdating Projects V2 Status → In Progress:")
|
|
240
|
+
v2_succeeded, v2_failed = _bulk_update_project_status(owner, project_number, open_issues, "In Progress")
|
|
241
|
+
|
|
242
|
+
typer.echo(
|
|
243
|
+
f"\nMilestone #{milestone.number} '{milestone.title}' started.\n"
|
|
244
|
+
f" {succeeded} transitioned, {skipped} already in-progress, {failed} failed."
|
|
245
|
+
)
|
|
246
|
+
if project_number:
|
|
247
|
+
typer.echo(f" Projects V2: {v2_succeeded} updated, {v2_failed} failed.")
|
|
248
|
+
typer.echo(
|
|
249
|
+
f"\nWork on individual items:\n"
|
|
250
|
+
f" /work-backlog-item {{title}}\n"
|
|
251
|
+
f"\nTrack progress:\n"
|
|
252
|
+
f" uv run .claude/skills/gh/scripts/github_project_setup.py issue list "
|
|
253
|
+
f"--repo {repo}"
|
|
254
|
+
)
|
|
255
|
+
if failed:
|
|
256
|
+
raise typer.Exit(1)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@milestone_app.command("close")
|
|
260
|
+
def milestone_close(
|
|
261
|
+
number: Annotated[int, typer.Option("--number", "-n", help="Milestone number")],
|
|
262
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
263
|
+
dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
|
|
264
|
+
project_number: Annotated[int, typer.Option("--project-number", "-p", help="Projects V2 number")] = 0,
|
|
265
|
+
owner: Annotated[str, typer.Option("--owner", help="GitHub owner for Projects V2")] = "Jamie-BitFlight",
|
|
266
|
+
) -> None:
|
|
267
|
+
"""Close a milestone: transition open issues to status:done and close the milestone.
|
|
268
|
+
|
|
269
|
+
When --project-number is set, also updates the Projects V2 Status field
|
|
270
|
+
to "Done" for each issue in the milestone (both open and already-closed).
|
|
271
|
+
"""
|
|
272
|
+
gh = get_github()
|
|
273
|
+
repository = get_repo(gh, repo)
|
|
274
|
+
milestone = _get_open_milestone(repository, number)
|
|
275
|
+
|
|
276
|
+
open_issues = list(repository.get_issues(milestone=milestone, state="open"))
|
|
277
|
+
closed_issues = list(repository.get_issues(milestone=milestone, state="closed"))
|
|
278
|
+
total = len(open_issues) + len(closed_issues)
|
|
279
|
+
|
|
280
|
+
typer.echo(f"Milestone #{milestone.number}: {milestone.title}")
|
|
281
|
+
typer.echo(f" {len(closed_issues)} closed, {len(open_issues)} open\n")
|
|
282
|
+
|
|
283
|
+
if open_issues:
|
|
284
|
+
typer.echo("Open issues (will be transitioned to status:done):")
|
|
285
|
+
for issue in open_issues:
|
|
286
|
+
label_names = [lbl.name for lbl in issue.labels]
|
|
287
|
+
typer.echo(f" #{issue.number:4d} {issue.title[:60]:<60} [{', '.join(label_names)}]")
|
|
288
|
+
typer.echo()
|
|
289
|
+
|
|
290
|
+
if dry_run:
|
|
291
|
+
typer.echo("[dry-run] No changes made.")
|
|
292
|
+
if project_number:
|
|
293
|
+
all_issues = open_issues + closed_issues
|
|
294
|
+
typer.echo("\nProjects V2 status updates (dry-run):")
|
|
295
|
+
_bulk_update_project_status(owner, project_number, all_issues, "Done", dry_run=True)
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
succeeded = skipped = failed = 0
|
|
299
|
+
if open_issues:
|
|
300
|
+
done_label = _ensure_label(repository, "status:done", "0E8A16", "Work complete, milestone closing")
|
|
301
|
+
succeeded, skipped, failed = _transition_to_done(open_issues, done_label)
|
|
302
|
+
|
|
303
|
+
# Close the milestone
|
|
304
|
+
milestone.edit(title=milestone.title, state="closed")
|
|
305
|
+
typer.echo(f"\nMilestone #{milestone.number} '{milestone.title}' closed.")
|
|
306
|
+
if open_issues:
|
|
307
|
+
typer.echo(f" {succeeded} transitioned to status:done, {skipped} already done, {failed} failed.")
|
|
308
|
+
typer.echo(f" {len(closed_issues)}/{total} issues were closed before milestone close.")
|
|
309
|
+
|
|
310
|
+
# Update Projects V2 Status for all issues in milestone
|
|
311
|
+
v2_succeeded = v2_failed = 0
|
|
312
|
+
if project_number:
|
|
313
|
+
all_issues = open_issues + closed_issues
|
|
314
|
+
typer.echo("\nUpdating Projects V2 Status → Done:")
|
|
315
|
+
v2_succeeded, v2_failed = _bulk_update_project_status(owner, project_number, all_issues, "Done")
|
|
316
|
+
typer.echo(f" Projects V2: {v2_succeeded} updated, {v2_failed} failed.")
|
|
317
|
+
|
|
318
|
+
if failed:
|
|
319
|
+
raise typer.Exit(1)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _transition_to_done(open_issues: list[Issue], done_label: Label) -> tuple[int, int, int]:
|
|
323
|
+
"""Apply status:done label to each open issue.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Tuple of (succeeded, skipped, failed) counts.
|
|
327
|
+
"""
|
|
328
|
+
status_labels_to_remove = {"status:in-progress", "status:needs-grooming"}
|
|
329
|
+
succeeded = failed = skipped = 0
|
|
330
|
+
typer.echo()
|
|
331
|
+
for issue in open_issues:
|
|
332
|
+
label_names = [lbl.name for lbl in issue.labels]
|
|
333
|
+
if "status:done" in label_names:
|
|
334
|
+
typer.echo(f" #{issue.number} already has status:done — skipped")
|
|
335
|
+
skipped += 1
|
|
336
|
+
continue
|
|
337
|
+
try:
|
|
338
|
+
new_label_names = [lbl.name for lbl in issue.labels if lbl.name not in status_labels_to_remove]
|
|
339
|
+
new_label_names.append(done_label.name)
|
|
340
|
+
issue.edit(labels=new_label_names)
|
|
341
|
+
typer.echo(f" #{issue.number} {issue.title[:60]} → status:done")
|
|
342
|
+
succeeded += 1
|
|
343
|
+
except GithubException as exc:
|
|
344
|
+
typer.echo(f" #{issue.number} FAILED: {exc}", err=True)
|
|
345
|
+
failed += 1
|
|
346
|
+
return succeeded, skipped, failed
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _get_open_milestone(repository: Repository, number: int) -> Milestone:
|
|
350
|
+
"""Fetch a milestone and verify it is open.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
repository: GitHub repository object.
|
|
354
|
+
number: Milestone number.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
The Milestone object.
|
|
358
|
+
|
|
359
|
+
Raises:
|
|
360
|
+
typer.Exit: If the milestone is not found or already closed.
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
363
|
+
milestone = repository.get_milestone(number)
|
|
364
|
+
except GithubException as exc:
|
|
365
|
+
typer.echo(f"ERROR: Milestone #{number} not found.", err=True)
|
|
366
|
+
open_milestones = list(repository.get_milestones(state="open"))
|
|
367
|
+
if open_milestones:
|
|
368
|
+
typer.echo("Open milestones:", err=True)
|
|
369
|
+
for m in open_milestones:
|
|
370
|
+
typer.echo(f" #{m.number} {m.title}", err=True)
|
|
371
|
+
raise typer.Exit(1) from exc
|
|
372
|
+
|
|
373
|
+
if milestone.state == "closed":
|
|
374
|
+
typer.echo(f"ERROR: Milestone #{number} '{milestone.title}' is already closed.", err=True)
|
|
375
|
+
raise typer.Exit(1)
|
|
376
|
+
|
|
377
|
+
return milestone
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _find_gh_cli() -> str:
|
|
381
|
+
"""Locate the gh CLI binary.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Path to gh binary.
|
|
385
|
+
|
|
386
|
+
Raises:
|
|
387
|
+
typer.Exit: If gh is not found on PATH.
|
|
388
|
+
"""
|
|
389
|
+
gh_path = shutil.which("gh")
|
|
390
|
+
if not gh_path:
|
|
391
|
+
typer.echo("ERROR: gh CLI not found. Install gh via your system package manager (brew/winget/apt).", err=True)
|
|
392
|
+
raise typer.Exit(1)
|
|
393
|
+
return gh_path
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _gh_graphql(query: str) -> dict:
|
|
397
|
+
"""Execute a GraphQL query via the gh CLI.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
query: GraphQL query string.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Parsed JSON response from the GitHub GraphQL API.
|
|
404
|
+
|
|
405
|
+
Raises:
|
|
406
|
+
typer.Exit: If the gh CLI call fails.
|
|
407
|
+
"""
|
|
408
|
+
gh_path = _find_gh_cli()
|
|
409
|
+
try:
|
|
410
|
+
result = subprocess.run(
|
|
411
|
+
[gh_path, "api", "graphql", "-f", f"query={query}"], capture_output=True, text=True, check=True
|
|
412
|
+
)
|
|
413
|
+
except subprocess.CalledProcessError as exc:
|
|
414
|
+
typer.echo(f"ERROR: GraphQL query failed: {exc.stderr or exc}", err=True)
|
|
415
|
+
raise typer.Exit(1) from exc
|
|
416
|
+
return json.loads(result.stdout)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _discover_project_fields(owner: str, project_number: int) -> tuple[str, str, dict[str, str]]:
|
|
420
|
+
"""Discover project ID, Status field ID, and option IDs via GraphQL.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
owner: GitHub user or organization login.
|
|
424
|
+
project_number: The project number (visible in the URL).
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Tuple of (project_id, status_field_id, option_map) where
|
|
428
|
+
option_map maps status name to option ID.
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
typer.Exit: If project or Status field not found.
|
|
432
|
+
"""
|
|
433
|
+
query = (
|
|
434
|
+
'{ user(login: "' + owner + '") { projectV2(number: ' + str(project_number) + ") { id fields(first: 30) {"
|
|
435
|
+
" nodes { ... on ProjectV2SingleSelectField {"
|
|
436
|
+
" id name options { id name } } } } } } }"
|
|
437
|
+
)
|
|
438
|
+
resp = _gh_graphql(query)
|
|
439
|
+
|
|
440
|
+
project = resp.get("data", {}).get("user", {}).get("projectV2")
|
|
441
|
+
if not project:
|
|
442
|
+
typer.echo(f"ERROR: Project #{project_number} not found for user '{owner}'.", err=True)
|
|
443
|
+
raise typer.Exit(1)
|
|
444
|
+
|
|
445
|
+
project_id = project["id"]
|
|
446
|
+
for field in project["fields"]["nodes"]:
|
|
447
|
+
if field.get("name") == "Status":
|
|
448
|
+
field_id = field["id"]
|
|
449
|
+
option_map = {opt["name"]: opt["id"] for opt in field["options"]}
|
|
450
|
+
return project_id, field_id, option_map
|
|
451
|
+
|
|
452
|
+
typer.echo(f"ERROR: No 'Status' field found in project #{project_number}.", err=True)
|
|
453
|
+
raise typer.Exit(1)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _find_project_item_id(project_id: str, issue_node_id: str) -> str:
|
|
457
|
+
"""Find or create the project item for a given issue.
|
|
458
|
+
|
|
459
|
+
Adds the issue to the project if not already present. The
|
|
460
|
+
``addProjectV2ItemById`` mutation is idempotent — it returns the
|
|
461
|
+
existing item if the issue is already on the board.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
project_id: GraphQL node ID of the project.
|
|
465
|
+
issue_node_id: GraphQL node ID of the issue.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
The project item ID.
|
|
469
|
+
|
|
470
|
+
Raises:
|
|
471
|
+
typer.Exit: If the mutation fails to return an item ID.
|
|
472
|
+
"""
|
|
473
|
+
query = (
|
|
474
|
+
"mutation { addProjectV2ItemById(input: {"
|
|
475
|
+
f'projectId: "{project_id}", contentId: "{issue_node_id}"'
|
|
476
|
+
"}) { item { id } } }"
|
|
477
|
+
)
|
|
478
|
+
resp = _gh_graphql(query)
|
|
479
|
+
item_id = resp.get("data", {}).get("addProjectV2ItemById", {}).get("item", {}).get("id")
|
|
480
|
+
if not item_id:
|
|
481
|
+
typer.echo("ERROR: Failed to add issue to project.", err=True)
|
|
482
|
+
raise typer.Exit(1)
|
|
483
|
+
return item_id
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _set_project_field(project_id: str, item_id: str, field_id: str, option_id: str) -> None:
|
|
487
|
+
"""Set a single-select field value on a project item.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
project_id: GraphQL node ID of the project.
|
|
491
|
+
item_id: GraphQL node ID of the project item.
|
|
492
|
+
field_id: GraphQL node ID of the field.
|
|
493
|
+
option_id: GraphQL node ID of the option to set.
|
|
494
|
+
"""
|
|
495
|
+
query = (
|
|
496
|
+
"mutation { updateProjectV2ItemFieldValue(input: {"
|
|
497
|
+
f'projectId: "{project_id}", itemId: "{item_id}", '
|
|
498
|
+
f'fieldId: "{field_id}", '
|
|
499
|
+
f'value: {{singleSelectOptionId: "{option_id}"}}'
|
|
500
|
+
"}) { projectV2Item { id } } }"
|
|
501
|
+
)
|
|
502
|
+
_gh_graphql(query)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _update_project_status(
|
|
506
|
+
owner: str, project_number: int, issue_node_id: str, status: str, *, dry_run: bool = False, issue_label: str = ""
|
|
507
|
+
) -> bool:
|
|
508
|
+
"""Update the Projects V2 Status field for a single issue.
|
|
509
|
+
|
|
510
|
+
This is the shared implementation used by both the ``project update-status``
|
|
511
|
+
command and the milestone start/close integration.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
owner: GitHub user or organization login.
|
|
515
|
+
project_number: The project number.
|
|
516
|
+
issue_node_id: GraphQL node ID of the issue.
|
|
517
|
+
status: Target status value (must be in VALID_STATUSES).
|
|
518
|
+
dry_run: If True, report what would happen without mutating.
|
|
519
|
+
issue_label: Optional label for dry-run output (e.g. "#42 title").
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
True if the update succeeded (or would succeed in dry-run).
|
|
523
|
+
"""
|
|
524
|
+
if status not in VALID_STATUSES:
|
|
525
|
+
typer.echo(f"ERROR: Invalid status '{status}'. Valid values: {', '.join(VALID_STATUSES)}", err=True)
|
|
526
|
+
return False
|
|
527
|
+
|
|
528
|
+
project_id, field_id, option_map = _discover_project_fields(owner, project_number)
|
|
529
|
+
|
|
530
|
+
option_id = option_map.get(status)
|
|
531
|
+
if not option_id:
|
|
532
|
+
typer.echo(
|
|
533
|
+
f"ERROR: Status '{status}' not found in project options. Available: {', '.join(option_map)}", err=True
|
|
534
|
+
)
|
|
535
|
+
return False
|
|
536
|
+
|
|
537
|
+
if dry_run:
|
|
538
|
+
label = issue_label or issue_node_id
|
|
539
|
+
typer.echo(f" [dry-run] Would set {label} → Status: {status}")
|
|
540
|
+
return True
|
|
541
|
+
|
|
542
|
+
item_id = _find_project_item_id(project_id, issue_node_id)
|
|
543
|
+
_set_project_field(project_id, item_id, field_id, option_id)
|
|
544
|
+
|
|
545
|
+
label = issue_label or issue_node_id
|
|
546
|
+
typer.echo(f" {label} → Status: {status}")
|
|
547
|
+
return True
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _bulk_update_project_status(
|
|
551
|
+
owner: str, project_number: int, issues: list[Issue], status: str, *, dry_run: bool = False
|
|
552
|
+
) -> tuple[int, int]:
|
|
553
|
+
"""Update Projects V2 Status for multiple issues.
|
|
554
|
+
|
|
555
|
+
Discovers project fields once and reuses for all issues.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
owner: GitHub user or organization login.
|
|
559
|
+
project_number: The project number.
|
|
560
|
+
issues: List of PyGithub Issue objects.
|
|
561
|
+
status: Target status value.
|
|
562
|
+
dry_run: If True, report without mutating.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
Tuple of (succeeded, failed) counts.
|
|
566
|
+
"""
|
|
567
|
+
if status not in VALID_STATUSES:
|
|
568
|
+
typer.echo(f"ERROR: Invalid status '{status}'. Valid values: {', '.join(VALID_STATUSES)}", err=True)
|
|
569
|
+
return 0, len(issues)
|
|
570
|
+
|
|
571
|
+
project_id, field_id, option_map = _discover_project_fields(owner, project_number)
|
|
572
|
+
option_id = option_map.get(status)
|
|
573
|
+
if not option_id:
|
|
574
|
+
typer.echo(
|
|
575
|
+
f"ERROR: Status '{status}' not found in project options. Available: {', '.join(option_map)}", err=True
|
|
576
|
+
)
|
|
577
|
+
return 0, len(issues)
|
|
578
|
+
|
|
579
|
+
succeeded = failed = 0
|
|
580
|
+
for issue in issues:
|
|
581
|
+
label = f"#{issue.number} {issue.title[:50]}"
|
|
582
|
+
if dry_run:
|
|
583
|
+
typer.echo(f" [dry-run] Would set {label} → Status: {status}")
|
|
584
|
+
succeeded += 1
|
|
585
|
+
continue
|
|
586
|
+
try:
|
|
587
|
+
item_id = _find_project_item_id(project_id, issue.node_id)
|
|
588
|
+
_set_project_field(project_id, item_id, field_id, option_id)
|
|
589
|
+
typer.echo(f" {label} → Status: {status}")
|
|
590
|
+
succeeded += 1
|
|
591
|
+
except (typer.Exit, subprocess.CalledProcessError) as exc:
|
|
592
|
+
typer.echo(f" {label} FAILED: {exc}", err=True)
|
|
593
|
+
failed += 1
|
|
594
|
+
return succeeded, failed
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _ensure_label(repository: Repository, name: str, color: str, description: str) -> Label:
|
|
598
|
+
"""Return the label, creating it if it does not exist.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
repository: GitHub repository object.
|
|
602
|
+
name: Label name to find or create.
|
|
603
|
+
color: Hex color code for the label (without ``#`` prefix).
|
|
604
|
+
description: Human-readable label description.
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
The existing or newly created Label object.
|
|
608
|
+
"""
|
|
609
|
+
try:
|
|
610
|
+
return repository.get_label(name)
|
|
611
|
+
except GithubException:
|
|
612
|
+
label = repository.create_label(name=name, color=color, description=description)
|
|
613
|
+
typer.echo(f"\n Created label: {name}")
|
|
614
|
+
return label
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def _transition_issues(open_issues: list[Issue], in_progress_label: Label) -> tuple[int, int, int]:
|
|
618
|
+
"""Apply label transition from ``status:needs-grooming`` to ``status:in-progress``.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
open_issues: List of open Issue objects to transition.
|
|
622
|
+
in_progress_label: The ``status:in-progress`` Label to apply.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
Tuple of (succeeded, skipped, failed) counts.
|
|
626
|
+
"""
|
|
627
|
+
succeeded = failed = skipped = 0
|
|
628
|
+
typer.echo()
|
|
629
|
+
for issue in open_issues:
|
|
630
|
+
label_names = [lbl.name for lbl in issue.labels]
|
|
631
|
+
if "status:in-progress" in label_names:
|
|
632
|
+
typer.echo(f" #{issue.number} already has status:in-progress — skipped")
|
|
633
|
+
skipped += 1
|
|
634
|
+
continue
|
|
635
|
+
try:
|
|
636
|
+
new_label_names = [lbl.name for lbl in issue.labels if lbl.name != "status:needs-grooming"]
|
|
637
|
+
new_label_names.append(in_progress_label.name)
|
|
638
|
+
issue.edit(labels=new_label_names)
|
|
639
|
+
typer.echo(f" #{issue.number} {issue.title[:60]} → status:in-progress")
|
|
640
|
+
succeeded += 1
|
|
641
|
+
except GithubException as exc:
|
|
642
|
+
typer.echo(f" #{issue.number} FAILED: {exc}", err=True)
|
|
643
|
+
failed += 1
|
|
644
|
+
return succeeded, skipped, failed
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@project_app.command("update-status")
|
|
648
|
+
def project_update_status(
|
|
649
|
+
issue_number: Annotated[int, typer.Option("--issue-number", "-i", help="GitHub issue number")],
|
|
650
|
+
status: Annotated[str, typer.Option("--status", "-s", help="Target status value")],
|
|
651
|
+
project_number: Annotated[int, typer.Option("--project-number", "-p", help="Project number")] = 1,
|
|
652
|
+
owner: Annotated[str, typer.Option("--owner")] = "Jamie-BitFlight",
|
|
653
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
654
|
+
dry_run: Annotated[bool, typer.Option("--dry-run")] = False,
|
|
655
|
+
) -> None:
|
|
656
|
+
"""Set the Projects V2 Status field for a GitHub issue.
|
|
657
|
+
|
|
658
|
+
Discovers field IDs dynamically via GraphQL, then updates the Status
|
|
659
|
+
single-select field. The issue is added to the project if not already present.
|
|
660
|
+
|
|
661
|
+
Valid statuses: Backlog, Grooming, In Progress, In Review, Done
|
|
662
|
+
"""
|
|
663
|
+
if status not in VALID_STATUSES:
|
|
664
|
+
typer.echo(f"ERROR: Invalid status '{status}'. Valid values: {', '.join(VALID_STATUSES)}", err=True)
|
|
665
|
+
raise typer.Exit(1)
|
|
666
|
+
|
|
667
|
+
gh = get_github()
|
|
668
|
+
repository = get_repo(gh, repo)
|
|
669
|
+
|
|
670
|
+
try:
|
|
671
|
+
issue = repository.get_issue(issue_number)
|
|
672
|
+
except GithubException as exc:
|
|
673
|
+
typer.echo(f"ERROR: Issue #{issue_number} not found: {exc}", err=True)
|
|
674
|
+
raise typer.Exit(1) from exc
|
|
675
|
+
|
|
676
|
+
typer.echo(f"Issue #{issue.number}: {issue.title}")
|
|
677
|
+
typer.echo(f" Project: {owner}/projects/{project_number}")
|
|
678
|
+
typer.echo(f" Target status: {status}")
|
|
679
|
+
|
|
680
|
+
ok = _update_project_status(
|
|
681
|
+
owner=owner,
|
|
682
|
+
project_number=project_number,
|
|
683
|
+
issue_node_id=issue.raw_data["node_id"],
|
|
684
|
+
status=status,
|
|
685
|
+
dry_run=dry_run,
|
|
686
|
+
issue_label=f"#{issue.number} {issue.title[:50]}",
|
|
687
|
+
)
|
|
688
|
+
if not ok:
|
|
689
|
+
raise typer.Exit(1)
|
|
690
|
+
if not dry_run:
|
|
691
|
+
typer.echo(" Done.")
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
@issue_app.command("create")
|
|
695
|
+
def issue_create(
|
|
696
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
697
|
+
title: Annotated[str, typer.Option("--title")] = "",
|
|
698
|
+
body: Annotated[str, typer.Option("--body")] = "",
|
|
699
|
+
priority_label: Annotated[str, typer.Option("--priority-label")] = "",
|
|
700
|
+
type_label: Annotated[str, typer.Option("--type-label")] = "",
|
|
701
|
+
milestone_number: Annotated[int, typer.Option("--milestone")] = 0,
|
|
702
|
+
) -> None:
|
|
703
|
+
"""Create a GitHub issue with priority/type labels and optional milestone."""
|
|
704
|
+
if not title:
|
|
705
|
+
typer.echo("ERROR: --title is required", err=True)
|
|
706
|
+
raise typer.Exit(1)
|
|
707
|
+
|
|
708
|
+
gh = get_github()
|
|
709
|
+
repository = get_repo(gh, repo)
|
|
710
|
+
|
|
711
|
+
label_names = ["status:needs-grooming"]
|
|
712
|
+
if priority_label:
|
|
713
|
+
label_names.append(priority_label)
|
|
714
|
+
if type_label:
|
|
715
|
+
label_names.append(type_label)
|
|
716
|
+
|
|
717
|
+
label_objects = []
|
|
718
|
+
for lbl_name in label_names:
|
|
719
|
+
try:
|
|
720
|
+
label_objects.append(repository.get_label(lbl_name))
|
|
721
|
+
except GithubException:
|
|
722
|
+
typer.echo(f" WARNING: label '{lbl_name}' not found — skipping", err=True)
|
|
723
|
+
|
|
724
|
+
milestone_obj = None
|
|
725
|
+
if milestone_number:
|
|
726
|
+
try:
|
|
727
|
+
milestone_obj = repository.get_milestone(milestone_number)
|
|
728
|
+
except GithubException:
|
|
729
|
+
typer.echo(f" WARNING: milestone #{milestone_number} not found — skipping", err=True)
|
|
730
|
+
|
|
731
|
+
if milestone_obj is not None:
|
|
732
|
+
issue = repository.create_issue(title=title, body=body or "", labels=label_objects, milestone=milestone_obj)
|
|
733
|
+
else:
|
|
734
|
+
issue = repository.create_issue(title=title, body=body or "", labels=label_objects)
|
|
735
|
+
typer.echo(f"Created issue #{issue.number}: {issue.title}")
|
|
736
|
+
typer.echo(f" URL: {issue.html_url}")
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@issue_app.command("list")
|
|
740
|
+
def issue_list(
|
|
741
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
742
|
+
priority: Annotated[str, typer.Option("--priority")] = "",
|
|
743
|
+
state: Annotated[str, typer.Option("--state")] = "open",
|
|
744
|
+
) -> None:
|
|
745
|
+
"""List issues, optionally filtered by priority."""
|
|
746
|
+
gh = get_github()
|
|
747
|
+
repository = get_repo(gh, repo)
|
|
748
|
+
|
|
749
|
+
kwargs: dict = {"state": state}
|
|
750
|
+
if priority:
|
|
751
|
+
label_name = PRIORITY_LABEL_MAP.get(priority.upper(), f"priority:{priority.lower()}")
|
|
752
|
+
try:
|
|
753
|
+
kwargs["labels"] = [repository.get_label(label_name)]
|
|
754
|
+
except GithubException:
|
|
755
|
+
typer.echo(f"Label '{label_name}' not found", err=True)
|
|
756
|
+
|
|
757
|
+
issues = list(repository.get_issues(**kwargs))
|
|
758
|
+
if not issues:
|
|
759
|
+
typer.echo("No issues found.")
|
|
760
|
+
return
|
|
761
|
+
for issue in issues:
|
|
762
|
+
milestone_title = issue.milestone.title if issue.milestone else "—"
|
|
763
|
+
label_names = ", ".join(lbl.name for lbl in issue.labels)
|
|
764
|
+
typer.echo(f" #{issue.number:4d} {issue.title[:55]:<55} [{label_names}] {milestone_title}")
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
@app.command()
|
|
768
|
+
def setup(
|
|
769
|
+
repo: Annotated[str, typer.Option("--repo", "-R")] = DEFAULT_REPO,
|
|
770
|
+
project_title: Annotated[str, typer.Option("--project-title")] = "claude_skills Backlog",
|
|
771
|
+
) -> None:
|
|
772
|
+
"""Full project setup: create label taxonomy and report next steps."""
|
|
773
|
+
typer.echo(f"Setting up GitHub project for {repo}...")
|
|
774
|
+
typer.echo("\n1. Creating label taxonomy...")
|
|
775
|
+
|
|
776
|
+
gh = get_github()
|
|
777
|
+
repository = get_repo(gh, repo)
|
|
778
|
+
|
|
779
|
+
existing = {lbl.name: lbl for lbl in repository.get_labels()}
|
|
780
|
+
created = skipped = 0
|
|
781
|
+
for spec in LABELS:
|
|
782
|
+
if spec["name"] not in existing:
|
|
783
|
+
repository.create_label(name=spec["name"], color=spec["color"], description=spec["description"])
|
|
784
|
+
typer.echo(f" created: {spec['name']}")
|
|
785
|
+
created += 1
|
|
786
|
+
else:
|
|
787
|
+
skipped += 1
|
|
788
|
+
|
|
789
|
+
typer.echo(f" Labels: {created} created, {skipped} already existed")
|
|
790
|
+
|
|
791
|
+
typer.echo(f"\n2. Project '{project_title}' — create via gh CLI:")
|
|
792
|
+
typer.echo(f' gh project create --owner {repo.split("/")[0]} --title "{project_title}"')
|
|
793
|
+
typer.echo("\nNote: GitHub Projects V2 requires project OAuth scope.")
|
|
794
|
+
typer.echo(" Use gh project commands or the GraphQL API for project creation.")
|
|
795
|
+
typer.echo(" See .claude/skills/gh/references/projects-v2.md for field setup commands.")
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
if __name__ == "__main__":
|
|
799
|
+
app()
|