@generacy-ai/workflow-engine 0.0.0-preview-20260304013206
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/LICENSE +191 -0
- package/README.md +265 -0
- package/dist/actions/base-action.d.ts +63 -0
- package/dist/actions/base-action.d.ts.map +1 -0
- package/dist/actions/base-action.js +130 -0
- package/dist/actions/base-action.js.map +1 -0
- package/dist/actions/builtin/agent-invoke.d.ts +16 -0
- package/dist/actions/builtin/agent-invoke.d.ts.map +1 -0
- package/dist/actions/builtin/agent-invoke.js +108 -0
- package/dist/actions/builtin/agent-invoke.js.map +1 -0
- package/dist/actions/builtin/humancy-api-handler.d.ts +191 -0
- package/dist/actions/builtin/humancy-api-handler.d.ts.map +1 -0
- package/dist/actions/builtin/humancy-api-handler.js +521 -0
- package/dist/actions/builtin/humancy-api-handler.js.map +1 -0
- package/dist/actions/builtin/humancy-review.d.ts +83 -0
- package/dist/actions/builtin/humancy-review.d.ts.map +1 -0
- package/dist/actions/builtin/humancy-review.js +250 -0
- package/dist/actions/builtin/humancy-review.js.map +1 -0
- package/dist/actions/builtin/pr-create.d.ts +16 -0
- package/dist/actions/builtin/pr-create.d.ts.map +1 -0
- package/dist/actions/builtin/pr-create.js +110 -0
- package/dist/actions/builtin/pr-create.js.map +1 -0
- package/dist/actions/builtin/shell.d.ts +16 -0
- package/dist/actions/builtin/shell.d.ts.map +1 -0
- package/dist/actions/builtin/shell.js +80 -0
- package/dist/actions/builtin/shell.js.map +1 -0
- package/dist/actions/builtin/speckit/index.d.ts +47 -0
- package/dist/actions/builtin/speckit/index.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/index.js +347 -0
- package/dist/actions/builtin/speckit/index.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/feature.d.ts +17 -0
- package/dist/actions/builtin/speckit/lib/feature.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/feature.js +430 -0
- package/dist/actions/builtin/speckit/lib/feature.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/fs.d.ts +62 -0
- package/dist/actions/builtin/speckit/lib/fs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/fs.js +181 -0
- package/dist/actions/builtin/speckit/lib/fs.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/paths.d.ts +7 -0
- package/dist/actions/builtin/speckit/lib/paths.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/paths.js +161 -0
- package/dist/actions/builtin/speckit/lib/paths.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/prereqs.d.ts +8 -0
- package/dist/actions/builtin/speckit/lib/prereqs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/prereqs.js +194 -0
- package/dist/actions/builtin/speckit/lib/prereqs.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.d.ts +14 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.js +31 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/templates.d.ts +15 -0
- package/dist/actions/builtin/speckit/lib/templates.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/templates.js +176 -0
- package/dist/actions/builtin/speckit/lib/templates.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.js +22 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/clarify.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/clarify.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/clarify.js +302 -0
- package/dist/actions/builtin/speckit/operations/clarify.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/copy-template.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/copy-template.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/copy-template.js +23 -0
- package/dist/actions/builtin/speckit/operations/copy-template.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/create-feature.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/create-feature.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/create-feature.js +25 -0
- package/dist/actions/builtin/speckit/operations/create-feature.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/get-paths.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/get-paths.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/get-paths.js +17 -0
- package/dist/actions/builtin/speckit/operations/get-paths.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/implement.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/implement.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/implement.js +246 -0
- package/dist/actions/builtin/speckit/operations/implement.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/plan.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/plan.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/plan.js +253 -0
- package/dist/actions/builtin/speckit/operations/plan.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/specify.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/specify.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/specify.js +178 -0
- package/dist/actions/builtin/speckit/operations/specify.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.d.ts +42 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.js +386 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/tasks.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks.js +234 -0
- package/dist/actions/builtin/speckit/operations/tasks.js.map +1 -0
- package/dist/actions/builtin/speckit/types.d.ts +268 -0
- package/dist/actions/builtin/speckit/types.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/types.js +11 -0
- package/dist/actions/builtin/speckit/types.js.map +1 -0
- package/dist/actions/builtin/verification-check.d.ts +16 -0
- package/dist/actions/builtin/verification-check.d.ts.map +1 -0
- package/dist/actions/builtin/verification-check.js +99 -0
- package/dist/actions/builtin/verification-check.js.map +1 -0
- package/dist/actions/builtin/workspace-prepare.d.ts +16 -0
- package/dist/actions/builtin/workspace-prepare.d.ts.map +1 -0
- package/dist/actions/builtin/workspace-prepare.js +108 -0
- package/dist/actions/builtin/workspace-prepare.js.map +1 -0
- package/dist/actions/cli-utils.d.ts +76 -0
- package/dist/actions/cli-utils.d.ts.map +1 -0
- package/dist/actions/cli-utils.js +278 -0
- package/dist/actions/cli-utils.js.map +1 -0
- package/dist/actions/epic/check-completion.d.ts +14 -0
- package/dist/actions/epic/check-completion.d.ts.map +1 -0
- package/dist/actions/epic/check-completion.js +57 -0
- package/dist/actions/epic/check-completion.js.map +1 -0
- package/dist/actions/epic/close.d.ts +18 -0
- package/dist/actions/epic/close.d.ts.map +1 -0
- package/dist/actions/epic/close.js +76 -0
- package/dist/actions/epic/close.js.map +1 -0
- package/dist/actions/epic/create-pr.d.ts +22 -0
- package/dist/actions/epic/create-pr.d.ts.map +1 -0
- package/dist/actions/epic/create-pr.js +140 -0
- package/dist/actions/epic/create-pr.js.map +1 -0
- package/dist/actions/epic/dispatch-children.d.ts +16 -0
- package/dist/actions/epic/dispatch-children.d.ts.map +1 -0
- package/dist/actions/epic/dispatch-children.js +95 -0
- package/dist/actions/epic/dispatch-children.js.map +1 -0
- package/dist/actions/epic/find-children.d.ts +16 -0
- package/dist/actions/epic/find-children.d.ts.map +1 -0
- package/dist/actions/epic/find-children.js +92 -0
- package/dist/actions/epic/find-children.js.map +1 -0
- package/dist/actions/epic/index.d.ts +20 -0
- package/dist/actions/epic/index.d.ts.map +1 -0
- package/dist/actions/epic/index.js +35 -0
- package/dist/actions/epic/index.js.map +1 -0
- package/dist/actions/epic/post-tasks-summary.d.ts +18 -0
- package/dist/actions/epic/post-tasks-summary.d.ts.map +1 -0
- package/dist/actions/epic/post-tasks-summary.js +123 -0
- package/dist/actions/epic/post-tasks-summary.js.map +1 -0
- package/dist/actions/epic/update-status.d.ts +22 -0
- package/dist/actions/epic/update-status.d.ts.map +1 -0
- package/dist/actions/epic/update-status.js +105 -0
- package/dist/actions/epic/update-status.js.map +1 -0
- package/dist/actions/github/add-comment.d.ts +14 -0
- package/dist/actions/github/add-comment.d.ts.map +1 -0
- package/dist/actions/github/add-comment.js +44 -0
- package/dist/actions/github/add-comment.js.map +1 -0
- package/dist/actions/github/client/gh-cli.d.ts +68 -0
- package/dist/actions/github/client/gh-cli.d.ts.map +1 -0
- package/dist/actions/github/client/gh-cli.js +781 -0
- package/dist/actions/github/client/gh-cli.js.map +1 -0
- package/dist/actions/github/client/index.d.ts +12 -0
- package/dist/actions/github/client/index.d.ts.map +1 -0
- package/dist/actions/github/client/index.js +10 -0
- package/dist/actions/github/client/index.js.map +1 -0
- package/dist/actions/github/client/interface.d.ts +270 -0
- package/dist/actions/github/client/interface.d.ts.map +1 -0
- package/dist/actions/github/client/interface.js +2 -0
- package/dist/actions/github/client/interface.js.map +1 -0
- package/dist/actions/github/commit-and-push.d.ts +19 -0
- package/dist/actions/github/commit-and-push.d.ts.map +1 -0
- package/dist/actions/github/commit-and-push.js +86 -0
- package/dist/actions/github/commit-and-push.js.map +1 -0
- package/dist/actions/github/create-draft-pr.d.ts +18 -0
- package/dist/actions/github/create-draft-pr.d.ts.map +1 -0
- package/dist/actions/github/create-draft-pr.js +76 -0
- package/dist/actions/github/create-draft-pr.js.map +1 -0
- package/dist/actions/github/get-context.d.ts +23 -0
- package/dist/actions/github/get-context.d.ts.map +1 -0
- package/dist/actions/github/get-context.js +109 -0
- package/dist/actions/github/get-context.js.map +1 -0
- package/dist/actions/github/index.d.ts +32 -0
- package/dist/actions/github/index.d.ts.map +1 -0
- package/dist/actions/github/index.js +67 -0
- package/dist/actions/github/index.js.map +1 -0
- package/dist/actions/github/label-definitions.d.ts +19 -0
- package/dist/actions/github/label-definitions.d.ts.map +1 -0
- package/dist/actions/github/label-definitions.js +71 -0
- package/dist/actions/github/label-definitions.js.map +1 -0
- package/dist/actions/github/mark-pr-ready.d.ts +14 -0
- package/dist/actions/github/mark-pr-ready.d.ts.map +1 -0
- package/dist/actions/github/mark-pr-ready.js +43 -0
- package/dist/actions/github/mark-pr-ready.js.map +1 -0
- package/dist/actions/github/merge-from-base.d.ts +15 -0
- package/dist/actions/github/merge-from-base.d.ts.map +1 -0
- package/dist/actions/github/merge-from-base.js +120 -0
- package/dist/actions/github/merge-from-base.js.map +1 -0
- package/dist/actions/github/preflight.d.ts +36 -0
- package/dist/actions/github/preflight.d.ts.map +1 -0
- package/dist/actions/github/preflight.js +292 -0
- package/dist/actions/github/preflight.js.map +1 -0
- package/dist/actions/github/read-pr-feedback.d.ts +14 -0
- package/dist/actions/github/read-pr-feedback.d.ts.map +1 -0
- package/dist/actions/github/read-pr-feedback.js +53 -0
- package/dist/actions/github/read-pr-feedback.js.map +1 -0
- package/dist/actions/github/respond-pr-feedback.d.ts +14 -0
- package/dist/actions/github/respond-pr-feedback.d.ts.map +1 -0
- package/dist/actions/github/respond-pr-feedback.js +58 -0
- package/dist/actions/github/respond-pr-feedback.js.map +1 -0
- package/dist/actions/github/review-changes.d.ts +19 -0
- package/dist/actions/github/review-changes.d.ts.map +1 -0
- package/dist/actions/github/review-changes.js +95 -0
- package/dist/actions/github/review-changes.js.map +1 -0
- package/dist/actions/github/sync-labels.d.ts +14 -0
- package/dist/actions/github/sync-labels.d.ts.map +1 -0
- package/dist/actions/github/sync-labels.js +81 -0
- package/dist/actions/github/sync-labels.js.map +1 -0
- package/dist/actions/github/update-pr.d.ts +14 -0
- package/dist/actions/github/update-pr.d.ts.map +1 -0
- package/dist/actions/github/update-pr.js +63 -0
- package/dist/actions/github/update-pr.js.map +1 -0
- package/dist/actions/index.d.ts +109 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +233 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/workflow/check-gate.d.ts +15 -0
- package/dist/actions/workflow/check-gate.d.ts.map +1 -0
- package/dist/actions/workflow/check-gate.js +75 -0
- package/dist/actions/workflow/check-gate.js.map +1 -0
- package/dist/actions/workflow/index.d.ts +21 -0
- package/dist/actions/workflow/index.d.ts.map +1 -0
- package/dist/actions/workflow/index.js +31 -0
- package/dist/actions/workflow/index.js.map +1 -0
- package/dist/actions/workflow/update-phase.d.ts +15 -0
- package/dist/actions/workflow/update-phase.d.ts.map +1 -0
- package/dist/actions/workflow/update-phase.js +143 -0
- package/dist/actions/workflow/update-phase.js.map +1 -0
- package/dist/actions/workflow/update-stage.d.ts +19 -0
- package/dist/actions/workflow/update-stage.d.ts.map +1 -0
- package/dist/actions/workflow/update-stage.js +137 -0
- package/dist/actions/workflow/update-stage.js.map +1 -0
- package/dist/errors/base-workflow-not-found.d.ts +10 -0
- package/dist/errors/base-workflow-not-found.d.ts.map +1 -0
- package/dist/errors/base-workflow-not-found.js +18 -0
- package/dist/errors/base-workflow-not-found.js.map +1 -0
- package/dist/errors/circular-extends.d.ts +9 -0
- package/dist/errors/circular-extends.d.ts.map +1 -0
- package/dist/errors/circular-extends.js +13 -0
- package/dist/errors/circular-extends.js.map +1 -0
- package/dist/errors/correlation-timeout.d.ts +9 -0
- package/dist/errors/correlation-timeout.d.ts.map +1 -0
- package/dist/errors/correlation-timeout.js +13 -0
- package/dist/errors/correlation-timeout.js.map +1 -0
- package/dist/errors/workflow-override.d.ts +9 -0
- package/dist/errors/workflow-override.d.ts.map +1 -0
- package/dist/errors/workflow-override.js +12 -0
- package/dist/errors/workflow-override.js.map +1 -0
- package/dist/executor/events.d.ts +55 -0
- package/dist/executor/events.d.ts.map +1 -0
- package/dist/executor/events.js +72 -0
- package/dist/executor/events.js.map +1 -0
- package/dist/executor/gate-handler.d.ts +55 -0
- package/dist/executor/gate-handler.d.ts.map +1 -0
- package/dist/executor/gate-handler.js +139 -0
- package/dist/executor/gate-handler.js.map +1 -0
- package/dist/executor/index.d.ts +140 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +681 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolation/context.d.ts +117 -0
- package/dist/interpolation/context.d.ts.map +1 -0
- package/dist/interpolation/context.js +256 -0
- package/dist/interpolation/context.js.map +1 -0
- package/dist/interpolation/index.d.ts +76 -0
- package/dist/interpolation/index.d.ts.map +1 -0
- package/dist/interpolation/index.js +264 -0
- package/dist/interpolation/index.js.map +1 -0
- package/dist/loader/extends.d.ts +48 -0
- package/dist/loader/extends.d.ts.map +1 -0
- package/dist/loader/extends.js +140 -0
- package/dist/loader/extends.js.map +1 -0
- package/dist/loader/index.d.ts +50 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +190 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/loader/schema.d.ts +506 -0
- package/dist/loader/schema.d.ts.map +1 -0
- package/dist/loader/schema.js +63 -0
- package/dist/loader/schema.js.map +1 -0
- package/dist/loader/validator.d.ts +33 -0
- package/dist/loader/validator.d.ts.map +1 -0
- package/dist/loader/validator.js +135 -0
- package/dist/loader/validator.js.map +1 -0
- package/dist/registry/index.d.ts +42 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +77 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/retry/index.d.ts +37 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/retry/index.js +225 -0
- package/dist/retry/index.js.map +1 -0
- package/dist/retry/strategies.d.ts +74 -0
- package/dist/retry/strategies.d.ts.map +1 -0
- package/dist/retry/strategies.js +136 -0
- package/dist/retry/strategies.js.map +1 -0
- package/dist/store/filesystem-store.d.ts +52 -0
- package/dist/store/filesystem-store.d.ts.map +1 -0
- package/dist/store/filesystem-store.js +230 -0
- package/dist/store/filesystem-store.js.map +1 -0
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +6 -0
- package/dist/store/index.js.map +1 -0
- package/dist/types/action.d.ts +358 -0
- package/dist/types/action.d.ts.map +1 -0
- package/dist/types/action.js +96 -0
- package/dist/types/action.js.map +1 -0
- package/dist/types/events.d.ts +25 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/execution.d.ts +145 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +2 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/gate.d.ts +98 -0
- package/dist/types/gate.d.ts.map +1 -0
- package/dist/types/gate.js +41 -0
- package/dist/types/gate.js.map +1 -0
- package/dist/types/github.d.ts +706 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +6 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/logger.d.ts +60 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +66 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/types/retry.d.ts +36 -0
- package/dist/types/retry.d.ts.map +1 -0
- package/dist/types/retry.js +6 -0
- package/dist/types/retry.js.map +1 -0
- package/dist/types/store.d.ts +88 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/store.js +6 -0
- package/dist/types/store.js.map +1 -0
- package/dist/types/workflow.d.ts +105 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +6 -0
- package/dist/types/workflow.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import { executeCommand, parseJSONSafe } from '../../cli-utils.js';
|
|
2
|
+
/**
|
|
3
|
+
* GitHubClient implementation using gh CLI and git commands
|
|
4
|
+
*/
|
|
5
|
+
export class GhCliGitHubClient {
|
|
6
|
+
workdir;
|
|
7
|
+
constructor(workdir) {
|
|
8
|
+
this.workdir = workdir ?? process.cwd();
|
|
9
|
+
}
|
|
10
|
+
// ==========================================================================
|
|
11
|
+
// Repository Info
|
|
12
|
+
// ==========================================================================
|
|
13
|
+
async getRepoInfo() {
|
|
14
|
+
const result = await executeCommand('gh', [
|
|
15
|
+
'repo', 'view',
|
|
16
|
+
'--json', 'owner,name,defaultBranchRef',
|
|
17
|
+
], { cwd: this.workdir });
|
|
18
|
+
if (result.exitCode !== 0) {
|
|
19
|
+
throw new Error(`Failed to get repo info: ${result.stderr}`);
|
|
20
|
+
}
|
|
21
|
+
const data = parseJSONSafe(result.stdout);
|
|
22
|
+
if (!data) {
|
|
23
|
+
throw new Error('Failed to parse repo info');
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
owner: data.owner.login,
|
|
27
|
+
repo: data.name,
|
|
28
|
+
default_branch: data.defaultBranchRef.name,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ==========================================================================
|
|
32
|
+
// Issue Operations
|
|
33
|
+
// ==========================================================================
|
|
34
|
+
async getIssue(owner, repo, number) {
|
|
35
|
+
const result = await executeCommand('gh', [
|
|
36
|
+
'issue', 'view', String(number),
|
|
37
|
+
'-R', `${owner}/${repo}`,
|
|
38
|
+
'--json', 'number,title,body,state,labels,assignees,milestone,createdAt,updatedAt',
|
|
39
|
+
], { cwd: this.workdir });
|
|
40
|
+
if (result.exitCode !== 0) {
|
|
41
|
+
throw new Error(`Failed to get issue #${number}: ${result.stderr}`);
|
|
42
|
+
}
|
|
43
|
+
const data = parseJSONSafe(result.stdout);
|
|
44
|
+
if (!data) {
|
|
45
|
+
throw new Error('Failed to parse issue data');
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
number: data['number'],
|
|
49
|
+
title: data['title'],
|
|
50
|
+
body: data['body'] ?? '',
|
|
51
|
+
state: data['state'].toLowerCase(),
|
|
52
|
+
labels: (data['labels'] ?? []).map(l => ({
|
|
53
|
+
name: l.name,
|
|
54
|
+
color: l.color,
|
|
55
|
+
description: l.description,
|
|
56
|
+
})),
|
|
57
|
+
assignees: (data['assignees'] ?? []).map(a => a.login),
|
|
58
|
+
milestone: data['milestone'] ? {
|
|
59
|
+
number: data['milestone'].number,
|
|
60
|
+
title: data['milestone'].title,
|
|
61
|
+
state: 'open',
|
|
62
|
+
} : undefined,
|
|
63
|
+
created_at: data['createdAt'],
|
|
64
|
+
updated_at: data['updatedAt'],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async listIssuesWithLabel(owner, repo, label) {
|
|
68
|
+
const result = await executeCommand('gh', [
|
|
69
|
+
'issue', 'list',
|
|
70
|
+
'-R', `${owner}/${repo}`,
|
|
71
|
+
'--label', label,
|
|
72
|
+
'--state', 'open',
|
|
73
|
+
'--json', 'number,title,body,state,labels,assignees,milestone,createdAt,updatedAt',
|
|
74
|
+
'--limit', '100',
|
|
75
|
+
], { cwd: this.workdir });
|
|
76
|
+
if (result.exitCode !== 0) {
|
|
77
|
+
throw new Error(`Failed to list issues with label "${label}": ${result.stderr}`);
|
|
78
|
+
}
|
|
79
|
+
const data = parseJSONSafe(result.stdout);
|
|
80
|
+
if (!data) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return data.map(item => ({
|
|
84
|
+
number: item['number'],
|
|
85
|
+
title: item['title'],
|
|
86
|
+
body: item['body'] ?? '',
|
|
87
|
+
state: (item['state'] ?? 'open').toLowerCase(),
|
|
88
|
+
labels: (item['labels'] ?? []).map(l => ({
|
|
89
|
+
name: l.name,
|
|
90
|
+
color: l.color,
|
|
91
|
+
description: l.description,
|
|
92
|
+
})),
|
|
93
|
+
assignees: (item['assignees'] ?? []).map(a => a.login),
|
|
94
|
+
milestone: item['milestone'] ? {
|
|
95
|
+
number: item['milestone'].number,
|
|
96
|
+
title: item['milestone'].title,
|
|
97
|
+
state: 'open',
|
|
98
|
+
} : undefined,
|
|
99
|
+
created_at: item['createdAt'],
|
|
100
|
+
updated_at: item['updatedAt'],
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
async updateIssue(owner, repo, number, data) {
|
|
104
|
+
const args = ['issue', 'edit', String(number), '-R', `${owner}/${repo}`];
|
|
105
|
+
if (data.title) {
|
|
106
|
+
args.push('--title', data.title);
|
|
107
|
+
}
|
|
108
|
+
if (data.body !== undefined) {
|
|
109
|
+
args.push('--body', data.body);
|
|
110
|
+
}
|
|
111
|
+
if (data.labels) {
|
|
112
|
+
// Clear existing labels and add new ones
|
|
113
|
+
args.push('--remove-label', '*');
|
|
114
|
+
for (const label of data.labels) {
|
|
115
|
+
args.push('--add-label', label);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (data.assignees) {
|
|
119
|
+
for (const assignee of data.assignees) {
|
|
120
|
+
args.push('--add-assignee', assignee);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
124
|
+
if (result.exitCode !== 0) {
|
|
125
|
+
throw new Error(`Failed to update issue #${number}: ${result.stderr}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async addIssueComment(owner, repo, number, body) {
|
|
129
|
+
const result = await executeCommand('gh', [
|
|
130
|
+
'issue', 'comment', String(number),
|
|
131
|
+
'-R', `${owner}/${repo}`,
|
|
132
|
+
'--body', body,
|
|
133
|
+
], { cwd: this.workdir });
|
|
134
|
+
if (result.exitCode !== 0) {
|
|
135
|
+
throw new Error(`Failed to add comment to issue #${number}: ${result.stderr}`);
|
|
136
|
+
}
|
|
137
|
+
// gh doesn't return the comment details, so we need to fetch the latest comment
|
|
138
|
+
const comments = await this.getIssueComments(owner, repo, number);
|
|
139
|
+
const latest = comments[comments.length - 1];
|
|
140
|
+
if (!latest) {
|
|
141
|
+
throw new Error('Failed to get created comment');
|
|
142
|
+
}
|
|
143
|
+
return latest;
|
|
144
|
+
}
|
|
145
|
+
async getIssueComments(owner, repo, number) {
|
|
146
|
+
const result = await executeCommand('gh', [
|
|
147
|
+
'issue', 'view', String(number),
|
|
148
|
+
'-R', `${owner}/${repo}`,
|
|
149
|
+
'--json', 'comments',
|
|
150
|
+
], { cwd: this.workdir });
|
|
151
|
+
if (result.exitCode !== 0) {
|
|
152
|
+
throw new Error(`Failed to get comments for issue #${number}: ${result.stderr}`);
|
|
153
|
+
}
|
|
154
|
+
const data = parseJSONSafe(result.stdout);
|
|
155
|
+
if (!data) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
return data.comments.map(c => ({
|
|
159
|
+
id: parseInt(c.id.split('/').pop() ?? '0', 10),
|
|
160
|
+
body: c.body,
|
|
161
|
+
author: c.author.login,
|
|
162
|
+
created_at: c.createdAt,
|
|
163
|
+
updated_at: c.updatedAt,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
async updateComment(owner, repo, commentId, body) {
|
|
167
|
+
// gh CLI doesn't have a direct command to edit comments, use API
|
|
168
|
+
const result = await executeCommand('gh', [
|
|
169
|
+
'api',
|
|
170
|
+
'-X', 'PATCH',
|
|
171
|
+
`/repos/${owner}/${repo}/issues/comments/${commentId}`,
|
|
172
|
+
'-f', `body=${body}`,
|
|
173
|
+
], { cwd: this.workdir });
|
|
174
|
+
if (result.exitCode !== 0) {
|
|
175
|
+
throw new Error(`Failed to update comment ${commentId}: ${result.stderr}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ==========================================================================
|
|
179
|
+
// PR Operations
|
|
180
|
+
// ==========================================================================
|
|
181
|
+
async createPullRequest(owner, repo, data) {
|
|
182
|
+
const args = [
|
|
183
|
+
'pr', 'create',
|
|
184
|
+
'-R', `${owner}/${repo}`,
|
|
185
|
+
'--title', data.title,
|
|
186
|
+
'--body', data.body ?? '',
|
|
187
|
+
'--head', data.head,
|
|
188
|
+
'--base', data.base,
|
|
189
|
+
];
|
|
190
|
+
if (data.draft) {
|
|
191
|
+
args.push('--draft');
|
|
192
|
+
}
|
|
193
|
+
// Add JSON output for details
|
|
194
|
+
args.push('--json', 'number,url,state,headRefName,baseRefName,isDraft,title,body,createdAt,updatedAt');
|
|
195
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
196
|
+
if (result.exitCode !== 0) {
|
|
197
|
+
throw new Error(`Failed to create PR: ${result.stderr}`);
|
|
198
|
+
}
|
|
199
|
+
const parsed = parseJSONSafe(result.stdout);
|
|
200
|
+
if (!parsed) {
|
|
201
|
+
// Try to extract URL from output
|
|
202
|
+
const urlMatch = result.stdout.match(/https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
|
|
203
|
+
if (urlMatch) {
|
|
204
|
+
return {
|
|
205
|
+
number: parseInt(urlMatch[1], 10),
|
|
206
|
+
title: data.title,
|
|
207
|
+
body: data.body ?? '',
|
|
208
|
+
state: data.draft ? 'open' : 'open',
|
|
209
|
+
draft: data.draft ?? false,
|
|
210
|
+
head: { ref: data.head, sha: '', repo: `${owner}/${repo}` },
|
|
211
|
+
base: { ref: data.base, sha: '', repo: `${owner}/${repo}` },
|
|
212
|
+
labels: [],
|
|
213
|
+
created_at: new Date().toISOString(),
|
|
214
|
+
updated_at: new Date().toISOString(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
throw new Error('Failed to parse PR creation response');
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
number: parsed['number'],
|
|
221
|
+
title: parsed['title'],
|
|
222
|
+
body: parsed['body'] ?? '',
|
|
223
|
+
state: 'open',
|
|
224
|
+
draft: parsed['isDraft'] ?? false,
|
|
225
|
+
head: { ref: parsed['headRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
226
|
+
base: { ref: parsed['baseRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
227
|
+
labels: [],
|
|
228
|
+
created_at: parsed['createdAt'],
|
|
229
|
+
updated_at: parsed['updatedAt'],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async getPullRequest(owner, repo, number) {
|
|
233
|
+
const result = await executeCommand('gh', [
|
|
234
|
+
'pr', 'view', String(number),
|
|
235
|
+
'-R', `${owner}/${repo}`,
|
|
236
|
+
'--json', 'number,title,body,state,isDraft,headRefName,baseRefName,labels,mergeable,createdAt,updatedAt',
|
|
237
|
+
], { cwd: this.workdir });
|
|
238
|
+
if (result.exitCode !== 0) {
|
|
239
|
+
throw new Error(`Failed to get PR #${number}: ${result.stderr}`);
|
|
240
|
+
}
|
|
241
|
+
const data = parseJSONSafe(result.stdout);
|
|
242
|
+
if (!data) {
|
|
243
|
+
throw new Error('Failed to parse PR data');
|
|
244
|
+
}
|
|
245
|
+
const state = data['state'].toLowerCase();
|
|
246
|
+
return {
|
|
247
|
+
number: data['number'],
|
|
248
|
+
title: data['title'],
|
|
249
|
+
body: data['body'] ?? '',
|
|
250
|
+
state: state === 'merged' ? 'merged' : state === 'closed' ? 'closed' : 'open',
|
|
251
|
+
draft: data['isDraft'] ?? false,
|
|
252
|
+
head: { ref: data['headRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
253
|
+
base: { ref: data['baseRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
254
|
+
labels: (data['labels'] ?? []).map(l => ({
|
|
255
|
+
name: l.name,
|
|
256
|
+
color: l.color,
|
|
257
|
+
})),
|
|
258
|
+
mergeable: data['mergeable'],
|
|
259
|
+
created_at: data['createdAt'],
|
|
260
|
+
updated_at: data['updatedAt'],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async updatePullRequest(owner, repo, number, data) {
|
|
264
|
+
const args = ['pr', 'edit', String(number), '-R', `${owner}/${repo}`];
|
|
265
|
+
if (data.title) {
|
|
266
|
+
args.push('--title', data.title);
|
|
267
|
+
}
|
|
268
|
+
if (data.body !== undefined) {
|
|
269
|
+
args.push('--body', data.body);
|
|
270
|
+
}
|
|
271
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
272
|
+
if (result.exitCode !== 0) {
|
|
273
|
+
throw new Error(`Failed to update PR #${number}: ${result.stderr}`);
|
|
274
|
+
}
|
|
275
|
+
// Handle state change separately
|
|
276
|
+
if (data.state === 'closed') {
|
|
277
|
+
const closeResult = await executeCommand('gh', [
|
|
278
|
+
'pr', 'close', String(number),
|
|
279
|
+
'-R', `${owner}/${repo}`,
|
|
280
|
+
], { cwd: this.workdir });
|
|
281
|
+
if (closeResult.exitCode !== 0) {
|
|
282
|
+
throw new Error(`Failed to close PR #${number}: ${closeResult.stderr}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async markPRReady(owner, repo, number) {
|
|
287
|
+
const result = await executeCommand('gh', [
|
|
288
|
+
'pr', 'ready', String(number),
|
|
289
|
+
'-R', `${owner}/${repo}`,
|
|
290
|
+
], { cwd: this.workdir });
|
|
291
|
+
if (result.exitCode !== 0) {
|
|
292
|
+
throw new Error(`Failed to mark PR #${number} as ready: ${result.stderr}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
async getPRComments(owner, repo, number) {
|
|
296
|
+
// Get review comments using API
|
|
297
|
+
const result = await executeCommand('gh', [
|
|
298
|
+
'api',
|
|
299
|
+
`/repos/${owner}/${repo}/pulls/${number}/comments`,
|
|
300
|
+
'--jq', '.[] | {id: .id, body: .body, author: .user.login, path: .path, line: .line, in_reply_to_id: .in_reply_to_id, created_at: .created_at, updated_at: .updated_at}',
|
|
301
|
+
], { cwd: this.workdir });
|
|
302
|
+
if (result.exitCode !== 0) {
|
|
303
|
+
// No comments is not an error
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
const lines = result.stdout.trim().split('\n').filter(l => l);
|
|
307
|
+
const comments = [];
|
|
308
|
+
for (const line of lines) {
|
|
309
|
+
const data = parseJSONSafe(line);
|
|
310
|
+
if (!data)
|
|
311
|
+
continue;
|
|
312
|
+
comments.push({
|
|
313
|
+
id: data['id'],
|
|
314
|
+
body: data['body'],
|
|
315
|
+
author: data['author'],
|
|
316
|
+
path: data['path'],
|
|
317
|
+
line: data['line'],
|
|
318
|
+
in_reply_to_id: data['in_reply_to_id'],
|
|
319
|
+
created_at: data['created_at'],
|
|
320
|
+
updated_at: data['updated_at'],
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return comments;
|
|
324
|
+
}
|
|
325
|
+
async replyToPRComment(owner, repo, number, commentId, body) {
|
|
326
|
+
const result = await executeCommand('gh', [
|
|
327
|
+
'api',
|
|
328
|
+
'-X', 'POST',
|
|
329
|
+
`/repos/${owner}/${repo}/pulls/${number}/comments/${commentId}/replies`,
|
|
330
|
+
'-f', `body=${body}`,
|
|
331
|
+
], { cwd: this.workdir });
|
|
332
|
+
if (result.exitCode !== 0) {
|
|
333
|
+
throw new Error(`Failed to reply to comment ${commentId}: ${result.stderr}`);
|
|
334
|
+
}
|
|
335
|
+
const data = parseJSONSafe(result.stdout);
|
|
336
|
+
if (!data) {
|
|
337
|
+
throw new Error('Failed to parse reply response');
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
id: data['id'],
|
|
341
|
+
body: data['body'],
|
|
342
|
+
author: data['user'].login,
|
|
343
|
+
created_at: data['created_at'],
|
|
344
|
+
updated_at: data['updated_at'],
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
async listOpenPullRequests(owner, repo) {
|
|
348
|
+
const result = await executeCommand('gh', [
|
|
349
|
+
'pr', 'list',
|
|
350
|
+
'-R', `${owner}/${repo}`,
|
|
351
|
+
'--state', 'open',
|
|
352
|
+
'--json', 'number,title,body,state,isDraft,headRefName,baseRefName,labels,createdAt,updatedAt',
|
|
353
|
+
'--limit', '100',
|
|
354
|
+
], { cwd: this.workdir });
|
|
355
|
+
if (result.exitCode !== 0) {
|
|
356
|
+
throw new Error(`Failed to list open PRs for ${owner}/${repo}: ${result.stderr}`);
|
|
357
|
+
}
|
|
358
|
+
const data = parseJSONSafe(result.stdout);
|
|
359
|
+
if (!data) {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
return data.map(pr => ({
|
|
363
|
+
number: pr['number'],
|
|
364
|
+
title: pr['title'],
|
|
365
|
+
body: pr['body'] ?? '',
|
|
366
|
+
state: 'open',
|
|
367
|
+
draft: pr['isDraft'] ?? false,
|
|
368
|
+
head: { ref: pr['headRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
369
|
+
base: { ref: pr['baseRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
370
|
+
labels: (pr['labels'] ?? []).map(l => ({
|
|
371
|
+
name: l.name,
|
|
372
|
+
color: l.color,
|
|
373
|
+
})),
|
|
374
|
+
created_at: pr['createdAt'],
|
|
375
|
+
updated_at: pr['updatedAt'],
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
378
|
+
async findPRForBranch(owner, repo, branch) {
|
|
379
|
+
const result = await executeCommand('gh', [
|
|
380
|
+
'pr', 'list',
|
|
381
|
+
'-R', `${owner}/${repo}`,
|
|
382
|
+
'--head', branch,
|
|
383
|
+
'--json', 'number,title,body,state,isDraft,headRefName,baseRefName,labels,createdAt,updatedAt',
|
|
384
|
+
'--limit', '1',
|
|
385
|
+
], { cwd: this.workdir });
|
|
386
|
+
if (result.exitCode !== 0) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
const data = parseJSONSafe(result.stdout);
|
|
390
|
+
if (!data || data.length === 0) {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
const pr = data[0];
|
|
394
|
+
return {
|
|
395
|
+
number: pr['number'],
|
|
396
|
+
title: pr['title'],
|
|
397
|
+
body: pr['body'] ?? '',
|
|
398
|
+
state: pr['state'].toLowerCase() === 'merged' ? 'merged' : 'open',
|
|
399
|
+
draft: pr['isDraft'] ?? false,
|
|
400
|
+
head: { ref: pr['headRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
401
|
+
base: { ref: pr['baseRefName'], sha: '', repo: `${owner}/${repo}` },
|
|
402
|
+
labels: (pr['labels'] ?? []).map(l => ({
|
|
403
|
+
name: l.name,
|
|
404
|
+
color: l.color,
|
|
405
|
+
})),
|
|
406
|
+
created_at: pr['createdAt'],
|
|
407
|
+
updated_at: pr['updatedAt'],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
// ==========================================================================
|
|
411
|
+
// Label Operations
|
|
412
|
+
// ==========================================================================
|
|
413
|
+
async addLabels(owner, repo, number, labels) {
|
|
414
|
+
if (labels.length === 0)
|
|
415
|
+
return;
|
|
416
|
+
const args = ['issue', 'edit', String(number), '-R', `${owner}/${repo}`];
|
|
417
|
+
for (const label of labels) {
|
|
418
|
+
args.push('--add-label', label);
|
|
419
|
+
}
|
|
420
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
421
|
+
if (result.exitCode !== 0) {
|
|
422
|
+
throw new Error(`Failed to add labels: ${result.stderr}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async removeLabels(owner, repo, number, labels) {
|
|
426
|
+
if (labels.length === 0)
|
|
427
|
+
return;
|
|
428
|
+
const args = ['issue', 'edit', String(number), '-R', `${owner}/${repo}`];
|
|
429
|
+
for (const label of labels) {
|
|
430
|
+
args.push('--remove-label', label);
|
|
431
|
+
}
|
|
432
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
433
|
+
if (result.exitCode !== 0) {
|
|
434
|
+
// Label might not exist, don't fail
|
|
435
|
+
if (!result.stderr.includes('not found')) {
|
|
436
|
+
throw new Error(`Failed to remove labels: ${result.stderr}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async getRepoLabels(owner, repo) {
|
|
441
|
+
const result = await executeCommand('gh', [
|
|
442
|
+
'label', 'list',
|
|
443
|
+
'-R', `${owner}/${repo}`,
|
|
444
|
+
'--json', 'name,color,description',
|
|
445
|
+
'--limit', '1000',
|
|
446
|
+
], { cwd: this.workdir });
|
|
447
|
+
if (result.exitCode !== 0) {
|
|
448
|
+
throw new Error(`Failed to get labels: ${result.stderr}`);
|
|
449
|
+
}
|
|
450
|
+
const data = parseJSONSafe(result.stdout);
|
|
451
|
+
return data ?? [];
|
|
452
|
+
}
|
|
453
|
+
async createOrUpdateLabel(owner, repo, label) {
|
|
454
|
+
// Check if label exists
|
|
455
|
+
const existing = await this.getRepoLabels(owner, repo);
|
|
456
|
+
const exists = existing.some(l => l.name === label.name);
|
|
457
|
+
if (exists) {
|
|
458
|
+
// Update
|
|
459
|
+
const args = [
|
|
460
|
+
'label', 'edit', label.name,
|
|
461
|
+
'-R', `${owner}/${repo}`,
|
|
462
|
+
'--color', label.color.replace('#', ''),
|
|
463
|
+
];
|
|
464
|
+
if (label.description) {
|
|
465
|
+
args.push('--description', label.description);
|
|
466
|
+
}
|
|
467
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
468
|
+
if (result.exitCode !== 0) {
|
|
469
|
+
throw new Error(`Failed to update label ${label.name}: ${result.stderr}`);
|
|
470
|
+
}
|
|
471
|
+
return { created: false };
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
// Create
|
|
475
|
+
const args = [
|
|
476
|
+
'label', 'create', label.name,
|
|
477
|
+
'-R', `${owner}/${repo}`,
|
|
478
|
+
'--color', label.color.replace('#', ''),
|
|
479
|
+
];
|
|
480
|
+
if (label.description) {
|
|
481
|
+
args.push('--description', label.description);
|
|
482
|
+
}
|
|
483
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
484
|
+
if (result.exitCode !== 0) {
|
|
485
|
+
throw new Error(`Failed to create label ${label.name}: ${result.stderr}`);
|
|
486
|
+
}
|
|
487
|
+
return { created: true };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// ==========================================================================
|
|
491
|
+
// Git Operations (Local)
|
|
492
|
+
// ==========================================================================
|
|
493
|
+
async getStatus() {
|
|
494
|
+
const branchResult = await executeCommand('git', ['branch', '--show-current'], { cwd: this.workdir });
|
|
495
|
+
const branch = branchResult.stdout.trim();
|
|
496
|
+
const statusResult = await executeCommand('git', ['status', '--porcelain'], { cwd: this.workdir });
|
|
497
|
+
const lines = statusResult.stdout.split('\n').filter(l => l);
|
|
498
|
+
const staged = [];
|
|
499
|
+
const unstaged = [];
|
|
500
|
+
const untracked = [];
|
|
501
|
+
for (const line of lines) {
|
|
502
|
+
const indexStatus = line[0];
|
|
503
|
+
const workingStatus = line[1];
|
|
504
|
+
const file = line.substring(3);
|
|
505
|
+
if (indexStatus === '?' && workingStatus === '?') {
|
|
506
|
+
untracked.push(file);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
if (indexStatus && indexStatus !== ' ' && indexStatus !== '?') {
|
|
510
|
+
staged.push(file);
|
|
511
|
+
}
|
|
512
|
+
if (workingStatus && workingStatus !== ' ' && workingStatus !== '?') {
|
|
513
|
+
unstaged.push(file);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
branch,
|
|
519
|
+
has_changes: lines.length > 0,
|
|
520
|
+
staged,
|
|
521
|
+
unstaged,
|
|
522
|
+
untracked,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
async getCurrentBranch() {
|
|
526
|
+
const result = await executeCommand('git', ['branch', '--show-current'], { cwd: this.workdir });
|
|
527
|
+
if (result.exitCode !== 0) {
|
|
528
|
+
throw new Error(`Failed to get current branch: ${result.stderr}`);
|
|
529
|
+
}
|
|
530
|
+
return result.stdout.trim();
|
|
531
|
+
}
|
|
532
|
+
async branchExists(branch, remote = false) {
|
|
533
|
+
if (remote) {
|
|
534
|
+
const result = await executeCommand('git', ['ls-remote', '--heads', 'origin', branch], { cwd: this.workdir });
|
|
535
|
+
return result.stdout.includes(branch);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
const result = await executeCommand('git', ['branch', '--list', branch], { cwd: this.workdir });
|
|
539
|
+
return result.stdout.trim().length > 0;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async createBranch(name, startPoint) {
|
|
543
|
+
const args = ['checkout', '-b', name];
|
|
544
|
+
if (startPoint) {
|
|
545
|
+
args.push(startPoint);
|
|
546
|
+
}
|
|
547
|
+
const result = await executeCommand('git', args, { cwd: this.workdir });
|
|
548
|
+
if (result.exitCode !== 0) {
|
|
549
|
+
throw new Error(`Failed to create branch ${name}: ${result.stderr}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async checkout(branch) {
|
|
553
|
+
const result = await executeCommand('git', ['checkout', branch], { cwd: this.workdir });
|
|
554
|
+
if (result.exitCode !== 0) {
|
|
555
|
+
throw new Error(`Failed to checkout ${branch}: ${result.stderr}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async stageFiles(files) {
|
|
559
|
+
if (files.length === 0)
|
|
560
|
+
return;
|
|
561
|
+
const result = await executeCommand('git', ['add', ...files], { cwd: this.workdir });
|
|
562
|
+
if (result.exitCode !== 0) {
|
|
563
|
+
throw new Error(`Failed to stage files: ${result.stderr}`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async stageAll() {
|
|
567
|
+
const result = await executeCommand('git', ['add', '-A'], { cwd: this.workdir });
|
|
568
|
+
if (result.exitCode !== 0) {
|
|
569
|
+
throw new Error(`Failed to stage all: ${result.stderr}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async commit(message) {
|
|
573
|
+
const result = await executeCommand('git', ['commit', '-m', message], { cwd: this.workdir });
|
|
574
|
+
if (result.exitCode !== 0) {
|
|
575
|
+
throw new Error(`Failed to commit: ${result.stderr}`);
|
|
576
|
+
}
|
|
577
|
+
// Get the commit SHA
|
|
578
|
+
const shaResult = await executeCommand('git', ['rev-parse', 'HEAD'], { cwd: this.workdir });
|
|
579
|
+
const sha = shaResult.stdout.trim();
|
|
580
|
+
// Get committed files
|
|
581
|
+
const diffResult = await executeCommand('git', ['diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD'], { cwd: this.workdir });
|
|
582
|
+
const files = diffResult.stdout.split('\n').filter(f => f);
|
|
583
|
+
return {
|
|
584
|
+
sha,
|
|
585
|
+
files_committed: files,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
async push(remote = 'origin', branch, setUpstream = false) {
|
|
589
|
+
const currentBranch = branch ?? await this.getCurrentBranch();
|
|
590
|
+
const args = ['push', remote, currentBranch];
|
|
591
|
+
if (setUpstream) {
|
|
592
|
+
args.splice(1, 0, '-u');
|
|
593
|
+
}
|
|
594
|
+
const result = await executeCommand('git', args, { cwd: this.workdir });
|
|
595
|
+
if (result.exitCode !== 0) {
|
|
596
|
+
throw new Error(`Failed to push: ${result.stderr}`);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
success: true,
|
|
600
|
+
ref: currentBranch,
|
|
601
|
+
remote,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
async fetch(remote = 'origin', prune = true) {
|
|
605
|
+
const args = ['fetch', remote];
|
|
606
|
+
if (prune) {
|
|
607
|
+
args.push('--prune');
|
|
608
|
+
}
|
|
609
|
+
const result = await executeCommand('git', args, { cwd: this.workdir });
|
|
610
|
+
if (result.exitCode !== 0) {
|
|
611
|
+
throw new Error(`Failed to fetch: ${result.stderr}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async merge(branch, noCommit = false) {
|
|
615
|
+
const args = ['merge', branch];
|
|
616
|
+
if (noCommit) {
|
|
617
|
+
args.push('--no-commit');
|
|
618
|
+
}
|
|
619
|
+
const result = await executeCommand('git', args, { cwd: this.workdir });
|
|
620
|
+
// Check for conflicts
|
|
621
|
+
if (result.exitCode !== 0 && result.stdout.includes('CONFLICT')) {
|
|
622
|
+
const conflicted = await this.getConflictedFiles();
|
|
623
|
+
const conflicts = conflicted.map(path => ({
|
|
624
|
+
path,
|
|
625
|
+
ours: '',
|
|
626
|
+
theirs: '',
|
|
627
|
+
resolved: false,
|
|
628
|
+
}));
|
|
629
|
+
return {
|
|
630
|
+
success: false,
|
|
631
|
+
commits_merged: 0,
|
|
632
|
+
already_up_to_date: false,
|
|
633
|
+
conflicts,
|
|
634
|
+
summary: `Merge conflict in ${conflicts.length} file(s)`,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
if (result.exitCode !== 0) {
|
|
638
|
+
throw new Error(`Failed to merge ${branch}: ${result.stderr}`);
|
|
639
|
+
}
|
|
640
|
+
// Check if already up to date
|
|
641
|
+
if (result.stdout.includes('Already up to date')) {
|
|
642
|
+
return {
|
|
643
|
+
success: true,
|
|
644
|
+
commits_merged: 0,
|
|
645
|
+
already_up_to_date: true,
|
|
646
|
+
conflicts: [],
|
|
647
|
+
summary: 'Already up to date',
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
// Count commits merged (rough estimate from output)
|
|
651
|
+
const commitMatch = result.stdout.match(/(\d+) files? changed/);
|
|
652
|
+
const filesChanged = commitMatch ? parseInt(commitMatch[1], 10) : 0;
|
|
653
|
+
return {
|
|
654
|
+
success: true,
|
|
655
|
+
commits_merged: filesChanged > 0 ? 1 : 0,
|
|
656
|
+
already_up_to_date: false,
|
|
657
|
+
conflicts: [],
|
|
658
|
+
summary: result.stdout.trim().split('\n')[0] ?? 'Merge completed',
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async mergeAbort() {
|
|
662
|
+
const result = await executeCommand('git', ['merge', '--abort'], { cwd: this.workdir });
|
|
663
|
+
if (result.exitCode !== 0) {
|
|
664
|
+
throw new Error(`Failed to abort merge: ${result.stderr}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async stash(message) {
|
|
668
|
+
const args = ['stash', 'push'];
|
|
669
|
+
if (message) {
|
|
670
|
+
args.push('-m', message);
|
|
671
|
+
}
|
|
672
|
+
const result = await executeCommand('git', args, { cwd: this.workdir });
|
|
673
|
+
// Returns true if something was stashed
|
|
674
|
+
return !result.stdout.includes('No local changes to save');
|
|
675
|
+
}
|
|
676
|
+
async stashPop() {
|
|
677
|
+
const result = await executeCommand('git', ['stash', 'pop'], { cwd: this.workdir });
|
|
678
|
+
if (result.exitCode !== 0) {
|
|
679
|
+
if (result.stderr.includes('CONFLICT')) {
|
|
680
|
+
return { success: false, conflicts: true };
|
|
681
|
+
}
|
|
682
|
+
throw new Error(`Failed to pop stash: ${result.stderr}`);
|
|
683
|
+
}
|
|
684
|
+
return { success: true, conflicts: false };
|
|
685
|
+
}
|
|
686
|
+
async getConflictedFiles() {
|
|
687
|
+
const result = await executeCommand('git', ['diff', '--name-only', '--diff-filter=U'], { cwd: this.workdir });
|
|
688
|
+
return result.stdout.split('\n').filter(f => f);
|
|
689
|
+
}
|
|
690
|
+
async getDefaultBranch() {
|
|
691
|
+
// Try to get from remote
|
|
692
|
+
const result = await executeCommand('git', ['symbolic-ref', 'refs/remotes/origin/HEAD'], { cwd: this.workdir });
|
|
693
|
+
if (result.exitCode === 0) {
|
|
694
|
+
return result.stdout.trim().replace('refs/remotes/origin/', '');
|
|
695
|
+
}
|
|
696
|
+
// Fallback to checking common names
|
|
697
|
+
for (const branch of ['develop', 'main', 'master']) {
|
|
698
|
+
const exists = await this.branchExists(branch, true);
|
|
699
|
+
if (exists)
|
|
700
|
+
return branch;
|
|
701
|
+
}
|
|
702
|
+
return 'main';
|
|
703
|
+
}
|
|
704
|
+
async getCommitsBetween(base, head) {
|
|
705
|
+
const result = await executeCommand('git', [
|
|
706
|
+
'log', `${base}..${head}`,
|
|
707
|
+
'--format=%H|%s',
|
|
708
|
+
], { cwd: this.workdir });
|
|
709
|
+
if (result.exitCode !== 0) {
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
return result.stdout.split('\n').filter(l => l).map(line => {
|
|
713
|
+
const [sha, ...messageParts] = line.split('|');
|
|
714
|
+
return {
|
|
715
|
+
sha: sha ?? '',
|
|
716
|
+
message: messageParts.join('|'),
|
|
717
|
+
};
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
// ==========================================================================
|
|
721
|
+
// Alias Methods (convenience wrappers)
|
|
722
|
+
// ==========================================================================
|
|
723
|
+
async listBranches(owner, repo) {
|
|
724
|
+
// List remote branches using gh api
|
|
725
|
+
const result = await executeCommand('gh', [
|
|
726
|
+
'api',
|
|
727
|
+
`/repos/${owner}/${repo}/branches`,
|
|
728
|
+
'--jq', '.[].name',
|
|
729
|
+
], { cwd: this.workdir });
|
|
730
|
+
if (result.exitCode !== 0) {
|
|
731
|
+
// Fallback to git
|
|
732
|
+
const gitResult = await executeCommand('git', [
|
|
733
|
+
'branch', '-r', '--format=%(refname:short)',
|
|
734
|
+
], { cwd: this.workdir });
|
|
735
|
+
return gitResult.stdout.split('\n')
|
|
736
|
+
.filter(b => b)
|
|
737
|
+
.map(b => b.replace('origin/', ''));
|
|
738
|
+
}
|
|
739
|
+
return result.stdout.split('\n').filter(b => b);
|
|
740
|
+
}
|
|
741
|
+
async createPR(owner, repo, data) {
|
|
742
|
+
return this.createPullRequest(owner, repo, data);
|
|
743
|
+
}
|
|
744
|
+
async updatePR(owner, repo, number, data) {
|
|
745
|
+
return this.updatePullRequest(owner, repo, number, data);
|
|
746
|
+
}
|
|
747
|
+
async getPRForBranch(owner, repo, branch) {
|
|
748
|
+
return this.findPRForBranch(owner, repo, branch);
|
|
749
|
+
}
|
|
750
|
+
async listLabels(owner, repo) {
|
|
751
|
+
return this.getRepoLabels(owner, repo);
|
|
752
|
+
}
|
|
753
|
+
async createLabel(owner, repo, name, color, description) {
|
|
754
|
+
const args = [
|
|
755
|
+
'label', 'create', name,
|
|
756
|
+
'-R', `${owner}/${repo}`,
|
|
757
|
+
'--color', color.replace('#', ''),
|
|
758
|
+
];
|
|
759
|
+
if (description) {
|
|
760
|
+
args.push('--description', description);
|
|
761
|
+
}
|
|
762
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
763
|
+
if (result.exitCode !== 0) {
|
|
764
|
+
throw new Error(`Failed to create label ${name}: ${result.stderr}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async updateLabel(owner, repo, name, data) {
|
|
768
|
+
const args = ['label', 'edit', name, '-R', `${owner}/${repo}`];
|
|
769
|
+
if (data.color) {
|
|
770
|
+
args.push('--color', data.color.replace('#', ''));
|
|
771
|
+
}
|
|
772
|
+
if (data.description !== undefined) {
|
|
773
|
+
args.push('--description', data.description);
|
|
774
|
+
}
|
|
775
|
+
const result = await executeCommand('gh', args, { cwd: this.workdir });
|
|
776
|
+
if (result.exitCode !== 0) {
|
|
777
|
+
throw new Error(`Failed to update label ${name}: ${result.stderr}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
//# sourceMappingURL=gh-cli.js.map
|