Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
02d5db1
docs: add atomic capability chaining design
aryeko Feb 19, 2026
be81a84
docs: clarify validation scope in chaining design
aryeko Feb 19, 2026
af47006
docs: update chaining design — concurrent Promise.all approach + CLI …
aryeko Feb 19, 2026
5f1b181
docs: update design doc + write implementation plan for card-driven t…
aryeko Feb 19, 2026
f31808b
feat(core): add graphql.resolution schema and types to OperationCard
aryeko Feb 19, 2026
6de3cd2
feat(core): add graphql.resolution blocks to 6 capability cards
aryeko Feb 19, 2026
be69efd
feat(core): add buildBatchQuery and generalise parseOperation in batc…
aryeko Feb 19, 2026
8579345
feat(core): add document-registry for lookup and mutation GQL documents
aryeko Feb 19, 2026
27fce16
feat(core): add ChainResultEnvelope, ChainStepResult, ChainStatus types
aryeko Feb 19, 2026
4747c4c
feat(core): add resolution engine helpers (applyInject, buildMutation…
aryeko Feb 19, 2026
3fbe806
fix(core): revert error message to 'mutation' for backward compatibility
aryeko Feb 19, 2026
039fd65
refactor(core): delete composite capability system
aryeko Feb 19, 2026
8affae8
feat(core): export executeTasks and chain result types
aryeko Feb 19, 2026
7bf0504
chore: add changeset for atomic capability chaining feature
aryeko Feb 19, 2026
7f46eae
docs: update documentation for atomic capability chaining
aryeko Feb 19, 2026
d898c19
fix: preserve GraphQL fragments in batch mutations and fix labelableI…
aryeko Feb 20, 2026
6dea73a
chore: merge main (PR #58 capability namespace standardization)
aryeko Feb 20, 2026
2cf69ca
docs(capabilities): fix stale capability IDs after PR #58 namespace r…
aryeko Feb 20, 2026
dfc6a08
feat(core): register 11 graphql mutations in document registry for at…
aryeko Feb 20, 2026
d35406f
feat(core): add pr.reviews.submit to atomic chaining with PrNodeId re…
aryeko Feb 20, 2026
eee85bf
docs: add atomic chaining follow-up work tracker
aryeko Feb 20, 2026
53ad12e
fix(chain): safe json parse, 30s abort timeout, catch sync throws in …
aryeko Feb 20, 2026
3a0e198
fix(engine): track preflight errors by step index; validate lookup va…
aryeko Feb 20, 2026
067cc6d
test(engine): fix vi.doMock, add attribution + lookup-var + mixed-res…
aryeko Feb 20, 2026
3c9efe2
docs(types): document scalar, map_array, and input inject source types
aryeko Feb 20, 2026
ae828b7
refactor: extract LookupSpec interface and use @core/ alias imports i…
aryeko Feb 20, 2026
7328167
docs: fix ResolutionBlock→ResolutionConfig, stale capability IDs, and…
aryeko Feb 20, 2026
6596739
fix(engine): queryMock assertion, 'in' operator for alias key, derive…
aryeko Feb 20, 2026
2d6914b
fix(gql): replace fragment regex with brace-depth counting in batch.ts
aryeko Feb 20, 2026
aff0bd2
fix(chain): resolve GQL endpoint from env vars; broaden ChainResultEn…
aryeko Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/atomic-capability-chaining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ghx-dev/core": minor
---

Add atomic capability chaining: `executeTasks()` function that executes multiple capabilities in a single GraphQL batch with ≤2 API round-trips. New `ghx chain --steps '<json-array>'` CLI command. Supersedes the unused composite capability system which has been removed.

7 changes: 0 additions & 7 deletions .changeset/composite-capabilities-gql-integration.md

This file was deleted.

6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ Check: **[Benchmark Documentation](benchmark/README.md)**

## Key Facts

**69 Capabilities** organized by domain (66 atomic + 3 composite):
**66 Capabilities** organized by domain:

| Domain | Count | Examples |
|--------|-------|----------|
| Issues | 21 | Create, update, close, assign labels, manage relations, composite batch ops |
| Pull Requests | 22 | View, comment, approve, merge, rerun checks, composite batch ops |
| Issues | 19 | Create, update, close, assign labels, manage relations |
| Pull Requests | 21 | View, comment, approve, merge, rerun checks |
| Workflows | 11 | List, dispatch, rerun, cancel, retrieve logs |
| Releases | 5 | List, create, publish, update drafts |
| Projects v2 | 6 | View, list items, update fields |
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ flowchart TB
end

subgraph Registry["Operation Registry 📋"]
Cards["69 Capability Cards"]
Cards["66 Capability Cards"]
Schema["JSON Schema Validation"]
end

Expand Down
8 changes: 3 additions & 5 deletions docs/architecture/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ Three adapters handle different GitHub interaction modes:
| Adapter | Coverage | Route Name | Status | Purpose |
|---------|----------|-----------|--------|---------|
| **CLI Adapter** | 66 atomic | `cli` | Active | Execute via `gh` command-line tool |
| **GraphQL Adapter** | ~28 atomic + 3 composite | `graphql` | Active | Execute via GitHub GraphQL API |
| **GraphQL Adapter** | ~28 atomic | `graphql` | Active | Execute via GitHub GraphQL API |
| **REST Adapter** | None | `rest` | Stub | Planned for future expansion |

> **Composite capabilities** (`issue.triage.composite`, `issue.update.composite`, `pr.threads.composite`) use a dedicated batch GraphQL path and do not have CLI routes.

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#9C27B0', 'primaryTextColor': '#fff', 'primaryBorderColor': '#7B1FA2', 'lineColor': '#666', 'secondaryColor': '#CE93D8', 'tertiaryColor': '#E1BEE7'}}}%%
graph TD
Expand Down Expand Up @@ -70,7 +68,7 @@ The CLI adapter executes capabilities through the GitHub command-line tool (`gh`

### Features

- **Full atomic coverage**: All 66 atomic capabilities have CLI routes defined (composite capabilities use GraphQL only)
- **Full atomic coverage**: All 66 capabilities have CLI routes defined where applicable
- **Safe spawning**: Uses `spawn()` with `shell: false` — no shell interpretation
- **Timeout enforcement**: Per-command timeout (default 30s)
- **Output limits**: Bounded stdout/stderr size (default 10 MB)
Expand Down Expand Up @@ -120,7 +118,7 @@ The GraphQL adapter executes capabilities through GitHub's GraphQL API.
### Features

- **Typed queries**: Generated operation SDKs with type safety
- **Selective coverage**: ~28 atomic + 3 composite capabilities support GraphQL routes (typically read-heavy or batch operations)
- **Selective coverage**: ~28 capabilities support GraphQL routes (typically read-heavy or mutation operations requiring typed queries)
- **Authentication**: Requires `GITHUB_TOKEN` environment variable
- **Error classification**: Maps GraphQL errors (auth, rate limit, not found, etc.) to normalized error codes
- **Field mapping**: Adapts GitHub's response shape to capability output schema
Expand Down
130 changes: 101 additions & 29 deletions docs/architecture/operation-cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ classDiagram
class GraphQLMetadata {
operation?: string
field_mapping?: Record
resolution?: ResolutionConfig
}

class CompositeMetadata {
steps: CompositeStep[]
output_strategy: string
class ResolutionConfig {
lookup: LookupSpec
inject: InjectSpec[]
}

class Route {
Expand All @@ -46,7 +47,7 @@ classDiagram
OperationCard --> RoutingPolicy
OperationCard --> CLIMetadata
OperationCard --> GraphQLMetadata
OperationCard --> CompositeMetadata
GraphQLMetadata --> ResolutionConfig
RoutingPolicy --> Route
```

Expand All @@ -64,30 +65,29 @@ Each operation card includes:
| `routing.preferred` | Primary route(s) to attempt first | Yes |
| `routing.fallbacks` | Secondary routes in order of preference | Yes |
| `cli` | CLI command metadata and output mapping | No |
| `graphql` | GraphQL operation and field mapping | No |
| `composite` | Composite capability steps and output strategy | No |
| `graphql` | GraphQL operation, field mapping, and optional resolution block | No |

## Current Capability Surface

`ghx` currently defines **69 capabilities** across these domains (66 atomic + 3 composite):
`ghx` currently defines **66 capabilities** across these domains:

| Domain | Count | Purpose |
|--------|-------|---------|
| **Issues** | 21 | Create, read, update, close, link, label, milestone, assign issues; composite batch operations |
| **Pull Requests** | 22 | Read PR metadata, lists, reviews, threads, checks, mergeability, mutations; composite batch operations |
| **Issues** | 19 | Create, read, update, close, link, label, milestone, assign issues |
| **Pull Requests** | 21 | Read PR metadata, lists, reviews, threads, checks, mergeability, mutations |
| **Releases** | 5 | Query and draft releases, publish, update |
| **Workflows** | 11 | Query workflow runs, jobs, logs, cancel, rerun; list workflows; dispatch |
| **Repositories** | 3 | Repo metadata, label listing, issue type listing |
| **Projects (v2)** | 6 | Query and mutate projects, fields, items |
| **Check Runs** | 1 | List check run annotations |

### Issue Capabilities (21)
### Issue Capabilities (19)

`issue.view`, `issue.list`, `issue.create`, `issue.update`, `issue.close`, `issue.reopen`, `issue.delete`, `issue.comments.list`, `issue.comments.create`, `issue.labels.update`, `issue.labels.add`, `issue.assignees.update`, `issue.milestone.set`, `issue.linked_prs.list`, `issue.parent.set`, `issue.parent.remove`, `issue.blocked_by.add`, `issue.blocked_by.remove`, `issue.relations.get`, `issue.triage.composite`, `issue.update.composite`
`issue.view`, `issue.list`, `issue.create`, `issue.update`, `issue.close`, `issue.reopen`, `issue.delete`, `issue.comments.list`, `issue.comments.create`, `issue.labels.set`, `issue.labels.add`, `issue.assignees.set`, `issue.milestone.set`, `issue.linked_prs.list`, `issue.parent.set`, `issue.parent.remove`, `issue.blocked_by.add`, `issue.blocked_by.remove`, `issue.relations.get`

### Pull Request Capabilities (22)
### Pull Request Capabilities (21)

`pr.view`, `pr.list`, `pr.create`, `pr.update`, `pr.thread.list`, `pr.thread.reply`, `pr.thread.resolve`, `pr.thread.unresolve`, `pr.review.list`, `pr.review.submit`, `pr.review.request`, `pr.diff.files`, `pr.diff.view`, `pr.checks.list`, `pr.checks.failed`, `pr.checks.rerun_failed`, `pr.checks.rerun_all`, `pr.merge.status`, `pr.merge`, `pr.branch.update`, `pr.assignees.update`, `pr.threads.composite`
`pr.view`, `pr.list`, `pr.create`, `pr.update`, `pr.thread.list`, `pr.thread.reply`, `pr.thread.resolve`, `pr.thread.unresolve`, `pr.review.list`, `pr.review.submit`, `pr.review.request`, `pr.diff.files`, `pr.diff.view`, `pr.checks.list`, `pr.checks.failed`, `pr.checks.rerun_failed`, `pr.checks.rerun_all`, `pr.merge.status`, `pr.merge`, `pr.branch.update`, `pr.assignees.update`

### Release Capabilities (5)

Expand All @@ -109,28 +109,100 @@ Each operation card includes:

`check_run.annotations.list`

## Composite Capabilities
## Card-Defined Resolution

Composite capabilities batch multiple GraphQL mutations into a single network round-trip using `gql/batch.ts`. They are declared with a `composite` field instead of `cli`/`graphql` fields:
Some GraphQL capabilities require a two-phase execution: first a **lookup query** to resolve
human-readable names (labels, assignees, milestones) into GitHub node IDs, then the actual
**mutation**. This is declared with a `graphql.resolution` block in the card.

### Resolution Block Schema

```yaml
composite:
steps:
- capability_id: issue.labels.update
params_map:
issueId: issueId
labels: labelIds
- capability_id: issue.comments.create
params_map:
graphql:
operationName: IssueAssigneesUpdate
resolution:
lookup:
operationName: IssueAssigneesLookup # registered lookup query name
vars:
issueId: issueId # lookup variable → input field
inject:
- target: assigneeIds # mutation variable to populate
source: map_array # inject variant
from_input: assignees # input field containing names
nodes_path: node.repository.assignableUsers.nodes
match_field: login # field to match against input values
extract_field: id # field to extract as the resolved ID
```

### Inject Source Variants

Three `source` types control how Phase 1 lookup results (or step inputs) map into Phase 2 mutation variables:

**`scalar`** — Extract a single value from the Phase 1 result at a dot-notation path.

```yaml
inject:
- target: pullRequestId # mutation variable name
source: scalar
path: repository.pullRequest.id # dot-path into Phase 1 response
```

**`map_array`** — Resolve a list of human-readable names to node IDs. Matching is case-insensitive.

```yaml
inject:
- target: labelIds # mutation variable name
source: map_array
from_input: labels # input field containing the list of names
nodes_path: repository.labels.nodes # path to array in Phase 1 response
match_field: name # field on each node to match against input names
extract_field: id # field on each node to extract as the resolved ID
```

**`input`** — Pass a value directly from the step's `input` into the mutation variable, with no Phase 1 lookup. Use this when the caller already has the required node ID.

```yaml
inject:
- target: labelableId # mutation variable name
source: input
from_input: issueId # the input field whose value is passed through
```

> **Tip:** Prefer `source: input` when the agent already has the required ID — it skips the Phase 1 resolution query entirely.

| `source` | Phase 1 required | Description |
|----------|-----------------|-------------|
| `scalar` | Yes | Extracts a single value at `path` from the lookup result |
| `map_array` | Yes | Maps an array of names to IDs using the lookup result |
| `input` | No | Passes a value directly from step input (no lookup needed) |

**Example — `issue.assignees.set.yaml`** (uses `map_array`):

```yaml
graphql:
operationName: IssueAssigneesUpdate
documentPath: src/gql/operations/issue-assignees-update.graphql
resolution:
lookup:
operationName: IssueAssigneesLookup
documentPath: src/gql/operations/issue-assignees-lookup.graphql
vars:
issueId: issueId
body: body
output_strategy: merge
inject:
- target: assigneeIds
source: map_array
from_input: assignees
nodes_path: node.repository.assignableUsers.nodes
match_field: login
extract_field: id
```

Composite capabilities:
- Use `routing.preferred: graphql` exclusively (no CLI fallback)
- Are expanded by `core/execute/composite.ts` → `gql/builders.ts` → `gql/batch.ts`
- Merge output from all steps into a single `ResultEnvelope`
When a capability declares `graphql.resolution`, `executeTasks()` batches all resolution
lookups into a single Phase 1 GraphQL query, then batches all mutations into a single Phase 2
GraphQL mutation — resulting in at most 2 HTTP round-trips regardless of chain length.

See [Chaining Capabilities](../guides/chaining-capabilities.md) for the full two-phase
execution model.

## Card Loading & Validation

Expand Down
7 changes: 4 additions & 3 deletions docs/architecture/repository-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ ghx/
| `core/registry/schema-utils.ts` | Schema helper utilities | JSON schema helpers |
| `core/registry/operation-card-schema.ts` | Card schema definition | `operationCardSchema` |
| `core/registry/ajv-instance.ts` | Shared AJV instance | `ajv` |
| `core/registry/cards/*.yaml` | Capability definitions | 69 operation cards (66 atomic + 3 composite) |
| `core/registry/cards/*.yaml` | Capability definitions | 66 operation cards |

### Routing Engine (`core/routing`)

Expand All @@ -111,7 +111,6 @@ ghx/
| Module | Purpose | Key Exports |
|--------|---------|-------------|
| `core/execute/execute.ts` | Route planning & retry loop | `execute()` |
| `core/execute/composite.ts` | Composite capability expansion | `expandCompositeSteps()` |
| `core/execution/preflight.ts` | Route readiness checks | `preflightCheck()` |
| `core/execution/normalizer.ts` | Output normalization | `normalizeResult()`, `normalizeError()` |
| `core/execution/adapters/cli-capability-adapter.ts` | CLI capability adapter | `runCliCapability()`, `CliCapabilityId` |
Expand Down Expand Up @@ -148,7 +147,9 @@ ghx/
| `gql/capability-registry.ts` | GQL handler registry by capability ID | `GraphqlHandler` map |
| `gql/types.ts` | GQL input/output contracts | GraphQL domain input/data types |
| `gql/assertions.ts` | GraphQL input validation helpers | `assert*` validation utilities |
| `gql/batch.ts` | Composite batch mutation builder | `buildBatchMutation()` |
| `gql/document-registry.ts` | Lookup & mutation document registry | `getLookupDocument()`, `getMutationDocument()` |
| `gql/resolve.ts` | Resolution inject helpers | `applyInject()`, `buildMutationVars()` |
| `gql/batch.ts` | Batch query/mutation builder | `buildBatchQuery()`, `buildBatchMutation()` |
| `gql/builders.ts` | Per-capability mutation builders | `OPERATION_BUILDERS` |
| `gql/domains/*.ts` | Domain operation modules | `run*` operation handlers |
| `gql/operations/*.generated.ts` | Generated operation SDKs | Operation-specific `getSdk()` |
Expand Down
21 changes: 16 additions & 5 deletions docs/architecture/system-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ flowchart TB
G --> H
H --> I
C --> J

subgraph "Atomic Chaining (2+ tasks)"
EC["executeTasks()"] --> PF2["Pre-flight validation\n(all steps)"]
PF2 -->|"any invalid"| REJ["Reject whole chain"]
PF2 -->|"all valid"| P1["Phase 1 — batch resolution query\n≤1 HTTP round-trip"]
P1 --> P2["Phase 2 — batch mutation\n≤1 HTTP round-trip"]
P2 --> CR["ChainResultEnvelope\nstatus: success / partial / failed"]
end
```

## Result Envelope
Expand All @@ -73,23 +81,26 @@ Every capability returns:

## Current Scope

69 capabilities (66 atomic + 3 composite) across 7 domains:
66 capabilities across 7 domains:

- **Issues** (21): view, list, create, update, close, reopen, delete, comment, label, assign, milestone, link, parent, block, relation, and 2 composite batch capabilities
- **Pull Requests** (22): view, list, create, update, thread operations, review operations, diff, checks, merge, branch update, and 1 composite batch capability
- **Issues** (19): view, list, create, update, close, reopen, delete, comment, label, assign, milestone, link, parent, block, relation
- **Pull Requests** (21): view, list, create, update, thread operations, review operations, diff, checks, merge, branch update
- **Workflows** (11): view, list, dispatch, run lifecycle, logs, cancel, rerun, artifacts
- **Releases** (5): get, list, create draft, publish draft, update
- **Repositories** (3): view, labels list, issue types list
- **Projects v2** (6): get, fields list, items list, add issue, update field
- **Check Runs** (1): annotations list

Composite capabilities batch multiple GraphQL mutations into a single network round-trip. Route preferences are capability-specific and defined in cards (`preferred` + `fallbacks`), with REST still outside active routing for current capabilities.
Route preferences are capability-specific and defined in cards (`preferred` + `fallbacks`), with REST still outside active routing for current capabilities. For multi-capability mutations, use `executeTasks()` — it batches all resolution lookups into one Phase 1 query and all mutations into one Phase 2 mutation (≤2 HTTP round-trips for any chain length).

## Source References

- `packages/core/src/core/execute/execute.ts`
- `packages/core/src/core/routing/engine.ts` — `executeTask()`, `executeTasks()`
- `packages/core/src/core/registry/cards/*.yaml`
- `packages/core/src/core/contracts/envelope.ts`
- `packages/core/src/core/contracts/envelope.ts` — `ChainResultEnvelope`, `ChainStepResult`, `ChainStatus`
- `packages/core/src/gql/document-registry.ts` — lookup & mutation document registry
- `packages/core/src/gql/resolve.ts` — resolution inject logic
- `packages/core/src/core/execute/execute-tool.ts`
- `packages/core/src/core/registry/list-capabilities.ts`
- `packages/core/src/core/registry/explain-capability.ts`
4 changes: 2 additions & 2 deletions docs/benchmark/workflow-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ Workflow scenarios differ from atomic scenarios by:

**Expected Capabilities:**
- `issue.view`
- `issue.labels.update`
- `issue.assignees.update`
- `issue.labels.set`
- `issue.assignees.set`
- `project-v2.items.list`
- `project-v2.item.add-issue`

Expand Down
Loading
Loading