@remogram/core 0.1.0-beta.6 → 0.1.0-beta.8
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/auth-classes.js +3 -0
- package/change-request-merge-execute.js +139 -0
- package/contracts/envelope.js +7 -0
- package/contracts/errors.js +2 -0
- package/contracts/forge-error-fields.js +1 -0
- package/contracts/semantic-diff-facts.js +13 -12
- package/cr-inventory.js +7 -7
- package/forge-changes.js +2 -2
- package/forge-identity.js +42 -0
- package/index.js +12 -0
- package/package.json +1 -1
- package/pr-head-reconcile.js +3 -3
- package/ref-inventory.js +2 -2
- package/resolve.js +2 -0
- package/write-config.js +1 -1
package/auth-classes.js
CHANGED
|
@@ -24,6 +24,7 @@ export const API_PROVIDER_COMMAND_AUTH = {
|
|
|
24
24
|
cr_files: AUTH_CLASS.TOKEN_REQUIRED,
|
|
25
25
|
cr_comments: AUTH_CLASS.TOKEN_REQUIRED,
|
|
26
26
|
forge_changes: AUTH_CLASS.TOKEN_REQUIRED,
|
|
27
|
+
merge_execute: AUTH_CLASS.TOKEN_REQUIRED,
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
export function commandCapability(name, { implemented = true } = {}) {
|
|
@@ -41,6 +42,7 @@ export function apiProviderCommands({
|
|
|
41
42
|
crFilesImplemented = false,
|
|
42
43
|
crCommentsImplemented = false,
|
|
43
44
|
forgeChangesImplemented = false,
|
|
45
|
+
mergeExecuteImplemented = false,
|
|
44
46
|
} = {}) {
|
|
45
47
|
return Object.keys(API_PROVIDER_COMMAND_AUTH).map((name) => {
|
|
46
48
|
let implemented = true;
|
|
@@ -50,6 +52,7 @@ export function apiProviderCommands({
|
|
|
50
52
|
if (name === 'cr_files') implemented = crFilesImplemented;
|
|
51
53
|
if (name === 'cr_comments') implemented = crCommentsImplemented;
|
|
52
54
|
if (name === 'forge_changes') implemented = forgeChangesImplemented;
|
|
55
|
+
if (name === 'merge_execute') implemented = mergeExecuteImplemented;
|
|
53
56
|
return commandCapability(name, { implemented });
|
|
54
57
|
});
|
|
55
58
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { sanitizeField } from './caps.js';
|
|
2
|
+
import { mergeBlockersFromFacts } from './merge-blockers.js';
|
|
3
|
+
|
|
4
|
+
const SHA40 = /^[0-9a-f]{40}$/i;
|
|
5
|
+
|
|
6
|
+
export function mergeExecuteViewFacts(view) {
|
|
7
|
+
return {
|
|
8
|
+
sourceBranchRef: view.forge_source_branch_ref ?? view.head_ref ?? null,
|
|
9
|
+
sourceSha: view.forge_source_sha ?? view.head_sha ?? null,
|
|
10
|
+
targetSha: view.forge_target_sha ?? view.base_sha ?? null,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function mergeExecuteChecksFacts(checks) {
|
|
15
|
+
return {
|
|
16
|
+
sourceSha: checks.forge_source_sha ?? checks.head_sha ?? null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function assertExpectedSha(value, flagName) {
|
|
21
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
22
|
+
if (!SHA40.test(normalized)) {
|
|
23
|
+
throw Object.assign(new Error(`Invalid ${flagName}`), {
|
|
24
|
+
invalidArgs: `${flagName} must be a 40-character git SHA`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildMergeExecuteBeforeFacts(view, checks, mergePlanBody, forgeHeadRefSha = null) {
|
|
31
|
+
const viewFacts = mergeExecuteViewFacts(view);
|
|
32
|
+
const checksFacts = mergeExecuteChecksFacts(checks);
|
|
33
|
+
return {
|
|
34
|
+
base_sha: viewFacts.targetSha ?? null,
|
|
35
|
+
head_sha: viewFacts.sourceSha ?? null,
|
|
36
|
+
checks_head_sha: checksFacts.sourceSha ?? null,
|
|
37
|
+
forge_head_ref_sha: forgeHeadRefSha ?? null,
|
|
38
|
+
mergeability: view.mergeability ?? 'unknown',
|
|
39
|
+
checks_conclusion: checks.check_conclusion ?? 'unknown',
|
|
40
|
+
checks_truncated: checks.checks_truncated === true,
|
|
41
|
+
merge_plan_blockers: Array.isArray(mergePlanBody.blockers) ? [...mergePlanBody.blockers] : [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Collect merge-execute blockers before forge mutation.
|
|
47
|
+
* @returns {string[]} normalized blocker ids
|
|
48
|
+
*/
|
|
49
|
+
export function collectMergeExecuteBlockers(
|
|
50
|
+
view,
|
|
51
|
+
checks,
|
|
52
|
+
mergePlanBody,
|
|
53
|
+
expected,
|
|
54
|
+
{ forgeHeadRefSha } = {},
|
|
55
|
+
) {
|
|
56
|
+
const viewFacts = mergeExecuteViewFacts(view);
|
|
57
|
+
const checksFacts = mergeExecuteChecksFacts(checks);
|
|
58
|
+
const blockers = [];
|
|
59
|
+
const baseSha = viewFacts.targetSha ? String(viewFacts.targetSha).toLowerCase() : null;
|
|
60
|
+
const headSha = viewFacts.sourceSha ? String(viewFacts.sourceSha).toLowerCase() : null;
|
|
61
|
+
|
|
62
|
+
if (baseSha && expected.baseSha !== baseSha) blockers.push('base_sha_mismatch');
|
|
63
|
+
if (headSha && expected.headSha !== headSha) blockers.push('head_sha_mismatch');
|
|
64
|
+
|
|
65
|
+
const forgeHead = forgeHeadRefSha ? String(forgeHeadRefSha).toLowerCase() : null;
|
|
66
|
+
const checksHead = checksFacts.sourceSha ? String(checksFacts.sourceSha).toLowerCase() : null;
|
|
67
|
+
if (headSha && checksHead && headSha !== checksHead) blockers.push('checks_head_sha_mismatch');
|
|
68
|
+
if (headSha && forgeHead && headSha !== forgeHead) blockers.push('forge_pr_head_mismatch');
|
|
69
|
+
if (checksHead && forgeHead && checksHead !== forgeHead) blockers.push('checks_forge_head_mismatch');
|
|
70
|
+
if (forgeHead && forgeHead !== expected.headSha) blockers.push('head_ref_moved');
|
|
71
|
+
|
|
72
|
+
const planBlockers = mergeBlockersFromFacts(view, checks, {});
|
|
73
|
+
for (const blocker of planBlockers) {
|
|
74
|
+
if (!blockers.includes(blocker)) blockers.push(blocker);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (view.mergeability !== 'clean') {
|
|
78
|
+
if (view.mergeability === 'conflicted' && !blockers.includes('merge_conflict')) {
|
|
79
|
+
blockers.push('merge_conflict');
|
|
80
|
+
} else if (view.mergeability !== 'conflicted' && !blockers.includes('mergeability_not_clean')) {
|
|
81
|
+
blockers.push('mergeability_not_clean');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return blockers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildCrMergeBlockedBody({
|
|
89
|
+
prNumber,
|
|
90
|
+
expected,
|
|
91
|
+
before,
|
|
92
|
+
blockers,
|
|
93
|
+
}) {
|
|
94
|
+
return {
|
|
95
|
+
change_request: { number: prNumber },
|
|
96
|
+
expected: {
|
|
97
|
+
base_sha: expected.baseSha,
|
|
98
|
+
head_sha: expected.headSha,
|
|
99
|
+
},
|
|
100
|
+
before,
|
|
101
|
+
blockers,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildCrMergedBody({
|
|
106
|
+
prNumber,
|
|
107
|
+
expected,
|
|
108
|
+
before,
|
|
109
|
+
merge,
|
|
110
|
+
after,
|
|
111
|
+
}) {
|
|
112
|
+
return {
|
|
113
|
+
change_request: { number: prNumber, state: 'merged' },
|
|
114
|
+
expected: {
|
|
115
|
+
base_sha: expected.baseSha,
|
|
116
|
+
head_sha: expected.headSha,
|
|
117
|
+
},
|
|
118
|
+
before,
|
|
119
|
+
merge,
|
|
120
|
+
after,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildMergeExecuteAfterFacts(view, mergeResult = {}) {
|
|
125
|
+
const viewFacts = mergeExecuteViewFacts(view);
|
|
126
|
+
return {
|
|
127
|
+
state: 'merged',
|
|
128
|
+
base_sha: mergeResult.base_sha ?? viewFacts.targetSha ?? null,
|
|
129
|
+
head_sha: viewFacts.sourceSha ?? null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function buildMergeExecuteMergeFacts(method, providerResult = {}) {
|
|
134
|
+
return {
|
|
135
|
+
method: sanitizeField(method),
|
|
136
|
+
commit_sha: providerResult.commit_sha ?? null,
|
|
137
|
+
provider_status: providerResult.provider_status ?? null,
|
|
138
|
+
};
|
|
139
|
+
}
|
package/contracts/envelope.js
CHANGED
|
@@ -20,6 +20,8 @@ export const PACKET_TYPES = {
|
|
|
20
20
|
CR_FILES: 'cr_files',
|
|
21
21
|
CR_COMMENTS: 'cr_comments',
|
|
22
22
|
FORGE_CHANGES: 'forge_changes',
|
|
23
|
+
CR_MERGED: 'cr_merged',
|
|
24
|
+
CR_MERGE_BLOCKED: 'cr_merge_blocked',
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export const FORBIDDEN_PACKET_KEYS = new Set([
|
|
@@ -57,6 +59,11 @@ export function forgePacket(type, context, body = {}, error = null) {
|
|
|
57
59
|
ok: error == null,
|
|
58
60
|
};
|
|
59
61
|
|
|
62
|
+
delete packet.base_url;
|
|
63
|
+
if (context.baseUrl) {
|
|
64
|
+
packet.base_url = context.baseUrl;
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
if (error) {
|
|
61
68
|
packet.error_code = error.code;
|
|
62
69
|
packet.error_message = sanitizeField(error.message);
|
package/contracts/errors.js
CHANGED
|
@@ -16,6 +16,8 @@ export const ERROR_CODES = {
|
|
|
16
16
|
WRITE_NOT_CONFIGURED: 'write_not_configured',
|
|
17
17
|
IDEMPOTENCY_SCAN_INCOMPLETE: 'idempotency_scan_incomplete',
|
|
18
18
|
INVENTORY_LIST_INCOMPLETE: 'inventory_list_incomplete',
|
|
19
|
+
MERGE_BLOCKED: 'merge_blocked',
|
|
20
|
+
MERGE_ENDPOINT_FAILED: 'merge_endpoint_failed',
|
|
19
21
|
};
|
|
20
22
|
|
|
21
23
|
export function forgeError(code, message, status = null, fields = null) {
|
|
@@ -28,7 +28,7 @@ export const V1_READ_PLAN_COMMANDS = Object.freeze([
|
|
|
28
28
|
]);
|
|
29
29
|
|
|
30
30
|
/** v1 write surface (Gitea cr open and status set in first slices). */
|
|
31
|
-
export const V1_WRITE_COMMANDS = Object.freeze(['cr open', 'status set']);
|
|
31
|
+
export const V1_WRITE_COMMANDS = Object.freeze(['cr open', 'status set', 'merge execute']);
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* Planned fact-inventory packet types (not emitted until wave 2+ commands ship).
|
|
@@ -46,6 +46,7 @@ export const TRUSTED_ENVELOPE_FIELDS = Object.freeze([
|
|
|
46
46
|
'provider_id',
|
|
47
47
|
'remote_name',
|
|
48
48
|
'repo_id',
|
|
49
|
+
'base_url',
|
|
49
50
|
'observed_at',
|
|
50
51
|
'ok',
|
|
51
52
|
]);
|
|
@@ -83,9 +84,9 @@ export const TRUSTED_NORMALIZED_BODY_FIELDS = Object.freeze({
|
|
|
83
84
|
*/
|
|
84
85
|
export const FORGE_SOURCED_STRING_LEAVES = Object.freeze({
|
|
85
86
|
repo_status: ['default_branch', 'auth_env', 'capabilities'],
|
|
86
|
-
ref_compare: ['
|
|
87
|
-
pr_status: ['url', 'title', '
|
|
88
|
-
pr_checks: ['
|
|
87
|
+
ref_compare: ['compare_base_ref', 'compare_head_ref', 'compare_base_sha', 'compare_head_sha'],
|
|
88
|
+
pr_status: ['url', 'title', 'forge_target_branch_ref', 'forge_source_branch_ref', 'forge_target_sha', 'forge_source_sha'],
|
|
89
|
+
pr_checks: ['forge_source_sha', 'statuses[].context', 'statuses[].description', 'statuses[].target_url'],
|
|
89
90
|
merge_plan: ['blockers[].message', 'blockers[].context'],
|
|
90
91
|
sync_plan: ['remote', 'local_sha', 'remote_sha', 'blockers[].message'],
|
|
91
92
|
ref_inventory: ['refs[].name', 'refs[].sha', 'default_ref'],
|
|
@@ -99,16 +100,16 @@ export const FORGE_SOURCED_STRING_LEAVES = Object.freeze({
|
|
|
99
100
|
],
|
|
100
101
|
cr_files: ['changed_paths[]'],
|
|
101
102
|
cr_comments: ['comments[].id', 'comments[].author', 'comments[].path', 'comments[].body'],
|
|
102
|
-
forge_changes: ['events[].title', 'events[].url', 'events[].
|
|
103
|
+
forge_changes: ['events[].title', 'events[].url', 'events[].forge_source_sha'],
|
|
103
104
|
cr_inventory_slice: [
|
|
104
105
|
'entries[].url',
|
|
105
106
|
'entries[].title',
|
|
106
|
-
'entries[].
|
|
107
|
-
'entries[].
|
|
108
|
-
'entries[].
|
|
109
|
-
'entries[].
|
|
107
|
+
'entries[].forge_target_branch_ref',
|
|
108
|
+
'entries[].forge_source_branch_ref',
|
|
109
|
+
'entries[].forge_target_sha',
|
|
110
|
+
'entries[].forge_source_sha',
|
|
110
111
|
'entries[].head_reconcile.local_head_sha',
|
|
111
|
-
'entries[].head_reconcile.
|
|
112
|
+
'entries[].head_reconcile.forge_source_sha',
|
|
112
113
|
'entries[].checks[].context',
|
|
113
114
|
'entries[].checks[].description',
|
|
114
115
|
],
|
|
@@ -125,11 +126,11 @@ export const FACT_INVENTORY_BODY_SHAPES = Object.freeze({
|
|
|
125
126
|
[FACT_INVENTORY_PACKET_TYPES.REF_INVENTORY]: {
|
|
126
127
|
refs: 'array<{ name: string, sha: string, kind?: string, is_default?: boolean }>',
|
|
127
128
|
default_ref: 'string optional',
|
|
128
|
-
ancestry_hints: 'array<{
|
|
129
|
+
ancestry_hints: 'array<{ compare_base_ref: string, compare_head_ref: string, ahead_by?: number, behind_by?: number }> optional',
|
|
129
130
|
},
|
|
130
131
|
[FACT_INVENTORY_PACKET_TYPES.CR_INVENTORY_SLICE]: {
|
|
131
132
|
entries:
|
|
132
|
-
'array<{ pr_number: number, url?: string, title?: string, state?: string,
|
|
133
|
+
'array<{ pr_number: number, url?: string, title?: string, state?: string, forge_target_branch_ref?: string, forge_source_branch_ref?: string, forge_target_sha?: string, forge_source_sha?: string, mergeability?: string, checks_conclusion?: string, checks_truncated?: boolean, blockers?: array, head_reconcile?: { stale: boolean, local_head_sha?: string, forge_source_sha?: string } }>',
|
|
133
134
|
entry_count: 'number',
|
|
134
135
|
/** true when list cap applied (entry_count > limit), not missing entries */
|
|
135
136
|
truncated: 'boolean',
|
package/cr-inventory.js
CHANGED
|
@@ -39,14 +39,14 @@ export function buildHeadReconcile(ctx, view) {
|
|
|
39
39
|
const details = staleHeadDetails(
|
|
40
40
|
ctx.cwd,
|
|
41
41
|
ctx.config?.remote ?? ctx.remoteName,
|
|
42
|
-
view.
|
|
43
|
-
view.
|
|
42
|
+
view.forge_source_branch_ref,
|
|
43
|
+
view.forge_source_sha,
|
|
44
44
|
);
|
|
45
45
|
if (!details) return { stale: false };
|
|
46
46
|
return {
|
|
47
47
|
stale: true,
|
|
48
48
|
local_head_sha: details.local_head_sha,
|
|
49
|
-
|
|
49
|
+
forge_source_sha: details.forge_source_sha,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -59,16 +59,16 @@ export function buildCrInventoryEntry(ctx, view, checks) {
|
|
|
59
59
|
url: view.url,
|
|
60
60
|
title: view.title,
|
|
61
61
|
state: view.state,
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
forge_target_branch_ref: view.forge_target_branch_ref,
|
|
63
|
+
forge_source_branch_ref: view.forge_source_branch_ref,
|
|
64
64
|
mergeability: view.mergeability,
|
|
65
65
|
checks_conclusion: checks.check_conclusion,
|
|
66
66
|
checks_truncated: checks.checks_truncated === true,
|
|
67
67
|
blockers: mergeBlockersFromFacts(view, checks),
|
|
68
68
|
head_reconcile: buildHeadReconcile(ctx, view),
|
|
69
69
|
};
|
|
70
|
-
if (view.
|
|
71
|
-
if (view.
|
|
70
|
+
if (view.forge_target_sha) entry.forge_target_sha = view.forge_target_sha;
|
|
71
|
+
if (view.forge_source_sha) entry.forge_source_sha = view.forge_source_sha;
|
|
72
72
|
return entry;
|
|
73
73
|
}
|
|
74
74
|
|
package/forge-changes.js
CHANGED
|
@@ -72,7 +72,7 @@ export function buildChecksConclusionObservedEvent(prNumber, checksBody) {
|
|
|
72
72
|
return {
|
|
73
73
|
kind: FORGE_CHANGE_EVENT_KINDS.CHECKS_CONCLUSION_OBSERVED,
|
|
74
74
|
pr_number: Math.floor(Number(prNumber)),
|
|
75
|
-
|
|
75
|
+
forge_source_sha: sanitizeField(checksBody?.forge_source_sha ?? '') || null,
|
|
76
76
|
check_conclusion: sanitizeField(checksBody?.check_conclusion ?? '') || 'unknown',
|
|
77
77
|
checks_truncated: Boolean(checksBody?.checks_truncated ?? false),
|
|
78
78
|
};
|
|
@@ -171,7 +171,7 @@ export function buildForgeChangesFromGiteaPulls(sinceIso, pullsArray, opts = {})
|
|
|
171
171
|
kind: FORGE_CHANGE_EVENT_KINDS.HEAD_SHA_MOVED,
|
|
172
172
|
...base,
|
|
173
173
|
state: 'open',
|
|
174
|
-
|
|
174
|
+
forge_source_sha: sanitizeField(pull.head?.sha ?? '') || null,
|
|
175
175
|
updated_at: normalizeIsoTimestamp(updatedAt),
|
|
176
176
|
});
|
|
177
177
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ERROR_CODES, forgeError } from './contracts/errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical forge origin (scheme + host + port) from configured baseUrl.
|
|
5
|
+
* Config-derived trusted identity — not forge-sourced.
|
|
6
|
+
*
|
|
7
|
+
* @param {{ baseUrl?: string }} config
|
|
8
|
+
* @returns {string | null}
|
|
9
|
+
*/
|
|
10
|
+
export function normalizedForgeOrigin(config) {
|
|
11
|
+
if (!config?.baseUrl) return null;
|
|
12
|
+
let url;
|
|
13
|
+
try {
|
|
14
|
+
url = new URL(config.baseUrl);
|
|
15
|
+
} catch {
|
|
16
|
+
throw Object.assign(new Error('Invalid baseUrl'), {
|
|
17
|
+
forgeError: forgeError(ERROR_CODES.CONFIG_INVALID, 'Invalid baseUrl in .remogram.json'),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (url.username || url.password) {
|
|
21
|
+
throw Object.assign(new Error('baseUrl must not contain userinfo'), {
|
|
22
|
+
forgeError: forgeError(ERROR_CODES.CONFIG_INVALID, 'baseUrl must not contain userinfo'),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (url.pathname && url.pathname !== '/') {
|
|
26
|
+
throw Object.assign(new Error('baseUrl must be a forge origin'), {
|
|
27
|
+
forgeError: forgeError(
|
|
28
|
+
ERROR_CODES.CONFIG_INVALID,
|
|
29
|
+
'baseUrl must be a forge origin without a path',
|
|
30
|
+
),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (url.search || url.hash) {
|
|
34
|
+
throw Object.assign(new Error('baseUrl must be a forge origin'), {
|
|
35
|
+
forgeError: forgeError(
|
|
36
|
+
ERROR_CODES.CONFIG_INVALID,
|
|
37
|
+
'baseUrl must be a forge origin without query or fragment',
|
|
38
|
+
),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return url.origin;
|
|
42
|
+
}
|
package/index.js
CHANGED
|
@@ -75,6 +75,17 @@ export {
|
|
|
75
75
|
appendSortQuery,
|
|
76
76
|
} from './open-pull-list.js';
|
|
77
77
|
export { buildChangeRequestOpenedBody } from './cr-open.js';
|
|
78
|
+
export {
|
|
79
|
+
assertExpectedSha,
|
|
80
|
+
mergeExecuteViewFacts,
|
|
81
|
+
mergeExecuteChecksFacts,
|
|
82
|
+
buildMergeExecuteBeforeFacts,
|
|
83
|
+
collectMergeExecuteBlockers,
|
|
84
|
+
buildCrMergeBlockedBody,
|
|
85
|
+
buildCrMergedBody,
|
|
86
|
+
buildMergeExecuteAfterFacts,
|
|
87
|
+
buildMergeExecuteMergeFacts,
|
|
88
|
+
} from './change-request-merge-execute.js';
|
|
78
89
|
export {
|
|
79
90
|
STATUS_SET_STATES,
|
|
80
91
|
assertCommitSha,
|
|
@@ -166,6 +177,7 @@ export {
|
|
|
166
177
|
throwIfStaleHeadByNumber,
|
|
167
178
|
} from './pr-head-reconcile.js';
|
|
168
179
|
export { parseConfigFile, configSchema } from './config-schema.js';
|
|
180
|
+
export { normalizedForgeOrigin } from './forge-identity.js';
|
|
169
181
|
export {
|
|
170
182
|
findConfigPath,
|
|
171
183
|
loadConfig,
|
package/package.json
CHANGED
package/pr-head-reconcile.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ERROR_CODES, forgeError } from './contracts/errors.js';
|
|
|
3
3
|
import { gitRevParse } from './git-local.js';
|
|
4
4
|
|
|
5
5
|
export const STALE_HEAD_MESSAGE =
|
|
6
|
-
'Forge PR head SHA diverges from locally resolved git; fetch or refresh before trusting
|
|
6
|
+
'Forge PR head SHA diverges from locally resolved git; fetch or refresh before trusting forge_source_sha';
|
|
7
7
|
|
|
8
8
|
export function localHeadShaForPr(cwd, remoteName, headRef) {
|
|
9
9
|
if (!headRef) return null;
|
|
@@ -18,8 +18,8 @@ export function staleHeadDetails(cwd, remoteName, headRef, forgeHeadSha) {
|
|
|
18
18
|
if (!localHeadSha) return null;
|
|
19
19
|
if (localHeadSha.toLowerCase() === String(forgeHeadSha).toLowerCase()) return null;
|
|
20
20
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
forge_source_branch_ref: headRef,
|
|
22
|
+
forge_source_sha: forgeHeadSha,
|
|
23
23
|
local_head_sha: localHeadSha,
|
|
24
24
|
};
|
|
25
25
|
}
|
package/ref-inventory.js
CHANGED
|
@@ -53,8 +53,8 @@ function buildAncestryHints(cwd, defaultRef, refs) {
|
|
|
53
53
|
|
|
54
54
|
return [
|
|
55
55
|
{
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
compare_base_ref: sanitizeField(defaultRef),
|
|
57
|
+
compare_head_ref: sanitizeField(headBranch),
|
|
58
58
|
ahead_by: counts.ahead_by,
|
|
59
59
|
behind_by: counts.behind_by,
|
|
60
60
|
},
|
package/resolve.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, existsSync, realpathSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { parseConfigFile } from './config-schema.js';
|
|
5
5
|
import { ERROR_CODES, forgeError } from './contracts/errors.js';
|
|
6
|
+
import { normalizedForgeOrigin } from './forge-identity.js';
|
|
6
7
|
import { assertGitRemote } from './git-args.js';
|
|
7
8
|
import { gitRepoRoot } from './git-local.js';
|
|
8
9
|
|
|
@@ -145,6 +146,7 @@ export function forgeContext(loaded) {
|
|
|
145
146
|
providerId: config.provider,
|
|
146
147
|
remoteName: config.remote,
|
|
147
148
|
repoId: `${parsed.owner}/${parsed.repo}`,
|
|
149
|
+
baseUrl: normalizedForgeOrigin(config),
|
|
148
150
|
config,
|
|
149
151
|
cwd: loaded.cwd,
|
|
150
152
|
parsed,
|
package/write-config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { ERROR_CODES, forgeError } from './contracts/errors.js';
|
|
3
3
|
|
|
4
4
|
/** Canonical v1 consumer write command ids (schema + gate single source). */
|
|
5
|
-
export const WRITE_COMMAND_IDS = Object.freeze(['cr_open', 'status_set']);
|
|
5
|
+
export const WRITE_COMMAND_IDS = Object.freeze(['cr_open', 'status_set', 'merge']);
|
|
6
6
|
|
|
7
7
|
export const writeCommandSchema = z.enum(WRITE_COMMAND_IDS);
|
|
8
8
|
|