forge-workflow 0.1.0-beta.5 → 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 +4 -0
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/bin/forge.js +12 -1
- package/docs/guides/MIGRATION.md +3 -3
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +4 -4
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/shepherd.md +63 -13
- package/lib/adapters/pr-state-adapter.js +15 -2
- 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/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +48 -18
- package/lib/commands/clean.js +57 -1
- 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 +54 -6
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +720 -73
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +2 -0
- package/lib/commands/preflight.js +10 -2
- package/lib/commands/push.js +108 -6
- package/lib/commands/recall.js +95 -61
- package/lib/commands/release.js +23 -2
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +132 -4
- package/lib/commands/shepherd.js +578 -72
- package/lib/commands/ship.js +15 -69
- package/lib/commands/skill.js +8 -0
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +163 -4
- package/lib/commands/validate.js +65 -21
- package/lib/commands/worktree.js +155 -19
- 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-issues.js +78 -0
- package/lib/gate-events.js +98 -10
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/hook-renderer.js +29 -1
- package/lib/issue-render.js +19 -0
- package/lib/kernel/broker.js +723 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- 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 +3322 -183
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +94 -27
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +59 -0
- package/lib/merge-rules.js +135 -17
- package/lib/npm-publish-workflow.js +233 -40
- package/lib/package-root.js +2 -0
- 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 +0 -37
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1101 -625
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +121 -24
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +28 -19
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +144 -38
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-shepherd.js +17 -3
- package/lib/project-memory.js +145 -3
- package/lib/protected-state-authority.js +799 -4
- package/lib/protected-state-surfaces.js +181 -3
- package/lib/release-readiness.js +2 -3
- package/lib/review-adapter.js +65 -0
- package/lib/skills-sync.js +65 -32
- 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 +8 -4
- package/scripts/commitlint.js +13 -15
- 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-runtime.js +3 -2
- package/scripts/process-tree.js +14 -2
- package/scripts/protected-state-check.js +440 -17
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/test-full-suite.js +704 -18
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +95 -14
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +2 -0
- package/skills/review/evals/scorecard.json +2 -2
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +19 -2
- package/skills/shepherd/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
package/scripts/commitlint.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Cross-platform commitlint runner for lefthook commit-msg hook.
|
|
4
|
-
*
|
|
5
|
-
* Works on Windows CMD, PowerShell, macOS, Linux.
|
|
4
|
+
* Executes the installed commitlint CLI directly on Windows, macOS, and Linux.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
const { spawnSync } = require('node:child_process');
|
|
9
8
|
|
|
10
|
-
// On Windows, npx is npx.cmd — shell: true resolves .cmd extensions
|
|
11
|
-
const isWindows = process.platform === 'win32';
|
|
12
|
-
|
|
13
9
|
const commitMsgFile = process.argv[2];
|
|
14
10
|
if (!commitMsgFile) {
|
|
15
11
|
// Write synchronously so the message is flushed before exit: on Windows the
|
|
@@ -18,17 +14,19 @@ if (!commitMsgFile) {
|
|
|
18
14
|
process.exit(1);
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
);
|
|
26
|
-
|
|
17
|
+
let commitlintCli;
|
|
18
|
+
try {
|
|
19
|
+
commitlintCli = require.resolve('@commitlint/cli/cli.js');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(`❌ Failed to resolve commitlint: ${error.message}`);
|
|
22
|
+
console.error(' Install project dependencies before committing.');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
27
25
|
|
|
28
26
|
const result = spawnSync(
|
|
29
|
-
|
|
30
|
-
[
|
|
31
|
-
{ stdio: 'inherit', shell:
|
|
27
|
+
process.execPath,
|
|
28
|
+
[commitlintCli, '--edit', commitMsgFile],
|
|
29
|
+
{ stdio: 'inherit', shell: false }
|
|
32
30
|
);
|
|
33
31
|
|
|
34
32
|
if (result.error) {
|
|
@@ -40,5 +38,5 @@ if (result.error) {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
if (result.status !== 0) {
|
|
43
|
-
process.exit(result.status);
|
|
41
|
+
process.exit(result.status ?? 1);
|
|
44
42
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { parse } = require('yaml');
|
|
7
|
+
const { stableStringify } = require('../lib/kernel/evaluators');
|
|
8
|
+
const { hashManifest, validateManifest } = require('../lib/validation/risk-manifest');
|
|
9
|
+
const crypto = require('node:crypto');
|
|
10
|
+
|
|
11
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
12
|
+
const SOURCE_PATH = path.join(ROOT, 'validation', 'risk-manifest.source.yaml');
|
|
13
|
+
const OUTPUT_PATH = path.join(ROOT, 'validation', 'risk-manifest.v1.json');
|
|
14
|
+
|
|
15
|
+
function sha256(value) {
|
|
16
|
+
return `sha256:${crypto.createHash('sha256').update(value, 'utf8').digest('hex')}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function compareText(left, right) {
|
|
20
|
+
if (left === right) return 0;
|
|
21
|
+
return left < right ? -1 : 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sortById(entries) {
|
|
25
|
+
return [...entries].sort((left, right) => compareText(left.id, right.id));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeSource(source) {
|
|
29
|
+
const canonicalSource = source.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
30
|
+
const parsed = parse(canonicalSource);
|
|
31
|
+
const manifest = {
|
|
32
|
+
schema_id: parsed.schema_id,
|
|
33
|
+
revision: parsed.revision,
|
|
34
|
+
source_hash: sha256(canonicalSource),
|
|
35
|
+
generator_version: 'forge-risk-manifest-generator.v1',
|
|
36
|
+
commands: sortById(parsed.commands).map((command) => ({
|
|
37
|
+
...command,
|
|
38
|
+
argv: [...command.argv],
|
|
39
|
+
})),
|
|
40
|
+
gates: sortById(parsed.gates).map((gate) => ({
|
|
41
|
+
...gate,
|
|
42
|
+
command_ids: [...gate.command_ids].sort(compareText),
|
|
43
|
+
})),
|
|
44
|
+
lanes: sortById(parsed.lanes).map((lane) => ({
|
|
45
|
+
...lane,
|
|
46
|
+
command_ids: [...lane.command_ids].sort(compareText),
|
|
47
|
+
})),
|
|
48
|
+
risks: sortById(parsed.risks).map((risk) => ({
|
|
49
|
+
...risk,
|
|
50
|
+
gate_ids: [...risk.gate_ids].sort(),
|
|
51
|
+
})),
|
|
52
|
+
owners: sortById(parsed.owners).map((owner) => ({
|
|
53
|
+
...owner,
|
|
54
|
+
risk_ids: [...owner.risk_ids].sort(),
|
|
55
|
+
canonical_test_ids: [...owner.canonical_test_ids].sort(),
|
|
56
|
+
dependent_routes: [...owner.dependent_routes].sort(),
|
|
57
|
+
platform_runtime_additions: [...owner.platform_runtime_additions].sort(),
|
|
58
|
+
lanes: [...owner.lanes].sort(),
|
|
59
|
+
package_roots: [...owner.package_roots].sort(),
|
|
60
|
+
selectors: [...owner.selectors].sort((left, right) => compareText(left.kind, right.kind)
|
|
61
|
+
|| compareText(left.prefix, right.prefix)),
|
|
62
|
+
})),
|
|
63
|
+
unknown_owner_fallback: {
|
|
64
|
+
...parsed.unknown_owner_fallback,
|
|
65
|
+
required_gates: [...parsed.unknown_owner_fallback.required_gates].sort(),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
manifest.manifest_hash = hashManifest(manifest);
|
|
69
|
+
validateManifest(manifest);
|
|
70
|
+
return JSON.parse(stableStringify(manifest));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function generateRiskManifest(source) {
|
|
74
|
+
return `${JSON.stringify(normalizeSource(source), null, 2)}\n`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function main(args = process.argv.slice(2)) {
|
|
78
|
+
const generated = generateRiskManifest(fs.readFileSync(SOURCE_PATH, 'utf8'));
|
|
79
|
+
if (args.includes('--check')) {
|
|
80
|
+
if (!fs.existsSync(OUTPUT_PATH) || fs.readFileSync(OUTPUT_PATH, 'utf8') !== generated) {
|
|
81
|
+
console.error('Risk manifest is stale. Run: node scripts/generate-risk-manifest.js');
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
fs.writeFileSync(OUTPUT_PATH, generated, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (require.main === module) main();
|
|
90
|
+
|
|
91
|
+
module.exports = { generateRiskManifest, normalizeSource };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# GH_CMD bridge: re-enter this Forge runtime without credentials in the Bash parent.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
: "${FORGE_GITHUB_RUNTIME:?Forge GitHub runtime is required}"
|
|
6
|
+
if [[ -n "${FORGE_GITHUB_ENTRY:-}" ]]; then
|
|
7
|
+
exec "$FORGE_GITHUB_RUNTIME" "$FORGE_GITHUB_ENTRY" github run -- gh "$@"
|
|
8
|
+
else
|
|
9
|
+
exec "$FORGE_GITHUB_RUNTIME" github run -- gh "$@"
|
|
10
|
+
fi
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
createBuiltinSQLiteDriver,
|
|
8
|
+
hardenBackupPermissions,
|
|
9
|
+
hardenBackupPermissionsBatch,
|
|
10
|
+
} = require('../lib/kernel/sqlite-driver');
|
|
11
|
+
const {
|
|
12
|
+
ClaimRepairError,
|
|
13
|
+
createVerifiedClaimRepairBackup,
|
|
14
|
+
} = require('../lib/kernel/legacy-claim-repair');
|
|
15
|
+
|
|
16
|
+
const HELP = `Usage:
|
|
17
|
+
bun scripts/legacy-claim-repair.js --dry-run --database <kernel.sqlite> --backup <separate.sqlite> --at <canonical-UTC>
|
|
18
|
+
bun scripts/legacy-claim-repair.js --apply --database <kernel.sqlite> --backup <separate.sqlite> --at <canonical-UTC> --approved-digest <sha256> --actor <identity>
|
|
19
|
+
|
|
20
|
+
The tool never discovers or repairs a database automatically. Apply is rejected
|
|
21
|
+
without a verified restorable backup and the exact human-approved dry-run digest.`;
|
|
22
|
+
|
|
23
|
+
function parseArgs(argv = []) {
|
|
24
|
+
const result = { mode: null };
|
|
25
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
26
|
+
const arg = argv[index];
|
|
27
|
+
if (arg === '--help' || arg === '-h') return { help: true };
|
|
28
|
+
if (arg === '--dry-run' || arg === '--apply') {
|
|
29
|
+
const mode = arg.slice(2);
|
|
30
|
+
if (result.mode && result.mode !== mode) throw new Error('Choose exactly one of --dry-run or --apply');
|
|
31
|
+
result.mode = mode;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const names = {
|
|
35
|
+
'--database': 'databasePath',
|
|
36
|
+
'--backup': 'backupPath',
|
|
37
|
+
'--at': 'observedAt',
|
|
38
|
+
'--approved-digest': 'approvedDigest',
|
|
39
|
+
'--actor': 'actor',
|
|
40
|
+
};
|
|
41
|
+
if (!Object.hasOwn(names, arg)) throw new Error(`Unknown argument: ${arg}`);
|
|
42
|
+
const name = names[arg];
|
|
43
|
+
const value = argv[index + 1];
|
|
44
|
+
if (!value || value.startsWith('--')) throw new Error(`${arg} requires a value`);
|
|
45
|
+
result[name] = value;
|
|
46
|
+
index += 1;
|
|
47
|
+
}
|
|
48
|
+
if (!result.mode) throw new Error('Choose exactly one of --dry-run or --apply');
|
|
49
|
+
for (const required of ['databasePath', 'backupPath', 'observedAt']) {
|
|
50
|
+
if (!result[required]) throw new Error(`Missing required option: ${required}`);
|
|
51
|
+
}
|
|
52
|
+
for (const requiredPath of ['databasePath', 'backupPath']) {
|
|
53
|
+
if (!path.isAbsolute(result[requiredPath])) {
|
|
54
|
+
throw new Error(`${requiredPath} must be an absolute path`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (result.mode === 'apply' && !result.approvedDigest) {
|
|
58
|
+
throw new Error('--apply requires --approved-digest');
|
|
59
|
+
}
|
|
60
|
+
if (result.mode === 'apply' && !result.actor) {
|
|
61
|
+
throw new Error('--apply requires --actor');
|
|
62
|
+
}
|
|
63
|
+
if (result.mode === 'dry-run' && result.approvedDigest) {
|
|
64
|
+
throw new Error('--approved-digest is valid only with --apply');
|
|
65
|
+
}
|
|
66
|
+
if (result.mode === 'dry-run' && result.actor) {
|
|
67
|
+
throw new Error('--actor is valid only with --apply');
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function run(options, dependencies = {}) {
|
|
73
|
+
const openDriver = dependencies.openDriver
|
|
74
|
+
|| (databasePath => createBuiltinSQLiteDriver({ databasePath }));
|
|
75
|
+
const createBackup = dependencies.createVerifiedClaimRepairBackup
|
|
76
|
+
|| createVerifiedClaimRepairBackup;
|
|
77
|
+
const hardenPath = dependencies.hardenPath || hardenBackupPermissions;
|
|
78
|
+
const hardenPaths = dependencies.hardenPaths
|
|
79
|
+
|| (dependencies.hardenPath ? dependencies.hardenPath.batch : hardenBackupPermissionsBatch);
|
|
80
|
+
const sourceDriver = openDriver(options.databasePath);
|
|
81
|
+
try {
|
|
82
|
+
if (options.mode === 'dry-run') {
|
|
83
|
+
const backup = await createBackup({
|
|
84
|
+
sourceDriver,
|
|
85
|
+
backupPath: options.backupPath,
|
|
86
|
+
observedAt: options.observedAt,
|
|
87
|
+
openDriver,
|
|
88
|
+
hardenPath,
|
|
89
|
+
hardenPaths,
|
|
90
|
+
});
|
|
91
|
+
const preflight = await sourceDriver.preflightLegacyClaimRepair({ observedAt: options.observedAt });
|
|
92
|
+
if (preflight.digest !== backup.plan_digest) {
|
|
93
|
+
throw new ClaimRepairError(
|
|
94
|
+
'CLAIM_REPAIR_BACKUP_DRIFT',
|
|
95
|
+
'Live claim authority changed while binding the reported preflight to its verified backup',
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return { ok: true, preflight, backup };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const receipt = await sourceDriver.applyLegacyClaimRepair({
|
|
102
|
+
observedAt: options.observedAt,
|
|
103
|
+
approvedDigest: options.approvedDigest,
|
|
104
|
+
backupPath: options.backupPath,
|
|
105
|
+
actor: options.actor,
|
|
106
|
+
});
|
|
107
|
+
return { ok: true, receipt };
|
|
108
|
+
} finally {
|
|
109
|
+
sourceDriver.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function main(argv = process.argv.slice(2)) {
|
|
114
|
+
try {
|
|
115
|
+
const options = parseArgs(argv);
|
|
116
|
+
if (options.help) {
|
|
117
|
+
process.stdout.write(`${HELP}\n`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`${JSON.stringify(await run(options), null, 2)}\n`);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const recoveryPath = error?.code === 'CLAIM_REPAIR_BACKUP_POSTCOMMIT_DRIFT'
|
|
123
|
+
&& typeof error?.details?.recovery_path === 'string'
|
|
124
|
+
? error.details.recovery_path
|
|
125
|
+
: null;
|
|
126
|
+
process.stderr.write(`${JSON.stringify({
|
|
127
|
+
ok: false,
|
|
128
|
+
error: {
|
|
129
|
+
code: error?.code || 'CLAIM_REPAIR_USAGE',
|
|
130
|
+
message: error?.message || String(error),
|
|
131
|
+
...(recoveryPath ? { recovery_path: recoveryPath } : {}),
|
|
132
|
+
},
|
|
133
|
+
}, null, 2)}\n`);
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (require.main === module) void main();
|
|
139
|
+
|
|
140
|
+
module.exports = {
|
|
141
|
+
HELP,
|
|
142
|
+
main,
|
|
143
|
+
parseArgs,
|
|
144
|
+
run,
|
|
145
|
+
};
|
|
@@ -156,6 +156,7 @@ function createPrAdapter(projectRoot, execFile = execFileSync) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
async function resolveAttribution(projectRoot, head, env, deps = {}) {
|
|
159
|
+
const prExec = deps.runGh ? (_command, args, options) => deps.runGh(args, options) : deps.execFileSync;
|
|
159
160
|
const branch = resolveBranch(projectRoot, deps.execFileSync);
|
|
160
161
|
const requestedIssueId = env.FORGE_EVAL_ISSUE_ID;
|
|
161
162
|
if (requestedIssueId && !UUID.test(String(requestedIssueId))) {
|
|
@@ -172,11 +173,11 @@ async function resolveAttribution(projectRoot, head, env, deps = {}) {
|
|
|
172
173
|
}
|
|
173
174
|
const pr = explicitPr
|
|
174
175
|
? Number(env.FORGE_EVAL_PR)
|
|
175
|
-
: resolvePrFromGh(projectRoot,
|
|
176
|
+
: resolvePrFromGh(projectRoot, prExec);
|
|
176
177
|
if (!pr) throw new Error('attribution.pr_unavailable');
|
|
177
178
|
let state;
|
|
178
179
|
try {
|
|
179
|
-
const adapter = deps.prAdapter || createPrAdapter(projectRoot,
|
|
180
|
+
const adapter = deps.prAdapter || createPrAdapter(projectRoot, prExec);
|
|
180
181
|
state = await adapter.readState(pr);
|
|
181
182
|
} catch {
|
|
182
183
|
throw new Error('attribution.pr_unavailable');
|
package/scripts/process-tree.js
CHANGED
|
@@ -9,6 +9,7 @@ const { spawnSync: defaultSpawnSync } = require('node:child_process');
|
|
|
9
9
|
const MANIFEST_ENV = 'FORGE_TEST_PROCESS_MANIFEST';
|
|
10
10
|
const TOKEN_ENV = 'FORGE_TEST_PROCESS_TOKEN';
|
|
11
11
|
const INSTANCE_ENV = 'FORGE_TEST_PROCESS_INSTANCE';
|
|
12
|
+
const SESSION_ENV = 'FORGE_SESSION_ID';
|
|
12
13
|
const MANIFEST_VERSION = 1;
|
|
13
14
|
const DEFAULT_MANIFEST_DIR = path.join(os.tmpdir(), 'forge-test-processes');
|
|
14
15
|
|
|
@@ -326,7 +327,11 @@ function createProcessTree(options = {}) {
|
|
|
326
327
|
if (!identity && !hasCustomIdentity) return `unverified:${pid}`;
|
|
327
328
|
return identity;
|
|
328
329
|
};
|
|
329
|
-
|
|
330
|
+
// A Forge claim records FORGE_SESSION_ID. Use that same stable runtime token
|
|
331
|
+
// for the root manifest, while preserving an inherited manifest token for
|
|
332
|
+
// nested process trees that are already attached to their parent run.
|
|
333
|
+
const inheritedToken = nonEmptyString(options.token) || nonEmptyString(env[TOKEN_ENV]);
|
|
334
|
+
const token = String(inheritedToken || env[SESSION_ENV] || randomUUID());
|
|
330
335
|
const explicitManifestPath = nonEmptyString(options.manifestPath);
|
|
331
336
|
const environmentManifestPath = nonEmptyString(env[MANIFEST_ENV]);
|
|
332
337
|
const manifestDir = options.manifestDir || DEFAULT_MANIFEST_DIR;
|
|
@@ -351,7 +356,11 @@ function createProcessTree(options = {}) {
|
|
|
351
356
|
const existing = readProcessManifest(manifestPath, fsApi, processApi);
|
|
352
357
|
// An environment-provided path may belong to another run. Treat that as
|
|
353
358
|
// unverifiable and fail closed; never overwrite or reap another run's marker.
|
|
354
|
-
const
|
|
359
|
+
const inheritedManifestVerified = explicitManifestPath
|
|
360
|
+
|| !environmentManifestPath
|
|
361
|
+
|| Boolean(inheritedToken && existing?.token === inheritedToken);
|
|
362
|
+
const usable = inheritedManifestVerified
|
|
363
|
+
&& (!markerExists || (existing && existing.token === token));
|
|
355
364
|
let manifest = existing && existing.token === token
|
|
356
365
|
? existing
|
|
357
366
|
: (usable ? createInitialManifest(token, processApi, getProcessIdentity, instanceId) : null);
|
|
@@ -749,11 +758,14 @@ function reconcileProcessManifests(options = {}) {
|
|
|
749
758
|
}
|
|
750
759
|
|
|
751
760
|
module.exports = {
|
|
761
|
+
DEFAULT_MANIFEST_DIR,
|
|
752
762
|
INSTANCE_ENV,
|
|
753
763
|
MANIFEST_ENV,
|
|
754
764
|
MANIFEST_VERSION,
|
|
755
765
|
TOKEN_ENV,
|
|
756
766
|
createProcessTree,
|
|
767
|
+
defaultGetProcessIdentity,
|
|
768
|
+
isValidManifest,
|
|
757
769
|
readProcessManifest,
|
|
758
770
|
reapProcessManifest,
|
|
759
771
|
reconcileProcessManifests,
|