@a5c-ai/kradle 5.0.1-staging.3abdf9534c25
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/Dockerfile +31 -0
- package/README.md +187 -0
- package/bin/kradle-demo.mjs +23 -0
- package/bin/kradle-server.mjs +14 -0
- package/dist/kradle-controller-ui.json +3482 -0
- package/dist/kradle-lifecycle.json +201 -0
- package/dist/kradle-runtime-snapshot.json +3125 -0
- package/dist/kradle-summary.json +724 -0
- package/docs/README.md +61 -0
- package/docs/agents/README.md +83 -0
- package/docs/agents/acceptance-test-matrix.md +193 -0
- package/docs/agents/agent-mux-adapter-contract.md +167 -0
- package/docs/agents/agent-mux-source-map.md +310 -0
- package/docs/agents/agent-run-memory-import-spec.md +256 -0
- package/docs/agents/agent-stack-management-spec.md +421 -0
- package/docs/agents/api-contract-spec.md +309 -0
- package/docs/agents/artifacts-writeback-spec.md +145 -0
- package/docs/agents/chart-packaging-spec.md +128 -0
- package/docs/agents/ci-orchestration-spec.md +140 -0
- package/docs/agents/context-assembly-spec.md +219 -0
- package/docs/agents/controller-reconciliation-spec.md +255 -0
- package/docs/agents/crd-schema-spec.md +315 -0
- package/docs/agents/decision-log-open-questions.md +169 -0
- package/docs/agents/developer-implementation-checklist.md +329 -0
- package/docs/agents/dispatching-design.md +262 -0
- package/docs/agents/gaps-agent-mux-to-kradle-crds.md +298 -0
- package/docs/agents/glossary.md +66 -0
- package/docs/agents/implementation-blueprint.md +324 -0
- package/docs/agents/implementation-rollout-slices.md +251 -0
- package/docs/agents/memory-context-integration-spec.md +194 -0
- package/docs/agents/memory-ontology-schema-spec.md +253 -0
- package/docs/agents/memory-operations-runbook.md +121 -0
- package/docs/agents/mvp-vertical-slice-spec.md +146 -0
- package/docs/agents/observability-audit-spec.md +265 -0
- package/docs/agents/operator-runbook.md +174 -0
- package/docs/agents/org-memory-api-payload-examples.md +333 -0
- package/docs/agents/org-memory-controller-sequence-spec.md +181 -0
- package/docs/agents/org-memory-e2e-fixture-plan.md +161 -0
- package/docs/agents/org-memory-ui-implementation-map.md +114 -0
- package/docs/agents/org-memory-vertical-slice-spec.md +168 -0
- package/docs/agents/org-resource-model-delta-spec.md +111 -0
- package/docs/agents/org-route-resource-model-spec.md +183 -0
- package/docs/agents/org-scoping-namespace-spec.md +114 -0
- package/docs/agents/rbac-secrets-management-spec.md +406 -0
- package/docs/agents/repository-page-integration-spec.md +255 -0
- package/docs/agents/resource-contract-examples.md +808 -0
- package/docs/agents/resource-relationship-map.md +190 -0
- package/docs/agents/security-threat-model.md +188 -0
- package/docs/agents/shared-memory-company-brain-spec.md +358 -0
- package/docs/agents/storage-migration-spec.md +168 -0
- package/docs/agents/subagent-orchestration-spec.md +152 -0
- package/docs/agents/system-overview.md +88 -0
- package/docs/agents/tools-mcp-skills-spec.md +189 -0
- package/docs/agents/traceability-matrix.md +79 -0
- package/docs/agents/ui-flow-spec.md +211 -0
- package/docs/agents/ui-ux-system-spec.md +426 -0
- package/docs/agents/workspace-lifecycle-spec.md +166 -0
- package/docs/architecture-spec.md +78 -0
- package/docs/architecture-v2.md +2759 -0
- package/docs/components/control-plane.md +78 -0
- package/docs/components/data-plane.md +69 -0
- package/docs/components/hooks-events.md +67 -0
- package/docs/components/identity-rbac-policy.md +73 -0
- package/docs/components/kubevela-oam.md +70 -0
- package/docs/components/operations-publishing.md +81 -0
- package/docs/components/runners-ci.md +66 -0
- package/docs/components/web-ui.md +94 -0
- package/docs/crd-behaviors-and-relationships.md +3926 -0
- package/docs/external/README.md +47 -0
- package/docs/external/bidirectional-sync-design.md +134 -0
- package/docs/external/cicd-interface.md +64 -0
- package/docs/external/external-backend-controllers.md +170 -0
- package/docs/external/external-backend-crds.md +234 -0
- package/docs/external/external-backend-ui-spec.md +151 -0
- package/docs/external/external-backend-ux-flows.md +115 -0
- package/docs/external/external-object-mapping.md +125 -0
- package/docs/external/git-forge-interface.md +68 -0
- package/docs/external/github-integration-design.md +151 -0
- package/docs/external/issue-tracking-interface.md +66 -0
- package/docs/external/provider-capability-manifests.md +204 -0
- package/docs/external/provider-catalog.md +139 -0
- package/docs/external/provider-rollout-testing.md +78 -0
- package/docs/external/research-results.md +48 -0
- package/docs/external/security-auth-permissions.md +81 -0
- package/docs/external/sync-state-machines.md +108 -0
- package/docs/external/unified-external-backend-model.md +107 -0
- package/docs/external/user-facing-changes.md +67 -0
- package/docs/gaps.md +161 -0
- package/docs/install.md +94 -0
- package/docs/integration-and-design-decisions.md +1530 -0
- package/docs/kradle-design.md +334 -0
- package/docs/local-minikube.md +55 -0
- package/docs/ontology/README.md +32 -0
- package/docs/ontology/bounded-contexts.md +29 -0
- package/docs/ontology/events-and-hooks.md +32 -0
- package/docs/ontology/oam-kubevela.md +32 -0
- package/docs/ontology/operations-and-release.md +25 -0
- package/docs/ontology/personas-and-actors.md +32 -0
- package/docs/ontology/policies-and-invariants.md +33 -0
- package/docs/ontology/problem-space.md +30 -0
- package/docs/ontology/resource-contracts.md +40 -0
- package/docs/ontology/resource-taxonomy.md +42 -0
- package/docs/ontology/runners-and-ci.md +29 -0
- package/docs/ontology/solution-space.md +24 -0
- package/docs/ontology/storage-and-data-boundaries.md +29 -0
- package/docs/ontology/validation-matrix.md +24 -0
- package/docs/ontology/web-ui-excellent-flows.md +32 -0
- package/docs/ontology/workflows.md +39 -0
- package/docs/ontology/world.md +35 -0
- package/docs/openapi.yaml +1291 -0
- package/docs/product-requirements.md +62 -0
- package/docs/requirements-v2.md +235 -0
- package/docs/roadmap-mvp.md +87 -0
- package/docs/sdk-api-reference.md +1108 -0
- package/docs/system-requirements.md +90 -0
- package/docs/system-spec-v2.md +1230 -0
- package/docs/tests/README.md +53 -0
- package/docs/tests/agent-qa-plan.md +63 -0
- package/docs/tests/browser-ui-tests.md +62 -0
- package/docs/tests/ci-quality-gates.md +48 -0
- package/docs/tests/coverage-model.md +64 -0
- package/docs/tests/e2e-scenario-tests.md +53 -0
- package/docs/tests/fixtures-test-data.md +63 -0
- package/docs/tests/observability-reliability-tests.md +54 -0
- package/docs/tests/product-test-matrix.md +145 -0
- package/docs/tests/qa-adoption-roadmap.md +130 -0
- package/docs/tests/qa-automation-plan.md +101 -0
- package/docs/tests/security-compliance-tests.md +57 -0
- package/docs/tests/test-framework-tools.md +88 -0
- package/docs/tests/test-suite-layout.md +121 -0
- package/docs/tests/unit-integration-tests.md +48 -0
- package/docs/todo-kyverno +714 -0
- package/docs/todos.md +4 -0
- package/docs/user-stories.md +78 -0
- package/docs/web-console-spec.md +533 -0
- package/examples/minikube-demo.yaml +190 -0
- package/examples/oam-application.yaml +23 -0
- package/examples/policy-kyverno-pr-title.yaml +18 -0
- package/package.json +66 -0
- package/scripts/build.mjs +29 -0
- package/scripts/setup-minikube.mjs +65 -0
- package/scripts/smoke.mjs +37 -0
- package/scripts/validate-doc-coverage.mjs +152 -0
- package/scripts/validate-package.mjs +95 -0
- package/scripts/validate-ui.mjs +305 -0
- package/src/agent-adapter-controller.js +169 -0
- package/src/agent-approval-controller.js +170 -0
- package/src/agent-context-bundles.js +242 -0
- package/src/agent-dispatch-controller.js +549 -0
- package/src/agent-gateway-config-controller.js +147 -0
- package/src/agent-identity-migration.js +115 -0
- package/src/agent-memory-controller.js +357 -0
- package/src/agent-memory-import.js +327 -0
- package/src/agent-memory-query.js +292 -0
- package/src/agent-memory-repository-source-controller.js +255 -0
- package/src/agent-mux-client.js +589 -0
- package/src/agent-permission-review.js +250 -0
- package/src/agent-persona-controller.js +135 -0
- package/src/agent-project-controller.js +117 -0
- package/src/agent-prompt-composition.js +55 -0
- package/src/agent-provider-config-controller.js +151 -0
- package/src/agent-secret-config-grant-controller.js +282 -0
- package/src/agent-session-transcript-controller.js +189 -0
- package/src/agent-stack-controller.js +421 -0
- package/src/agent-subagent-controller.js +160 -0
- package/src/agent-transport-binding-controller.js +121 -0
- package/src/agent-trigger-controller.js +387 -0
- package/src/agent-workspace-controller.js +702 -0
- package/src/agent-writeback-controller.js +302 -0
- package/src/api-controller.js +621 -0
- package/src/argocd-gitops.js +43 -0
- package/src/artifact-registry-controller.js +542 -0
- package/src/assistant-runtime.js +284 -0
- package/src/async-controller.js +207 -0
- package/src/audit-controller.js +191 -0
- package/src/auth.js +310 -0
- package/src/component-catalog.js +41 -0
- package/src/control-plane.js +136 -0
- package/src/controller-client.js +112 -0
- package/src/controller-ui.js +620 -0
- package/src/data-plane.js +179 -0
- package/src/event-bus.js +397 -0
- package/src/external/conflict-controller.js +225 -0
- package/src/external/github/auth.js +96 -0
- package/src/external/github/cicd.js +180 -0
- package/src/external/github/git-forge.js +240 -0
- package/src/external/github/index.js +144 -0
- package/src/external/github/issue-tracking.js +163 -0
- package/src/external/provider-adapter.js +161 -0
- package/src/external/provider-resource-factory.js +221 -0
- package/src/external/sync-controller.js +235 -0
- package/src/external/webhook-controller.js +144 -0
- package/src/external/write-controller.js +283 -0
- package/src/gitea-backend.js +131 -0
- package/src/gitea-service.js +173 -0
- package/src/handoff.js +98 -0
- package/src/health-probes.js +134 -0
- package/src/hooks-events.js +63 -0
- package/src/hooks-lifecycle.js +117 -0
- package/src/http-server.js +409 -0
- package/src/identity-policy.js +86 -0
- package/src/index.js +71 -0
- package/src/jitsi-agent-bridge.js +141 -0
- package/src/jitsi-meeting-controller.js +291 -0
- package/src/jitsi-sync-controller.js +198 -0
- package/src/kradle-inference-service-controller.js +246 -0
- package/src/kubernetes-controller-async.js +531 -0
- package/src/kubernetes-controller.js +904 -0
- package/src/kubernetes-resource-gateway.js +48 -0
- package/src/model-route-controller.js +364 -0
- package/src/notification-controller.js +178 -0
- package/src/operations.js +112 -0
- package/src/org-scoping.js +5 -0
- package/src/resource-model.js +282 -0
- package/src/runner-controller.js +272 -0
- package/src/runners-ci.js +48 -0
- package/src/runtime.js +196 -0
- package/src/snapshot-cache.js +157 -0
- package/src/virtual-model-controller.js +538 -0
- package/src/virtual-model-hook-bridge.js +200 -0
- package/src/web-ui.js +40 -0
- package/tests/agent-adapter-controller.test.js +361 -0
- package/tests/agent-approval-controller.test.js +173 -0
- package/tests/agent-context-bundles.test.js +278 -0
- package/tests/agent-dispatch-controller.test.js +679 -0
- package/tests/agent-gateway-config-controller.test.js +386 -0
- package/tests/agent-identity-migration.test.js +87 -0
- package/tests/agent-memory-controller.test.js +461 -0
- package/tests/agent-memory-import-snapshot.test.js +477 -0
- package/tests/agent-memory-query.test.js +404 -0
- package/tests/agent-memory-repository-source.test.js +514 -0
- package/tests/agent-mux-client.test.js +389 -0
- package/tests/agent-mux-integration.test.js +971 -0
- package/tests/agent-permission-review-v2.test.js +317 -0
- package/tests/agent-permission-review.test.js +209 -0
- package/tests/agent-persona-controller.test.js +127 -0
- package/tests/agent-project-controller.test.js +302 -0
- package/tests/agent-prompt-composition.test.js +76 -0
- package/tests/agent-provider-config-controller.test.js +376 -0
- package/tests/agent-resources.test.js +303 -0
- package/tests/agent-secret-config-grant.test.js +231 -0
- package/tests/agent-session-transcript-controller.test.js +499 -0
- package/tests/agent-stack-controller.test.js +283 -0
- package/tests/agent-subagent-controller.test.js +201 -0
- package/tests/agent-transport-binding-controller.test.js +294 -0
- package/tests/agent-trigger-controller.test.js +271 -0
- package/tests/agent-trigger-routes.test.js +190 -0
- package/tests/agent-trigger-sources.test.js +245 -0
- package/tests/agent-workspace-controller.test.js +181 -0
- package/tests/agent-writeback.test.js +292 -0
- package/tests/approval-persistence.test.js +171 -0
- package/tests/artifact-registry.test.js +511 -0
- package/tests/assistant-runtime.test.js +506 -0
- package/tests/async-controller.test.js +252 -0
- package/tests/audit-controller.test.js +227 -0
- package/tests/codespace-controller.test.js +318 -0
- package/tests/controller-client.test.js +133 -0
- package/tests/deployment.test.js +527 -0
- package/tests/e2e/lifecycle.test.js +120 -0
- package/tests/event-bus-integration.test.js +355 -0
- package/tests/external-github-forge.test.js +560 -0
- package/tests/external-github-issues-cicd.test.js +520 -0
- package/tests/external-integration.test.js +470 -0
- package/tests/external-persistence.test.js +415 -0
- package/tests/external-provider-adapter.test.js +365 -0
- package/tests/external-resource-model.test.js +223 -0
- package/tests/external-webhook-sync.test.js +287 -0
- package/tests/external-write-conflict.test.js +353 -0
- package/tests/gitea-service.test.js +253 -0
- package/tests/health-check-real.test.js +165 -0
- package/tests/health-probes.test.js +90 -0
- package/tests/hooks-lifecycle.test.js +364 -0
- package/tests/integration/full-flow.test.js +266 -0
- package/tests/jitsi-agent-bridge.test.js +119 -0
- package/tests/jitsi-helm-integration.test.js +77 -0
- package/tests/jitsi-meeting-controller.test.js +170 -0
- package/tests/jitsi-resource-model.test.js +73 -0
- package/tests/jitsi-sync-controller.test.js +112 -0
- package/tests/kradle-inference-service.test.js +689 -0
- package/tests/kradle.test.js +779 -0
- package/tests/memory-search-wiring.test.js +270 -0
- package/tests/model-route-controller.test.js +733 -0
- package/tests/notification-controller.test.js +196 -0
- package/tests/notification-integration.test.js +179 -0
- package/tests/org-scoping.test.js +687 -0
- package/tests/runner-controller.test.js +327 -0
- package/tests/runner-integration.test.js +231 -0
- package/tests/session-cookie-hmac.test.js +151 -0
- package/tests/snapshot-performance.test.js +315 -0
- package/tests/sse-events.test.js +107 -0
- package/tests/virtual-model-controller.test.js +877 -0
- package/tests/virtual-model-hook-bridge.test.js +384 -0
- package/tests/webhook-trigger.test.js +198 -0
- package/tests/workspace-volumes.test.js +312 -0
- package/tests/writeback-persistence.test.js +207 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// GitHub Git Forge implementation — Slice 3.3a
|
|
2
|
+
// Implements the gitForge interface contract:
|
|
3
|
+
// listRepositories, getPullRequest, createPullRequest, mergePullRequest,
|
|
4
|
+
// listRefs, syncDeployKeys, syncBranchProtection
|
|
5
|
+
//
|
|
6
|
+
// All HTTP calls are injected via fetchImpl for full testability.
|
|
7
|
+
|
|
8
|
+
const GITHUB_API = 'https://api.github.com';
|
|
9
|
+
const VALID_MERGE_METHODS = new Set(['merge', 'squash', 'rebase']);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {{ name: string, fullName: string, private: boolean, defaultBranch: string, cloneUrl: string }} NormalizedRepo
|
|
13
|
+
* @typedef {{ number: number, title: string, state: string, head: object, base: object, body: string, merged: boolean, htmlUrl: string }} NormalizedPR
|
|
14
|
+
* @typedef {{ name: string, sha: string, protected: boolean }} NormalizedBranch
|
|
15
|
+
* @typedef {{ name: string, sha: string }} NormalizedTag
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* GitHub implementation of the git forge interface.
|
|
20
|
+
*
|
|
21
|
+
* @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
|
|
22
|
+
*/
|
|
23
|
+
export class GitHubGitForge {
|
|
24
|
+
constructor({ owner, installationToken, fetchImpl = globalThis.fetch } = {}) {
|
|
25
|
+
if (!owner) throw new Error('GitHubGitForge: owner (org or user) is required');
|
|
26
|
+
if (!installationToken) throw new Error('GitHubGitForge: installationToken is required');
|
|
27
|
+
if (!fetchImpl) throw new Error('GitHubGitForge: a fetch implementation is required');
|
|
28
|
+
|
|
29
|
+
this.role = 'github-git-forge';
|
|
30
|
+
this._owner = owner;
|
|
31
|
+
this._token = installationToken;
|
|
32
|
+
this._fetch = fetchImpl;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Internal helpers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
_headers() {
|
|
40
|
+
return {
|
|
41
|
+
Accept: 'application/vnd.github+json',
|
|
42
|
+
Authorization: `Bearer ${this._token}`,
|
|
43
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
44
|
+
'Content-Type': 'application/json'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async _request(method, path, body) {
|
|
49
|
+
const url = `${GITHUB_API}${path}`;
|
|
50
|
+
const options = {
|
|
51
|
+
method,
|
|
52
|
+
headers: this._headers(),
|
|
53
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
54
|
+
};
|
|
55
|
+
const response = await this._fetch(url, options);
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
throw new Error(`GitHub ${method} ${path} failed with status ${response.status}`);
|
|
58
|
+
}
|
|
59
|
+
if (response.status === 204) return null;
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_repoPath(repo, suffix = '') {
|
|
64
|
+
return `/repos/${encodeURIComponent(this._owner)}/${encodeURIComponent(repo)}${suffix}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Interface methods
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* List all repositories for the installation.
|
|
73
|
+
* @returns {Promise<NormalizedRepo[]>}
|
|
74
|
+
*/
|
|
75
|
+
async listRepositories() {
|
|
76
|
+
const data = await this._request('GET', `/installation/repositories`);
|
|
77
|
+
const repos = data?.repositories ?? data ?? [];
|
|
78
|
+
return repos.map(r => ({
|
|
79
|
+
id: r.id,
|
|
80
|
+
name: r.name,
|
|
81
|
+
fullName: r.full_name,
|
|
82
|
+
private: r.private,
|
|
83
|
+
defaultBranch: r.default_branch,
|
|
84
|
+
cloneUrl: r.clone_url,
|
|
85
|
+
sshUrl: r.ssh_url
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get a single pull request by number.
|
|
91
|
+
* @param {{ repo: string, pullNumber: number }} opts
|
|
92
|
+
* @returns {Promise<NormalizedPR>}
|
|
93
|
+
*/
|
|
94
|
+
async getPullRequest({ repo, pullNumber }) {
|
|
95
|
+
const data = await this._request('GET', this._repoPath(repo, `/pulls/${pullNumber}`));
|
|
96
|
+
return this._normalizePR(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create a pull request.
|
|
101
|
+
* @param {{ repo: string, title: string, head: string, base: string, body?: string }} opts
|
|
102
|
+
* @returns {Promise<NormalizedPR>}
|
|
103
|
+
*/
|
|
104
|
+
async createPullRequest({ repo, title, head, base, body = '' }) {
|
|
105
|
+
const data = await this._request('POST', this._repoPath(repo, '/pulls'), {
|
|
106
|
+
title,
|
|
107
|
+
head,
|
|
108
|
+
base,
|
|
109
|
+
body
|
|
110
|
+
});
|
|
111
|
+
return this._normalizePR(data);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Merge a pull request.
|
|
116
|
+
* @param {{ repo: string, pullNumber: number, mergeMethod?: 'merge'|'squash'|'rebase', commitTitle?: string }} opts
|
|
117
|
+
* @returns {Promise<{ merged: boolean, sha: string, message: string }>}
|
|
118
|
+
*/
|
|
119
|
+
async mergePullRequest({ repo, pullNumber, mergeMethod = 'merge', commitTitle } = {}) {
|
|
120
|
+
if (!VALID_MERGE_METHODS.has(mergeMethod)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`GitHubGitForge.mergePullRequest: invalid mergeMethod "${mergeMethod}". ` +
|
|
123
|
+
`Valid values are: ${[...VALID_MERGE_METHODS].join(', ')}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const payload = { merge_method: mergeMethod };
|
|
127
|
+
if (commitTitle) payload.commit_title = commitTitle;
|
|
128
|
+
const data = await this._request('PUT', this._repoPath(repo, `/pulls/${pullNumber}/merge`), payload);
|
|
129
|
+
return {
|
|
130
|
+
merged: data?.merged ?? true,
|
|
131
|
+
sha: data?.sha ?? '',
|
|
132
|
+
message: data?.message ?? ''
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* List branches and tags for a repository.
|
|
138
|
+
* @param {{ repo: string }} opts
|
|
139
|
+
* @returns {Promise<{ branches: NormalizedBranch[], tags: NormalizedTag[] }>}
|
|
140
|
+
*/
|
|
141
|
+
async listRefs({ repo }) {
|
|
142
|
+
const [branchData, tagData] = await Promise.all([
|
|
143
|
+
this._request('GET', this._repoPath(repo, '/branches')),
|
|
144
|
+
this._request('GET', this._repoPath(repo, '/tags'))
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const branches = (branchData ?? []).map(b => ({
|
|
148
|
+
name: b.name,
|
|
149
|
+
sha: b.commit?.sha ?? b.sha,
|
|
150
|
+
protected: b.protected ?? false
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
const tags = (tagData ?? []).map(t => ({
|
|
154
|
+
name: t.name,
|
|
155
|
+
sha: t.commit?.sha ?? t.sha
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
return { branches, tags };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Sync deploy keys: add missing, remove extra.
|
|
163
|
+
* @param {{ repo: string, desiredKeys: Array<{ title: string, key: string, readOnly: boolean }> }} opts
|
|
164
|
+
* @returns {Promise<{ added: number, removed: number }>}
|
|
165
|
+
*/
|
|
166
|
+
async syncDeployKeys({ repo, desiredKeys = [] }) {
|
|
167
|
+
const current = await this._request('GET', this._repoPath(repo, '/keys'));
|
|
168
|
+
const currentList = current ?? [];
|
|
169
|
+
|
|
170
|
+
// Find keys to remove (title not in desired)
|
|
171
|
+
const desiredTitles = new Set(desiredKeys.map(k => k.title));
|
|
172
|
+
const toRemove = currentList.filter(k => !desiredTitles.has(k.title));
|
|
173
|
+
|
|
174
|
+
// Find keys to add (title not in current)
|
|
175
|
+
const currentTitles = new Set(currentList.map(k => k.title));
|
|
176
|
+
const toAdd = desiredKeys.filter(k => !currentTitles.has(k.title));
|
|
177
|
+
|
|
178
|
+
// Perform deletions
|
|
179
|
+
await Promise.all(toRemove.map(k =>
|
|
180
|
+
this._request('DELETE', this._repoPath(repo, `/keys/${k.id}`))
|
|
181
|
+
));
|
|
182
|
+
|
|
183
|
+
// Perform additions
|
|
184
|
+
await Promise.all(toAdd.map(k =>
|
|
185
|
+
this._request('POST', this._repoPath(repo, '/keys'), {
|
|
186
|
+
title: k.title,
|
|
187
|
+
key: k.key,
|
|
188
|
+
read_only: k.readOnly ?? true
|
|
189
|
+
})
|
|
190
|
+
));
|
|
191
|
+
|
|
192
|
+
return { added: toAdd.length, removed: toRemove.length };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Sync branch protection rules.
|
|
197
|
+
* @param {{ repo: string, branch: string, requiredReviews?: number, requiredStatusChecks?: string[], dismissStaleReviews?: boolean, enforceAdmins?: boolean }} opts
|
|
198
|
+
* @returns {Promise<object>}
|
|
199
|
+
*/
|
|
200
|
+
async syncBranchProtection({
|
|
201
|
+
repo,
|
|
202
|
+
branch = 'main',
|
|
203
|
+
requiredReviews = 1,
|
|
204
|
+
requiredStatusChecks = [],
|
|
205
|
+
dismissStaleReviews = false,
|
|
206
|
+
enforceAdmins = false
|
|
207
|
+
} = {}) {
|
|
208
|
+
const payload = {
|
|
209
|
+
required_status_checks: requiredStatusChecks.length > 0
|
|
210
|
+
? { strict: true, contexts: requiredStatusChecks }
|
|
211
|
+
: null,
|
|
212
|
+
enforce_admins: enforceAdmins,
|
|
213
|
+
required_pull_request_reviews: {
|
|
214
|
+
dismiss_stale_reviews: dismissStaleReviews,
|
|
215
|
+
require_code_owner_reviews: false,
|
|
216
|
+
required_approving_review_count: requiredReviews
|
|
217
|
+
},
|
|
218
|
+
restrictions: null
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return this._request('PUT', this._repoPath(repo, `/branches/${encodeURIComponent(branch)}/protection`), payload);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Internal normalizer
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
_normalizePR(data) {
|
|
229
|
+
return {
|
|
230
|
+
number: data.number,
|
|
231
|
+
title: data.title,
|
|
232
|
+
state: data.state,
|
|
233
|
+
head: { ref: data.head?.ref, sha: data.head?.sha },
|
|
234
|
+
base: { ref: data.base?.ref, sha: data.base?.sha },
|
|
235
|
+
body: data.body ?? '',
|
|
236
|
+
merged: data.merged ?? false,
|
|
237
|
+
htmlUrl: data.html_url ?? ''
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// GitHub External Provider — Slice 3.3a / 3.3b
|
|
2
|
+
// Entry point for the GitHub provider adapter.
|
|
3
|
+
// Re-exports auth helpers, git forge class, issue tracking, CI/CD, boundary objects, and provider factory.
|
|
4
|
+
|
|
5
|
+
export { createGitHubJwt, exchangeInstallationToken } from './auth.js';
|
|
6
|
+
|
|
7
|
+
import { GitHubGitForge } from './git-forge.js';
|
|
8
|
+
import { GitHubIssueTracking } from './issue-tracking.js';
|
|
9
|
+
import { GitHubCicd } from './cicd.js';
|
|
10
|
+
|
|
11
|
+
export { GitHubGitForge };
|
|
12
|
+
export { GitHubIssueTracking };
|
|
13
|
+
export { GitHubCicd };
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Boundary declarations
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export const GITHUB_GIT_FORGE_BOUNDARY = Object.freeze({
|
|
20
|
+
role: 'github-git-forge',
|
|
21
|
+
scope: 'GitHub App authentication (JWT signing, installation token exchange) and Git forge operations (repositories, PRs, refs, deploy keys, branch protection)',
|
|
22
|
+
owns: [
|
|
23
|
+
'GitHub App JWT creation',
|
|
24
|
+
'installation token exchange',
|
|
25
|
+
'repository listing',
|
|
26
|
+
'pull request lifecycle',
|
|
27
|
+
'ref enumeration',
|
|
28
|
+
'deploy key synchronisation',
|
|
29
|
+
'branch protection synchronisation'
|
|
30
|
+
],
|
|
31
|
+
delegatesTo: [],
|
|
32
|
+
mustNotOwn: [
|
|
33
|
+
'GitHub secret storage',
|
|
34
|
+
'Kubernetes resources',
|
|
35
|
+
'CI pipeline execution',
|
|
36
|
+
'webhook delivery'
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const GITHUB_ISSUE_TRACKING_BOUNDARY = Object.freeze({
|
|
41
|
+
role: 'github-issue-tracking',
|
|
42
|
+
scope: 'GitHub Issues API: listing, creating, updating, closing issues and managing issue comments',
|
|
43
|
+
owns: [
|
|
44
|
+
'issue listing',
|
|
45
|
+
'issue creation',
|
|
46
|
+
'issue updates',
|
|
47
|
+
'issue closing',
|
|
48
|
+
'comment listing',
|
|
49
|
+
'comment creation'
|
|
50
|
+
],
|
|
51
|
+
delegatesTo: [],
|
|
52
|
+
mustNotOwn: [
|
|
53
|
+
'GitHub secret storage',
|
|
54
|
+
'Kubernetes resources',
|
|
55
|
+
'CI pipeline execution',
|
|
56
|
+
'webhook delivery',
|
|
57
|
+
'pull requests',
|
|
58
|
+
'branch protection'
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const GITHUB_CICD_BOUNDARY = Object.freeze({
|
|
63
|
+
role: 'github-cicd',
|
|
64
|
+
scope: 'GitHub Actions API: workflow runs, jobs, rerun/cancel operations, and check runs',
|
|
65
|
+
owns: [
|
|
66
|
+
'workflow run listing',
|
|
67
|
+
'job listing',
|
|
68
|
+
'workflow rerun',
|
|
69
|
+
'workflow cancellation',
|
|
70
|
+
'check run creation',
|
|
71
|
+
'check run updates'
|
|
72
|
+
],
|
|
73
|
+
delegatesTo: [],
|
|
74
|
+
mustNotOwn: [
|
|
75
|
+
'GitHub secret storage',
|
|
76
|
+
'Kubernetes resources',
|
|
77
|
+
'webhook delivery',
|
|
78
|
+
'issue tracking',
|
|
79
|
+
'pull requests'
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Provider factory
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Create a GitHub ExternalProviderAdapter.
|
|
89
|
+
*
|
|
90
|
+
* Returns a lightweight descriptor that carries GitHub App credentials and
|
|
91
|
+
* exposes factory methods for constructing GitHubGitForge, GitHubIssueTracking,
|
|
92
|
+
* and GitHubCicd instances bound to specific installation tokens.
|
|
93
|
+
*
|
|
94
|
+
* @param {{ appId: string, privateKey: string, installationId?: string, fetchImpl?: Function }} opts
|
|
95
|
+
* @returns {{ type: string, config: object, createForge: Function, createIssueTracker: Function, createCicd: Function }}
|
|
96
|
+
*/
|
|
97
|
+
export function createGitHubProvider({ appId, privateKey, installationId, fetchImpl } = {}) {
|
|
98
|
+
if (!appId) throw new Error('createGitHubProvider: appId is required');
|
|
99
|
+
if (!privateKey) throw new Error('createGitHubProvider: privateKey is required');
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
type: 'github',
|
|
103
|
+
config: Object.freeze({ appId, installationId }),
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Construct a GitHubGitForge for the given owner and installation token.
|
|
107
|
+
* @param {{ owner: string, installationToken: string, fetchImpl?: Function }} forgeOpts
|
|
108
|
+
* @returns {GitHubGitForge}
|
|
109
|
+
*/
|
|
110
|
+
createForge({ owner, installationToken, fetchImpl: forgeFetch } = {}) {
|
|
111
|
+
return new GitHubGitForge({
|
|
112
|
+
owner,
|
|
113
|
+
installationToken,
|
|
114
|
+
fetchImpl: forgeFetch ?? fetchImpl ?? globalThis.fetch
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Construct a GitHubIssueTracking for the given owner and installation token.
|
|
120
|
+
* @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
|
|
121
|
+
* @returns {GitHubIssueTracking}
|
|
122
|
+
*/
|
|
123
|
+
createIssueTracker({ owner, installationToken, fetchImpl: trackerFetch } = {}) {
|
|
124
|
+
return new GitHubIssueTracking({
|
|
125
|
+
owner,
|
|
126
|
+
installationToken,
|
|
127
|
+
fetchImpl: trackerFetch ?? fetchImpl ?? globalThis.fetch
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Construct a GitHubCicd for the given owner and installation token.
|
|
133
|
+
* @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
|
|
134
|
+
* @returns {GitHubCicd}
|
|
135
|
+
*/
|
|
136
|
+
createCicd({ owner, installationToken, fetchImpl: cicdFetch } = {}) {
|
|
137
|
+
return new GitHubCicd({
|
|
138
|
+
owner,
|
|
139
|
+
installationToken,
|
|
140
|
+
fetchImpl: cicdFetch ?? fetchImpl ?? globalThis.fetch
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// GitHub Issue Tracking implementation — Slice 3.3b
|
|
2
|
+
// Implements the issueTracking interface contract:
|
|
3
|
+
// listIssues, createIssue, updateIssue, closeIssue, listComments, createComment
|
|
4
|
+
//
|
|
5
|
+
// All HTTP calls are injected via fetchImpl for full testability.
|
|
6
|
+
|
|
7
|
+
const GITHUB_API = 'https://api.github.com';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ number: number, title: string, state: string, body: string, labels: string[], author: string, htmlUrl: string }} NormalizedIssue
|
|
11
|
+
* @typedef {{ id: number, body: string, author: string, createdAt: string, htmlUrl: string }} NormalizedComment
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GitHub implementation of the issueTracking interface.
|
|
16
|
+
*
|
|
17
|
+
* @param {{ owner: string, installationToken: string, fetchImpl?: Function }} opts
|
|
18
|
+
*/
|
|
19
|
+
export class GitHubIssueTracking {
|
|
20
|
+
constructor({ owner, installationToken, fetchImpl = globalThis.fetch } = {}) {
|
|
21
|
+
if (!owner) throw new Error('GitHubIssueTracking: owner (org or user) is required');
|
|
22
|
+
if (!installationToken) throw new Error('GitHubIssueTracking: installationToken is required');
|
|
23
|
+
if (!fetchImpl) throw new Error('GitHubIssueTracking: a fetch implementation is required');
|
|
24
|
+
|
|
25
|
+
this.role = 'github-issue-tracking';
|
|
26
|
+
this._owner = owner;
|
|
27
|
+
this._token = installationToken;
|
|
28
|
+
this._fetch = fetchImpl;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Internal helpers
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
_headers() {
|
|
36
|
+
return {
|
|
37
|
+
Accept: 'application/vnd.github+json',
|
|
38
|
+
Authorization: `Bearer ${this._token}`,
|
|
39
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async _request(method, path, body) {
|
|
45
|
+
const url = `${GITHUB_API}${path}`;
|
|
46
|
+
const options = {
|
|
47
|
+
method,
|
|
48
|
+
headers: this._headers(),
|
|
49
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
50
|
+
};
|
|
51
|
+
const response = await this._fetch(url, options);
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`GitHub ${method} ${path} failed with status ${response.status}`);
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 204) return null;
|
|
56
|
+
return response.json();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_repoPath(repo, suffix = '') {
|
|
60
|
+
return `/repos/${encodeURIComponent(this._owner)}/${encodeURIComponent(repo)}${suffix}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Normalizers
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
_normalizeIssue(data) {
|
|
68
|
+
return {
|
|
69
|
+
id: data.id,
|
|
70
|
+
number: data.number,
|
|
71
|
+
title: data.title,
|
|
72
|
+
state: data.state,
|
|
73
|
+
body: data.body ?? '',
|
|
74
|
+
labels: (data.labels ?? []).map(l => (typeof l === 'string' ? l : l.name)),
|
|
75
|
+
author: data.user?.login ?? '',
|
|
76
|
+
htmlUrl: data.html_url ?? ''
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_normalizeComment(data) {
|
|
81
|
+
return {
|
|
82
|
+
id: data.id,
|
|
83
|
+
body: data.body ?? '',
|
|
84
|
+
author: data.user?.login ?? '',
|
|
85
|
+
createdAt: data.created_at ?? '',
|
|
86
|
+
htmlUrl: data.html_url ?? ''
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Interface methods
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* List issues for a repository.
|
|
96
|
+
* @param {{ repo: string, state?: 'open'|'closed'|'all' }} opts
|
|
97
|
+
* @returns {Promise<NormalizedIssue[]>}
|
|
98
|
+
*/
|
|
99
|
+
async listIssues({ repo, state = 'open' } = {}) {
|
|
100
|
+
const data = await this._request('GET', this._repoPath(repo, `/issues?state=${state}`));
|
|
101
|
+
const issues = data ?? [];
|
|
102
|
+
return issues.map(i => this._normalizeIssue(i));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create an issue.
|
|
107
|
+
* @param {{ repo: string, title: string, body?: string, labels?: string[] }} opts
|
|
108
|
+
* @returns {Promise<NormalizedIssue>}
|
|
109
|
+
*/
|
|
110
|
+
async createIssue({ repo, title, body = '', labels = [] }) {
|
|
111
|
+
const data = await this._request('POST', this._repoPath(repo, '/issues'), {
|
|
112
|
+
title,
|
|
113
|
+
body,
|
|
114
|
+
...(labels.length > 0 ? { labels } : {})
|
|
115
|
+
});
|
|
116
|
+
return this._normalizeIssue(data);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update an issue (title, body, labels).
|
|
121
|
+
* @param {{ repo: string, issueNumber: number, title?: string, body?: string, labels?: string[] }} opts
|
|
122
|
+
* @returns {Promise<NormalizedIssue>}
|
|
123
|
+
*/
|
|
124
|
+
async updateIssue({ repo, issueNumber, title, body, labels } = {}) {
|
|
125
|
+
const payload = {};
|
|
126
|
+
if (title !== undefined) payload.title = title;
|
|
127
|
+
if (body !== undefined) payload.body = body;
|
|
128
|
+
if (labels !== undefined) payload.labels = labels;
|
|
129
|
+
const data = await this._request('PATCH', this._repoPath(repo, `/issues/${issueNumber}`), payload);
|
|
130
|
+
return this._normalizeIssue(data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Close an issue by setting its state to 'closed'.
|
|
135
|
+
* @param {{ repo: string, issueNumber: number }} opts
|
|
136
|
+
* @returns {Promise<NormalizedIssue>}
|
|
137
|
+
*/
|
|
138
|
+
async closeIssue({ repo, issueNumber } = {}) {
|
|
139
|
+
const data = await this._request('PATCH', this._repoPath(repo, `/issues/${issueNumber}`), { state: 'closed' });
|
|
140
|
+
return this._normalizeIssue(data);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* List comments for an issue.
|
|
145
|
+
* @param {{ repo: string, issueNumber: number }} opts
|
|
146
|
+
* @returns {Promise<NormalizedComment[]>}
|
|
147
|
+
*/
|
|
148
|
+
async listComments({ repo, issueNumber } = {}) {
|
|
149
|
+
const data = await this._request('GET', this._repoPath(repo, `/issues/${issueNumber}/comments`));
|
|
150
|
+
const comments = data ?? [];
|
|
151
|
+
return comments.map(c => this._normalizeComment(c));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Create a comment on an issue.
|
|
156
|
+
* @param {{ repo: string, issueNumber: number, body: string }} opts
|
|
157
|
+
* @returns {Promise<NormalizedComment>}
|
|
158
|
+
*/
|
|
159
|
+
async createComment({ repo, issueNumber, body }) {
|
|
160
|
+
const data = await this._request('POST', this._repoPath(repo, `/issues/${issueNumber}/comments`), { body });
|
|
161
|
+
return this._normalizeComment(data);
|
|
162
|
+
}
|
|
163
|
+
}
|