forge-workflow 0.1.0-beta.4 → 0.1.0-beta.6
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/AGENTS.md +18 -7
- package/CHANGELOG.md +79 -1
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/README.md +6 -2
- package/bin/forge-cmd.js +20 -0
- package/bin/forge.js +28 -375
- package/docs/INDEX.md +1 -1
- package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
- package/docs/guides/MIGRATION.md +4 -4
- package/docs/guides/SETUP.md +16 -16
- package/docs/reference/COMMANDS.md +8 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSIGHTS_RECAP.md +9 -20
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/TOOLCHAIN.md +8 -0
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/protected-state-surfaces.md +4 -4
- package/docs/reference/shepherd.md +114 -35
- package/lefthook.yml +12 -0
- package/lib/activation/ensure-forge-home.js +33 -15
- package/lib/adapters/pr-state-adapter.js +359 -144
- package/lib/audit-evidence.js +71 -110
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/capped-jsonl-log.js +236 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +50 -20
- package/lib/commands/clean.js +252 -32
- package/lib/commands/dev.js +4 -33
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +276 -30
- package/lib/commands/insights.js +8 -3
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +1265 -58
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +3 -1
- package/lib/commands/preflight.js +21 -4
- package/lib/commands/prime.js +21 -8
- package/lib/commands/push.js +146 -54
- package/lib/commands/recall.js +127 -49
- package/lib/commands/recap.js +6 -1
- package/lib/commands/release.js +39 -3
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +323 -98
- package/lib/commands/shepherd.js +591 -73
- package/lib/commands/ship.js +36 -91
- package/lib/commands/skill.js +127 -11
- package/lib/commands/status.js +17 -1
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +187 -38
- package/lib/commands/validate.js +65 -21
- package/lib/commands/worktree.js +359 -45
- package/lib/core/runtime-graph.js +1 -1
- package/lib/doc-assertions.js +297 -0
- package/lib/existing-tdd-gate.js +253 -0
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-context.js +1 -4
- package/lib/forge-issues.js +134 -32
- package/lib/gate-events.js +98 -10
- package/lib/git-defaults.js +56 -0
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/harness-capability-matrix.js +3 -3
- package/lib/hook-renderer.js +122 -5
- package/lib/insights.js +96 -80
- package/lib/issue-render.js +19 -0
- package/lib/kernel/backing-issue.js +14 -2
- package/lib/kernel/broker.js +739 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/cli-broker-factory.js +12 -1
- package/lib/kernel/close-on-merge.js +154 -0
- package/lib/kernel/fs-class.js +42 -25
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3435 -172
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/lefthook-wiring.js +21 -1
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +110 -28
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +106 -15
- package/lib/memory-recall-events.js +145 -0
- package/lib/memory-recall.js +71 -10
- package/lib/merge-rules.js +143 -21
- package/lib/npm-publish-workflow.js +465 -0
- package/lib/orientation.js +68 -43
- package/lib/package-root.js +2 -0
- package/lib/plugin-catalog.js +14 -4
- package/lib/pr-bundle.js +5 -6
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +18 -39
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1129 -470
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +293 -0
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +38 -20
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +145 -27
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-pull.js +33 -14
- package/lib/pr-shepherd.js +51 -11
- package/lib/preflight/gates.js +65 -18
- package/lib/preflight/runner.js +5 -0
- package/lib/project-memory.js +178 -4
- package/lib/protected-state-authority.js +1100 -0
- package/lib/protected-state-surfaces.js +243 -45
- package/lib/release-readiness.js +53 -7
- package/lib/review-adapter.js +65 -0
- package/lib/shell-utils.js +1 -1
- package/lib/skills-sync.js +71 -35
- package/lib/smart-merge.js +28 -4
- package/lib/symlink-utils.js +74 -26
- package/lib/upgrade-safety.js +39 -0
- package/lib/using-forge.js +19 -6
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/package.json +12 -9
- package/scripts/commitlint.js +13 -15
- package/scripts/doc-asserting-tests.js +158 -0
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runner.js +310 -0
- package/scripts/lib/behavioral-eval-runtime.js +457 -0
- package/scripts/lib/eval-evidence.js +328 -0
- package/scripts/lib/eval-runner.js +81 -41
- package/scripts/lib/immutable-eval-corpus.js +309 -0
- package/scripts/lib/promotion-evidence-loader.js +94 -0
- package/scripts/lib/promotion-scorecard.js +314 -0
- package/scripts/npm-release-receipt.js +134 -0
- package/scripts/process-tree.js +773 -0
- package/scripts/protected-state-check.js +479 -31
- package/scripts/run-command-eval.js +29 -1
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/sync-d20-audit.js +172 -0
- package/scripts/test-full-suite.js +935 -37
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +271 -57
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +6 -11
- package/skills/review/evals/scorecard.json +4 -4
- package/skills/rollback/SKILL.md +4 -11
- package/skills/rollback/evals/scorecard.json +3 -3
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +39 -16
- package/skills/shepherd/evals/scorecard.json +4 -4
- package/skills/ship/SKILL.md +4 -12
- package/skills/ship/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
- package/skills/worktree/SKILL.md +6 -1
- package/skills/worktree/evals/scorecard.json +2 -2
- package/lib/beads-setup.js +0 -538
- package/lib/beads-sync-scaffold.js +0 -189
- package/lib/pat-setup.js +0 -207
- package/lib/pr-monitor/render-sticky.js +0 -206
- package/lib/pr-monitor/upsert-sticky.js +0 -169
- package/scripts/beads-context.sh +0 -577
- package/scripts/beads-migrate-to-dolt.sh +0 -7
- package/scripts/beads-upgrade-smoke.sh +0 -284
- package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
package/lib/audit-evidence.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
const { execFileSync } = require('node:child_process');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
1
|
const path = require('node:path');
|
|
2
|
+
const { randomUUID } = require('node:crypto');
|
|
3
|
+
const { appendCappedJsonlRecord } = require('./capped-jsonl-log');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Subagent evidence lands in the local append-only log D25 specifies, now that
|
|
7
|
+
* the Beads audit CLI is retired along with the rest of the Beads runtime. The
|
|
8
|
+
* log is the only copy of an entry, so a record carries the prompt and response
|
|
9
|
+
* the Beads entry used to hold; it is capped like the protected-state log so a
|
|
10
|
+
* long dev run cannot grow it unbounded.
|
|
11
|
+
*/
|
|
12
|
+
const AUDIT_EVIDENCE_LOG = '.forge/log.jsonl';
|
|
13
|
+
const AUDIT_EVIDENCE_MAX_RECORDS = 500;
|
|
4
14
|
|
|
5
15
|
const VERDICT_LABELS = {
|
|
6
16
|
PASS: 'good',
|
|
@@ -40,15 +50,6 @@ const SECRET_TEXT_PATTERNS = [
|
|
|
40
50
|
/\bsk-[A-Za-z0-9_-]{8,}\b/g,
|
|
41
51
|
];
|
|
42
52
|
|
|
43
|
-
function defaultRunCommand(command, args, options = {}) {
|
|
44
|
-
return execFileSync(command, args, {
|
|
45
|
-
cwd: options.cwd || process.cwd(),
|
|
46
|
-
encoding: 'utf8',
|
|
47
|
-
input: options.input,
|
|
48
|
-
timeout: options.timeout || 120000,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
53
|
function redactString(value) {
|
|
53
54
|
return SECRET_TEXT_PATTERNS.reduce(
|
|
54
55
|
(current, entry) => {
|
|
@@ -132,96 +133,62 @@ function buildSubagentAuditPayload(event) {
|
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Best-effort: a failed append is reported back to the caller, never thrown, so
|
|
138
|
+
* losing the evidence can never fail the command that produced it.
|
|
139
|
+
*/
|
|
140
|
+
function appendAuditRecord(record, options) {
|
|
141
|
+
const logPath = path.resolve(options.cwd || process.cwd(), AUDIT_EVIDENCE_LOG);
|
|
142
|
+
const appendRecord = options.appendRecord || appendCappedJsonlRecord;
|
|
143
|
+
|
|
137
144
|
try {
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
-
} catch (
|
|
141
|
-
|
|
142
|
-
const match = /\bint-[A-Za-z0-9_-]+\b/.exec(String(output));
|
|
143
|
-
return match ? match[0] : null;
|
|
145
|
+
appendRecord(logPath, record, options.maxRecords || AUDIT_EVIDENCE_MAX_RECORDS);
|
|
146
|
+
return { success: true, logPath };
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return { success: false, logPath, error: error.message };
|
|
144
149
|
}
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
return String(output).includes('--meta-json');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function writeFallbackMetadata(payload, entryId, options) {
|
|
153
|
-
if (!entryId || !payload.metadata || Object.keys(payload.metadata).length === 0) {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const cwd = options.cwd || process.cwd();
|
|
158
|
-
const fsImpl = options.fs || fs;
|
|
159
|
-
const forgeDir = path.join(cwd, '.forge').replace(/\\/g, '/');
|
|
160
|
-
const logPath = path.join(forgeDir, 'log.jsonl').replace(/\\/g, '/');
|
|
161
|
-
const line = {
|
|
152
|
+
function buildAuditEvidenceRecord(payload, entryId, recordedAt) {
|
|
153
|
+
return {
|
|
162
154
|
kind: 'forge.auditEvidence',
|
|
163
|
-
sourceOfTruth: '
|
|
164
|
-
|
|
155
|
+
sourceOfTruth: 'forge_log',
|
|
156
|
+
entryId,
|
|
157
|
+
recordedAt,
|
|
165
158
|
command: redact(payload.command),
|
|
159
|
+
issueId: redact(payload.issueId),
|
|
166
160
|
role: redact(payload.role),
|
|
167
161
|
phase: redact(payload.phase),
|
|
168
162
|
taskId: redact(payload.taskId),
|
|
169
163
|
taskTitle: redact(payload.taskTitle),
|
|
170
|
-
|
|
164
|
+
model: redact(payload.model),
|
|
165
|
+
verdict: payload.verdict,
|
|
166
|
+
// Already redacted by buildSubagentAuditPayload.
|
|
167
|
+
prompt: payload.prompt,
|
|
168
|
+
response: payload.response,
|
|
169
|
+
metadata: payload.metadata,
|
|
171
170
|
};
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
fsImpl.mkdirSync(forgeDir, { recursive: true });
|
|
175
|
-
fsImpl.appendFileSync(logPath, `${JSON.stringify(line)}\n`);
|
|
176
|
-
return { path: logPath, line };
|
|
177
|
-
} catch (error) {
|
|
178
|
-
return { path: logPath, line, skipped: true, error: error.message };
|
|
179
|
-
}
|
|
180
171
|
}
|
|
181
172
|
|
|
182
173
|
function recordSubagentAuditEvent(event, options = {}) {
|
|
183
174
|
const payload = buildSubagentAuditPayload(event);
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const args = [
|
|
190
|
-
'audit',
|
|
191
|
-
'record',
|
|
192
|
-
'--json',
|
|
193
|
-
'--kind',
|
|
194
|
-
'llm_call',
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
if (payload.issueId) {
|
|
198
|
-
args.push('--issue-id', payload.issueId);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
args.push(
|
|
202
|
-
'--model',
|
|
203
|
-
payload.model,
|
|
204
|
-
'--prompt',
|
|
205
|
-
payload.prompt,
|
|
206
|
-
'--response',
|
|
207
|
-
payload.response,
|
|
175
|
+
const entryId = (options.newId || randomUUID)();
|
|
176
|
+
const record = buildAuditEvidenceRecord(
|
|
177
|
+
payload,
|
|
178
|
+
entryId,
|
|
179
|
+
options.now || new Date().toISOString(),
|
|
208
180
|
);
|
|
181
|
+
const written = appendAuditRecord(record, options);
|
|
209
182
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const output = runCommand('bd', args, { cwd: options.cwd || process.cwd() });
|
|
215
|
-
const entryId = parseRecordId(output, { requireJson: true });
|
|
216
|
-
const fallback = metaJsonSupported ? null : writeFallbackMetadata(payload, entryId, options);
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
success: Boolean(entryId),
|
|
220
|
-
entryId,
|
|
221
|
-
output,
|
|
183
|
+
const result = {
|
|
184
|
+
success: written.success,
|
|
185
|
+
entryId: written.success ? entryId : null,
|
|
186
|
+
record,
|
|
222
187
|
payload,
|
|
223
|
-
|
|
188
|
+
logPath: written.logPath,
|
|
224
189
|
};
|
|
190
|
+
if (!written.success) result.error = written.error;
|
|
191
|
+
return result;
|
|
225
192
|
}
|
|
226
193
|
|
|
227
194
|
function labelSubagentAuditEvent(entryId, event, options = {}) {
|
|
@@ -233,36 +200,29 @@ function labelSubagentAuditEvent(entryId, event, options = {}) {
|
|
|
233
200
|
return { skipped: true };
|
|
234
201
|
}
|
|
235
202
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
], { cwd: options.cwd || process.cwd() });
|
|
250
|
-
} catch (error) {
|
|
251
|
-
return {
|
|
252
|
-
success: false,
|
|
253
|
-
label,
|
|
254
|
-
error: error.message,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const labeledEntryId = parseRecordId(output, { requireJson: true });
|
|
203
|
+
// A label is its own record rather than a rewrite of the recorded entry: the
|
|
204
|
+
// log is append-only, so a verdict is read by joining on entryId.
|
|
205
|
+
const record = {
|
|
206
|
+
kind: 'forge.auditEvidenceLabel',
|
|
207
|
+
sourceOfTruth: 'forge_log',
|
|
208
|
+
entryId,
|
|
209
|
+
recordedAt: options.now || new Date().toISOString(),
|
|
210
|
+
role,
|
|
211
|
+
verdict,
|
|
212
|
+
label,
|
|
213
|
+
reason: `${role} verdict: ${verdict}`,
|
|
214
|
+
};
|
|
215
|
+
const written = appendAuditRecord(record, options);
|
|
259
216
|
|
|
260
|
-
|
|
261
|
-
success:
|
|
217
|
+
const result = {
|
|
218
|
+
success: written.success,
|
|
262
219
|
label,
|
|
263
|
-
entryId:
|
|
264
|
-
|
|
220
|
+
entryId: written.success ? entryId : null,
|
|
221
|
+
record,
|
|
222
|
+
logPath: written.logPath,
|
|
265
223
|
};
|
|
224
|
+
if (!written.success) result.error = written.error;
|
|
225
|
+
return result;
|
|
266
226
|
}
|
|
267
227
|
|
|
268
228
|
function recordAndLabelSubagentAuditEvent(event, options = {}) {
|
|
@@ -272,11 +232,12 @@ function recordAndLabelSubagentAuditEvent(event, options = {}) {
|
|
|
272
232
|
}
|
|
273
233
|
|
|
274
234
|
module.exports = {
|
|
235
|
+
AUDIT_EVIDENCE_LOG,
|
|
236
|
+
AUDIT_EVIDENCE_MAX_RECORDS,
|
|
275
237
|
VERDICT_LABELS,
|
|
276
238
|
buildSubagentAuditPayload,
|
|
277
239
|
recordSubagentAuditEvent,
|
|
278
240
|
labelSubagentAuditEvent,
|
|
279
241
|
recordAndLabelSubagentAuditEvent,
|
|
280
|
-
hasAuditMetaJsonSupport,
|
|
281
242
|
redact,
|
|
282
243
|
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const BASE_REMOTE_CANDIDATES = ['upstream', 'origin'];
|
|
6
|
+
const DEFAULT_BRANCH_CANDIDATES = ['main', 'master'];
|
|
7
|
+
const GIT_PROBE_TIMEOUT_MS = 120000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Quiet git probe options. `stdio: 'pipe'` keeps failed probes from leaking to
|
|
11
|
+
* the terminal — these are speculative queries, not user-facing commands.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} cwd - Working directory for the probe.
|
|
14
|
+
* @returns {object} execFileSync options.
|
|
15
|
+
*/
|
|
16
|
+
function getQuietProbeOptions(cwd) {
|
|
17
|
+
return { encoding: 'utf8', cwd, timeout: GIT_PROBE_TIMEOUT_MS, stdio: 'pipe' };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve `refs/remotes/<remote>/HEAD` to the ref it points at, verifying the
|
|
22
|
+
* target actually exists.
|
|
23
|
+
*
|
|
24
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
25
|
+
* @param {string} [cwd] - Repository directory.
|
|
26
|
+
* @param {string} [remoteName] - Remote to probe.
|
|
27
|
+
* @returns {string|null} Fully-qualified ref, or null.
|
|
28
|
+
*/
|
|
29
|
+
function resolveRemoteHeadTarget(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
|
|
30
|
+
if (!remoteName) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const symbolicRef = exec('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], getQuietProbeOptions(cwd)).trim();
|
|
35
|
+
if (!symbolicRef) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
exec('git', ['rev-parse', '--verify', symbolicRef], getQuietProbeOptions(cwd));
|
|
39
|
+
return symbolicRef;
|
|
40
|
+
} catch (_error) { // NOSONAR S2486 - intentional: missing ref is the expected probe failure
|
|
41
|
+
void _error;
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* True when the remote has fetched tracking refs we can treat as a base:
|
|
48
|
+
* a recorded HEAD, or one of the conventional default branches.
|
|
49
|
+
*
|
|
50
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
51
|
+
* @param {string} [cwd] - Repository directory.
|
|
52
|
+
* @param {string} [remoteName] - Remote to probe.
|
|
53
|
+
* @returns {boolean} Whether a usable tracking base exists.
|
|
54
|
+
*/
|
|
55
|
+
function remoteHasTrackingBase(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
|
|
56
|
+
if (!remoteName) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (resolveRemoteHeadTarget(exec, cwd, remoteName)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
|
|
64
|
+
try {
|
|
65
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
|
|
66
|
+
return true;
|
|
67
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
68
|
+
void _error;
|
|
69
|
+
// Probe next candidate.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the remote that owns the integration base. `upstream` wins over
|
|
78
|
+
* `origin` so a fork-style checkout resolves the official repository rather
|
|
79
|
+
* than the contributor's fork.
|
|
80
|
+
*
|
|
81
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
82
|
+
* @param {string} [cwd] - Repository directory.
|
|
83
|
+
* @returns {string} Remote name ('origin' when nothing else qualifies).
|
|
84
|
+
*/
|
|
85
|
+
function resolveBaseRemote(exec = execFileSync, cwd = process.cwd()) {
|
|
86
|
+
for (const candidate of BASE_REMOTE_CANDIDATES) {
|
|
87
|
+
try {
|
|
88
|
+
exec('git', ['remote', 'get-url', candidate], getQuietProbeOptions(cwd));
|
|
89
|
+
if (remoteHasTrackingBase(exec, cwd, candidate)) {
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
93
|
+
void _error;
|
|
94
|
+
// Probe next candidate.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return 'origin';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve the base remote's default branch name.
|
|
103
|
+
*
|
|
104
|
+
* @param {Function} [exec] - Injected execFileSync.
|
|
105
|
+
* @param {string} [cwd] - Repository directory.
|
|
106
|
+
* @param {string} [remoteName] - Remote to probe; defaults to the base remote.
|
|
107
|
+
* @returns {string} Branch name ('master' when nothing else resolves).
|
|
108
|
+
*/
|
|
109
|
+
function resolveBaseBranch(exec = execFileSync, cwd = process.cwd(), remoteName = resolveBaseRemote(exec, cwd)) {
|
|
110
|
+
const symbolicRef = resolveRemoteHeadTarget(exec, cwd, remoteName);
|
|
111
|
+
if (symbolicRef) {
|
|
112
|
+
const match = new RegExp(`^refs/remotes/${remoteName}/(.+)$`).exec(symbolicRef);
|
|
113
|
+
if (match?.[1]) {
|
|
114
|
+
return match[1];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
|
|
119
|
+
try {
|
|
120
|
+
exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
|
|
121
|
+
return candidate;
|
|
122
|
+
} catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
|
|
123
|
+
void _error;
|
|
124
|
+
// Probe next candidate.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 'master';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
BASE_REMOTE_CANDIDATES,
|
|
133
|
+
DEFAULT_BRANCH_CANDIDATES,
|
|
134
|
+
resolveRemoteHeadTarget,
|
|
135
|
+
remoteHasTrackingBase,
|
|
136
|
+
resolveBaseRemote,
|
|
137
|
+
resolveBaseBranch,
|
|
138
|
+
};
|