@the-open-engine/zeroshot 6.28.0 → 6.29.1
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/README.md +3 -3
- package/cli/index.js +20 -224
- package/docker/zeroshot-cluster/Dockerfile +2 -3
- package/lib/cluster/client.cjs +30 -7
- package/lib/cluster/client.d.cts +52 -0
- package/lib/cluster/client.d.mts +52 -0
- package/lib/cluster/client.d.ts +4 -6
- package/lib/cluster/client.mjs +32 -9
- package/lib/cluster/connection-state.cjs +25 -0
- package/lib/cluster/connection-state.d.cts +13 -0
- package/lib/cluster/connection-state.d.mts +13 -0
- package/lib/cluster/connection-state.d.ts +13 -0
- package/lib/cluster/connection-state.mjs +21 -0
- package/lib/cluster/connection-types.cjs +2 -0
- package/lib/cluster/connection-types.d.cts +33 -0
- package/lib/cluster/connection-types.d.mts +33 -0
- package/lib/cluster/connection-types.d.ts +33 -0
- package/lib/cluster/connection-types.mjs +1 -0
- package/lib/cluster/connection.cjs +18 -46
- package/lib/cluster/connection.d.cts +26 -0
- package/lib/cluster/connection.d.mts +26 -0
- package/lib/cluster/connection.d.ts +7 -25
- package/lib/cluster/connection.mjs +12 -42
- package/lib/cluster/errors.cjs +9 -1
- package/lib/cluster/errors.d.cts +29 -0
- package/lib/cluster/errors.d.mts +29 -0
- package/lib/cluster/errors.d.ts +4 -0
- package/lib/cluster/errors.mjs +7 -0
- package/lib/cluster/frames.d.cts +12 -0
- package/lib/cluster/frames.d.mts +12 -0
- package/lib/cluster/generated/protocol-schema.d.cts +1 -0
- package/lib/cluster/generated/protocol-schema.d.mts +1 -0
- package/lib/cluster/generated/protocol.d.cts +781 -0
- package/lib/cluster/generated/protocol.d.mts +781 -0
- package/lib/cluster/index.cjs +2 -2
- package/lib/cluster/index.d.cts +13 -0
- package/lib/cluster/index.d.mts +13 -0
- package/lib/cluster/index.d.ts +3 -3
- package/lib/cluster/index.mjs +2 -2
- package/lib/cluster/json-source.cjs +1 -0
- package/lib/cluster/json-source.d.cts +5 -0
- package/lib/cluster/json-source.d.mts +5 -0
- package/lib/cluster/json-source.d.ts +1 -0
- package/lib/cluster/json-source.mjs +1 -0
- package/lib/cluster/payload-value.cjs +1 -0
- package/lib/cluster/payload-value.d.cts +8 -0
- package/lib/cluster/payload-value.d.mts +8 -0
- package/lib/cluster/payload-value.d.ts +1 -0
- package/lib/cluster/payload-value.mjs +1 -0
- package/lib/cluster/queue.d.cts +15 -0
- package/lib/cluster/queue.d.mts +15 -0
- package/lib/cluster/socket.cjs +8 -0
- package/lib/cluster/socket.d.cts +13 -0
- package/lib/cluster/socket.d.mts +13 -0
- package/lib/cluster/socket.d.ts +2 -0
- package/lib/cluster/socket.mjs +7 -0
- package/lib/cluster/subscriptions.d.cts +73 -0
- package/lib/cluster/subscriptions.d.mts +73 -0
- package/lib/cluster/subscriptions.d.ts +5 -1
- package/lib/cluster/validators.cjs +1 -0
- package/lib/cluster/validators.d.cts +7 -0
- package/lib/cluster/validators.d.mts +7 -0
- package/lib/cluster/validators.d.ts +1 -0
- package/lib/cluster/validators.mjs +1 -0
- package/lib/cluster-worker/engine-adapter.js +1 -11
- package/lib/cluster-worker/engine-input.js +14 -0
- package/lib/hosted-session/authority.cjs +61 -0
- package/lib/hosted-session/authority.d.cts +3 -0
- package/lib/hosted-session/authority.d.mts +3 -0
- package/lib/hosted-session/authority.d.ts +3 -0
- package/lib/hosted-session/authority.mjs +57 -0
- package/lib/hosted-session/coordinator.cjs +78 -51
- package/lib/hosted-session/coordinator.d.cts +11 -0
- package/lib/hosted-session/coordinator.d.mts +11 -0
- package/lib/hosted-session/coordinator.d.ts +4 -2
- package/lib/hosted-session/coordinator.mjs +75 -51
- package/lib/hosted-session/errors.cjs +25 -0
- package/lib/hosted-session/errors.d.cts +10 -0
- package/lib/hosted-session/errors.d.mts +10 -0
- package/lib/hosted-session/errors.d.ts +10 -0
- package/lib/hosted-session/errors.mjs +19 -0
- package/lib/hosted-session/index.cjs +4 -1
- package/lib/hosted-session/index.d.cts +2 -0
- package/lib/hosted-session/index.d.mts +2 -0
- package/lib/hosted-session/index.d.ts +2 -2
- package/lib/hosted-session/index.mjs +1 -1
- package/lib/hosted-session/reconnecting-watch.cjs +131 -0
- package/lib/hosted-session/reconnecting-watch.d.cts +13 -0
- package/lib/hosted-session/reconnecting-watch.d.mts +13 -0
- package/lib/hosted-session/reconnecting-watch.d.ts +13 -0
- package/lib/hosted-session/reconnecting-watch.mjs +127 -0
- package/lib/hosted-session/types.d.cts +32 -0
- package/lib/hosted-session/types.d.mts +32 -0
- package/lib/hosted-session/types.d.ts +19 -7
- package/lib/hosted-target/access-url.cjs +44 -0
- package/lib/hosted-target/access-url.d.cts +2 -0
- package/lib/hosted-target/access-url.d.mts +2 -0
- package/lib/hosted-target/access-url.d.ts +2 -0
- package/lib/hosted-target/access-url.mjs +41 -0
- package/lib/hosted-target/adapter-request.cjs +12 -0
- package/lib/hosted-target/adapter-request.d.cts +17 -0
- package/lib/hosted-target/adapter-request.d.mts +17 -0
- package/lib/hosted-target/adapter-request.d.ts +17 -0
- package/lib/hosted-target/adapter-request.mjs +9 -0
- package/lib/hosted-target/adapter-types.cjs +2 -0
- package/lib/hosted-target/adapter-types.d.cts +27 -0
- package/lib/hosted-target/adapter-types.d.mts +27 -0
- package/lib/hosted-target/adapter-types.d.ts +27 -0
- package/lib/hosted-target/adapter-types.mjs +1 -0
- package/lib/hosted-target/bounds.cjs +10 -0
- package/lib/hosted-target/bounds.d.cts +7 -0
- package/lib/hosted-target/bounds.d.mts +7 -0
- package/lib/hosted-target/bounds.d.ts +7 -0
- package/lib/hosted-target/bounds.mjs +7 -0
- package/lib/hosted-target/capsule-error-response.cjs +76 -0
- package/lib/hosted-target/capsule-error-response.d.cts +2 -0
- package/lib/hosted-target/capsule-error-response.d.mts +2 -0
- package/lib/hosted-target/capsule-error-response.d.ts +2 -0
- package/lib/hosted-target/capsule-error-response.mjs +73 -0
- package/lib/hosted-target/errors.cjs +88 -0
- package/lib/hosted-target/errors.d.cts +36 -0
- package/lib/hosted-target/errors.d.mts +36 -0
- package/lib/hosted-target/errors.d.ts +36 -0
- package/lib/hosted-target/errors.mjs +75 -0
- package/lib/hosted-target/index.cjs +16 -0
- package/lib/hosted-target/index.d.cts +3 -0
- package/lib/hosted-target/index.d.mts +3 -0
- package/lib/hosted-target/index.d.ts +3 -0
- package/lib/hosted-target/index.mjs +2 -0
- package/lib/hosted-target/response-validation.cjs +169 -0
- package/lib/hosted-target/response-validation.d.cts +5 -0
- package/lib/hosted-target/response-validation.d.mts +5 -0
- package/lib/hosted-target/response-validation.d.ts +5 -0
- package/lib/hosted-target/response-validation.mjs +163 -0
- package/lib/hosted-target/retry-executor.cjs +51 -0
- package/lib/hosted-target/retry-executor.d.cts +8 -0
- package/lib/hosted-target/retry-executor.d.mts +8 -0
- package/lib/hosted-target/retry-executor.d.ts +8 -0
- package/lib/hosted-target/retry-executor.mjs +48 -0
- package/lib/hosted-target/retry.cjs +78 -0
- package/lib/hosted-target/retry.d.cts +9 -0
- package/lib/hosted-target/retry.d.mts +9 -0
- package/lib/hosted-target/retry.d.ts +9 -0
- package/lib/hosted-target/retry.mjs +73 -0
- package/lib/hosted-target/target-adapter.cjs +10 -0
- package/lib/hosted-target/target-adapter.d.cts +3 -0
- package/lib/hosted-target/target-adapter.d.mts +3 -0
- package/lib/hosted-target/target-adapter.d.ts +3 -0
- package/lib/hosted-target/target-adapter.mjs +7 -0
- package/lib/hosted-target/types.cjs +4 -0
- package/lib/hosted-target/types.d.cts +50 -0
- package/lib/hosted-target/types.d.mts +50 -0
- package/lib/hosted-target/types.d.ts +50 -0
- package/lib/hosted-target/types.mjs +1 -0
- package/lib/hosted-target/zero-cloud-v1-adapter.cjs +185 -0
- package/lib/hosted-target/zero-cloud-v1-adapter.d.cts +13 -0
- package/lib/hosted-target/zero-cloud-v1-adapter.d.mts +13 -0
- package/lib/hosted-target/zero-cloud-v1-adapter.d.ts +13 -0
- package/lib/hosted-target/zero-cloud-v1-adapter.mjs +181 -0
- package/lib/target/bounded-response.cjs +51 -0
- package/lib/target/bounded-response.d.cts +1 -0
- package/lib/target/bounded-response.d.mts +1 -0
- package/lib/target/bounded-response.d.ts +1 -0
- package/lib/target/bounded-response.js +51 -0
- package/lib/target/bounded-response.mjs +48 -0
- package/lib/target/credential-lock.js +1 -3
- package/lib/target/credential-store.js +9 -4
- package/lib/target/device-flow.cjs +213 -0
- package/lib/target/device-flow.d.cts +54 -0
- package/lib/target/device-flow.d.mts +54 -0
- package/lib/target/device-flow.d.ts +26 -10
- package/lib/target/device-flow.js +162 -53
- package/lib/target/device-flow.mjs +203 -0
- package/lib/target/discovery-errors.cjs +10 -0
- package/lib/target/discovery-errors.d.cts +3 -0
- package/lib/target/discovery-errors.d.mts +3 -0
- package/lib/target/discovery-errors.d.ts +3 -0
- package/lib/target/discovery-errors.js +10 -0
- package/lib/target/discovery-errors.mjs +6 -0
- package/lib/target/discovery-sections.cjs +191 -0
- package/lib/target/discovery-sections.d.cts +52 -0
- package/lib/target/discovery-sections.d.mts +52 -0
- package/lib/target/discovery-sections.d.ts +52 -0
- package/lib/target/discovery-sections.js +191 -0
- package/lib/target/discovery-sections.mjs +179 -0
- package/lib/target/discovery-validation.cjs +109 -0
- package/lib/target/discovery-validation.d.cts +28 -0
- package/lib/target/discovery-validation.d.mts +28 -0
- package/lib/target/discovery-validation.d.ts +28 -0
- package/lib/target/discovery-validation.js +109 -0
- package/lib/target/discovery-validation.mjs +99 -0
- package/lib/target/discovery.cjs +112 -0
- package/lib/target/discovery.d.cts +74 -0
- package/lib/target/discovery.d.mts +74 -0
- package/lib/target/discovery.d.ts +68 -5
- package/lib/target/discovery.js +93 -101
- package/lib/target/discovery.mjs +105 -0
- package/lib/target/index.d.ts +6 -6
- package/lib/target/index.js +36 -36
- package/lib/target/oauth-http.d.ts +2 -0
- package/lib/target/oauth-http.js +25 -0
- package/lib/target/refresh-exchange.d.ts +11 -0
- package/lib/target/refresh-exchange.js +24 -0
- package/lib/target/refresh-revocation.d.ts +8 -0
- package/lib/target/refresh-revocation.js +19 -0
- package/lib/target/route-template.cjs +85 -0
- package/lib/target/route-template.d.cts +7 -0
- package/lib/target/route-template.d.mts +7 -0
- package/lib/target/route-template.d.ts +7 -0
- package/lib/target/route-template.js +85 -0
- package/lib/target/route-template.mjs +81 -0
- package/lib/target/session-errors.d.ts +4 -0
- package/lib/target/session-errors.js +12 -0
- package/lib/target/session-verification.d.ts +10 -0
- package/lib/target/session-verification.js +42 -0
- package/lib/target/target-registry.d.ts +7 -3
- package/lib/target/target-registry.js +41 -10
- package/lib/target/target-session-manager.d.ts +48 -0
- package/lib/target/target-session-manager.js +226 -0
- package/lib/target/target-session.d.ts +39 -31
- package/lib/target/target-session.js +63 -150
- package/lib/target/token-response.cjs +51 -0
- package/lib/target/token-response.d.cts +7 -0
- package/lib/target/token-response.d.mts +7 -0
- package/lib/target/token-response.d.ts +7 -0
- package/lib/target/token-response.js +51 -0
- package/lib/target/token-response.mjs +48 -0
- package/package.json +16 -6
- package/scripts/audit-production-dependencies.js +150 -0
- package/scripts/build-cluster.js +23 -3
- package/scripts/opcore-agent-gate.js +159 -0
- package/scripts/opcore-agent-tool-overlays.js +154 -0
- package/scripts/opcore-introduced-check.js +288 -0
- package/src/cluster/client.ts +42 -15
- package/src/cluster/connection-state.ts +33 -0
- package/src/cluster/connection-types.ts +28 -0
- package/src/cluster/connection.ts +37 -68
- package/src/cluster/errors.ts +5 -0
- package/src/cluster/index.ts +3 -1
- package/src/cluster/json-source.ts +1 -0
- package/src/cluster/payload-value.ts +1 -0
- package/src/cluster/socket.ts +13 -0
- package/src/cluster/subscriptions.ts +7 -2
- package/src/cluster/validators.ts +1 -0
- package/src/cluster/ws.d.ts +1 -1
- package/src/hosted-session/authority.ts +77 -0
- package/src/hosted-session/coordinator.ts +113 -56
- package/src/hosted-session/errors.ts +21 -0
- package/src/hosted-session/index.ts +14 -2
- package/src/hosted-session/reconnecting-watch.ts +148 -0
- package/src/hosted-session/types.ts +22 -8
- package/src/hosted-target/access-url.ts +46 -0
- package/src/hosted-target/adapter-request.ts +28 -0
- package/src/hosted-target/adapter-types.ts +39 -0
- package/src/hosted-target/bounds.ts +1 -0
- package/src/hosted-target/capsule-error-response.ts +107 -0
- package/src/hosted-target/errors.ts +36 -35
- package/src/hosted-target/index.ts +10 -23
- package/src/hosted-target/response-validation.ts +170 -62
- package/src/hosted-target/retry-executor.ts +62 -0
- package/src/hosted-target/retry.ts +47 -11
- package/src/hosted-target/target-adapter.ts +12 -8
- package/src/hosted-target/types.ts +19 -18
- package/src/hosted-target/zero-cloud-v1-adapter.ts +229 -316
- package/src/target/bounded-response.ts +68 -0
- package/src/target/credential-lock.ts +1 -3
- package/src/target/credential-store.ts +14 -10
- package/src/target/device-flow.ts +206 -71
- package/src/target/discovery-errors.ts +6 -0
- package/src/target/discovery-sections.ts +251 -0
- package/src/target/discovery-validation.ts +144 -0
- package/src/target/discovery.ts +163 -115
- package/src/target/index.ts +15 -11
- package/src/target/oauth-http.ts +27 -0
- package/src/target/refresh-exchange.ts +36 -0
- package/src/target/refresh-revocation.ts +29 -0
- package/src/target/route-template.ts +105 -0
- package/src/target/session-errors.ts +9 -0
- package/src/target/session-verification.ts +51 -0
- package/src/target/target-registry.ts +60 -27
- package/src/target/target-session-manager.ts +285 -0
- package/src/target/target-session.ts +105 -222
- package/src/target/token-response.ts +62 -0
package/scripts/build-cluster.js
CHANGED
|
@@ -6,8 +6,11 @@ const path = require('node:path');
|
|
|
6
6
|
const root = path.resolve(__dirname, '..');
|
|
7
7
|
const clusterBuildRoot = path.join(root, '.cluster-build');
|
|
8
8
|
const hostedSessionBuildRoot = path.join(root, '.hosted-session-build');
|
|
9
|
+
const hostedTargetBuildRoot = path.join(root, '.hosted-target-build');
|
|
9
10
|
const clusterOutputRoot = path.join(root, 'lib/cluster');
|
|
10
11
|
const hostedSessionOutputRoot = path.join(root, 'lib/hosted-session');
|
|
12
|
+
const hostedTargetOutputRoot = path.join(root, 'lib/hosted-target');
|
|
13
|
+
const targetOutputRoot = path.join(root, 'lib/target');
|
|
11
14
|
|
|
12
15
|
function filesBelow(directory) {
|
|
13
16
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
@@ -17,6 +20,15 @@ function filesBelow(directory) {
|
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
function declarationContent(source, localExtension) {
|
|
24
|
+
return fs
|
|
25
|
+
.readFileSync(source, 'utf8')
|
|
26
|
+
.replace(/(["'])(\.\.?\/[^"']+)\.ts\1/g, (_match, quote, specifier) => {
|
|
27
|
+
const extension = specifier.startsWith('../target/') ? '.js' : localExtension;
|
|
28
|
+
return `${quote}${specifier}${extension}${quote}`;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
function copyBuild(sourceRoot, outputRoot, mode) {
|
|
21
33
|
for (const source of filesBelow(sourceRoot)) {
|
|
22
34
|
const relative = path.relative(sourceRoot, source);
|
|
@@ -32,15 +44,18 @@ function copyBuild(sourceRoot, outputRoot, mode) {
|
|
|
32
44
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
33
45
|
fs.writeFileSync(target, content);
|
|
34
46
|
} else if (mode === 'cjs' && extension === '.ts' && relative.endsWith('.d.ts')) {
|
|
35
|
-
const
|
|
36
|
-
fs.mkdirSync(path.dirname(
|
|
37
|
-
fs.
|
|
47
|
+
const base = path.join(outputRoot, relative.slice(0, -5));
|
|
48
|
+
fs.mkdirSync(path.dirname(base), { recursive: true });
|
|
49
|
+
fs.writeFileSync(`${base}.d.ts`, declarationContent(source, '.js'));
|
|
50
|
+
fs.writeFileSync(`${base}.d.cts`, declarationContent(source, '.cjs'));
|
|
51
|
+
fs.writeFileSync(`${base}.d.mts`, declarationContent(source, '.mjs'));
|
|
38
52
|
}
|
|
39
53
|
}
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
fs.rmSync(clusterOutputRoot, { recursive: true, force: true });
|
|
43
57
|
fs.rmSync(hostedSessionOutputRoot, { recursive: true, force: true });
|
|
58
|
+
fs.rmSync(hostedTargetOutputRoot, { recursive: true, force: true });
|
|
44
59
|
copyBuild(path.join(clusterBuildRoot, 'cjs'), clusterOutputRoot, 'cjs');
|
|
45
60
|
copyBuild(path.join(clusterBuildRoot, 'esm'), clusterOutputRoot, 'esm');
|
|
46
61
|
copyBuild(
|
|
@@ -53,5 +68,10 @@ copyBuild(
|
|
|
53
68
|
hostedSessionOutputRoot,
|
|
54
69
|
'esm'
|
|
55
70
|
);
|
|
71
|
+
copyBuild(path.join(hostedTargetBuildRoot, 'cjs', 'hosted-target'), hostedTargetOutputRoot, 'cjs');
|
|
72
|
+
copyBuild(path.join(hostedTargetBuildRoot, 'esm', 'hosted-target'), hostedTargetOutputRoot, 'esm');
|
|
73
|
+
copyBuild(path.join(hostedTargetBuildRoot, 'cjs', 'target'), targetOutputRoot, 'cjs');
|
|
74
|
+
copyBuild(path.join(hostedTargetBuildRoot, 'esm', 'target'), targetOutputRoot, 'esm');
|
|
56
75
|
fs.rmSync(clusterBuildRoot, { recursive: true, force: true });
|
|
57
76
|
fs.rmSync(hostedSessionBuildRoot, { recursive: true, force: true });
|
|
77
|
+
fs.rmSync(hostedTargetBuildRoot, { recursive: true, force: true });
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { extractToolRequest, isRecord, overlaysForTool } = require('./opcore-agent-tool-overlays');
|
|
8
|
+
|
|
9
|
+
const validationTimeoutMs = 120_000;
|
|
10
|
+
const maxFeedbackChars = 4000;
|
|
11
|
+
const preWriteChecks = [
|
|
12
|
+
'typescript.syntax',
|
|
13
|
+
'typescript.types',
|
|
14
|
+
'typescript.lint',
|
|
15
|
+
'typescript.function-metrics',
|
|
16
|
+
'typescript.file-length',
|
|
17
|
+
'rust.source-hygiene',
|
|
18
|
+
'rust.fmt',
|
|
19
|
+
'rust.file-length',
|
|
20
|
+
'rust.function-metrics',
|
|
21
|
+
'docs.staleness',
|
|
22
|
+
'docs.freshness',
|
|
23
|
+
'docs.length',
|
|
24
|
+
'docs.dry',
|
|
25
|
+
'docs.content-quality',
|
|
26
|
+
'docs.code-blocks',
|
|
27
|
+
'docs.rules-why',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function parseArgs(argv) {
|
|
31
|
+
const args = { harness: 'unknown' };
|
|
32
|
+
let index = 0;
|
|
33
|
+
while (index < argv.length) {
|
|
34
|
+
const arg = argv[index];
|
|
35
|
+
const next = argv[index + 1];
|
|
36
|
+
if (arg === '--harness') args.harness = next || 'unknown';
|
|
37
|
+
else if (arg.startsWith('--harness=')) args.harness = arg.slice('--harness='.length);
|
|
38
|
+
else if (arg === '--repo') args.repo = next;
|
|
39
|
+
else if (arg.startsWith('--repo=')) args.repo = arg.slice('--repo='.length);
|
|
40
|
+
index += arg === '--harness' || arg === '--repo' ? 2 : 1;
|
|
41
|
+
}
|
|
42
|
+
return args;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveRepoRoot(explicitRepo, cwd) {
|
|
46
|
+
if (explicitRepo) return path.resolve(explicitRepo);
|
|
47
|
+
const start = path.resolve(cwd || process.cwd());
|
|
48
|
+
const result = spawnSync('git', ['rev-parse', '--show-toplevel'], {
|
|
49
|
+
cwd: start,
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
52
|
+
});
|
|
53
|
+
return result.status === 0 && result.stdout.trim() ? path.resolve(result.stdout.trim()) : start;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runValidation(request) {
|
|
57
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zeroshot-opcore-prewrite-'));
|
|
58
|
+
const requestPath = path.join(tempDir, 'validation-request.json');
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(requestPath, `${JSON.stringify(request)}\n`);
|
|
61
|
+
const opcoreEntrypoint = require.resolve('opcore');
|
|
62
|
+
const result = spawnSync(
|
|
63
|
+
process.execPath,
|
|
64
|
+
[
|
|
65
|
+
opcoreEntrypoint,
|
|
66
|
+
'validate',
|
|
67
|
+
'pre-write',
|
|
68
|
+
'--request-file',
|
|
69
|
+
requestPath,
|
|
70
|
+
'--timeout-ms',
|
|
71
|
+
String(validationTimeoutMs),
|
|
72
|
+
'--json',
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
cwd: request.repo.repoRoot,
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
if (result.error) throw result.error;
|
|
81
|
+
const payload = JSON.parse((result.stdout || '').trim());
|
|
82
|
+
if (!payload.receipt) {
|
|
83
|
+
throw new Error(result.stderr || result.stdout || `Opcore exited ${result.status}`);
|
|
84
|
+
}
|
|
85
|
+
return payload.receipt;
|
|
86
|
+
} finally {
|
|
87
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function feedback(toolName, receipt) {
|
|
92
|
+
const summary = receipt.failureSummary?.message || 'Pre-write validation failed';
|
|
93
|
+
const checks = receipt.checks?.length ? ` checks=${receipt.checks.join(',')}` : '';
|
|
94
|
+
const paths = receipt.overlays?.paths?.length ? ` paths=${receipt.overlays.paths.join(',')}` : '';
|
|
95
|
+
const message = `Opcore write gate blocked ${toolName}: ${summary} status=${receipt.validationStatus}${checks}${paths}\n`;
|
|
96
|
+
return message.length <= maxFeedbackChars
|
|
97
|
+
? message
|
|
98
|
+
: `${message.slice(0, maxFeedbackChars - 15).trimEnd()} [truncated]\n`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function readStdin() {
|
|
102
|
+
let input = '';
|
|
103
|
+
for await (const chunk of process.stdin) input += chunk.toString();
|
|
104
|
+
return input;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const args = parseArgs(process.argv.slice(2));
|
|
109
|
+
let mapped;
|
|
110
|
+
try {
|
|
111
|
+
const raw = await readStdin();
|
|
112
|
+
if (!raw.trim()) return 0;
|
|
113
|
+
const envelope = JSON.parse(raw);
|
|
114
|
+
if (!isRecord(envelope)) throw new Error('hook payload must be a JSON object');
|
|
115
|
+
const tool = extractToolRequest(envelope);
|
|
116
|
+
if (!tool) return 0;
|
|
117
|
+
const repoRoot = resolveRepoRoot(args.repo, tool.cwd);
|
|
118
|
+
const overlays = await overlaysForTool(repoRoot, tool);
|
|
119
|
+
if (overlays.length === 0) return 0;
|
|
120
|
+
mapped = {
|
|
121
|
+
toolName: tool.toolName,
|
|
122
|
+
request: {
|
|
123
|
+
requestId: `zeroshot-opcore-agent-gate-${Date.now()}`,
|
|
124
|
+
repo: { repoRoot },
|
|
125
|
+
scope: { kind: 'files', files: overlays.map((overlay) => overlay.path) },
|
|
126
|
+
graph: { mode: 'optional', provider: 'opcore-graph' },
|
|
127
|
+
overlays,
|
|
128
|
+
checks: preWriteChecks,
|
|
129
|
+
reportMode: 'introduced',
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
process.stderr.write(
|
|
134
|
+
`Opcore write gate skipped: ${error instanceof Error ? error.message : String(error)}\n`
|
|
135
|
+
);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const receipt = runValidation(mapped.request);
|
|
141
|
+
if (receipt.ok) return 0;
|
|
142
|
+
process.stderr.write(feedback(mapped.toolName, receipt));
|
|
143
|
+
return 2;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
process.stderr.write(
|
|
146
|
+
`Opcore write gate blocked ${mapped.toolName}: validation command failed: ${error instanceof Error ? error.message : String(error)}\n`
|
|
147
|
+
);
|
|
148
|
+
return 2;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main()
|
|
153
|
+
.then((exitCode) => {
|
|
154
|
+
process.exitCode = exitCode;
|
|
155
|
+
})
|
|
156
|
+
.catch((error) => {
|
|
157
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
158
|
+
process.exitCode = 2;
|
|
159
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
const { pathToFileURL } = require('node:url');
|
|
4
|
+
|
|
5
|
+
const writeTools = new Set(['write']);
|
|
6
|
+
const editTools = new Set(['edit']);
|
|
7
|
+
const multiEditTools = new Set(['multiedit', 'multi_edit']);
|
|
8
|
+
const applyPatchTools = new Set(['applypatch', 'apply_patch']);
|
|
9
|
+
let editApiPromise;
|
|
10
|
+
|
|
11
|
+
function firstString(...values) {
|
|
12
|
+
return values.find((value) => typeof value === 'string');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isRecord(value) {
|
|
16
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function firstRecord(...values) {
|
|
20
|
+
return values.find(isRecord);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requiredString(value, name) {
|
|
24
|
+
if (value === undefined) throw new Error(`${name} must be a string`);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveTargetPath(repoRoot, filePath) {
|
|
29
|
+
const absolute = path.isAbsolute(filePath)
|
|
30
|
+
? path.resolve(filePath)
|
|
31
|
+
: path.resolve(repoRoot, filePath);
|
|
32
|
+
const relative = path.relative(repoRoot, absolute);
|
|
33
|
+
if (
|
|
34
|
+
!relative ||
|
|
35
|
+
relative.startsWith('..') ||
|
|
36
|
+
path.isAbsolute(relative) ||
|
|
37
|
+
relative.split(path.sep).includes('..')
|
|
38
|
+
) {
|
|
39
|
+
throw new Error(`pre-write target must stay inside the repo: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
return relative.replaceAll('\\', '/');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractToolRequest(envelope) {
|
|
45
|
+
const nested = firstRecord(envelope.tool, envelope.toolCall, envelope.tool_call);
|
|
46
|
+
const toolName = firstString(
|
|
47
|
+
envelope.tool_name,
|
|
48
|
+
envelope.toolName,
|
|
49
|
+
envelope.name,
|
|
50
|
+
nested?.name,
|
|
51
|
+
nested?.tool_name,
|
|
52
|
+
nested?.toolName
|
|
53
|
+
);
|
|
54
|
+
if (!toolName) return null;
|
|
55
|
+
return {
|
|
56
|
+
toolName,
|
|
57
|
+
normalizedToolName: toolName.toLowerCase().replaceAll('-', '_'),
|
|
58
|
+
input:
|
|
59
|
+
firstRecord(
|
|
60
|
+
envelope.tool_input,
|
|
61
|
+
envelope.toolInput,
|
|
62
|
+
envelope.input,
|
|
63
|
+
nested?.input,
|
|
64
|
+
nested?.tool_input,
|
|
65
|
+
nested?.toolInput
|
|
66
|
+
) || envelope,
|
|
67
|
+
cwd: firstString(envelope.cwd),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function writeOverlay(repoRoot, input) {
|
|
72
|
+
return {
|
|
73
|
+
action: 'write',
|
|
74
|
+
path: resolveTargetPath(
|
|
75
|
+
repoRoot,
|
|
76
|
+
requiredString(firstString(input.file_path, input.filePath, input.path), 'file_path')
|
|
77
|
+
),
|
|
78
|
+
content: requiredString(firstString(input.content, input.text), 'content'),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function editOverlay(repoRoot, input) {
|
|
83
|
+
const relative = resolveTargetPath(
|
|
84
|
+
repoRoot,
|
|
85
|
+
requiredString(firstString(input.file_path, input.filePath, input.path), 'file_path')
|
|
86
|
+
);
|
|
87
|
+
const oldString = requiredString(firstString(input.old_string, input.oldString), 'old_string');
|
|
88
|
+
const newString = requiredString(firstString(input.new_string, input.newString), 'new_string');
|
|
89
|
+
const existing = fs.readFileSync(path.resolve(repoRoot, relative), 'utf8');
|
|
90
|
+
if (!existing.includes(oldString)) throw new Error(`old_string was not found in ${relative}`);
|
|
91
|
+
return { action: 'write', path: relative, content: existing.replace(oldString, newString) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function multiEditOverlay(repoRoot, input) {
|
|
95
|
+
const relative = resolveTargetPath(
|
|
96
|
+
repoRoot,
|
|
97
|
+
requiredString(firstString(input.file_path, input.filePath, input.path), 'file_path')
|
|
98
|
+
);
|
|
99
|
+
let content = fs.readFileSync(path.resolve(repoRoot, relative), 'utf8');
|
|
100
|
+
for (const edit of Array.isArray(input.edits) ? input.edits : []) {
|
|
101
|
+
if (!isRecord(edit)) throw new Error(`MultiEdit edit for ${relative} must be an object`);
|
|
102
|
+
const oldString = requiredString(firstString(edit.old_string, edit.oldString), 'old_string');
|
|
103
|
+
const newString = requiredString(firstString(edit.new_string, edit.newString), 'new_string');
|
|
104
|
+
if (!content.includes(oldString)) throw new Error(`old_string was not found in ${relative}`);
|
|
105
|
+
content = content.replace(oldString, newString);
|
|
106
|
+
}
|
|
107
|
+
return { action: 'write', path: relative, content };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function loadEditApi() {
|
|
111
|
+
if (!editApiPromise) {
|
|
112
|
+
const opcoreEntrypoint = require.resolve('opcore');
|
|
113
|
+
const editModule = path.resolve(
|
|
114
|
+
path.dirname(opcoreEntrypoint),
|
|
115
|
+
'../node_modules/@the-open-engine/opcore-edit/dist/index.js'
|
|
116
|
+
);
|
|
117
|
+
editApiPromise = import(pathToFileURL(editModule).href);
|
|
118
|
+
}
|
|
119
|
+
return editApiPromise;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function patchOverlays(repoRoot, input) {
|
|
123
|
+
const patch = firstString(input.command, input.patch);
|
|
124
|
+
if (!patch) return [];
|
|
125
|
+
const { createNodeEditWorkspace, createPatchEditPlan, isCodexApplyPatch } = await loadEditApi();
|
|
126
|
+
if (!isCodexApplyPatch(patch)) return [];
|
|
127
|
+
const workspace = await createNodeEditWorkspace({ repoRoot });
|
|
128
|
+
const planned = await createPatchEditPlan(workspace, {
|
|
129
|
+
repo: { repoRoot },
|
|
130
|
+
validation: { required: false },
|
|
131
|
+
patch,
|
|
132
|
+
});
|
|
133
|
+
if (!planned.ok) throw new Error(planned.refusal.message);
|
|
134
|
+
return Object.entries(planned.afterState).flatMap(([filePath, content]) => {
|
|
135
|
+
if (content === undefined) return [];
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
action: content === null ? 'delete' : 'write',
|
|
139
|
+
path: filePath,
|
|
140
|
+
...(content === null ? {} : { content }),
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function overlaysForTool(repoRoot, tool) {
|
|
147
|
+
if (writeTools.has(tool.normalizedToolName)) return [writeOverlay(repoRoot, tool.input)];
|
|
148
|
+
if (editTools.has(tool.normalizedToolName)) return [editOverlay(repoRoot, tool.input)];
|
|
149
|
+
if (multiEditTools.has(tool.normalizedToolName)) return [multiEditOverlay(repoRoot, tool.input)];
|
|
150
|
+
if (applyPatchTools.has(tool.normalizedToolName)) return patchOverlays(repoRoot, tool.input);
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = { extractToolRequest, isRecord, overlaysForTool };
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { isUtf8 } = require('node:buffer');
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
|
|
9
|
+
const TYPESCRIPT_AUTHORITIES = [
|
|
10
|
+
'tsconfig.agent-cli-provider.json',
|
|
11
|
+
'tsconfig.cluster.json',
|
|
12
|
+
'tsconfig.hosted-session.json',
|
|
13
|
+
'tsconfig.hosted-target.json',
|
|
14
|
+
'tsconfig.target.json',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CHECKS = [
|
|
18
|
+
'typescript.syntax',
|
|
19
|
+
'typescript.types',
|
|
20
|
+
'typescript.import-graph',
|
|
21
|
+
'typescript.dead-code',
|
|
22
|
+
'typescript.function-metrics',
|
|
23
|
+
'typescript.relevant-tests',
|
|
24
|
+
'typescript.file-length',
|
|
25
|
+
'rust.source-hygiene',
|
|
26
|
+
'rust.fmt',
|
|
27
|
+
'rust.cargo-check',
|
|
28
|
+
'rust.clippy',
|
|
29
|
+
'rust.rustdoc',
|
|
30
|
+
'rust.import-graph',
|
|
31
|
+
'rust.dead-code',
|
|
32
|
+
'rust.graph-signals',
|
|
33
|
+
'rust.file-length',
|
|
34
|
+
'rust.function-metrics',
|
|
35
|
+
'clone.duplication',
|
|
36
|
+
].join(',');
|
|
37
|
+
|
|
38
|
+
function run(command, args, options = {}) {
|
|
39
|
+
const result = spawnSync(command, args, {
|
|
40
|
+
cwd: options.cwd,
|
|
41
|
+
encoding: options.encoding ?? 'buffer',
|
|
42
|
+
env: options.env ?? process.env,
|
|
43
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
44
|
+
timeout: options.timeout ?? 300_000,
|
|
45
|
+
});
|
|
46
|
+
if (result.error) throw result.error;
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function git(repo, args, options = {}) {
|
|
51
|
+
const result = run('git', args, {
|
|
52
|
+
cwd: repo,
|
|
53
|
+
encoding: options.encoding ?? 'buffer',
|
|
54
|
+
env: options.env,
|
|
55
|
+
});
|
|
56
|
+
if (result.status !== 0) {
|
|
57
|
+
const stderr = Buffer.isBuffer(result.stderr) ? result.stderr.toString('utf8') : result.stderr;
|
|
58
|
+
throw new Error(`git ${args[0]} failed: ${stderr.trim() || `exit ${result.status}`}`);
|
|
59
|
+
}
|
|
60
|
+
return result.stdout;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function withoutGitLocalEnv(repo) {
|
|
64
|
+
const names = git(repo, ['rev-parse', '--local-env-vars'], { encoding: 'utf8' })
|
|
65
|
+
.split('\n')
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
const env = { ...process.env };
|
|
68
|
+
for (const name of names) delete env[name];
|
|
69
|
+
return env;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function valueOption(argv, index) {
|
|
73
|
+
const arg = argv[index];
|
|
74
|
+
for (const key of ['base', 'checks']) {
|
|
75
|
+
const flag = `--${key}`;
|
|
76
|
+
if (arg === flag) return { key, value: argv[index + 1], consumed: 1 };
|
|
77
|
+
if (arg.startsWith(`${flag}=`)) return { key, value: arg.slice(flag.length + 1), consumed: 0 };
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseArgs(argv) {
|
|
83
|
+
const options = { base: 'HEAD', checks: DEFAULT_CHECKS, staged: false };
|
|
84
|
+
let index = 0;
|
|
85
|
+
while (index < argv.length) {
|
|
86
|
+
const arg = argv[index];
|
|
87
|
+
if (arg === '--staged') {
|
|
88
|
+
options.staged = true;
|
|
89
|
+
index += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (arg === '--json') {
|
|
93
|
+
index += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const option = valueOption(argv, index);
|
|
97
|
+
if (!option) throw new Error(`Unsupported Opcore introduced-check argument: ${arg}`);
|
|
98
|
+
options[option.key] = option.value;
|
|
99
|
+
index += option.consumed + 1;
|
|
100
|
+
}
|
|
101
|
+
if (!options.base) throw new Error('--base requires a Git revision');
|
|
102
|
+
if (options.staged && options.base !== 'HEAD') {
|
|
103
|
+
throw new Error('--staged cannot be combined with a non-HEAD --base');
|
|
104
|
+
}
|
|
105
|
+
return options;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function nulTokens(buffer) {
|
|
109
|
+
return buffer
|
|
110
|
+
.toString('utf8')
|
|
111
|
+
.split('\0')
|
|
112
|
+
.filter((token) => token.length > 0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseChanges(repo, options) {
|
|
116
|
+
const args = options.staged
|
|
117
|
+
? ['diff', '--cached', '--name-status', '-z', '--find-renames', 'HEAD', '--']
|
|
118
|
+
: ['diff', '--name-status', '-z', '--find-renames', options.base, '--'];
|
|
119
|
+
const tokens = nulTokens(git(repo, args));
|
|
120
|
+
const changes = [];
|
|
121
|
+
|
|
122
|
+
for (let index = 0; index < tokens.length; ) {
|
|
123
|
+
const status = tokens[index++];
|
|
124
|
+
const kind = status[0];
|
|
125
|
+
if (kind === 'R' || kind === 'C') {
|
|
126
|
+
changes.push({ kind, oldPath: tokens[index++], path: tokens[index++] });
|
|
127
|
+
} else if ('AMDT'.includes(kind)) {
|
|
128
|
+
changes.push({ kind, path: tokens[index++] });
|
|
129
|
+
} else {
|
|
130
|
+
throw new Error(`Unsupported Git change status: ${status}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!options.staged) {
|
|
135
|
+
for (const untrackedPath of nulTokens(
|
|
136
|
+
git(repo, ['ls-files', '--others', '--exclude-standard', '-z', '--'])
|
|
137
|
+
)) {
|
|
138
|
+
changes.push({ kind: 'A', path: untrackedPath });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const byPath = new Map();
|
|
143
|
+
for (const change of changes) byPath.set(change.path, change);
|
|
144
|
+
return [...byPath.values()];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function safePath(root, relativePath) {
|
|
148
|
+
const absolute = path.resolve(root, relativePath);
|
|
149
|
+
const relative = path.relative(root, absolute);
|
|
150
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
151
|
+
throw new Error(`Changed path escapes repository root: ${relativePath}`);
|
|
152
|
+
}
|
|
153
|
+
return absolute;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function readAfter(repo, change, staged) {
|
|
157
|
+
let content;
|
|
158
|
+
if (staged) {
|
|
159
|
+
content = git(repo, ['show', `:${change.path}`]);
|
|
160
|
+
} else {
|
|
161
|
+
const absolute = safePath(repo, change.path);
|
|
162
|
+
const stat = fs.lstatSync(absolute);
|
|
163
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
164
|
+
throw new Error(`Opcore introduced gate supports regular changed files only: ${change.path}`);
|
|
165
|
+
}
|
|
166
|
+
content = fs.readFileSync(absolute);
|
|
167
|
+
}
|
|
168
|
+
return isUtf8(content) && !content.includes(0) ? content.toString('utf8') : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function copyPolicy(repo, baseline) {
|
|
172
|
+
const source = path.join(repo, '.opcore', 'config');
|
|
173
|
+
if (!fs.existsSync(source)) return;
|
|
174
|
+
const target = path.join(baseline, '.opcore', 'config');
|
|
175
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
176
|
+
fs.copyFileSync(source, target);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function linkDependencies(repo, baseline) {
|
|
180
|
+
const source = path.join(repo, 'node_modules');
|
|
181
|
+
const target = path.join(baseline, 'node_modules');
|
|
182
|
+
if (fs.existsSync(source) && !fs.existsSync(target)) fs.symlinkSync(source, target, 'dir');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeBaselineRename(baseline, change) {
|
|
186
|
+
if (change.kind !== 'R') return;
|
|
187
|
+
const source = safePath(baseline, change.oldPath);
|
|
188
|
+
const target = safePath(baseline, change.path);
|
|
189
|
+
if (!fs.existsSync(source)) return;
|
|
190
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
191
|
+
fs.renameSync(source, target);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createRequest(repo, baseline, changes, options) {
|
|
195
|
+
const overlays = [];
|
|
196
|
+
const files = [];
|
|
197
|
+
for (const change of changes) {
|
|
198
|
+
normalizeBaselineRename(baseline, change);
|
|
199
|
+
if (change.kind === 'D') {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const content = readAfter(repo, change, options.staged);
|
|
203
|
+
if (content === null) continue;
|
|
204
|
+
overlays.push({ action: 'write', path: change.path, content });
|
|
205
|
+
files.push(change.path);
|
|
206
|
+
}
|
|
207
|
+
for (const authority of TYPESCRIPT_AUTHORITIES) {
|
|
208
|
+
if (fs.existsSync(safePath(baseline, authority))) files.push(authority);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
requestId: `zeroshot-opcore-introduced-${process.pid}`,
|
|
213
|
+
repo: { repoRoot: baseline },
|
|
214
|
+
scope: { kind: 'files', files: [...new Set(files)] },
|
|
215
|
+
graph: { mode: 'optional', provider: 'opcore-graph' },
|
|
216
|
+
overlays,
|
|
217
|
+
...(options.checks ? { checks: options.checks.split(',').filter(Boolean) } : {}),
|
|
218
|
+
reportMode: 'introduced',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function runOpcoreDirect(repo, args, env = process.env) {
|
|
223
|
+
const entrypoint = require.resolve('opcore');
|
|
224
|
+
return run(process.execPath, [entrypoint, ...args], {
|
|
225
|
+
cwd: repo,
|
|
226
|
+
encoding: 'utf8',
|
|
227
|
+
env: {
|
|
228
|
+
...env,
|
|
229
|
+
PATH: `${path.join(repo, 'node_modules', '.bin')}${path.delimiter}${env.PATH || ''}`,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function emit(result) {
|
|
235
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
236
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
237
|
+
process.exitCode = result.status ?? 1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function main() {
|
|
241
|
+
const options = parseArgs(process.argv.slice(2));
|
|
242
|
+
const repo = git(process.cwd(), ['rev-parse', '--show-toplevel'], {
|
|
243
|
+
encoding: 'utf8',
|
|
244
|
+
}).trim();
|
|
245
|
+
git(repo, ['rev-parse', '--verify', `${options.base}^{commit}`]);
|
|
246
|
+
const changes = parseChanges(repo, options);
|
|
247
|
+
if (changes.length === 0) {
|
|
248
|
+
emit(runOpcoreDirect(repo, ['check', '--changed', '--json']));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'zeroshot-opcore-baseline-'));
|
|
253
|
+
const baseline = path.join(tempRoot, 'repo');
|
|
254
|
+
try {
|
|
255
|
+
const baselineEnv = withoutGitLocalEnv(repo);
|
|
256
|
+
git(repo, ['clone', '--quiet', '--shared', '--no-checkout', repo, baseline], {
|
|
257
|
+
env: baselineEnv,
|
|
258
|
+
});
|
|
259
|
+
git(baseline, ['checkout', '--quiet', '--detach', options.base], {
|
|
260
|
+
env: baselineEnv,
|
|
261
|
+
});
|
|
262
|
+
linkDependencies(repo, baseline);
|
|
263
|
+
copyPolicy(repo, baseline);
|
|
264
|
+
const request = createRequest(repo, baseline, changes, options);
|
|
265
|
+
if (request.overlays.length === 0) {
|
|
266
|
+
emit(runOpcoreDirect(repo, ['check', '--changed', '--json']));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const requestPath = path.join(tempRoot, 'validation-request.json');
|
|
270
|
+
fs.writeFileSync(requestPath, `${JSON.stringify(request)}\n`);
|
|
271
|
+
emit(
|
|
272
|
+
runOpcoreDirect(
|
|
273
|
+
baseline,
|
|
274
|
+
['validate', 'hypothetical', '--request-file', requestPath, '--json'],
|
|
275
|
+
baselineEnv
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
} finally {
|
|
279
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
main();
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
}
|