morphisms 0.1.0-beta.1 → 0.1.0-beta.4
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/lib/cli.js +3 -1
- package/lib/onboarding.js +113 -0
- package/lib/platform.js +0 -1
- package/lib/workbench-health.js +126 -0
- package/lib/workbench.js +92 -18
- package/package.json +3 -2
- package/templates/agent/skills/morphisms/SKILL.md +19 -3
- package/templates/claude/skills/morphisms/SKILL.md +19 -3
- package/templates/instructions/starter-issues-agent-manual.md +167 -54
- package/templates/instructions/workspace-agent-manual.md +1 -1
package/lib/cli.js
CHANGED
|
@@ -2,9 +2,10 @@ const pkg = require('../package.json');
|
|
|
2
2
|
const { initCommand } = require('./init');
|
|
3
3
|
const { updateCommand } = require('./update');
|
|
4
4
|
const { workbenchCommand } = require('./workbench');
|
|
5
|
+
const { onboardingCommand } = require('./onboarding');
|
|
5
6
|
|
|
6
7
|
function help() {
|
|
7
|
-
console.log(`Morphisms ${pkg.version}\n\nUsage:\n morphisms init\n morphisms workbench [--port N]\n morphisms update\n morphisms --version`);
|
|
8
|
+
console.log(`Morphisms ${pkg.version}\n\nUsage:\n morphisms init\n morphisms workbench [--port N]\n morphisms onboarding [--thread CHANNEL] [--json]\n morphisms update\n morphisms --version`);
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
async function main(argv) {
|
|
@@ -20,6 +21,7 @@ async function main(argv) {
|
|
|
20
21
|
if (command === 'init') return initCommand(rest);
|
|
21
22
|
if (command === 'update') return updateCommand(rest);
|
|
22
23
|
if (command === 'workbench') return workbenchCommand(rest);
|
|
24
|
+
if (command === 'onboarding') return onboardingCommand(rest);
|
|
23
25
|
console.error(`unknown morphisms command: ${command}`);
|
|
24
26
|
help();
|
|
25
27
|
return 1;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { spawn } = require('node:child_process');
|
|
2
|
+
const { buildWorkbenchArgv, configuredPort } = require('./workbench');
|
|
3
|
+
const {
|
|
4
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
5
|
+
DEFAULT_POLL_TIMEOUT_MS,
|
|
6
|
+
DEFAULT_PROBE_TIMEOUT_MS,
|
|
7
|
+
occupiedPortReason,
|
|
8
|
+
probeVersion,
|
|
9
|
+
waitForWorkbench,
|
|
10
|
+
watchChildProcess,
|
|
11
|
+
} = require('./workbench-health');
|
|
12
|
+
|
|
13
|
+
function parseOnboardingArgs(argv) {
|
|
14
|
+
let thread;
|
|
15
|
+
let json = false;
|
|
16
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
17
|
+
const arg = argv[i];
|
|
18
|
+
if (arg === '--json') {
|
|
19
|
+
json = true;
|
|
20
|
+
} else if (arg === '--thread') {
|
|
21
|
+
thread = argv[++i];
|
|
22
|
+
if (!thread) throw new Error('--thread requires a value');
|
|
23
|
+
} else if (arg.startsWith('--thread=')) {
|
|
24
|
+
thread = arg.slice('--thread='.length);
|
|
25
|
+
if (!thread) throw new Error('--thread requires a value');
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(`unknown onboarding argument: ${arg}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { thread, json };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildOnboardingUrl(port, thread) {
|
|
34
|
+
const base = `http://localhost:${port}/#workbench?onboarding=1`;
|
|
35
|
+
if (!thread) return base;
|
|
36
|
+
return `${base}&thread=${encodeURIComponent(thread)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function startDetachedWorkbench(repoRoot, port) {
|
|
40
|
+
const workspaceRegistry = process.env.MORPHISMS_WORKBENCH_REGISTRY || undefined;
|
|
41
|
+
const child = spawn('mor-workbench', buildWorkbenchArgv(repoRoot, { port, workspaceRegistry }), {
|
|
42
|
+
detached: true,
|
|
43
|
+
stdio: 'ignore',
|
|
44
|
+
});
|
|
45
|
+
const childState = watchChildProcess(child);
|
|
46
|
+
child.unref();
|
|
47
|
+
return childState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function onboardingCommand(argv, options = {}) {
|
|
51
|
+
const stdout = options.stdout || process.stdout;
|
|
52
|
+
const stderr = options.stderr || process.stderr;
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = parseOnboardingArgs(argv);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return reportFailure(error.message, argv.includes('--json'), stdout, stderr);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const repoRoot = options.cwd || process.cwd();
|
|
61
|
+
const port = configuredPort(repoRoot);
|
|
62
|
+
const initialProbe = await probeVersion(port, { timeoutMs: options.probeTimeoutMs || DEFAULT_PROBE_TIMEOUT_MS });
|
|
63
|
+
let started = false;
|
|
64
|
+
let healthy = initialProbe;
|
|
65
|
+
let spawnState;
|
|
66
|
+
|
|
67
|
+
if (!initialProbe.ok) {
|
|
68
|
+
if (initialProbe.kind === 'occupied') {
|
|
69
|
+
return reportFailure(occupiedPortReason(port, initialProbe.reason), parsed.json, stdout, stderr);
|
|
70
|
+
}
|
|
71
|
+
started = true;
|
|
72
|
+
try {
|
|
73
|
+
const starter = options.startWorkbench || startDetachedWorkbench;
|
|
74
|
+
spawnState = starter(repoRoot, port);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return reportFailure(`failed to run mor-workbench: ${error.message}`, parsed.json, stdout, stderr);
|
|
77
|
+
}
|
|
78
|
+
healthy = await waitForWorkbench(port, {
|
|
79
|
+
pollTimeoutMs: options.pollTimeoutMs || DEFAULT_POLL_TIMEOUT_MS,
|
|
80
|
+
pollIntervalMs: options.pollIntervalMs || DEFAULT_POLL_INTERVAL_MS,
|
|
81
|
+
probeTimeoutMs: options.probeTimeoutMs || DEFAULT_PROBE_TIMEOUT_MS,
|
|
82
|
+
spawnState,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!healthy.ok) {
|
|
87
|
+
return reportFailure(healthy.reason || 'workbench is not healthy', parsed.json, stdout, stderr);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const url = buildOnboardingUrl(port, parsed.thread);
|
|
91
|
+
if (parsed.json) {
|
|
92
|
+
stdout.write(`${JSON.stringify({ ok: true, port, started, url })}\n`);
|
|
93
|
+
} else {
|
|
94
|
+
stdout.write(`workbench: running (port ${port})\n${url}\n`);
|
|
95
|
+
}
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function reportFailure(reason, asJson, stdout, stderr) {
|
|
100
|
+
if (asJson) {
|
|
101
|
+
stdout.write(`${JSON.stringify({ ok: false, reason })}\n`);
|
|
102
|
+
} else {
|
|
103
|
+
stderr.write(`morphisms onboarding failed: ${reason}\n`);
|
|
104
|
+
}
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
buildOnboardingUrl,
|
|
110
|
+
onboardingCommand,
|
|
111
|
+
parseOnboardingArgs,
|
|
112
|
+
startDetachedWorkbench,
|
|
113
|
+
};
|
package/lib/platform.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const SUPPORTED_PLATFORMS = [
|
|
2
2
|
{ platform: 'linux', arch: 'x64', packageName: '@morphisms/bin-linux-x64' },
|
|
3
3
|
{ platform: 'darwin', arch: 'arm64', packageName: '@morphisms/bin-darwin-arm64' },
|
|
4
|
-
{ platform: 'darwin', arch: 'x64', packageName: '@morphisms/bin-darwin-x64' },
|
|
5
4
|
];
|
|
6
5
|
|
|
7
6
|
function platformPackageName(platform = process.platform, arch = process.arch) {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const http = require('node:http');
|
|
2
|
+
|
|
3
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 1000;
|
|
4
|
+
const DEFAULT_POLL_TIMEOUT_MS = 30000;
|
|
5
|
+
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
6
|
+
|
|
7
|
+
function probeVersion(port, options = {}) {
|
|
8
|
+
const timeoutMs = options.timeoutMs || DEFAULT_PROBE_TIMEOUT_MS;
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
let settled = false;
|
|
11
|
+
function finish(result) {
|
|
12
|
+
if (settled) return;
|
|
13
|
+
settled = true;
|
|
14
|
+
resolve(result);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const req = http.get({
|
|
18
|
+
hostname: '127.0.0.1',
|
|
19
|
+
port,
|
|
20
|
+
path: '/version',
|
|
21
|
+
timeout: timeoutMs,
|
|
22
|
+
agent: false,
|
|
23
|
+
}, (res) => {
|
|
24
|
+
res.resume();
|
|
25
|
+
res.on('end', () => {
|
|
26
|
+
if (res.statusCode === 200) {
|
|
27
|
+
finish({ ok: true, kind: 'healthy' });
|
|
28
|
+
} else {
|
|
29
|
+
finish({ ok: false, kind: 'occupied', reason: `GET /version returned ${res.statusCode}` });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
req.setTimeout(timeoutMs, () => {
|
|
35
|
+
const error = new Error(`GET /version timed out after ${timeoutMs}ms`);
|
|
36
|
+
error.code = 'ETIMEDOUT';
|
|
37
|
+
req.destroy(error);
|
|
38
|
+
});
|
|
39
|
+
req.on('error', (error) => {
|
|
40
|
+
finish({ ok: false, kind: 'unavailable', reason: error.message, code: error.code });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sleep(ms) {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function occupiedPortReason(port, detail) {
|
|
50
|
+
return `port ${port} is occupied by something that is not the Morphisms Workbench${detail ? ` (${detail})` : ''}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function watchChildProcess(child) {
|
|
54
|
+
let failureReason;
|
|
55
|
+
|
|
56
|
+
child.once('error', (error) => {
|
|
57
|
+
failureReason = `failed to run mor-workbench: ${error.message}`;
|
|
58
|
+
});
|
|
59
|
+
child.once('exit', (code, signal) => {
|
|
60
|
+
if (!failureReason) failureReason = childExitReason(code, signal);
|
|
61
|
+
});
|
|
62
|
+
child.once('close', (code, signal) => {
|
|
63
|
+
if (!failureReason) failureReason = childExitReason(code, signal);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
getFailureReason() {
|
|
68
|
+
return failureReason;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function childExitReason(code, signal) {
|
|
74
|
+
if (signal) return `mor-workbench exited before becoming healthy (signal ${signal})`;
|
|
75
|
+
return `mor-workbench exited before becoming healthy (code ${typeof code === 'number' ? code : 'unknown'})`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function spawnFailureReason(spawnState) {
|
|
79
|
+
if (!spawnState) return undefined;
|
|
80
|
+
if (spawnState.getFailureReason) return spawnState.getFailureReason();
|
|
81
|
+
if (spawnState.getError) {
|
|
82
|
+
const error = spawnState.getError();
|
|
83
|
+
if (error) return `failed to run mor-workbench: ${error.message}`;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function waitForWorkbench(port, options = {}) {
|
|
89
|
+
const pollTimeoutMs = options.pollTimeoutMs || DEFAULT_POLL_TIMEOUT_MS;
|
|
90
|
+
const pollIntervalMs = options.pollIntervalMs || DEFAULT_POLL_INTERVAL_MS;
|
|
91
|
+
const probeTimeoutMs = options.probeTimeoutMs || DEFAULT_PROBE_TIMEOUT_MS;
|
|
92
|
+
const spawnState = options.spawnState;
|
|
93
|
+
const deadline = Date.now() + pollTimeoutMs;
|
|
94
|
+
let lastReason = 'not probed';
|
|
95
|
+
|
|
96
|
+
for (;;) {
|
|
97
|
+
const failureReason = spawnFailureReason(spawnState);
|
|
98
|
+
if (failureReason) return { ok: false, reason: failureReason };
|
|
99
|
+
|
|
100
|
+
const probe = await probeVersion(port, { timeoutMs: probeTimeoutMs });
|
|
101
|
+
if (probe.ok) return { ok: true };
|
|
102
|
+
if (probe.kind === 'occupied') return { ok: false, reason: occupiedPortReason(port, probe.reason) };
|
|
103
|
+
lastReason = probe.reason || lastReason;
|
|
104
|
+
|
|
105
|
+
const remaining = deadline - Date.now();
|
|
106
|
+
if (remaining <= 0) break;
|
|
107
|
+
await sleep(Math.min(pollIntervalMs, remaining));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const failureReason = spawnFailureReason(spawnState);
|
|
111
|
+
if (failureReason) return { ok: false, reason: failureReason };
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
reason: `workbench did not become healthy on port ${port} within ${Math.round(pollTimeoutMs / 1000)}s (${lastReason})`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
120
|
+
DEFAULT_POLL_TIMEOUT_MS,
|
|
121
|
+
DEFAULT_PROBE_TIMEOUT_MS,
|
|
122
|
+
occupiedPortReason,
|
|
123
|
+
probeVersion,
|
|
124
|
+
waitForWorkbench,
|
|
125
|
+
watchChildProcess,
|
|
126
|
+
};
|
package/lib/workbench.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
const { spawn } = require('node:child_process');
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const { exists } = require('./files');
|
|
4
|
+
const {
|
|
5
|
+
DEFAULT_POLL_INTERVAL_MS,
|
|
6
|
+
DEFAULT_POLL_TIMEOUT_MS,
|
|
7
|
+
DEFAULT_PROBE_TIMEOUT_MS,
|
|
8
|
+
waitForWorkbench,
|
|
9
|
+
watchChildProcess,
|
|
10
|
+
} = require('./workbench-health');
|
|
4
11
|
|
|
5
12
|
const WORKBENCH_LAUNCH = {
|
|
6
13
|
source: '.morphisms-contract/issues_front_half/contract.mor',
|
|
@@ -91,30 +98,97 @@ function buildWorkbenchArgv(repoRoot, options = {}) {
|
|
|
91
98
|
];
|
|
92
99
|
}
|
|
93
100
|
|
|
94
|
-
function
|
|
101
|
+
function childExitPromise(child) {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
child.once('error', (error) => resolve({ error }));
|
|
104
|
+
child.once('exit', (code, signal) => resolve({ code, signal }));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function waitForExitOrKill(child, graceMs) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
let settled = false;
|
|
111
|
+
let timer;
|
|
112
|
+
function finish() {
|
|
113
|
+
if (settled) return;
|
|
114
|
+
settled = true;
|
|
115
|
+
if (timer) clearTimeout(timer);
|
|
116
|
+
resolve();
|
|
117
|
+
}
|
|
118
|
+
child.once('exit', finish);
|
|
119
|
+
child.once('close', finish);
|
|
120
|
+
timer = setTimeout(() => {
|
|
121
|
+
if (settled) return;
|
|
122
|
+
try {
|
|
123
|
+
child.kill('SIGKILL');
|
|
124
|
+
} catch (_) {
|
|
125
|
+
// The child may already be gone; the command is already on a failure path.
|
|
126
|
+
}
|
|
127
|
+
finish();
|
|
128
|
+
}, graceMs);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function terminateChild(child, options = {}) {
|
|
133
|
+
if (!child || typeof child.kill !== 'function') return;
|
|
134
|
+
const graceMs = options.killGraceMs || 250;
|
|
135
|
+
try {
|
|
136
|
+
child.kill('SIGTERM');
|
|
137
|
+
} catch (_) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await waitForExitOrKill(child, graceMs);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function workbenchCommand(argv, options = {}) {
|
|
144
|
+
const stdout = options.stdout || process.stdout;
|
|
145
|
+
const stderr = options.stderr || process.stderr;
|
|
146
|
+
const cwd = options.cwd || process.cwd();
|
|
95
147
|
let parsed;
|
|
96
148
|
try {
|
|
97
149
|
parsed = parseWorkbenchArgs(argv);
|
|
98
150
|
} catch (error) {
|
|
99
|
-
|
|
151
|
+
stderr.write(`${error.message}\n`);
|
|
100
152
|
return 1;
|
|
101
153
|
}
|
|
102
|
-
const port = parsed.port || configuredPort(
|
|
103
|
-
const childArgv = buildWorkbenchArgv(
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
child
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
process.exit(typeof code === 'number' ? code : 1);
|
|
154
|
+
const port = parsed.port || configuredPort(cwd);
|
|
155
|
+
const childArgv = buildWorkbenchArgv(cwd, parsed);
|
|
156
|
+
stdout.write(`starting workbench on port ${port}...\n`);
|
|
157
|
+
const spawnWorkbench = options.spawnWorkbench || ((args, spawnOptions) => spawn('mor-workbench', args, { cwd: spawnOptions.cwd, stdio: 'inherit' }));
|
|
158
|
+
const child = spawnWorkbench(childArgv, { port, cwd });
|
|
159
|
+
const childExit = childExitPromise(child);
|
|
160
|
+
const spawnState = watchChildProcess(child);
|
|
161
|
+
const healthy = await waitForWorkbench(port, {
|
|
162
|
+
pollTimeoutMs: options.pollTimeoutMs || DEFAULT_POLL_TIMEOUT_MS,
|
|
163
|
+
pollIntervalMs: options.pollIntervalMs || DEFAULT_POLL_INTERVAL_MS,
|
|
164
|
+
probeTimeoutMs: options.probeTimeoutMs || DEFAULT_PROBE_TIMEOUT_MS,
|
|
165
|
+
spawnState,
|
|
116
166
|
});
|
|
117
|
-
|
|
167
|
+
if (!healthy.ok) {
|
|
168
|
+
await terminateChild(child, { killGraceMs: options.killGraceMs });
|
|
169
|
+
stderr.write(`morphisms workbench failed: ${healthy.reason || 'workbench is not healthy'}\n`);
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
stdout.write(`Morphisms Workbench: http://127.0.0.1:${port}/#workbench\n`);
|
|
174
|
+
const exit = await childExit;
|
|
175
|
+
if (exit.error) {
|
|
176
|
+
stderr.write(`failed to run mor-workbench: ${exit.error.message}\n`);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (exit.signal) {
|
|
180
|
+
if (!options.noSignalForward) process.kill(process.pid, exit.signal);
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
return typeof exit.code === 'number' ? exit.code : 1;
|
|
118
184
|
}
|
|
119
185
|
|
|
120
|
-
module.exports = {
|
|
186
|
+
module.exports = {
|
|
187
|
+
WORKBENCH_LAUNCH,
|
|
188
|
+
buildWorkbenchArgv,
|
|
189
|
+
configuredPort,
|
|
190
|
+
parsePort,
|
|
191
|
+
parseWorkbenchArgs,
|
|
192
|
+
terminateChild,
|
|
193
|
+
workbenchCommand,
|
|
194
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "morphisms",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "Morphisms workflow runtime shim and onboarding wizard",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"node": ">=18"
|
|
20
20
|
},
|
|
21
21
|
"optionalDependencies": {
|
|
22
|
-
"@morphisms/bin-linux-x64": "0.1.0-beta.
|
|
22
|
+
"@morphisms/bin-linux-x64": "0.1.0-beta.4",
|
|
23
|
+
"@morphisms/bin-darwin-arm64": "0.1.0-beta.4"
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -10,8 +10,8 @@ You are the orchestrator. Keep this session focused on coordination; delegate on
|
|
|
10
10
|
## Orient first
|
|
11
11
|
|
|
12
12
|
1. Run `mor agent instructions` and read the S1/S2/S3 paths it reports. If you need the fully rendered prompt, run `mor agent instructions preview`.
|
|
13
|
-
2.
|
|
14
|
-
3. If you are inside a MOR Workbench thread capsule, narrate with `mor-thread reply` and propose viewer movement with `mor-thread suggest`; otherwise narrate in the current agent conversation and include the Workbench deep-link
|
|
13
|
+
2. Run `morphisms onboarding` (optionally `--thread <channelId>` when you have a thread id) and open only the Workbench URL it prints. Use Workbench, not legacy graph routes.
|
|
14
|
+
3. If you are inside a MOR Workbench thread capsule, narrate with `mor-thread reply` and propose viewer movement with `mor-thread suggest`; otherwise narrate in the current agent conversation and include the Workbench deep-link from `morphisms onboarding`.
|
|
15
15
|
|
|
16
16
|
## Frontier
|
|
17
17
|
|
|
@@ -79,8 +79,24 @@ A refusal is a success signal from the runtime, not a CLI problem. Read the diag
|
|
|
79
79
|
|
|
80
80
|
## Concierge trigger
|
|
81
81
|
|
|
82
|
-
If `.mor/onboarding-complete` is missing,
|
|
82
|
+
If `.mor/onboarding-complete` is missing, stop normal orchestration and run the S3
|
|
83
|
+
"Concierge onboarding" beat script instead. It is the human's first-ever contact with
|
|
84
|
+
Morphisms; the S3 section is the authority on wording and pacing. The shape:
|
|
85
|
+
|
|
86
|
+
1. Step zero: `morphisms onboarding` (health-gated; the only legal source of a Workbench
|
|
87
|
+
URL), then ask for one real bug or annoyance.
|
|
88
|
+
2. Baton pass: materialize the `onboarding` thread, greet the human inside it via
|
|
89
|
+
`mor-thread reply`, hand over the deep link from `morphisms onboarding --thread
|
|
90
|
+
onboarding`, and continue the entire conversation in that thread.
|
|
91
|
+
3. Land the four proofs in order — record filed (show it), a deliberate refusal on the
|
|
92
|
+
graph, an investigation citing their code, and one real attention decision — one
|
|
93
|
+
consented step at a time, using only the ten allowed words from S3.
|
|
94
|
+
4. Close the journey, offer the replay, then write one line to `.mor/onboarding-complete`:
|
|
83
95
|
|
|
84
96
|
```text
|
|
85
97
|
<ISO-8601 date> <first issue id>
|
|
86
98
|
```
|
|
99
|
+
|
|
100
|
+
During concierge onboarding, the normal spawn/delegation policy is suspended — the
|
|
101
|
+
onboarding agent works in-session; normal spawn/delegation policy resumes after
|
|
102
|
+
`.mor/onboarding-complete` exists.
|
|
@@ -10,8 +10,8 @@ You are the orchestrator. Keep this Claude Code session focused on coordination;
|
|
|
10
10
|
## Orient first
|
|
11
11
|
|
|
12
12
|
1. Run `mor agent instructions` and read the S1/S2/S3 paths it reports. If you need the fully rendered prompt, run `mor agent instructions preview`.
|
|
13
|
-
2.
|
|
14
|
-
3. If you are inside a MOR Workbench thread capsule, narrate with `mor-thread reply` and propose viewer movement with `mor-thread suggest`; otherwise narrate in the current Claude conversation and include the Workbench deep-link
|
|
13
|
+
2. Run `morphisms onboarding` (optionally `--thread <channelId>` when you have a thread id) and open only the Workbench URL it prints. Use Workbench, not legacy graph routes.
|
|
14
|
+
3. If you are inside a MOR Workbench thread capsule, narrate with `mor-thread reply` and propose viewer movement with `mor-thread suggest`; otherwise narrate in the current Claude conversation and include the Workbench deep-link from `morphisms onboarding`.
|
|
15
15
|
|
|
16
16
|
## Frontier
|
|
17
17
|
|
|
@@ -79,8 +79,24 @@ A refusal is a success signal from the runtime, not a CLI problem. Read the diag
|
|
|
79
79
|
|
|
80
80
|
## Concierge trigger
|
|
81
81
|
|
|
82
|
-
If `.mor/onboarding-complete` is missing,
|
|
82
|
+
If `.mor/onboarding-complete` is missing, stop normal orchestration and run the S3
|
|
83
|
+
"Concierge onboarding" beat script instead. It is the human's first-ever contact with
|
|
84
|
+
Morphisms; the S3 section is the authority on wording and pacing. The shape:
|
|
85
|
+
|
|
86
|
+
1. Step zero: `morphisms onboarding` (health-gated; the only legal source of a Workbench
|
|
87
|
+
URL), then ask for one real bug or annoyance.
|
|
88
|
+
2. Baton pass: materialize the `onboarding` thread, greet the human inside it via
|
|
89
|
+
`mor-thread reply`, hand over the deep link from `morphisms onboarding --thread
|
|
90
|
+
onboarding`, and continue the entire conversation in that thread.
|
|
91
|
+
3. Land the four proofs in order — record filed (show it), a deliberate refusal on the
|
|
92
|
+
graph, an investigation citing their code, and one real attention decision — one
|
|
93
|
+
consented step at a time, using only the ten allowed words from S3.
|
|
94
|
+
4. Close the journey, offer the replay, then write one line to `.mor/onboarding-complete`:
|
|
83
95
|
|
|
84
96
|
```text
|
|
85
97
|
<ISO-8601 date> <first issue id>
|
|
86
98
|
```
|
|
99
|
+
|
|
100
|
+
During concierge onboarding, the normal spawn/delegation policy is suspended — the
|
|
101
|
+
onboarding agent works in-session; normal spawn/delegation policy resumes after
|
|
102
|
+
`.mor/onboarding-complete` exists.
|
|
@@ -68,51 +68,152 @@ Attention:
|
|
|
68
68
|
|
|
69
69
|
## Concierge onboarding
|
|
70
70
|
|
|
71
|
-
If `.mor/onboarding-complete` does **not** exist, you are in concierge mode.
|
|
71
|
+
If `.mor/onboarding-complete` does **not** exist, you are in concierge mode. This is the
|
|
72
|
+
human's first-ever contact with Morphisms. Your job is not to demonstrate features; it is
|
|
73
|
+
to land four proofs, in order, about a problem the human actually has:
|
|
74
|
+
|
|
75
|
+
1. **My words became a record I can inspect.**
|
|
76
|
+
2. **The rules are real** — you will try to skip ahead and the graph will visibly refuse you.
|
|
77
|
+
3. **The evidence is real** — a report citing their code, attached to the record.
|
|
78
|
+
4. **The system formally summons the human** when a decision needs them.
|
|
79
|
+
|
|
80
|
+
Everything below serves those proofs. Target: proof 1 within 3 minutes of starting; done
|
|
81
|
+
in about 7.
|
|
82
|
+
|
|
83
|
+
### Non-negotiable rules (entire concierge flow)
|
|
84
|
+
|
|
85
|
+
- **Consent cadence.** A "step" is one movement of the record (its evidence plus the edge
|
|
86
|
+
that uses it). Before each step: one plain sentence of what you will do and why, then
|
|
87
|
+
ask. After: one sentence of what changed, plus a way to see it. Never two steps between
|
|
88
|
+
consents.
|
|
89
|
+
- **Vocabulary.** To the human, use only: record, graph, state, journey, evidence, report,
|
|
90
|
+
plan, attention, thread, Workbench. Never say: hydrate, enrich, walk, payload, fibre,
|
|
91
|
+
morphism, distro, release, plateau, witness, capsule, armed, harness, tmux, artifact.
|
|
92
|
+
The commands you run are machinery; the words you say are the product.
|
|
93
|
+
- **URL rule.** Never print or link a Workbench URL unless `morphisms onboarding` has just
|
|
94
|
+
succeeded; quote only the URL it prints.
|
|
95
|
+
- **Failure narration.** If a command fails unexpectedly, say in the thread what you
|
|
96
|
+
tried, that it failed, and what you will do next — in plain words. Never paste raw
|
|
97
|
+
JSON or diagnostics at the human, never go silent, never pretend it worked.
|
|
98
|
+
- **One surface.** After the baton pass (beat 2), the Workbench thread is the only
|
|
99
|
+
conversation surface. The terminal prints nothing further except a hard-failure notice.
|
|
100
|
+
- **Never fabricate the human's answer.** The attention step uses their real reply.
|
|
101
|
+
- **Spawn policy is suspended.** You work in-session; normal spawn/delegation policy
|
|
102
|
+
resumes after `.mor/onboarding-complete` exists.
|
|
103
|
+
- **Go slow.** The human does not know what Morphisms is. Introduce each of the ten
|
|
104
|
+
allowed words only at the moment the thing it names is on their screen. If they ask a
|
|
105
|
+
question, answer it at their level before moving on.
|
|
106
|
+
|
|
107
|
+
### Beat 1 — step zero and the one question (terminal, under a minute)
|
|
108
|
+
|
|
109
|
+
Silently run step zero first:
|
|
72
110
|
|
|
73
|
-
|
|
111
|
+
```bash
|
|
112
|
+
morphisms onboarding
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This proves the Workbench is alive (starting it if needed) and prints the base URL. If it
|
|
116
|
+
fails, tell the human honestly that the visual Workbench could not start (quote its
|
|
117
|
+
one-line reason), offer to continue in the terminal as a degraded walkthrough, and never
|
|
118
|
+
show a URL.
|
|
119
|
+
|
|
120
|
+
Then ask the single opening question:
|
|
121
|
+
|
|
122
|
+
> Let's make this real. Give me one bug, annoyance, or confusing behavior in this repo.
|
|
123
|
+
> If you don't have one ready, I can scan the repo and propose a small first issue.
|
|
124
|
+
|
|
125
|
+
If they ask you to scan, choose a small truthful annoyance grounded in real files.
|
|
126
|
+
Do not file anything yet — filing happens in the thread, where they can watch.
|
|
127
|
+
|
|
128
|
+
### Beat 2 — the baton pass (terminal → thread)
|
|
129
|
+
|
|
130
|
+
Create the onboarding thread, greet them in it, and hand over one link.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Materialize the thread channel and its working capsule (port from
|
|
134
|
+
# .morphisms-contract/config.json workbench_port):
|
|
135
|
+
curl -s "http://127.0.0.1:<port>/api/agent/thread/v1?channel=onboarding"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Read `working_capsule_path` from the JSON response; call it `$CAPSULE` below.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
mor-thread --thread "$CAPSULE" reply --message "👋 I'm your Morphisms agent. This thread is where we'll work — reply here and we'll get started."
|
|
142
|
+
morphisms onboarding --thread onboarding
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Print in the terminal ONLY this handoff (with the URL the command printed):
|
|
146
|
+
|
|
147
|
+
> Everything happens in the Workbench from here. Click:
|
|
148
|
+
> `<printed URL>`
|
|
149
|
+
> I've already opened our thread there and said hello.
|
|
150
|
+
|
|
151
|
+
Then wait for their reply:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
mor-thread --thread "$CAPSULE" next --wait
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Loop while it returns no message. After roughly three empty waits, print one gentle
|
|
158
|
+
terminal nudge ("Still here — click the link above when you're ready") and keep waiting.
|
|
159
|
+
|
|
160
|
+
Fallback: if `mor-thread` or the capsule fails, post messages over HTTP instead
|
|
161
|
+
(`POST /api/agent/outbox/v1` with `{"channel":"onboarding","message":"..."}`), read
|
|
162
|
+
replies by polling `GET /api/agent/thread/v1?channel=onboarding`, and skip the look-here
|
|
163
|
+
suggestions in later beats — the flow continues without them.
|
|
74
164
|
|
|
75
|
-
|
|
165
|
+
### Beat 3 — roundtrip proof and the plan
|
|
76
166
|
|
|
77
|
-
|
|
167
|
+
When their first reply arrives, set the frame in one short message:
|
|
78
168
|
|
|
79
|
-
|
|
169
|
+
> Great — you made it. Here's the plan: I'll file your bug as a **record**, investigate
|
|
170
|
+
> it, and write a plan — and you'll watch every step move across the graph you're looking
|
|
171
|
+
> at. I'll ask before each step. Ready?
|
|
80
172
|
|
|
81
|
-
|
|
173
|
+
### Beat 4 — proof 1: their words become a record
|
|
174
|
+
|
|
175
|
+
On consent, file it (add-accepted fields only):
|
|
82
176
|
|
|
83
177
|
```json
|
|
84
|
-
{"title":"<short title>","kind":"bug","summary":"<
|
|
178
|
+
{"title":"<short title>","kind":"bug","summary":"<their words plus any repo clue>"}
|
|
85
179
|
```
|
|
86
180
|
|
|
87
181
|
```bash
|
|
88
182
|
mor issue add --payload /tmp/morphisms-first-issue.json
|
|
89
183
|
```
|
|
90
184
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### 2. Hydrate and explain the packet
|
|
185
|
+
Report and show:
|
|
94
186
|
|
|
95
|
-
|
|
187
|
+
> Your bug is now record `<id>`, sitting at the first state of its journey — you can see
|
|
188
|
+
> it on the graph. Don't take my word for it:
|
|
96
189
|
|
|
97
190
|
```bash
|
|
98
|
-
mor
|
|
191
|
+
mor-thread --thread "$CAPSULE" suggest open_record --record-id <id> --label "Show me the record"
|
|
99
192
|
```
|
|
100
193
|
|
|
101
|
-
|
|
194
|
+
Tell them they can press `i` on anything they open to see the raw truth underneath.
|
|
195
|
+
|
|
196
|
+
### Beat 5 — proof 2: the rules are real
|
|
102
197
|
|
|
103
|
-
|
|
198
|
+
Announce it first — eyes on the graph before you act:
|
|
104
199
|
|
|
105
|
-
Before
|
|
200
|
+
> Before I investigate, let me show you why this isn't just a chat log. I'm going to try
|
|
201
|
+
> to skip straight to investigating **without any evidence** — watch the edge leading out
|
|
202
|
+
> of the first state.
|
|
106
203
|
|
|
107
204
|
```bash
|
|
108
205
|
mor issue walk <id> journey.choose_standard_investigation
|
|
109
206
|
```
|
|
110
207
|
|
|
111
|
-
|
|
208
|
+
This is refused (the record has no triage evidence yet) and the edge flashes red on the
|
|
209
|
+
graph. Explain:
|
|
112
210
|
|
|
113
|
-
|
|
211
|
+
> The graph refused me. I cannot move your issue until the evidence it requires actually
|
|
212
|
+
> exists. The rules live in the graph — not in my good intentions.
|
|
114
213
|
|
|
115
|
-
|
|
214
|
+
### Beat 6 — proof 3: real evidence from their repo
|
|
215
|
+
|
|
216
|
+
Step 6a (consent, then triage): attach the decision and move — note the contrast:
|
|
116
217
|
|
|
117
218
|
```json
|
|
118
219
|
{"decision":"standard","rationale":"Needs one file/line investigation before scoping.","signals":["<signal>"]}
|
|
@@ -124,11 +225,10 @@ mor issue walk <id> journey.choose_standard_investigation
|
|
|
124
225
|
mor issue hydrate <id> journey.investigating
|
|
125
226
|
```
|
|
126
227
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
### 5. Investigate with citations
|
|
228
|
+
> This time the same edge turned green — the evidence existed, so the move was legal.
|
|
130
229
|
|
|
131
|
-
|
|
230
|
+
Step 6b (consent, then investigate): do a real repo read. `citations` must be non-empty
|
|
231
|
+
with real `{file,line,excerpt,why_it_matters}` entries:
|
|
132
232
|
|
|
133
233
|
```json
|
|
134
234
|
{
|
|
@@ -146,9 +246,24 @@ mor issue walk <id> journey.submit_standard_investigation
|
|
|
146
246
|
mor issue hydrate <id> journey.scoping
|
|
147
247
|
```
|
|
148
248
|
|
|
149
|
-
|
|
249
|
+
Report with a 2–3 sentence summary of what you found in their code, then show:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
mor-thread --thread "$CAPSULE" suggest open_record --record-id <id> --label "Read the investigation report"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> The report is attached to the record itself — it doesn't scroll away.
|
|
256
|
+
|
|
257
|
+
Offer depth exactly once, and don't push it:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
mor-thread --thread "$CAPSULE" suggest open_context_packet --store issues --dimension journey --state investigating --label "See my briefing for that state"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
> Curious what I was told to do there? That briefing — who I'm supposed to be and what I
|
|
264
|
+
> get to see — is defined per state, for every state on this graph.
|
|
150
265
|
|
|
151
|
-
|
|
266
|
+
Step 6c (consent, then scope): attach the plan with a handoff prompt:
|
|
152
267
|
|
|
153
268
|
```json
|
|
154
269
|
{
|
|
@@ -165,37 +280,32 @@ Attach a scoped plan with a handoff prompt:
|
|
|
165
280
|
|
|
166
281
|
```bash
|
|
167
282
|
mor issue enrich <id> scoped_plan --role scoped_plan --payload /tmp/scoped-plan.json
|
|
168
|
-
mor issue inspect <id>
|
|
169
283
|
```
|
|
170
284
|
|
|
171
|
-
If the honest outcome is blocked,
|
|
285
|
+
If the honest outcome is blocked, use `plan_outcome=blocked` with `blocker` — and say so
|
|
286
|
+
plainly; do not close the journey until after beat 7.
|
|
172
287
|
|
|
173
|
-
### 7
|
|
288
|
+
### Beat 7 — proof 4: the system summons them
|
|
174
289
|
|
|
175
|
-
|
|
290
|
+
Check the current attention state with `mor issue inspect <id>`, then route:
|
|
176
291
|
|
|
177
292
|
```bash
|
|
178
|
-
mor issue
|
|
293
|
+
mor issue walk <id> attention.request_operator_from_entry # if attention=autonomous_entry
|
|
294
|
+
mor issue walk <id> attention.request_operator # if attention=autonomous
|
|
179
295
|
```
|
|
180
296
|
|
|
181
|
-
|
|
297
|
+
Then hand them the moment:
|
|
182
298
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
299
|
+
> Now the record is waiting for **you** — you can see it sitting at the waiting-for-you
|
|
300
|
+
> state on the attention graph. This is the other half of Morphisms: agents run on their
|
|
301
|
+
> own, and the graph decides when a human is required. No pings, no hoping someone reads
|
|
302
|
+
> the chat. So, your first real decision: should I close this out with the plan as
|
|
303
|
+
> written, or revise the scope?
|
|
186
304
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
mor issue walk <id> attention.request_operator
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Tell the human: "The record is now waiting for YOU at `attention.awaiting_operator`. In an armed Workbench thread, this is the `Review now` moment; otherwise use the record card in Workbench: http://localhost:4400/#workbench. Please answer whether I should resume and close this scoped plan, or revise scope."
|
|
194
|
-
|
|
195
|
-
Use the human's actual answer as the `operator_decision`; do not fabricate it. For a resume answer, attach:
|
|
305
|
+
Use their actual answer:
|
|
196
306
|
|
|
197
307
|
```json
|
|
198
|
-
{"decision":"resume","rationale":"<
|
|
308
|
+
{"decision":"resume","rationale":"<their answer>","instructions":"Proceed with the scoped plan outcome.","answered_questions":["Resume and close this scoped plan?"]}
|
|
199
309
|
```
|
|
200
310
|
|
|
201
311
|
```bash
|
|
@@ -203,30 +313,33 @@ mor issue enrich <id> operator_decision --role operator_decision --payload /tmp/
|
|
|
203
313
|
mor issue walk <id> attention.return_to_autonomous
|
|
204
314
|
```
|
|
205
315
|
|
|
206
|
-
|
|
316
|
+
> That's how future work pauses only when it needs you — and resumes on your decision.
|
|
207
317
|
|
|
208
|
-
### 8
|
|
318
|
+
### Beat 8 — the whole story, then normal mode
|
|
209
319
|
|
|
210
|
-
|
|
320
|
+
Close the journey (consent first):
|
|
211
321
|
|
|
212
322
|
```bash
|
|
213
|
-
mor issue walk <id> journey.complete_scoped_plan
|
|
214
|
-
mor issue
|
|
323
|
+
mor issue walk <id> journey.complete_scoped_plan # ready quick/standard plan
|
|
324
|
+
mor issue walk <id> journey.close_blocked_from_scoping # honest blocked outcome
|
|
215
325
|
```
|
|
216
326
|
|
|
217
|
-
|
|
327
|
+
Finale — give them the record's full story:
|
|
218
328
|
|
|
219
329
|
```bash
|
|
220
|
-
mor
|
|
221
|
-
mor issue inspect <id>
|
|
330
|
+
mor-thread --thread "$CAPSULE" suggest start_replay --record-id <id> --label "Replay the whole story"
|
|
222
331
|
```
|
|
223
332
|
|
|
224
|
-
|
|
333
|
+
> That's the entire journey of your issue, step by step — arrow keys to move, `i` to
|
|
334
|
+
> inspect anything.
|
|
225
335
|
|
|
226
|
-
|
|
336
|
+
Write the sentinel:
|
|
227
337
|
|
|
228
338
|
```bash
|
|
229
339
|
printf '%s %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "<id>" > .mor/onboarding-complete
|
|
230
340
|
```
|
|
231
341
|
|
|
232
|
-
|
|
342
|
+
Sign off with the one next action:
|
|
343
|
+
|
|
344
|
+
> Onboarding is done. From now on, just tell me about bugs and ideas — they'll appear
|
|
345
|
+
> here as records and move across this graph. Keep this tab: it's your workflow's home.
|
|
@@ -5,7 +5,7 @@ This is the S2 workspace layer for a repo initialized by `morphisms init`. It ap
|
|
|
5
5
|
## Startup
|
|
6
6
|
|
|
7
7
|
- Run `mor agent instructions` before orchestrating. Read the reported S1/S2/S3 files; use `mor agent instructions preview` only when you need the rendered stack.
|
|
8
|
-
- Workbench is the lens: `
|
|
8
|
+
- Workbench is the lens: run `morphisms onboarding` first and use only the Workbench URL it prints.
|
|
9
9
|
- Keep runtime state under `.mor/`. Do not edit SQLite files or registry material by hand.
|
|
10
10
|
- Do not create `.mor/onboarding-complete`; the onboarding agent writes it only after the first real issue reaches `journey.done`.
|
|
11
11
|
|