@ran-sh/dsh-crew 1.10.8 → 1.10.9
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/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync,
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { renameTree } from './tree-move.mjs';
|
|
6
7
|
|
|
7
8
|
function plan(home, root) {
|
|
8
9
|
const bridge = join(root, 'official-web-bridge');
|
|
@@ -61,13 +62,17 @@ export function installOfficialFrontendAssets({ home = homedir(), root } = {}) {
|
|
|
61
62
|
mkdirSync(dirname(file), { recursive: true });
|
|
62
63
|
writeFileSync(file, bytes);
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
// Windows can refuse this rename for a moment when something else holds a
|
|
66
|
+
// handle under the destination, and the refusal is not about the rename:
|
|
67
|
+
// the same call succeeds shortly after. Producing the snapshot first in a
|
|
68
|
+
// concurrent installer is the other outcome this tolerates.
|
|
69
|
+
try { renameTree(stage, p.snapshotRoot); stage = null; }
|
|
65
70
|
catch (error) { if (!matches(p)) throw error; }
|
|
66
71
|
}
|
|
67
72
|
if (!matches(p)) throw new Error('frontend snapshot conflicts with its revision');
|
|
68
73
|
temp = `${p.overlayFile}.${randomUUID()}.tmp`;
|
|
69
74
|
writeFileSync(temp, p.overlay, { flag: 'wx', mode: 0o600 });
|
|
70
|
-
|
|
75
|
+
renameTree(temp, p.overlayFile); temp = null;
|
|
71
76
|
return { ok: true, changed: true, revision: p.revision, snapshotRoot: p.snapshotRoot, overlayFile: p.overlayFile };
|
|
72
77
|
} catch (error) {
|
|
73
78
|
return { ok: false, code: 'OFFICIAL_FRONTEND_ASSETS_FAILED', error: error.message };
|
package/src/runtime-identity.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export {
|
|
|
36
36
|
// included in the identity contract.
|
|
37
37
|
const RUNTIME_ID = randomUUID();
|
|
38
38
|
|
|
39
|
-
export const RUNTIME_VERSION = '1.10.
|
|
39
|
+
export const RUNTIME_VERSION = '1.10.9';
|
|
40
40
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
41
41
|
|
|
42
42
|
export const HUB_CAPABILITIES = Object.freeze([
|
package/src/workflow-runtime.mjs
CHANGED
|
@@ -38,8 +38,23 @@ function lines(value) {
|
|
|
38
38
|
return value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
// A reviewer writes its verdict as one line, and it decorates that line the way
|
|
42
|
+
// prose is decorated: bolded, bulleted, quoted, or introduced by a label. The
|
|
43
|
+
// contract asks for "approved / needs changes / rejected", and every one of
|
|
44
|
+
// those decorations used to fall through to `inconclusive` — which blocks
|
|
45
|
+
// acceptance, so turning on the automatic reviewer could reject correct work
|
|
46
|
+
// because the reviewer wrote **Approved** instead of Approved.
|
|
47
|
+
function stripVerdictDecoration(value) {
|
|
48
|
+
return String(value ?? '')
|
|
49
|
+
.replace(/^[\s>*_`#-]+/, '')
|
|
50
|
+
.replace(/^\**\s*(?:final\s+)?verdict\s*[:\-—]\s*/i, '')
|
|
51
|
+
.replace(/^[\s>*_`#-]+/, '')
|
|
52
|
+
.replace(/[\s*_`]+$/, '')
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
export function normalizeReviewVerdict(value) {
|
|
42
|
-
const s =
|
|
57
|
+
const s = stripVerdictDecoration(value).toLowerCase();
|
|
43
58
|
if (/^(approve|approved|pass)\b/.test(s) || s === 'pass' || s === 'approve') return 'approve';
|
|
44
59
|
if (/request.*chang|chang.*request|reject|needs changes/i.test(s)) return 'request_changes';
|
|
45
60
|
return 'inconclusive';
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
// Drives the installed release's own MCP server over stdio, so the client-side
|
|
2
|
-
// dispatch path is exercised by the shipped code rather than by a long-running
|
|
3
|
-
// session whose MCP process predates the fix.
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
|
|
6
|
-
const serverPath = process.argv[2];
|
|
7
|
-
const scenario = process.argv[3] ?? 'zero-change';
|
|
8
|
-
const child = spawn(process.execPath, [serverPath], { stdio: ['pipe', 'pipe', 'pipe'], env: process.env });
|
|
9
|
-
|
|
10
|
-
let buffer = '';
|
|
11
|
-
let nextId = 1;
|
|
12
|
-
const pending = new Map();
|
|
13
|
-
|
|
14
|
-
child.stdout.on('data', (chunk) => {
|
|
15
|
-
buffer += chunk.toString();
|
|
16
|
-
let index;
|
|
17
|
-
while ((index = buffer.indexOf('\n')) >= 0) {
|
|
18
|
-
const line = buffer.slice(0, index).trim();
|
|
19
|
-
buffer = buffer.slice(index + 1);
|
|
20
|
-
if (line === '') continue;
|
|
21
|
-
let message;
|
|
22
|
-
try { message = JSON.parse(line); } catch { continue; }
|
|
23
|
-
if (message.id !== undefined && pending.has(message.id)) {
|
|
24
|
-
const { resolve } = pending.get(message.id);
|
|
25
|
-
pending.delete(message.id);
|
|
26
|
-
resolve(message);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
const stderr = [];
|
|
31
|
-
child.stderr.on('data', (chunk) => stderr.push(chunk.toString()));
|
|
32
|
-
|
|
33
|
-
function send(method, params) {
|
|
34
|
-
const id = nextId++;
|
|
35
|
-
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
36
|
-
return new Promise((resolve) => pending.set(id, { resolve }));
|
|
37
|
-
}
|
|
38
|
-
function notify(method, params) {
|
|
39
|
-
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method, params })}\n`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const scenarios = {
|
|
43
|
-
'zero-change': {
|
|
44
|
-
tool: 'dsh_run_worker',
|
|
45
|
-
arguments: {
|
|
46
|
-
task: 'Create a temporary PowerShell script at work/tmp-hello.ps1 that writes exactly the text 你好 to standard output with no trailing newline, run it, and verify its stdout is exactly 你好 byte-for-byte under both CP936 and UTF-8. Then delete the script and confirm the path no longer exists. Do not leave any file behind: the final workspace must be unchanged.',
|
|
47
|
-
cwd: 'D:/Users/48376/Desktop/crew-verify-1',
|
|
48
|
-
role: 'worker',
|
|
49
|
-
workspace: { worktree: 'none' },
|
|
50
|
-
constraints: { allow_no_changes: true, timeout_seconds: 600 },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
'tool-list': { tool: null },
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const scenarioSpec = scenarios[scenario];
|
|
57
|
-
const init = await send('initialize', {
|
|
58
|
-
protocolVersion: '2024-11-05',
|
|
59
|
-
capabilities: {},
|
|
60
|
-
clientInfo: { name: 'crew-verify', version: '1' },
|
|
61
|
-
});
|
|
62
|
-
if (init.error) {
|
|
63
|
-
console.log('INIT_FAILED', JSON.stringify(init.error));
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
notify('notifications/initialized', {});
|
|
67
|
-
console.log('SERVER', JSON.stringify(init.result?.serverInfo ?? null));
|
|
68
|
-
|
|
69
|
-
const listed = await send('tools/list', {});
|
|
70
|
-
const tools = (listed.result?.tools ?? []).map((t) => t.name);
|
|
71
|
-
console.log('TOOLS', JSON.stringify(tools));
|
|
72
|
-
|
|
73
|
-
if (scenarioSpec.tool) {
|
|
74
|
-
const started = Date.now();
|
|
75
|
-
const call = await send('tools/call', { name: scenarioSpec.tool, arguments: scenarioSpec.arguments });
|
|
76
|
-
console.log('ELAPSED_MS', Date.now() - started);
|
|
77
|
-
if (call.error) {
|
|
78
|
-
console.log('CALL_ERROR', JSON.stringify(call.error));
|
|
79
|
-
} else {
|
|
80
|
-
const text = (call.result?.content ?? []).map((c) => c.text ?? '').join('');
|
|
81
|
-
let parsed = null;
|
|
82
|
-
try { parsed = JSON.parse(text); } catch { /* keep raw */ }
|
|
83
|
-
if (parsed) {
|
|
84
|
-
const summary = parsed.evidence?.summary ?? {};
|
|
85
|
-
console.log('RESULT', JSON.stringify({
|
|
86
|
-
id: parsed.id,
|
|
87
|
-
phase: parsed.phase,
|
|
88
|
-
status: parsed.status,
|
|
89
|
-
isolation: parsed.isolation,
|
|
90
|
-
execution_cwd: parsed.execution_cwd,
|
|
91
|
-
task_status: summary.task_status,
|
|
92
|
-
tests_status: summary.tests_status,
|
|
93
|
-
delivery_complete: summary.delivery_complete,
|
|
94
|
-
workspace_evidence_ok: parsed.workspace_evidence_ok ?? parsed.evidence?.workspace_evidence_ok,
|
|
95
|
-
no_change_verified: parsed.outcome?.no_change_verified,
|
|
96
|
-
failure: parsed.failure?.reason_code ?? null,
|
|
97
|
-
error: parsed.error ?? null,
|
|
98
|
-
}, null, 1));
|
|
99
|
-
} else {
|
|
100
|
-
console.log('RAW', text.slice(0, 1500));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
child.kill();
|
|
106
|
-
if (stderr.length) console.log('STDERR_SAMPLE', stderr.join('').slice(0, 400));
|