forge-workflow 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/CHANGELOG.md +36 -0
- package/bin/forge-gh-proxy.js +15 -0
- package/bin/forge-github-credential.js +21 -0
- package/bin/forge.js +16 -1
- package/docs/guides/MIGRATION.md +4 -3
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/VALIDATION.md +14 -0
- package/docs/reference/github-accounts.md +114 -38
- package/lib/bun-workflow-pins.js +129 -3
- package/lib/commands/github.js +234 -49
- package/lib/commands/merge.js +2 -2
- package/lib/commands/push.js +308 -47
- package/lib/commands/release.js +18 -1
- package/lib/commands/shepherd.js +1 -1
- package/lib/commands/test.js +8 -0
- package/lib/commands/validate.js +219 -67
- package/lib/gh-proxy.js +309 -0
- package/lib/github-context.js +160 -25
- package/lib/github-credential.js +47 -0
- package/lib/github-router.js +774 -0
- package/lib/kernel/migrations.js +1 -1
- package/lib/kernel/sqlite-driver.js +1 -1
- package/lib/memory/router.js +1 -1
- package/lib/memory/usage-evidence.js +1 -1
- package/lib/native-gh.js +177 -0
- package/lib/npm-publish-workflow.js +5 -5
- package/lib/pr-monitor/flow-monitor.js +127 -28
- package/lib/pr-monitor/reconcile-executor.js +1 -1
- package/lib/project-memory.js +1 -1
- package/lib/protected-state-authority.js +214 -13
- package/lib/release-readiness.js +57 -41
- package/lib/shell-utils.js +41 -15
- package/lib/test-workflow.js +389 -0
- package/lib/validation-receipt.js +305 -0
- package/lib/workflow-templates/test.yml +654 -0
- package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/node_modules/@forge/contracts/index.js +32 -0
- package/node_modules/@forge/contracts/package.json +35 -0
- package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/node_modules/@forge/contracts/src/identity.js +42 -0
- package/node_modules/@forge/contracts/src/schema.js +72 -0
- package/node_modules/@forge/contracts/src/validate.js +305 -0
- package/node_modules/@forge/flow/index.js +114 -0
- package/node_modules/@forge/flow/package.json +34 -0
- package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
- package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
- package/node_modules/@forge/flow/src/executor.js +279 -0
- package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
- package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
- package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
- package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
- package/node_modules/@forge/memory/index.js +331 -0
- package/node_modules/@forge/memory/package.json +34 -0
- package/node_modules/@forge/memory/src/authority-provider.js +67 -0
- package/node_modules/@forge/memory/src/backend-registry.js +166 -0
- package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
- package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
- package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
- package/package.json +12 -2
- package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
- package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
- package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
- package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
- package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
- package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
- package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
- package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
- package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
- package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
- package/scripts/check-forge-token.js +85 -67
- package/scripts/gen-embedded-assets.mjs +31 -5
- package/scripts/test-full-suite.js +69 -15
- package/scripts/test.js +11 -1
package/lib/kernel/migrations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { getKernelSchema, validateKernelSchema } = require('./schema');
|
|
2
|
-
const { USAGE_EVIDENCE_MIGRATION } = require('
|
|
2
|
+
const { USAGE_EVIDENCE_MIGRATION } = require('@forge/memory');
|
|
3
3
|
|
|
4
4
|
function assertIdentifier(identifier, label) {
|
|
5
5
|
if (!/^[a-z][a-z0-9_]*$/.test(identifier)) {
|
|
@@ -30,7 +30,7 @@ const {
|
|
|
30
30
|
const { CONFLICT_SIGNAL, classifyConflictSignal } = require('./conflict-signal');
|
|
31
31
|
const { normalizeRecallHit } = require('../memory-recall');
|
|
32
32
|
const { getPackageRoot } = require('../package-root');
|
|
33
|
-
const { appendUsageEvidence, rebuildUsageProjection } = require('
|
|
33
|
+
const { appendUsageEvidence, rebuildUsageProjection } = require('@forge/memory');
|
|
34
34
|
|
|
35
35
|
const BUILTIN_SQLITE_RUNTIME_ORDER = Object.freeze(['bun:sqlite', 'node:sqlite']);
|
|
36
36
|
let probeCounter = 0;
|
package/lib/memory/router.js
CHANGED
|
@@ -32,7 +32,7 @@ const projectMemory = require('../project-memory');
|
|
|
32
32
|
const {
|
|
33
33
|
assertBackend,
|
|
34
34
|
createMemoryBackendRegistry,
|
|
35
|
-
} = require('
|
|
35
|
+
} = require('@forge/memory');
|
|
36
36
|
|
|
37
37
|
/** Supported PUBLIC backends, default first. */
|
|
38
38
|
const MEMORY_BACKENDS = ['local', 'graphiti'];
|
package/lib/native-gh.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const { isCompiledBinary } = require('./package-root');
|
|
8
|
+
|
|
9
|
+
const ROUTER_MARKER = 'forge-gh-router-v1';
|
|
10
|
+
const LOCAL_COMMANDS = new Set([
|
|
11
|
+
'accessibility', 'actions', 'alias', 'auth', 'completion', 'config', 'environment', 'exit-codes', 'formatting',
|
|
12
|
+
'help', 'licenses', 'mintty', 'preview', 'reference', 'telemetry', 'version',
|
|
13
|
+
]);
|
|
14
|
+
const HELP_COMMANDS = new Set([
|
|
15
|
+
'agent-task', 'api', 'attestation', 'browse', 'cache', 'co', 'codespace', 'copilot', 'discussion', 'extension',
|
|
16
|
+
'gist', 'gpg-key', 'issue', 'label', 'org', 'pr', 'project', 'release', 'repo', 'ruleset', 'run', 'search',
|
|
17
|
+
'secret', 'skill', 'skills', 'ssh-key', 'status', 'variable', 'workflow',
|
|
18
|
+
]);
|
|
19
|
+
const GLOBAL_VALUE_FLAGS = new Set(['--hostname', '--repo', '-R']);
|
|
20
|
+
const SKILL_VALUE_FLAGS = new Set(['--agent', '--dir', '--pin', '--scope']);
|
|
21
|
+
const TRUE_BOOLEAN_VALUES = new Set(['1', 't', 'T', 'TRUE', 'true', 'True']);
|
|
22
|
+
|
|
23
|
+
function environmentValue(environment, name) {
|
|
24
|
+
const key = Object.keys(environment).find(candidate => candidate.toUpperCase() === name);
|
|
25
|
+
return key ? environment[key] : undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function withoutProxyMarker(environment) {
|
|
29
|
+
return Object.fromEntries(Object.entries(environment).filter(([key]) => key.toUpperCase() !== 'FORGE_GH_PROXY_ACTIVE'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function commandName(args) {
|
|
33
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
34
|
+
const arg = args[index];
|
|
35
|
+
if (GLOBAL_VALUE_FLAGS.has(arg)) { index += 1; continue; }
|
|
36
|
+
if (!arg.startsWith('-')) return arg;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function enabledBooleanFlag(argument, name) {
|
|
42
|
+
return argument === name || (argument.startsWith(`${name}=`)
|
|
43
|
+
&& TRUE_BOOLEAN_VALUES.has(argument.slice(name.length + 1)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shortOptionValue(arg, resolveOptionValue) {
|
|
47
|
+
for (let offset = 1; offset < arg.length; offset += 1) {
|
|
48
|
+
const flag = `-${arg[offset]}`;
|
|
49
|
+
const takesValue = resolveOptionValue(flag);
|
|
50
|
+
if (typeof takesValue !== 'boolean') throw new Error(`Unable to determine GitHub CLI option arity for ${flag}.`);
|
|
51
|
+
if (takesValue) return { flag, attached: arg.slice(offset + 1).replace(/^=/, '') };
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isLocalSkillInvocation(args, command) {
|
|
57
|
+
const commandIndex = args.indexOf(command);
|
|
58
|
+
const subcommand = args[commandIndex + 1];
|
|
59
|
+
if (subcommand === 'list') return true;
|
|
60
|
+
if (!['install', 'add'].includes(subcommand)) return false;
|
|
61
|
+
let local = false;
|
|
62
|
+
for (let index = commandIndex + 2; index < args.length; index += 1) {
|
|
63
|
+
const arg = args[index];
|
|
64
|
+
if (arg === '--') return false;
|
|
65
|
+
if (SKILL_VALUE_FLAGS.has(arg)) { index += 1; continue; }
|
|
66
|
+
if (enabledBooleanFlag(arg, '--upstream')) return false;
|
|
67
|
+
if (enabledBooleanFlag(arg, '--from-local')) local = true;
|
|
68
|
+
}
|
|
69
|
+
return local;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isLocalExtensionInvocation(args, command) {
|
|
73
|
+
const commandIndex = args.indexOf(command);
|
|
74
|
+
if (args[commandIndex + 1] !== 'install') return false;
|
|
75
|
+
const values = [];
|
|
76
|
+
for (let index = commandIndex + 2; index < args.length; index += 1) {
|
|
77
|
+
const arg = args[index];
|
|
78
|
+
if (arg === '--') return false;
|
|
79
|
+
if (arg === '--pin') { index += 1; continue; }
|
|
80
|
+
if (arg === '--force' || arg.startsWith('--force=') || arg.startsWith('--pin=')) continue;
|
|
81
|
+
if (arg.startsWith('-')) return false;
|
|
82
|
+
values.push(arg);
|
|
83
|
+
}
|
|
84
|
+
return values.length === 1 && values[0] === '.';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLocalGhInvocation(args, resolveOptionValue = () => null) {
|
|
88
|
+
if (args.length === 0) return true;
|
|
89
|
+
const command = commandName(args);
|
|
90
|
+
if (LOCAL_COMMANDS.has(command)) return true;
|
|
91
|
+
if (['skill', 'skills'].includes(command) && isLocalSkillInvocation(args, command)) return true;
|
|
92
|
+
if (command === 'extension' && isLocalExtensionInvocation(args, command)) return true;
|
|
93
|
+
const help = argument => {
|
|
94
|
+
if (enabledBooleanFlag(argument, '--help')) return true;
|
|
95
|
+
if (!enabledBooleanFlag(argument, '-h')) return false;
|
|
96
|
+
try { return resolveOptionValue('-h') === false; } catch { return false; }
|
|
97
|
+
};
|
|
98
|
+
if (args.length === 1) return enabledBooleanFlag(args[0], '--help') || enabledBooleanFlag(args[0], '-h')
|
|
99
|
+
|| enabledBooleanFlag(args[0], '--version');
|
|
100
|
+
const delimiter = args.indexOf('--');
|
|
101
|
+
const end = delimiter < 0 ? args.length : delimiter;
|
|
102
|
+
if (!HELP_COMMANDS.has(command) || !args.slice(0, end).some(help)) return false;
|
|
103
|
+
for (let index = 0; index < end; index += 1) {
|
|
104
|
+
const arg = args[index];
|
|
105
|
+
if (help(arg)) return true;
|
|
106
|
+
if (!arg.startsWith('-') || arg.includes('=')) continue;
|
|
107
|
+
try {
|
|
108
|
+
if (arg.startsWith('--')) {
|
|
109
|
+
const takesValue = GLOBAL_VALUE_FLAGS.has(arg) || resolveOptionValue(arg);
|
|
110
|
+
if (typeof takesValue !== 'boolean') return false;
|
|
111
|
+
if (takesValue) index += 1;
|
|
112
|
+
} else {
|
|
113
|
+
const option = shortOptionValue(arg, resolveOptionValue);
|
|
114
|
+
if (option && !option.attached) index += 1;
|
|
115
|
+
}
|
|
116
|
+
} catch { return false; }
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function exitCode(result) {
|
|
122
|
+
if (result?.error?.code === 'ENOENT') return 127;
|
|
123
|
+
if (result?.error?.code === 'EACCES') return 126;
|
|
124
|
+
if (Number.isInteger(result?.status)) return result.status;
|
|
125
|
+
return result?.signal && os.constants.signals[result.signal] ? 128 + os.constants.signals[result.signal] : 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isForgeProxy(candidate, ownPath) {
|
|
129
|
+
const candidatePath = fs.realpathSync(candidate);
|
|
130
|
+
try {
|
|
131
|
+
if (candidatePath === fs.realpathSync(ownPath)) return true;
|
|
132
|
+
} catch { /* compiled Bun entrypoints may not exist on the host filesystem */ }
|
|
133
|
+
const stat = fs.statSync(candidatePath);
|
|
134
|
+
if (!stat.isFile()) throw new Error('GitHub CLI candidate is not a file.');
|
|
135
|
+
if (stat.size > 64 * 1024) return false;
|
|
136
|
+
const text = fs.readFileSync(candidatePath, 'utf8').replaceAll('\\', '/').toLowerCase();
|
|
137
|
+
return text.includes(ROUTER_MARKER) || (text.includes('forge-workflow') && text.includes('/bin/gh.js'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveRealGh(options = {}) {
|
|
141
|
+
const platform = options.platform || process.platform;
|
|
142
|
+
const environment = options.env || process.env;
|
|
143
|
+
const pathValue = options.pathEnv ?? environmentValue(environment, 'PATH') ?? '';
|
|
144
|
+
const ownPath = options.ownPath ?? (isCompiledBinary() ? process.execPath : process.argv[1]);
|
|
145
|
+
const extensions = platform === 'win32'
|
|
146
|
+
? (options.pathExt || environmentValue(environment, 'PATHEXT') || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
|
|
147
|
+
: [''];
|
|
148
|
+
for (const directory of pathValue.split(path.delimiter).filter(Boolean)) {
|
|
149
|
+
for (const extension of extensions) {
|
|
150
|
+
const candidate = path.join(directory, `gh${extension.toLowerCase()}`);
|
|
151
|
+
try {
|
|
152
|
+
fs.accessSync(candidate, platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
|
|
153
|
+
if (!isForgeProxy(candidate, ownPath)) return candidate;
|
|
154
|
+
} catch { /* keep searching */ }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function runNativeGh(args, projectRoot = process.cwd(), options = {}) {
|
|
161
|
+
const writeError = options.writeError || (value => process.stderr.write(value));
|
|
162
|
+
const executable = (options.resolveExecutable || resolveRealGh)(options);
|
|
163
|
+
if (!executable) {
|
|
164
|
+
writeError('GitHub CLI executable not found outside the Forge proxy.\n');
|
|
165
|
+
return 127;
|
|
166
|
+
}
|
|
167
|
+
return exitCode((options.spawnSync || spawnSync)(executable, args, {
|
|
168
|
+
cwd: projectRoot,
|
|
169
|
+
env: withoutProxyMarker(options.baseEnv || process.env),
|
|
170
|
+
stdio: 'inherit',
|
|
171
|
+
shell: false,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
commandName, exitCode, isForgeProxy, isLocalGhInvocation, resolveRealGh, runNativeGh, shortOptionValue, withoutProxyMarker,
|
|
177
|
+
};
|
|
@@ -12,11 +12,11 @@ const {
|
|
|
12
12
|
} = require('./protected-state-surfaces');
|
|
13
13
|
const {
|
|
14
14
|
completeNpmPublishWorkflowAuthorization,
|
|
15
|
+
isValidGitObjectId,
|
|
15
16
|
issueNpmPublishWorkflowAuthorization,
|
|
16
17
|
} = require('./protected-state-authority');
|
|
17
18
|
|
|
18
19
|
const NPM_PUBLISH_WORKFLOW_PATH = '.github/workflows/npm-publish.yml';
|
|
19
|
-
const FULL_COMMIT_SHA = /^[0-9a-f]{40}$/;
|
|
20
20
|
const BUN_VERSION = /^bun@(\d+\.\d+\.\d+)$/.exec(require('../package.json').packageManager)?.[1];
|
|
21
21
|
if (!BUN_VERSION) throw new Error('package.json must pin an exact stable Bun version.');
|
|
22
22
|
|
|
@@ -64,8 +64,8 @@ function verifyExpectedHead(projectRoot, expectedHead, resolveHead = resolveCurr
|
|
|
64
64
|
if (!expectedHead) {
|
|
65
65
|
return { success: false, error: '--expect-head is required for protected workflow generation.' };
|
|
66
66
|
}
|
|
67
|
-
if (!
|
|
68
|
-
return { success: false, error: '--expect-head must be a full 40-character lowercase commit SHA.' };
|
|
67
|
+
if (!isValidGitObjectId(expectedHead)) {
|
|
68
|
+
return { success: false, error: '--expect-head must be a full 40- or 64-character lowercase commit SHA.' };
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
let currentHead;
|
|
@@ -74,8 +74,8 @@ function verifyExpectedHead(projectRoot, expectedHead, resolveHead = resolveCurr
|
|
|
74
74
|
} catch (error) {
|
|
75
75
|
return { success: false, error: `Could not resolve current HEAD: ${error.message}` };
|
|
76
76
|
}
|
|
77
|
-
if (!
|
|
78
|
-
return { success: false, error: 'Current HEAD did not resolve to a full 40-character lowercase commit SHA.' };
|
|
77
|
+
if (!isValidGitObjectId(currentHead)) {
|
|
78
|
+
return { success: false, error: 'Current HEAD did not resolve to a full 40- or 64-character lowercase commit SHA.' };
|
|
79
79
|
}
|
|
80
80
|
if (currentHead !== expectedHead) {
|
|
81
81
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const zlib = require('node:zlib');
|
|
5
5
|
|
|
6
|
-
const { computeContentHash, validateContractStructure } = require('
|
|
6
|
+
const { computeContentHash, validateContractStructure } = require('@forge/contracts');
|
|
7
7
|
const {
|
|
8
8
|
MonitorRuntimeError,
|
|
9
9
|
createMonitorDurabilityBridge,
|
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
createMonitorState,
|
|
12
12
|
reduceMonitor,
|
|
13
13
|
reduceMonitorBatch,
|
|
14
|
-
} = require('
|
|
14
|
+
} = require('@forge/flow');
|
|
15
15
|
const { diffSnapshots } = require('./differ');
|
|
16
16
|
const { EVENT_TYPES: T, makeEvent } = require('./events');
|
|
17
17
|
const { finalizeRecords, MAX_EVENT_DELTAS } = require('./monitor');
|
|
@@ -139,7 +139,7 @@ function durableMonitorRecord(record) {
|
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
function
|
|
142
|
+
function encodeBoundedPendingBatch(ctx, subjectRevision, records, snapshot) {
|
|
143
143
|
if (records.length < 1 || records.length > PENDING_BATCH_RECORD_LIMIT) {
|
|
144
144
|
throw new FlowMonitorError('INVALID_OBSERVATION', 'Pending monitor transition batch exceeds its record bound');
|
|
145
145
|
}
|
|
@@ -147,7 +147,7 @@ function encodePendingBatch(ctx, subjectRevision, records, snapshot) {
|
|
|
147
147
|
monitor_id: ctx.monitorId,
|
|
148
148
|
subject_revision: subjectRevision,
|
|
149
149
|
snapshot_identity: snapshot === null ? null : snapshotIdentity(snapshot),
|
|
150
|
-
records
|
|
150
|
+
records,
|
|
151
151
|
snapshot,
|
|
152
152
|
};
|
|
153
153
|
const serialized = JSON.stringify({
|
|
@@ -164,6 +164,18 @@ function encodePendingBatch(ctx, subjectRevision, records, snapshot) {
|
|
|
164
164
|
return encoded;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
function encodePendingBatch(ctx, subjectRevision, records, snapshot) {
|
|
168
|
+
if (records.length < 1 || records.length > PENDING_BATCH_RECORD_LIMIT) {
|
|
169
|
+
throw new FlowMonitorError('INVALID_OBSERVATION', 'Pending monitor transition batch exceeds its record bound');
|
|
170
|
+
}
|
|
171
|
+
return encodeBoundedPendingBatch(
|
|
172
|
+
ctx,
|
|
173
|
+
subjectRevision,
|
|
174
|
+
records.map(durableMonitorRecord),
|
|
175
|
+
snapshot,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
167
179
|
function decodePendingBatch(value, ctx, subjectRevision) {
|
|
168
180
|
if (typeof value !== 'string' || value.length > PENDING_BATCH_LIMIT) {
|
|
169
181
|
throw new FlowMonitorError('MONITOR_HISTORY_INCOMPLETE', 'Pending monitor transition batch is missing');
|
|
@@ -196,41 +208,126 @@ function segmentablePendingBatchError(error) {
|
|
|
196
208
|
].includes(error.message);
|
|
197
209
|
}
|
|
198
210
|
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
let
|
|
202
|
-
|
|
211
|
+
function findAdaptiveBatchBoundary(maxCount, preferredCount, encode) {
|
|
212
|
+
if (!Number.isSafeInteger(maxCount) || maxCount < 1) return null;
|
|
213
|
+
let accepted = null;
|
|
214
|
+
const attempt = (count) => {
|
|
203
215
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
break;
|
|
216
|
+
accepted = { count, encoded: encode(count) };
|
|
217
|
+
return true;
|
|
207
218
|
} catch (error) {
|
|
208
219
|
if (!segmentablePendingBatchError(error)) throw error;
|
|
220
|
+
return false;
|
|
209
221
|
}
|
|
210
|
-
|
|
222
|
+
};
|
|
223
|
+
const initialCount = Math.min(maxCount, Math.max(1, preferredCount || 1));
|
|
224
|
+
if (!attempt(initialCount)) {
|
|
211
225
|
let low = 1;
|
|
212
|
-
let high =
|
|
213
|
-
let accepted = null;
|
|
226
|
+
let high = initialCount - 1;
|
|
214
227
|
while (low <= high) {
|
|
215
228
|
const count = Math.floor((low + high) / 2);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
if (attempt(count)) low = count + 1;
|
|
230
|
+
else high = count - 1;
|
|
231
|
+
}
|
|
232
|
+
return accepted;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let low = initialCount + 1;
|
|
236
|
+
let probe = Math.min(maxCount, Math.max(low, initialCount * 2));
|
|
237
|
+
while (low <= maxCount) {
|
|
238
|
+
if (attempt(probe)) {
|
|
239
|
+
low = probe + 1;
|
|
240
|
+
if (probe === maxCount) break;
|
|
241
|
+
probe = Math.min(maxCount, Math.max(low, probe * 2));
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
let high = probe - 1;
|
|
245
|
+
while (low <= high) {
|
|
246
|
+
const count = Math.floor((low + high) / 2);
|
|
247
|
+
if (attempt(count)) low = count + 1;
|
|
248
|
+
else high = count - 1;
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
return accepted;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function splitPendingBatches(
|
|
256
|
+
ctx,
|
|
257
|
+
subjectRevision,
|
|
258
|
+
records,
|
|
259
|
+
snapshot,
|
|
260
|
+
encodeBatch = encodeBoundedPendingBatch,
|
|
261
|
+
) {
|
|
262
|
+
const batches = [];
|
|
263
|
+
if (records.length === 0) return batches;
|
|
264
|
+
if (records.length > PENDING_BATCH_RECORD_LIMIT) {
|
|
265
|
+
throw new FlowMonitorError('INVALID_OBSERVATION', 'Pending monitor transition batch exceeds its record bound');
|
|
266
|
+
}
|
|
267
|
+
const boundedRecords = records.map(durableMonitorRecord);
|
|
268
|
+
let offset = 0;
|
|
269
|
+
let preferredCount = 1;
|
|
270
|
+
let failedTerminalCount = null;
|
|
271
|
+
const appendTerminalBatch = (start) => {
|
|
272
|
+
const remainingCount = records.length - start;
|
|
273
|
+
try {
|
|
274
|
+
const encoded = encodeBatch(ctx, subjectRevision, boundedRecords.slice(start), snapshot);
|
|
275
|
+
appendPendingBatch(batches, { encoded, records: records.slice(start), snapshot });
|
|
276
|
+
return true;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (!segmentablePendingBatchError(error)) throw error;
|
|
279
|
+
failedTerminalCount = remainingCount;
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (appendTerminalBatch(0)) return batches;
|
|
285
|
+
|
|
286
|
+
while (offset < records.length) {
|
|
287
|
+
const remainingCount = records.length - offset;
|
|
288
|
+
let accepted = findAdaptiveBatchBoundary(
|
|
289
|
+
remainingCount,
|
|
290
|
+
preferredCount,
|
|
291
|
+
count => encodeBatch(
|
|
292
|
+
ctx,
|
|
293
|
+
subjectRevision,
|
|
294
|
+
boundedRecords.slice(offset, offset + count),
|
|
295
|
+
null,
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
if (!accepted && failedTerminalCount !== remainingCount && appendTerminalBatch(offset)) break;
|
|
299
|
+
if (accepted?.count === remainingCount && failedTerminalCount !== remainingCount) {
|
|
300
|
+
if (appendTerminalBatch(offset)) break;
|
|
301
|
+
}
|
|
302
|
+
if (accepted?.count === remainingCount) {
|
|
303
|
+
accepted = findAdaptiveBatchBoundary(
|
|
304
|
+
remainingCount - 1,
|
|
305
|
+
Math.min(preferredCount, remainingCount - 1),
|
|
306
|
+
count => encodeBatch(
|
|
307
|
+
ctx,
|
|
308
|
+
subjectRevision,
|
|
309
|
+
boundedRecords.slice(offset, offset + count),
|
|
310
|
+
null,
|
|
311
|
+
),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (accepted?.count < remainingCount
|
|
315
|
+
&& batches.length === BATCH_PLAN_SEGMENT_LIMIT - 1) {
|
|
316
|
+
if (failedTerminalCount !== remainingCount && appendTerminalBatch(offset)) break;
|
|
317
|
+
throw new FlowMonitorError(
|
|
318
|
+
'INVALID_OBSERVATION',
|
|
319
|
+
'Pending monitor transition plan exceeds its segment bound',
|
|
320
|
+
);
|
|
229
321
|
}
|
|
230
322
|
if (!accepted) {
|
|
231
323
|
throw new FlowMonitorError('INVALID_OBSERVATION', 'Pending monitor transition record exceeds its durable bound');
|
|
232
324
|
}
|
|
233
|
-
appendPendingBatch(batches,
|
|
325
|
+
appendPendingBatch(batches, {
|
|
326
|
+
encoded: accepted.encoded,
|
|
327
|
+
records: records.slice(offset, offset + accepted.count),
|
|
328
|
+
snapshot: null,
|
|
329
|
+
});
|
|
330
|
+
preferredCount = accepted.count;
|
|
234
331
|
offset += accepted.count;
|
|
235
332
|
}
|
|
236
333
|
return batches;
|
|
@@ -1418,7 +1515,9 @@ module.exports = {
|
|
|
1418
1515
|
appendPendingBatch,
|
|
1419
1516
|
decodePrivateSafeBase64,
|
|
1420
1517
|
encodePrivateSafeBase64,
|
|
1518
|
+
findAdaptiveBatchBoundary,
|
|
1421
1519
|
remainingSegmentedRecords,
|
|
1520
|
+
splitPendingBatches,
|
|
1422
1521
|
validOrphanedPrecommitPlan,
|
|
1423
1522
|
},
|
|
1424
1523
|
};
|
|
@@ -1081,7 +1081,7 @@ async function migrateLegacyAuthority(projectRoot, opts = {}) {
|
|
|
1081
1081
|
|
|
1082
1082
|
async function defaultBuildBroker({ projectRoot, gitCommonDir }) {
|
|
1083
1083
|
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
1084
|
-
const { createMonitorStore } = require('
|
|
1084
|
+
const { createMonitorStore } = require('@forge/memory');
|
|
1085
1085
|
const deps = await buildMigratedKernelIssueDeps({ projectRoot, gitCommonDir });
|
|
1086
1086
|
const store = createMonitorStore(deps.kernelDriver);
|
|
1087
1087
|
return {
|
package/lib/project-memory.js
CHANGED
|
@@ -8,7 +8,7 @@ const { resolveKernelDatabasePath } = require('./kernel/cli-broker-factory');
|
|
|
8
8
|
const { resolveGitCommonDir } = require('./kernel/broker');
|
|
9
9
|
const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
|
|
10
10
|
const { normalizeRecallHit } = require('./memory-recall');
|
|
11
|
-
const { createUsageEvidenceStore } = require('
|
|
11
|
+
const { createUsageEvidenceStore } = require('@forge/memory');
|
|
12
12
|
|
|
13
13
|
// Project memory is a Forge read model persisted in the kernel store (kernel_memories),
|
|
14
14
|
// written DIRECTLY rather than through the issue CAS/guarded-event path. The store seam
|