a2acalling 0.6.10 → 0.6.12
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/bin/cli.js
CHANGED
|
@@ -200,11 +200,14 @@ function parseArgs(argv) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async function promptYesNo(question) {
|
|
203
|
-
const
|
|
204
|
-
|
|
203
|
+
const q = String(question || '');
|
|
204
|
+
// Support both bracket and paren styles: [Y/n], (y/N), etc.
|
|
205
|
+
// Convention: uppercase letter is the default when user presses Enter.
|
|
206
|
+
const defaultValue = q.includes('y/N')
|
|
207
|
+
? false
|
|
208
|
+
: q.includes('Y/n')
|
|
205
209
|
? true
|
|
206
|
-
:
|
|
207
|
-
: true;
|
|
210
|
+
: true;
|
|
208
211
|
|
|
209
212
|
if (!isInteractiveShell()) {
|
|
210
213
|
return defaultValue;
|
|
@@ -296,17 +299,22 @@ function readWorkspaceContext(baseDir = process.cwd()) {
|
|
|
296
299
|
}
|
|
297
300
|
|
|
298
301
|
function printWorkspaceScan(context) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
302
|
+
const fileLabels = {
|
|
303
|
+
USER: 'USER.md — identity hints',
|
|
304
|
+
SOUL: 'SOUL.md — personality notes',
|
|
305
|
+
HEARTBEAT: 'HEARTBEAT.md — scheduled tasks',
|
|
306
|
+
SKILL: 'SKILL.md — capabilities',
|
|
307
|
+
CLAUDE: 'CLAUDE.md — agent instructions'
|
|
308
|
+
};
|
|
309
|
+
const found = Object.entries(fileLabels)
|
|
310
|
+
.filter(([key]) => context.found[key])
|
|
311
|
+
.map(([, label]) => label);
|
|
312
|
+
if (context.memoryCount > 0) found.push(`${context.memoryCount} memory file(s)`);
|
|
313
|
+
|
|
314
|
+
if (found.length) {
|
|
315
|
+
console.log(`Workspace context: ${found.join(', ')}`);
|
|
308
316
|
} else {
|
|
309
|
-
console.log('
|
|
317
|
+
console.log('Workspace context: no context files found (disclosure topics will be minimal)');
|
|
310
318
|
}
|
|
311
319
|
}
|
|
312
320
|
|
|
@@ -1205,6 +1213,33 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1205
1213
|
require('../src/server.js');
|
|
1206
1214
|
},
|
|
1207
1215
|
|
|
1216
|
+
// ==========================================================================
|
|
1217
|
+
// quickstart — full onboarding flow
|
|
1218
|
+
//
|
|
1219
|
+
// OBJECTIVE: Walk an installing agent (or human) through every step needed
|
|
1220
|
+
// to get A2A Calling running, in a single pass. The postinstall hook calls
|
|
1221
|
+
// this automatically, so `npm install -g a2acalling` completes the entire
|
|
1222
|
+
// setup without any follow-up commands.
|
|
1223
|
+
//
|
|
1224
|
+
// Every prompt auto-accepts its default when there is no TTY, so the full
|
|
1225
|
+
// flow completes unattended in non-interactive environments (agents).
|
|
1226
|
+
//
|
|
1227
|
+
// STEPS:
|
|
1228
|
+
// Step 1 — Port selection: scan for an available port and bind it.
|
|
1229
|
+
// Step 2 — Hostname detection: look up the external IP so remote agents
|
|
1230
|
+
// know where to reach this instance.
|
|
1231
|
+
// Step 3 — Server start: launch the A2A server as a detached process,
|
|
1232
|
+
// confirm it's listening, save config, and print a verify hint.
|
|
1233
|
+
// Step 4 — Disclosure prompt: output a full agent-readable prompt that
|
|
1234
|
+
// instructs the agent to scan its own workspace files, extract
|
|
1235
|
+
// tiered disclosure topics, and submit them back via
|
|
1236
|
+
// `a2a quickstart --submit '<json>'`.
|
|
1237
|
+
//
|
|
1238
|
+
// The disclosure prompt does NOT pre-scan files itself — it tells the agent
|
|
1239
|
+
// which files to look for (USER.md, SOUL.md, etc.) and lets the agent read
|
|
1240
|
+
// them with its own tools. This is intentional: the installer runs in a
|
|
1241
|
+
// subprocess where it has no access to the agent's file-reading capabilities.
|
|
1242
|
+
// ==========================================================================
|
|
1208
1243
|
quickstart: async (args) => {
|
|
1209
1244
|
const { A2AConfig } = require('../src/lib/config');
|
|
1210
1245
|
const { isPortListening } = require('../src/lib/port-scanner');
|
|
@@ -1214,6 +1249,8 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1214
1249
|
const config = new A2AConfig();
|
|
1215
1250
|
const interactive = isInteractiveShell();
|
|
1216
1251
|
|
|
1252
|
+
// Handle `quickstart --submit '<json>'` — this is the agent calling back
|
|
1253
|
+
// after it has scanned its workspace and built the disclosure JSON.
|
|
1217
1254
|
if (await handleDisclosureSubmit(args, 'quickstart')) {
|
|
1218
1255
|
return;
|
|
1219
1256
|
}
|
|
@@ -1228,10 +1265,9 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1228
1265
|
return;
|
|
1229
1266
|
}
|
|
1230
1267
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
// If server is already running and awaiting disclosure, skip to Step 2
|
|
1268
|
+
// Resume point: if the server is already running and we're waiting for the
|
|
1269
|
+
// agent to submit disclosure topics, skip straight to the disclosure prompt.
|
|
1270
|
+
// This happens when the agent re-runs quickstart after a previous partial run.
|
|
1235
1271
|
let currentStep = 'not_started';
|
|
1236
1272
|
try {
|
|
1237
1273
|
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
@@ -1244,42 +1280,46 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1244
1280
|
if (currentStep === 'awaiting_disclosure' && !args.flags.force) {
|
|
1245
1281
|
console.log('\nStep 1 already complete. Server is running.\n');
|
|
1246
1282
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1247
|
-
console.log(buildExtractionPrompt(
|
|
1283
|
+
console.log(buildExtractionPrompt());
|
|
1248
1284
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1249
1285
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1250
1286
|
return;
|
|
1251
1287
|
}
|
|
1252
1288
|
|
|
1253
1289
|
printStepHeader('🤝 A2A Calling — First-Time Setup');
|
|
1254
|
-
printWorkspaceScan(context);
|
|
1255
1290
|
|
|
1291
|
+
// Interactive: ask for confirmation. Non-interactive: auto-accepts (Y).
|
|
1256
1292
|
const continueSetup = await promptYesNo('Continue with setup? [Y/n] ');
|
|
1257
1293
|
if (!continueSetup) {
|
|
1258
1294
|
console.log('\nSetup cancelled.\n');
|
|
1259
1295
|
return;
|
|
1260
1296
|
}
|
|
1261
1297
|
|
|
1298
|
+
// ── Step 1: Port selection ───────────────────────────────────────────
|
|
1299
|
+
// Scan ports 80, 3001-3020 and pick the first available one.
|
|
1300
|
+
// Only show the selected port — agents don't need to see every candidate.
|
|
1301
|
+
// Interactive users can override with a custom port; non-interactive
|
|
1302
|
+
// auto-accepts the recommendation.
|
|
1262
1303
|
printSection('Port Configuration');
|
|
1263
1304
|
const preferredPort = parsePort(args.flags.port || args.flags.p, null);
|
|
1264
1305
|
const candidates = await inspectPorts(preferredPort);
|
|
1265
1306
|
const availableCandidates = candidates.filter(c => c.available);
|
|
1266
1307
|
const recommendedPort = availableCandidates.length ? availableCandidates[0].port : null;
|
|
1267
1308
|
|
|
1268
|
-
summarizePortResults(candidates).forEach(line => {
|
|
1269
|
-
console.log(` ${line}`);
|
|
1270
|
-
});
|
|
1271
|
-
|
|
1272
1309
|
if (!recommendedPort) {
|
|
1273
1310
|
console.error(' Could not find a bindable port in the scan range.');
|
|
1274
1311
|
console.error(' Re-run with --port <number> after freeing one of these ports.\n');
|
|
1312
|
+
if (interactive) {
|
|
1313
|
+
console.log(' Ports scanned:');
|
|
1314
|
+
summarizePortResults(candidates).forEach(line => console.log(` ${line}`));
|
|
1315
|
+
}
|
|
1275
1316
|
process.exit(1);
|
|
1276
1317
|
}
|
|
1277
1318
|
|
|
1278
|
-
console.log(
|
|
1319
|
+
console.log(` Selected port: ${recommendedPort}`);
|
|
1279
1320
|
let serverPort = recommendedPort;
|
|
1280
1321
|
const portChoice = await promptText(`Use port ${recommendedPort}? [Y/n/custom]: `, 'y');
|
|
1281
1322
|
if (!interactive) {
|
|
1282
|
-
// explicit default for non-interactive mode
|
|
1283
1323
|
serverPort = recommendedPort;
|
|
1284
1324
|
} else if (!['', 'y', 'Y', 'yes', 'YES', 'ye'].includes(String(portChoice).trim())) {
|
|
1285
1325
|
if (/^(n|no|custom|c)$/i.test(String(portChoice).trim())) {
|
|
@@ -1318,6 +1358,10 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1318
1358
|
}
|
|
1319
1359
|
}
|
|
1320
1360
|
|
|
1361
|
+
// ── Step 2: Hostname detection ───────────────────────────────────────
|
|
1362
|
+
// Look up the machine's external IP so invite URLs point to a routable
|
|
1363
|
+
// address. Non-interactive: auto-uses the detected IP. Interactive: lets
|
|
1364
|
+
// the user choose IP, domain, or skip.
|
|
1321
1365
|
printSection('Hostname Configuration');
|
|
1322
1366
|
const ipResult = await getExternalIp();
|
|
1323
1367
|
const externalIp = ipResult.ip || null;
|
|
@@ -1364,17 +1408,23 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1364
1408
|
console.log(` External IP lookup failed: ${ipResult.error}`);
|
|
1365
1409
|
}
|
|
1366
1410
|
|
|
1411
|
+
// ── Step 3: Server start ─────────────────────────────────────────────
|
|
1412
|
+
// Launch the A2A Express server as a detached background process, wait
|
|
1413
|
+
// for it to bind, then save the config with the server PID and port.
|
|
1414
|
+
// Non-interactive: auto-starts. Interactive: asks for confirmation.
|
|
1415
|
+
// Also prints a one-line networking hint (reverse proxy or firewall)
|
|
1416
|
+
// and a curl command the agent can use to verify external reachability.
|
|
1367
1417
|
printSection('Starting Server');
|
|
1368
1418
|
console.log(' Configuration summary:');
|
|
1369
1419
|
console.log(` Port: ${serverPort}`);
|
|
1370
1420
|
console.log(` Public host: ${publicHost}`);
|
|
1421
|
+
|
|
1371
1422
|
const startServer = await promptYesNo('Start the A2A server now? [Y/n] ');
|
|
1372
1423
|
if (!startServer) {
|
|
1373
1424
|
console.log('\nServer not started. Run with:\n a2a server --port <port> --hostname <host>\n');
|
|
1374
1425
|
return;
|
|
1375
1426
|
}
|
|
1376
1427
|
|
|
1377
|
-
// Start server
|
|
1378
1428
|
const isAlreadyListening = await isPortListening(serverPort, '127.0.0.1', { timeoutMs: 250 });
|
|
1379
1429
|
let serverPid = null;
|
|
1380
1430
|
if (!isAlreadyListening.listening) {
|
|
@@ -1414,28 +1464,32 @@ https://github.com/onthegonow/a2a_calling`;
|
|
|
1414
1464
|
}
|
|
1415
1465
|
console.log(' ✅ A2A server is running');
|
|
1416
1466
|
|
|
1417
|
-
if (serverPort !== 80 && externalIp) {
|
|
1418
|
-
console.log('\n External access required because port 80 is not in use.');
|
|
1419
|
-
console.log(' Option A (recommended): Set up a reverse proxy (HTTP or HTTPS).');
|
|
1420
|
-
console.log(` Configure your web server to forward /api/a2a/* to localhost:${serverPort}.`);
|
|
1421
|
-
console.log(' If you serve HTTPS on port 443, proxy from there instead.');
|
|
1422
|
-
console.log(' A reverse proxy avoids firewall changes entirely.');
|
|
1423
|
-
console.log(`\n Option B: Open port ${serverPort} in your firewall (e.g. ufw allow ${serverPort}).`);
|
|
1424
|
-
console.log(' Most users prefer not to modify firewall settings.\n');
|
|
1425
|
-
}
|
|
1426
1467
|
if (externalIp) {
|
|
1427
1468
|
const verifyUrl = `http://${publicHost}/api/a2a/ping`;
|
|
1428
|
-
|
|
1469
|
+
if (serverPort !== 80) {
|
|
1470
|
+
console.log(`\n Note: Port ${serverPort} must be reachable externally.`);
|
|
1471
|
+
console.log(` Reverse-proxy /api/a2a/* to localhost:${serverPort}, or open the port in your firewall.`);
|
|
1472
|
+
}
|
|
1473
|
+
console.log(` Verify: curl -s ${verifyUrl}`);
|
|
1429
1474
|
}
|
|
1430
1475
|
|
|
1431
|
-
// Save server config and advance onboarding
|
|
1476
|
+
// Save server config and advance onboarding state to awaiting_disclosure.
|
|
1432
1477
|
config.setAgent({ hostname: publicHost });
|
|
1433
1478
|
config.setOnboarding({ step: 'awaiting_disclosure' });
|
|
1434
1479
|
|
|
1435
|
-
// Step
|
|
1480
|
+
// ── Step 4: Disclosure prompt ────────────────────────────────────────
|
|
1481
|
+
// Output a full agent-readable prompt that tells the agent to:
|
|
1482
|
+
// 1. Scan its own workspace for USER.md, SOUL.md, etc.
|
|
1483
|
+
// 2. Extract tiered disclosure topics from those files
|
|
1484
|
+
// 3. Build the required JSON structure
|
|
1485
|
+
// 4. Submit it back via `a2a quickstart --submit '<json>'`
|
|
1486
|
+
//
|
|
1487
|
+
// IMPORTANT: We do NOT pre-scan files here. The installer runs in a
|
|
1488
|
+
// subprocess and has no access to the agent's file-reading tools. The
|
|
1489
|
+
// prompt lists which files to look for and the agent reads them itself.
|
|
1436
1490
|
printSection('Disclosure Topic Extraction');
|
|
1437
1491
|
console.log('Step 2 of 4: Configure disclosure topics\n');
|
|
1438
|
-
console.log(buildExtractionPrompt(
|
|
1492
|
+
console.log(buildExtractionPrompt());
|
|
1439
1493
|
console.log('\n Read your workspace files, extract topics, and present to your owner for review.');
|
|
1440
1494
|
console.log(" Then submit with: a2a quickstart --submit '<json>'\n");
|
|
1441
1495
|
},
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,35 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// npm postinstall hook — automatic onboarding launcher
|
|
5
|
+
//
|
|
6
|
+
// OBJECTIVE: When a user (or agent) runs `npm install -g a2acalling`, this
|
|
7
|
+
// script fires automatically and launches the full quickstart onboarding flow.
|
|
8
|
+
// The agent should never need a second command — install IS onboarding.
|
|
9
|
+
//
|
|
10
|
+
// KEY DESIGN DECISIONS:
|
|
11
|
+
//
|
|
12
|
+
// 1. No TTY gate. Agents are the primary users of this package and they
|
|
13
|
+
// run in non-interactive (piped) environments. Every prompt in the
|
|
14
|
+
// quickstart flow auto-accepts sensible defaults when there is no TTY,
|
|
15
|
+
// so the full onboarding completes unattended: port selection, hostname
|
|
16
|
+
// detection, server start, and disclosure prompt output.
|
|
17
|
+
//
|
|
18
|
+
// 2. stdio: 'inherit'. The onboarding output goes straight to whatever
|
|
19
|
+
// called npm — if that's a terminal the user sees it; if it's an agent
|
|
20
|
+
// the agent reads it. No /dev/tty tricks needed.
|
|
21
|
+
//
|
|
22
|
+
// 3. Never fail the install. If quickstart can't launch (e.g. missing node
|
|
23
|
+
// binary edge case), we print a hint and exit 0. A broken postinstall
|
|
24
|
+
// would prevent the package from installing at all.
|
|
25
|
+
//
|
|
26
|
+
// 4. Skip non-global installs, CI, and Docker builds. Local `npm install`
|
|
27
|
+
// in a project shouldn't trigger onboarding. CI and Docker are build
|
|
28
|
+
// environments, not runtime hosts.
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
4
31
|
if (process.env.CI || process.env.CONTINUOUS_INTEGRATION) process.exit(0);
|
|
5
32
|
if (process.env.DOCKER) process.exit(0);
|
|
6
33
|
if (process.env.npm_config_global !== 'true') process.exit(0);
|
|
7
34
|
|
|
35
|
+
const path = require('path');
|
|
8
36
|
const { spawnSync } = require('child_process');
|
|
9
|
-
const isInteractive = Boolean(process.stdout && process.stderr && process.stdin &&
|
|
10
|
-
process.stdout.isTTY && process.stderr.isTTY && process.stdin.isTTY);
|
|
11
37
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.warn('\n⚠️ Output may be suppressed. Run \'a2a quickstart\' manually if you don\'t see prompts.');
|
|
15
|
-
}
|
|
16
|
-
}
|
|
38
|
+
const initCwd = process.env.INIT_CWD || process.env.HOME || process.cwd();
|
|
39
|
+
const cliPath = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
17
40
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// Launch quickstart directly — stdio: 'inherit' forces foreground output
|
|
21
|
-
// even when npm v10+ suppresses postinstall stdout by default.
|
|
22
|
-
const result = spawnSync('a2a', ['quickstart'], {
|
|
41
|
+
// Launch quickstart — prompts auto-accept defaults when there's no TTY.
|
|
42
|
+
const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
23
43
|
stdio: 'inherit',
|
|
24
|
-
|
|
25
|
-
|
|
44
|
+
cwd: initCwd,
|
|
45
|
+
env: {
|
|
46
|
+
...process.env,
|
|
47
|
+
A2A_WORKSPACE: process.env.A2A_WORKSPACE || initCwd
|
|
48
|
+
}
|
|
26
49
|
});
|
|
27
50
|
|
|
28
|
-
if (result.error
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.error('
|
|
32
|
-
console.log('\nRun manually: a2a quickstart');
|
|
51
|
+
if (result.error) {
|
|
52
|
+
console.error('\nCould not auto-launch onboarding.');
|
|
53
|
+
console.error(`Reason: ${result.error.message}`);
|
|
54
|
+
console.error('\nRun manually: a2a quickstart\n');
|
|
33
55
|
process.exit(0); // don't fail the install
|
|
34
56
|
}
|
|
35
57
|
|
|
@@ -1,986 +0,0 @@
|
|
|
1
|
-
# Agent-Driven Disclosure Extraction — Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Replace the naive file-parser in `generateDefaultManifest()` with an agent-driven extraction flow where the onboarder instructs the agent what structured data to return, the agent extracts and presents to the human for confirmation, and the onboarder validates the submission before storing.
|
|
6
|
-
|
|
7
|
-
**Architecture:** The onboarding system provides explicit instructions (a prompt) telling the agent the exact JSON schema it needs to return. The agent reads workspace files itself, structures the data, gets human confirmation, and submits. Our code validates the submission against the schema and either stores it or returns errors. `generateDefaultManifest()` is stripped to a minimal fallback (no file parsing). Two new functions are added: `buildExtractionPrompt()` (instructions for the agent) and `validateDisclosureSubmission()` (validates agent output). The CLI `onboard` command gains `--submit` mode.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Node.js, existing test framework (`test/run.js`), existing config validation (`config.js:validateTierPatch`)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## New flow
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
1. a2a onboard → prints extraction prompt for agent
|
|
17
|
-
2. Agent reads workspace files → extracts topics/goals semantically
|
|
18
|
-
3. Agent presents to human → human confirms or amends
|
|
19
|
-
4. a2a onboard --submit '{...}' → validates structured output
|
|
20
|
-
5. Valid? → save to manifest + config tiers, print success
|
|
21
|
-
Invalid → print errors, agent retries
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## JSON schema the agent must return
|
|
25
|
-
|
|
26
|
-
```json
|
|
27
|
-
{
|
|
28
|
-
"topics": {
|
|
29
|
-
"public": {
|
|
30
|
-
"lead_with": [
|
|
31
|
-
{ "topic": "OpenClaw development", "detail": "Building agent-to-agent communication tools" }
|
|
32
|
-
],
|
|
33
|
-
"discuss_freely": [
|
|
34
|
-
{ "topic": "A2A federation protocol", "detail": "Design and implementation of federation" }
|
|
35
|
-
],
|
|
36
|
-
"deflect": [
|
|
37
|
-
{ "topic": "Personal finances", "detail": "Redirect to direct owner contact" }
|
|
38
|
-
]
|
|
39
|
-
},
|
|
40
|
-
"friends": {
|
|
41
|
-
"lead_with": [],
|
|
42
|
-
"discuss_freely": [],
|
|
43
|
-
"deflect": []
|
|
44
|
-
},
|
|
45
|
-
"family": {
|
|
46
|
-
"lead_with": [],
|
|
47
|
-
"discuss_freely": [],
|
|
48
|
-
"deflect": []
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
"never_disclose": ["API keys", "Other users' data"],
|
|
52
|
-
"personality_notes": "Direct and technical. Prefers depth over breadth."
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
### Task 1: Add `validateDisclosureSubmission()` to disclosure.js
|
|
59
|
-
|
|
60
|
-
**Files:**
|
|
61
|
-
- Test: `test/unit/disclosure.test.js`
|
|
62
|
-
- Modify: `src/lib/disclosure.js`
|
|
63
|
-
|
|
64
|
-
This is the validator that checks agent-submitted JSON against the expected schema.
|
|
65
|
-
|
|
66
|
-
**Step 1: Write failing tests**
|
|
67
|
-
|
|
68
|
-
Add to `test/unit/disclosure.test.js` inside the module.exports function, before the closing `};`:
|
|
69
|
-
|
|
70
|
-
```javascript
|
|
71
|
-
// ── Disclosure Submission Validation ──────────────────────────
|
|
72
|
-
|
|
73
|
-
test('validateDisclosureSubmission accepts valid submission', () => {
|
|
74
|
-
const disc = freshDisclosure();
|
|
75
|
-
const result = disc.validateDisclosureSubmission({
|
|
76
|
-
topics: {
|
|
77
|
-
public: {
|
|
78
|
-
lead_with: [{ topic: 'AI development', detail: 'Building AI tools' }],
|
|
79
|
-
discuss_freely: [{ topic: 'Open source', detail: 'Contributing to OSS' }],
|
|
80
|
-
deflect: [{ topic: 'Personal life', detail: 'Redirect to owner' }]
|
|
81
|
-
},
|
|
82
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
83
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
84
|
-
},
|
|
85
|
-
never_disclose: ['API keys'],
|
|
86
|
-
personality_notes: 'Friendly and technical'
|
|
87
|
-
});
|
|
88
|
-
assert.ok(result.valid);
|
|
89
|
-
assert.equal(result.errors.length, 0);
|
|
90
|
-
assert.ok(result.manifest);
|
|
91
|
-
assert.equal(result.manifest.version, 1);
|
|
92
|
-
tmp.cleanup();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test('validateDisclosureSubmission rejects non-object input', () => {
|
|
96
|
-
const disc = freshDisclosure();
|
|
97
|
-
const result = disc.validateDisclosureSubmission('not an object');
|
|
98
|
-
assert.equal(result.valid, false);
|
|
99
|
-
assert.ok(result.errors.length > 0);
|
|
100
|
-
assert.ok(result.errors[0].includes('object'));
|
|
101
|
-
tmp.cleanup();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('validateDisclosureSubmission rejects missing topics', () => {
|
|
105
|
-
const disc = freshDisclosure();
|
|
106
|
-
const result = disc.validateDisclosureSubmission({
|
|
107
|
-
never_disclose: ['secrets'],
|
|
108
|
-
personality_notes: 'Nice'
|
|
109
|
-
});
|
|
110
|
-
assert.equal(result.valid, false);
|
|
111
|
-
assert.ok(result.errors.some(e => e.includes('topics')));
|
|
112
|
-
tmp.cleanup();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('validateDisclosureSubmission rejects missing tier', () => {
|
|
116
|
-
const disc = freshDisclosure();
|
|
117
|
-
const result = disc.validateDisclosureSubmission({
|
|
118
|
-
topics: {
|
|
119
|
-
public: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
120
|
-
// missing friends and family
|
|
121
|
-
},
|
|
122
|
-
never_disclose: [],
|
|
123
|
-
personality_notes: ''
|
|
124
|
-
});
|
|
125
|
-
assert.equal(result.valid, false);
|
|
126
|
-
assert.ok(result.errors.some(e => e.includes('friends')));
|
|
127
|
-
tmp.cleanup();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('validateDisclosureSubmission rejects invalid topic shape', () => {
|
|
131
|
-
const disc = freshDisclosure();
|
|
132
|
-
const result = disc.validateDisclosureSubmission({
|
|
133
|
-
topics: {
|
|
134
|
-
public: {
|
|
135
|
-
lead_with: ['just a string'], // wrong: should be {topic, detail}
|
|
136
|
-
discuss_freely: [],
|
|
137
|
-
deflect: []
|
|
138
|
-
},
|
|
139
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
140
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
141
|
-
},
|
|
142
|
-
never_disclose: [],
|
|
143
|
-
personality_notes: ''
|
|
144
|
-
});
|
|
145
|
-
assert.equal(result.valid, false);
|
|
146
|
-
assert.ok(result.errors.some(e => e.includes('topic')));
|
|
147
|
-
tmp.cleanup();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('validateDisclosureSubmission rejects topics with technical content', () => {
|
|
151
|
-
const disc = freshDisclosure();
|
|
152
|
-
const result = disc.validateDisclosureSubmission({
|
|
153
|
-
topics: {
|
|
154
|
-
public: {
|
|
155
|
-
lead_with: [
|
|
156
|
-
{ topic: 'Run `npm test` to verify', detail: 'Code command' },
|
|
157
|
-
{ topic: 'https://github.com/example', detail: 'Raw URL' }
|
|
158
|
-
],
|
|
159
|
-
discuss_freely: [],
|
|
160
|
-
deflect: []
|
|
161
|
-
},
|
|
162
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
163
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
164
|
-
},
|
|
165
|
-
never_disclose: [],
|
|
166
|
-
personality_notes: ''
|
|
167
|
-
});
|
|
168
|
-
assert.equal(result.valid, false);
|
|
169
|
-
assert.ok(result.errors.some(e => e.includes('technical')));
|
|
170
|
-
tmp.cleanup();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('validateDisclosureSubmission enforces max topic length', () => {
|
|
174
|
-
const disc = freshDisclosure();
|
|
175
|
-
const longTopic = 'A'.repeat(200);
|
|
176
|
-
const result = disc.validateDisclosureSubmission({
|
|
177
|
-
topics: {
|
|
178
|
-
public: {
|
|
179
|
-
lead_with: [{ topic: longTopic, detail: 'Too long topic' }],
|
|
180
|
-
discuss_freely: [],
|
|
181
|
-
deflect: []
|
|
182
|
-
},
|
|
183
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
184
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
185
|
-
},
|
|
186
|
-
never_disclose: [],
|
|
187
|
-
personality_notes: ''
|
|
188
|
-
});
|
|
189
|
-
assert.equal(result.valid, false);
|
|
190
|
-
assert.ok(result.errors.some(e => e.includes('160')));
|
|
191
|
-
tmp.cleanup();
|
|
192
|
-
});
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**Step 2: Run tests to verify they fail**
|
|
196
|
-
|
|
197
|
-
Run: `npm test 2>&1 | grep -E 'validateDisclosureSubmission|passing|failing'`
|
|
198
|
-
Expected: 7 new FAILs — `validateDisclosureSubmission is not a function`
|
|
199
|
-
|
|
200
|
-
**Step 3: Implement `validateDisclosureSubmission()` in disclosure.js**
|
|
201
|
-
|
|
202
|
-
Add before the `module.exports` block:
|
|
203
|
-
|
|
204
|
-
```javascript
|
|
205
|
-
/**
|
|
206
|
-
* Validate structured disclosure data submitted by an agent.
|
|
207
|
-
* Returns { valid: boolean, manifest: object|null, errors: string[] }.
|
|
208
|
-
*/
|
|
209
|
-
function validateDisclosureSubmission(data) {
|
|
210
|
-
const errors = [];
|
|
211
|
-
|
|
212
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
213
|
-
return { valid: false, manifest: null, errors: ['Submission must be a JSON object'] };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Require topics object
|
|
217
|
-
if (!data.topics || typeof data.topics !== 'object' || Array.isArray(data.topics)) {
|
|
218
|
-
errors.push('Missing or invalid "topics" — must be an object with public, friends, family keys');
|
|
219
|
-
return { valid: false, manifest: null, errors };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Require all three tiers
|
|
223
|
-
for (const tier of TIER_HIERARCHY) {
|
|
224
|
-
if (!data.topics[tier] || typeof data.topics[tier] !== 'object') {
|
|
225
|
-
errors.push(`Missing tier "${tier}" in topics`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (errors.length > 0) return { valid: false, manifest: null, errors };
|
|
229
|
-
|
|
230
|
-
// Validate each tier's topic arrays
|
|
231
|
-
const TOPIC_CATEGORIES = ['lead_with', 'discuss_freely', 'deflect'];
|
|
232
|
-
for (const tier of TIER_HIERARCHY) {
|
|
233
|
-
const tierData = data.topics[tier];
|
|
234
|
-
for (const cat of TOPIC_CATEGORIES) {
|
|
235
|
-
if (!Array.isArray(tierData[cat])) {
|
|
236
|
-
errors.push(`topics.${tier}.${cat} must be an array`);
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
for (let i = 0; i < tierData[cat].length; i++) {
|
|
240
|
-
const item = tierData[cat][i];
|
|
241
|
-
if (!item || typeof item !== 'object' || typeof item.topic !== 'string' || typeof item.detail !== 'string') {
|
|
242
|
-
errors.push(`topics.${tier}.${cat}[${i}] must have "topic" and "detail" strings`);
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
if (item.topic.length > 160) {
|
|
246
|
-
errors.push(`topics.${tier}.${cat}[${i}].topic exceeds 160 chars`);
|
|
247
|
-
}
|
|
248
|
-
if (item.detail.length > 500) {
|
|
249
|
-
errors.push(`topics.${tier}.${cat}[${i}].detail exceeds 500 chars`);
|
|
250
|
-
}
|
|
251
|
-
if (isTechnicalContent(item.topic)) {
|
|
252
|
-
errors.push(`topics.${tier}.${cat}[${i}].topic contains technical content (code, URLs, or formatting) — topics should be human-readable`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Validate never_disclose (optional, defaults to [])
|
|
259
|
-
if (data.never_disclose !== undefined) {
|
|
260
|
-
if (!Array.isArray(data.never_disclose)) {
|
|
261
|
-
errors.push('"never_disclose" must be an array of strings');
|
|
262
|
-
} else {
|
|
263
|
-
for (let i = 0; i < data.never_disclose.length; i++) {
|
|
264
|
-
if (typeof data.never_disclose[i] !== 'string') {
|
|
265
|
-
errors.push(`never_disclose[${i}] must be a string`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Validate personality_notes (optional, defaults to '')
|
|
272
|
-
if (data.personality_notes !== undefined && typeof data.personality_notes !== 'string') {
|
|
273
|
-
errors.push('"personality_notes" must be a string');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (errors.length > 0) return { valid: false, manifest: null, errors };
|
|
277
|
-
|
|
278
|
-
// Build valid manifest
|
|
279
|
-
const now = new Date().toISOString();
|
|
280
|
-
const manifest = {
|
|
281
|
-
version: 1,
|
|
282
|
-
generated_at: now,
|
|
283
|
-
updated_at: now,
|
|
284
|
-
topics: data.topics,
|
|
285
|
-
never_disclose: data.never_disclose || ['API keys', 'Other users\' data', 'Financial figures'],
|
|
286
|
-
personality_notes: data.personality_notes || 'Direct and technical. Prefers depth over breadth.'
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
return { valid: true, manifest, errors: [] };
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
**Step 4: Export it**
|
|
294
|
-
|
|
295
|
-
In `module.exports`, add `validateDisclosureSubmission`.
|
|
296
|
-
|
|
297
|
-
**Step 5: Run tests to verify they pass**
|
|
298
|
-
|
|
299
|
-
Run: `npm test`
|
|
300
|
-
Expected: All 7 new tests PASS, all existing tests still pass.
|
|
301
|
-
|
|
302
|
-
**Step 6: Commit**
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
git add src/lib/disclosure.js test/unit/disclosure.test.js
|
|
306
|
-
git commit -m "feat(disclosure): add validateDisclosureSubmission for agent-driven extraction"
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
### Task 2: Add `buildExtractionPrompt()` to disclosure.js
|
|
312
|
-
|
|
313
|
-
**Files:**
|
|
314
|
-
- Test: `test/unit/disclosure.test.js`
|
|
315
|
-
- Modify: `src/lib/disclosure.js`
|
|
316
|
-
|
|
317
|
-
This function generates the explicit instructions the agent receives.
|
|
318
|
-
|
|
319
|
-
**Step 1: Write failing tests**
|
|
320
|
-
|
|
321
|
-
```javascript
|
|
322
|
-
// ── Extraction Prompt Generation ──────────────────────────────
|
|
323
|
-
|
|
324
|
-
test('buildExtractionPrompt returns string with JSON schema', () => {
|
|
325
|
-
const disc = freshDisclosure();
|
|
326
|
-
const prompt = disc.buildExtractionPrompt();
|
|
327
|
-
assert.equal(typeof prompt, 'string');
|
|
328
|
-
assert.includes(prompt, 'lead_with');
|
|
329
|
-
assert.includes(prompt, 'discuss_freely');
|
|
330
|
-
assert.includes(prompt, 'deflect');
|
|
331
|
-
assert.includes(prompt, 'never_disclose');
|
|
332
|
-
assert.includes(prompt, 'personality_notes');
|
|
333
|
-
assert.includes(prompt, 'public');
|
|
334
|
-
assert.includes(prompt, 'friends');
|
|
335
|
-
assert.includes(prompt, 'family');
|
|
336
|
-
tmp.cleanup();
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test('buildExtractionPrompt lists available context files', () => {
|
|
340
|
-
const disc = freshDisclosure();
|
|
341
|
-
const prompt = disc.buildExtractionPrompt({ user: true, soul: true, heartbeat: false });
|
|
342
|
-
assert.includes(prompt, 'USER.md');
|
|
343
|
-
assert.includes(prompt, 'SOUL.md');
|
|
344
|
-
tmp.cleanup();
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test('buildExtractionPrompt includes guidance on what NOT to extract', () => {
|
|
348
|
-
const disc = freshDisclosure();
|
|
349
|
-
const prompt = disc.buildExtractionPrompt();
|
|
350
|
-
// Should warn against extracting code, URLs, instructions
|
|
351
|
-
assert.includes(prompt, 'URL');
|
|
352
|
-
assert.includes(prompt, 'code');
|
|
353
|
-
tmp.cleanup();
|
|
354
|
-
});
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
**Step 2: Run tests to verify they fail**
|
|
358
|
-
|
|
359
|
-
Run: `npm test`
|
|
360
|
-
Expected: 3 new FAILs
|
|
361
|
-
|
|
362
|
-
**Step 3: Implement `buildExtractionPrompt()` in disclosure.js**
|
|
363
|
-
|
|
364
|
-
```javascript
|
|
365
|
-
/**
|
|
366
|
-
* Generate the extraction prompt that instructs an agent on exactly what
|
|
367
|
-
* structured disclosure data to return. The agent reads workspace files,
|
|
368
|
-
* determines topics semantically, and returns the JSON schema below.
|
|
369
|
-
*
|
|
370
|
-
* @param {Object} [availableFiles] - Map of filename → truthy if present
|
|
371
|
-
* @returns {string} The instruction prompt for the agent
|
|
372
|
-
*/
|
|
373
|
-
function buildExtractionPrompt(availableFiles = {}) {
|
|
374
|
-
const fileList = Object.entries(availableFiles)
|
|
375
|
-
.filter(([, present]) => present)
|
|
376
|
-
.map(([name]) => ` - ${name}`)
|
|
377
|
-
.join('\n') || ' (no workspace files detected)';
|
|
378
|
-
|
|
379
|
-
return `## A2A Disclosure Extraction
|
|
380
|
-
|
|
381
|
-
You are helping the owner set up their A2A disclosure profile — the topics and information their agent is willing to discuss with other agents at different trust levels.
|
|
382
|
-
|
|
383
|
-
### Available workspace files
|
|
384
|
-
${fileList}
|
|
385
|
-
|
|
386
|
-
Read the available files above and extract disclosure topics. Focus on what the OWNER cares about, works on, and wants to discuss — NOT on agent instructions, code documentation, or operational tasks.
|
|
387
|
-
|
|
388
|
-
### What to extract
|
|
389
|
-
|
|
390
|
-
For each trust tier, identify topics the owner would want to discuss:
|
|
391
|
-
|
|
392
|
-
- **public** — safe for anyone: professional role, public interests, general project descriptions
|
|
393
|
-
- **friends** — for trusted contacts: current goals, collaboration interests, values, detailed project work
|
|
394
|
-
- **family** — inner circle only: personal interests, private projects, sensitive plans
|
|
395
|
-
|
|
396
|
-
For each tier, categorize topics as:
|
|
397
|
-
- **lead_with** — proactively bring up (max 3 per tier)
|
|
398
|
-
- **discuss_freely** — happy to discuss if asked (max 8 per tier)
|
|
399
|
-
- **deflect** — redirect or decline (max 3 per tier)
|
|
400
|
-
|
|
401
|
-
Also identify:
|
|
402
|
-
- **never_disclose** — information that should never be shared regardless of tier (API keys, credentials, financial data, etc.)
|
|
403
|
-
- **personality_notes** — a 1-2 sentence description of the owner's communication style
|
|
404
|
-
|
|
405
|
-
### What NOT to extract
|
|
406
|
-
|
|
407
|
-
Do NOT include as topics:
|
|
408
|
-
- Code snippets, CLI commands, or technical documentation
|
|
409
|
-
- URLs or file paths
|
|
410
|
-
- Agent instructions or operational tasks (e.g., "post 50 comments/day")
|
|
411
|
-
- Markdown formatting artifacts (bold markers, backticks)
|
|
412
|
-
- Anything from HEARTBEAT.md (these are agent tasks, not disclosure topics)
|
|
413
|
-
|
|
414
|
-
### Required JSON format
|
|
415
|
-
|
|
416
|
-
Return ONLY valid JSON in this exact structure:
|
|
417
|
-
|
|
418
|
-
\`\`\`json
|
|
419
|
-
{
|
|
420
|
-
"topics": {
|
|
421
|
-
"public": {
|
|
422
|
-
"lead_with": [
|
|
423
|
-
{ "topic": "Short label (max 60 chars)", "detail": "Longer description of the topic" }
|
|
424
|
-
],
|
|
425
|
-
"discuss_freely": [],
|
|
426
|
-
"deflect": []
|
|
427
|
-
},
|
|
428
|
-
"friends": {
|
|
429
|
-
"lead_with": [],
|
|
430
|
-
"discuss_freely": [],
|
|
431
|
-
"deflect": []
|
|
432
|
-
},
|
|
433
|
-
"family": {
|
|
434
|
-
"lead_with": [],
|
|
435
|
-
"discuss_freely": [],
|
|
436
|
-
"deflect": []
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
"never_disclose": ["API keys", "Credentials", "Financial figures"],
|
|
440
|
-
"personality_notes": "Brief description of communication style"
|
|
441
|
-
}
|
|
442
|
-
\`\`\`
|
|
443
|
-
|
|
444
|
-
### Rules
|
|
445
|
-
|
|
446
|
-
1. Each "topic" string must be a short, human-readable label (max 160 chars)
|
|
447
|
-
2. Each "detail" string explains the topic more fully (max 500 chars)
|
|
448
|
-
3. Topics should be things a person would discuss, not technical artifacts
|
|
449
|
-
4. Higher tiers (friends, family) inherit lower-tier topics automatically — don't duplicate
|
|
450
|
-
5. Present this to the owner for review before submitting
|
|
451
|
-
6. The owner may edit, remove, or add topics before final submission`;
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
**Step 4: Export it**
|
|
456
|
-
|
|
457
|
-
Add `buildExtractionPrompt` to module.exports.
|
|
458
|
-
|
|
459
|
-
**Step 5: Run tests**
|
|
460
|
-
|
|
461
|
-
Run: `npm test`
|
|
462
|
-
Expected: All 3 new tests PASS.
|
|
463
|
-
|
|
464
|
-
**Step 6: Commit**
|
|
465
|
-
|
|
466
|
-
```bash
|
|
467
|
-
git add src/lib/disclosure.js test/unit/disclosure.test.js
|
|
468
|
-
git commit -m "feat(disclosure): add buildExtractionPrompt for agent-driven extraction"
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
### Task 3: Strip file parsing from `generateDefaultManifest()`
|
|
474
|
-
|
|
475
|
-
**Files:**
|
|
476
|
-
- Test: `test/unit/disclosure.test.js`
|
|
477
|
-
- Modify: `src/lib/disclosure.js`
|
|
478
|
-
|
|
479
|
-
Remove all the context-file parsing from `generateDefaultManifest()`. It becomes a minimal-starter-only function. The agent-driven flow replaces file parsing.
|
|
480
|
-
|
|
481
|
-
**Step 1: Update existing tests**
|
|
482
|
-
|
|
483
|
-
Several tests call `generateDefaultManifest(contextFiles)` and expect parsed topics. These need to change:
|
|
484
|
-
|
|
485
|
-
- `generateDefaultManifest extracts goals from USER.md content` → remove (agent does this now)
|
|
486
|
-
- `generateDefaultManifest extracts personality from SOUL.md` → remove (agent does this now)
|
|
487
|
-
- `generateDefaultManifest uses memory content to add topics` → remove
|
|
488
|
-
- `generateDefaultManifest uses CLAUDE.md context` → remove
|
|
489
|
-
- The 3 new bug-fix tests (HEARTBEAT, code filtering, truncation) → remove (no longer relevant)
|
|
490
|
-
- `generateDefaultManifest ignores HEARTBEAT.md entirely` → remove
|
|
491
|
-
- `generateDefaultManifest ignores SKILL.md...` → remove
|
|
492
|
-
|
|
493
|
-
Keep:
|
|
494
|
-
- `generateDefaultManifest with no context returns starter` → update to verify it returns same starter even WITH context
|
|
495
|
-
|
|
496
|
-
Add new test:
|
|
497
|
-
|
|
498
|
-
```javascript
|
|
499
|
-
test('generateDefaultManifest returns minimal starter regardless of context', () => {
|
|
500
|
-
const disc = freshDisclosure();
|
|
501
|
-
const manifest = disc.generateDefaultManifest({
|
|
502
|
-
user: '## Goals\n- Build AI tools\n',
|
|
503
|
-
soul: 'Refined and precise.\n## Values\n- Craftsmanship\n',
|
|
504
|
-
memory: '- Working on distributed systems\n',
|
|
505
|
-
claude: '## Quick Context\n- A2A enables agent-to-agent communication\n'
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
// Should return only the minimal starter, no parsed content
|
|
509
|
-
assert.equal(manifest.topics.public.lead_with.length, 1);
|
|
510
|
-
assert.equal(manifest.topics.public.lead_with[0].topic, 'What I do');
|
|
511
|
-
assert.equal(manifest.topics.friends.lead_with.length, 0);
|
|
512
|
-
assert.equal(manifest.topics.friends.discuss_freely.length, 0);
|
|
513
|
-
tmp.cleanup();
|
|
514
|
-
});
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
**Step 2: Run tests to verify the new test fails (old behavior parses files)**
|
|
518
|
-
|
|
519
|
-
Run: `npm test`
|
|
520
|
-
Expected: New test FAILS because `generateDefaultManifest` still parses files.
|
|
521
|
-
|
|
522
|
-
**Step 3: Strip file parsing from `generateDefaultManifest()`**
|
|
523
|
-
|
|
524
|
-
Replace the entire function body with:
|
|
525
|
-
|
|
526
|
-
```javascript
|
|
527
|
-
function generateDefaultManifest() {
|
|
528
|
-
const now = new Date().toISOString();
|
|
529
|
-
|
|
530
|
-
return {
|
|
531
|
-
version: 1,
|
|
532
|
-
generated_at: now,
|
|
533
|
-
updated_at: now,
|
|
534
|
-
topics: {
|
|
535
|
-
public: {
|
|
536
|
-
lead_with: [{ topic: 'What I do', detail: 'Brief professional description' }],
|
|
537
|
-
discuss_freely: [{ topic: 'General interests', detail: 'Non-sensitive topics and hobbies' }],
|
|
538
|
-
deflect: [{ topic: 'Personal details', detail: 'Redirect to direct owner contact' }]
|
|
539
|
-
},
|
|
540
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
541
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
542
|
-
},
|
|
543
|
-
never_disclose: ['API keys', 'Other users\' data', 'Financial figures'],
|
|
544
|
-
personality_notes: 'Direct and technical. Prefers depth over breadth.'
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
Remove `isTechnicalContent()` and `truncateAtWord()` — they are no longer needed in the manifest generator. **Keep `isTechnicalContent()`** — it's still used by `validateDisclosureSubmission()`.
|
|
550
|
-
|
|
551
|
-
Remove `truncateAtWord()` — no longer used.
|
|
552
|
-
|
|
553
|
-
**Step 4: Remove tests that tested the old parsing behavior**
|
|
554
|
-
|
|
555
|
-
Delete these tests:
|
|
556
|
-
- `generateDefaultManifest extracts goals from USER.md content`
|
|
557
|
-
- `generateDefaultManifest extracts personality from SOUL.md`
|
|
558
|
-
- `generateDefaultManifest uses memory content to add topics`
|
|
559
|
-
- `generateDefaultManifest uses CLAUDE.md context`
|
|
560
|
-
- `generateDefaultManifest ignores SKILL.md bullet lists for disclosure topics`
|
|
561
|
-
- `generateDefaultManifest ignores HEARTBEAT.md entirely`
|
|
562
|
-
- `generateDefaultManifest filters out code/technical content from topics`
|
|
563
|
-
- `generateDefaultManifest truncates at word boundaries`
|
|
564
|
-
|
|
565
|
-
**Step 5: Run tests**
|
|
566
|
-
|
|
567
|
-
Run: `npm test`
|
|
568
|
-
Expected: All tests PASS. Fewer total tests (removed old parsing tests).
|
|
569
|
-
|
|
570
|
-
**Step 6: Commit**
|
|
571
|
-
|
|
572
|
-
```bash
|
|
573
|
-
git add src/lib/disclosure.js test/unit/disclosure.test.js
|
|
574
|
-
git commit -m "refactor(disclosure): strip file parsing from generateDefaultManifest
|
|
575
|
-
|
|
576
|
-
generateDefaultManifest now returns only the minimal starter manifest.
|
|
577
|
-
Topic extraction is handled by agent-driven flow via
|
|
578
|
-
buildExtractionPrompt + validateDisclosureSubmission."
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
### Task 4: Update CLI `onboard` command for agent-driven flow
|
|
584
|
-
|
|
585
|
-
**Files:**
|
|
586
|
-
- Modify: `bin/cli.js` (the `onboard` command, ~lines 1499-1538)
|
|
587
|
-
- Test: `test/integration/onboarding.test.js`
|
|
588
|
-
|
|
589
|
-
**Step 1: Write failing test**
|
|
590
|
-
|
|
591
|
-
Add to `test/integration/onboarding.test.js`:
|
|
592
|
-
|
|
593
|
-
```javascript
|
|
594
|
-
test('onboard --submit validates and saves agent disclosure submission', () => {
|
|
595
|
-
tmp = helpers.tmpConfigDir('onboard-submit');
|
|
596
|
-
const fs = require('fs');
|
|
597
|
-
const path = require('path');
|
|
598
|
-
const { execFileSync } = require('child_process');
|
|
599
|
-
|
|
600
|
-
const cliPath = path.join(__dirname, '..', '..', 'bin', 'cli.js');
|
|
601
|
-
const env = { ...process.env, A2A_CONFIG_DIR: tmp.dir };
|
|
602
|
-
|
|
603
|
-
const submission = JSON.stringify({
|
|
604
|
-
topics: {
|
|
605
|
-
public: {
|
|
606
|
-
lead_with: [{ topic: 'AI development', detail: 'Building AI-powered tools' }],
|
|
607
|
-
discuss_freely: [{ topic: 'Open source', detail: 'Contributing to OSS projects' }],
|
|
608
|
-
deflect: [{ topic: 'Personal finances', detail: 'Redirect to owner' }]
|
|
609
|
-
},
|
|
610
|
-
friends: {
|
|
611
|
-
lead_with: [{ topic: 'Current projects', detail: 'Deep work on A2A protocol' }],
|
|
612
|
-
discuss_freely: [],
|
|
613
|
-
deflect: []
|
|
614
|
-
},
|
|
615
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
616
|
-
},
|
|
617
|
-
never_disclose: ['API keys', 'Passwords'],
|
|
618
|
-
personality_notes: 'Technical and direct'
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
const result = execFileSync(process.execPath, [cliPath, 'onboard', '--submit', submission], {
|
|
622
|
-
env,
|
|
623
|
-
encoding: 'utf8'
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
assert.includes(result, 'Disclosure manifest saved');
|
|
627
|
-
|
|
628
|
-
// Verify manifest was saved correctly
|
|
629
|
-
delete require.cache[require.resolve('../../src/lib/disclosure')];
|
|
630
|
-
const disc = require('../../src/lib/disclosure');
|
|
631
|
-
const manifest = disc.loadManifest();
|
|
632
|
-
assert.equal(manifest.version, 1);
|
|
633
|
-
assert.equal(manifest.topics.public.lead_with[0].topic, 'AI development');
|
|
634
|
-
assert.equal(manifest.topics.friends.lead_with[0].topic, 'Current projects');
|
|
635
|
-
|
|
636
|
-
tmp.cleanup();
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
test('onboard --submit rejects invalid submission with errors', () => {
|
|
640
|
-
tmp = helpers.tmpConfigDir('onboard-submit-fail');
|
|
641
|
-
const { execFileSync } = require('child_process');
|
|
642
|
-
const path = require('path');
|
|
643
|
-
|
|
644
|
-
const cliPath = path.join(__dirname, '..', '..', 'bin', 'cli.js');
|
|
645
|
-
const env = { ...process.env, A2A_CONFIG_DIR: tmp.dir };
|
|
646
|
-
|
|
647
|
-
const badSubmission = JSON.stringify({ not: 'valid' });
|
|
648
|
-
|
|
649
|
-
let threw = false;
|
|
650
|
-
try {
|
|
651
|
-
execFileSync(process.execPath, [cliPath, 'onboard', '--submit', badSubmission], {
|
|
652
|
-
env,
|
|
653
|
-
encoding: 'utf8',
|
|
654
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
655
|
-
});
|
|
656
|
-
} catch (err) {
|
|
657
|
-
threw = true;
|
|
658
|
-
const stderr = err.stderr || '';
|
|
659
|
-
const stdout = err.stdout || '';
|
|
660
|
-
const output = stderr + stdout;
|
|
661
|
-
assert.ok(output.includes('topics') || output.includes('Validation'), 'Should mention validation error');
|
|
662
|
-
}
|
|
663
|
-
assert.ok(threw, 'Should exit with non-zero code on invalid submission');
|
|
664
|
-
|
|
665
|
-
tmp.cleanup();
|
|
666
|
-
});
|
|
667
|
-
|
|
668
|
-
test('onboard without --submit prints extraction prompt', () => {
|
|
669
|
-
tmp = helpers.tmpConfigDir('onboard-prompt');
|
|
670
|
-
const fs = require('fs');
|
|
671
|
-
const path = require('path');
|
|
672
|
-
const { execFileSync } = require('child_process');
|
|
673
|
-
|
|
674
|
-
const cliPath = path.join(__dirname, '..', '..', 'bin', 'cli.js');
|
|
675
|
-
const workspaceDir = path.join(tmp.dir, 'ws');
|
|
676
|
-
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
677
|
-
fs.writeFileSync(path.join(workspaceDir, 'USER.md'), '## Goals\n- Build cool tools\n');
|
|
678
|
-
|
|
679
|
-
const env = { ...process.env, A2A_CONFIG_DIR: tmp.dir, A2A_WORKSPACE: workspaceDir };
|
|
680
|
-
|
|
681
|
-
const result = execFileSync(process.execPath, [cliPath, 'onboard'], {
|
|
682
|
-
env,
|
|
683
|
-
encoding: 'utf8'
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
assert.includes(result, 'lead_with');
|
|
687
|
-
assert.includes(result, 'discuss_freely');
|
|
688
|
-
assert.includes(result, 'USER.md');
|
|
689
|
-
|
|
690
|
-
tmp.cleanup();
|
|
691
|
-
});
|
|
692
|
-
```
|
|
693
|
-
|
|
694
|
-
**Step 2: Run tests to verify they fail**
|
|
695
|
-
|
|
696
|
-
Run: `npm test`
|
|
697
|
-
Expected: 3 new FAILs
|
|
698
|
-
|
|
699
|
-
**Step 3: Rewrite the `onboard` CLI command**
|
|
700
|
-
|
|
701
|
-
Replace the `onboard` handler in `bin/cli.js` (~lines 1499-1538):
|
|
702
|
-
|
|
703
|
-
```javascript
|
|
704
|
-
onboard: (args) => {
|
|
705
|
-
const { A2AConfig } = require('../src/lib/config');
|
|
706
|
-
const {
|
|
707
|
-
readContextFiles,
|
|
708
|
-
buildExtractionPrompt,
|
|
709
|
-
validateDisclosureSubmission,
|
|
710
|
-
saveManifest,
|
|
711
|
-
MANIFEST_FILE
|
|
712
|
-
} = require('../src/lib/disclosure');
|
|
713
|
-
const config = new A2AConfig();
|
|
714
|
-
|
|
715
|
-
// ── Submit mode: agent sends structured JSON ──────────────
|
|
716
|
-
const submitRaw = args.flags.submit;
|
|
717
|
-
if (submitRaw) {
|
|
718
|
-
let parsed;
|
|
719
|
-
try {
|
|
720
|
-
parsed = JSON.parse(String(submitRaw));
|
|
721
|
-
} catch (e) {
|
|
722
|
-
console.error('\n\u274c Invalid JSON in --submit flag.');
|
|
723
|
-
console.error(` Parse error: ${e.message}\n`);
|
|
724
|
-
process.exit(1);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
const result = validateDisclosureSubmission(parsed);
|
|
728
|
-
if (!result.valid) {
|
|
729
|
-
console.error('\n\u274c Disclosure submission validation failed:\n');
|
|
730
|
-
result.errors.forEach(err => console.error(` \u2022 ${err}`));
|
|
731
|
-
console.error(`\nFix the errors above and resubmit with: a2a onboard --submit '<json>'\n`);
|
|
732
|
-
process.exit(1);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
saveManifest(result.manifest);
|
|
736
|
-
|
|
737
|
-
const agentName = args.flags.name || config.getAgent().name || process.env.A2A_AGENT_NAME || '';
|
|
738
|
-
const hostname = args.flags.hostname || config.getAgent().hostname || process.env.A2A_HOSTNAME || '';
|
|
739
|
-
if (agentName) config.setAgent({ name: agentName });
|
|
740
|
-
if (hostname) config.setAgent({ hostname });
|
|
741
|
-
|
|
742
|
-
console.log('\n\u2705 Disclosure manifest saved.');
|
|
743
|
-
console.log(` Manifest: ${MANIFEST_FILE}`);
|
|
744
|
-
console.log(' Next: a2a quickstart\n');
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// ── Prompt mode: print extraction instructions for agent ──
|
|
749
|
-
if (config.isOnboarded() && !args.flags.force) {
|
|
750
|
-
console.log('\u2705 Onboarding already complete. Use --force to regenerate.');
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const workspaceDir = process.env.A2A_WORKSPACE || process.cwd();
|
|
755
|
-
const contextFiles = readContextFiles(workspaceDir);
|
|
756
|
-
|
|
757
|
-
const availableFiles = {
|
|
758
|
-
'USER.md': Boolean(contextFiles.user),
|
|
759
|
-
'SOUL.md': Boolean(contextFiles.soul),
|
|
760
|
-
'HEARTBEAT.md': Boolean(contextFiles.heartbeat),
|
|
761
|
-
'SKILL.md': Boolean(contextFiles.skill),
|
|
762
|
-
'CLAUDE.md': Boolean(contextFiles.claude),
|
|
763
|
-
'memory/*.md': Boolean(contextFiles.memory)
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
console.log(buildExtractionPrompt(availableFiles));
|
|
767
|
-
console.log('\n---');
|
|
768
|
-
console.log('After the owner confirms, submit with:');
|
|
769
|
-
console.log(" a2a onboard --submit '<json>'\n");
|
|
770
|
-
},
|
|
771
|
-
```
|
|
772
|
-
|
|
773
|
-
**Step 4: Run tests**
|
|
774
|
-
|
|
775
|
-
Run: `npm test`
|
|
776
|
-
Expected: All 3 new tests PASS, all existing tests still pass.
|
|
777
|
-
|
|
778
|
-
**Step 5: Commit**
|
|
779
|
-
|
|
780
|
-
```bash
|
|
781
|
-
git add bin/cli.js test/integration/onboarding.test.js
|
|
782
|
-
git commit -m "feat(cli): rewrite onboard command for agent-driven disclosure extraction
|
|
783
|
-
|
|
784
|
-
onboard now has two modes:
|
|
785
|
-
- Default: prints extraction prompt with JSON schema for agent
|
|
786
|
-
- --submit: validates agent's structured JSON and saves manifest"
|
|
787
|
-
```
|
|
788
|
-
|
|
789
|
-
---
|
|
790
|
-
|
|
791
|
-
### Task 5: Update CLI `quickstart` Step 1 to stop auto-parsing
|
|
792
|
-
|
|
793
|
-
**Files:**
|
|
794
|
-
- Modify: `bin/cli.js` (~lines 1105-1127, quickstart Step 1)
|
|
795
|
-
- Test: `test/integration/onboarding.test.js`
|
|
796
|
-
|
|
797
|
-
**Step 1: Write failing test**
|
|
798
|
-
|
|
799
|
-
```javascript
|
|
800
|
-
test('quickstart uses existing manifest without regenerating from files', () => {
|
|
801
|
-
tmp = helpers.tmpConfigDir('quickstart-no-regen');
|
|
802
|
-
const fs = require('fs');
|
|
803
|
-
const path = require('path');
|
|
804
|
-
const http = require('http');
|
|
805
|
-
const { execFileSync } = require('child_process');
|
|
806
|
-
|
|
807
|
-
// Pre-save a manifest via --submit
|
|
808
|
-
const cliPath = path.join(__dirname, '..', '..', 'bin', 'cli.js');
|
|
809
|
-
const env = { ...process.env, A2A_CONFIG_DIR: tmp.dir };
|
|
810
|
-
|
|
811
|
-
const submission = JSON.stringify({
|
|
812
|
-
topics: {
|
|
813
|
-
public: {
|
|
814
|
-
lead_with: [{ topic: 'Agent-submitted topic', detail: 'This was submitted by agent' }],
|
|
815
|
-
discuss_freely: [],
|
|
816
|
-
deflect: []
|
|
817
|
-
},
|
|
818
|
-
friends: { lead_with: [], discuss_freely: [], deflect: [] },
|
|
819
|
-
family: { lead_with: [], discuss_freely: [], deflect: [] }
|
|
820
|
-
},
|
|
821
|
-
never_disclose: [],
|
|
822
|
-
personality_notes: 'Agent-set personality'
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
execFileSync(process.execPath, [cliPath, 'onboard', '--submit', submission], { env, encoding: 'utf8' });
|
|
826
|
-
|
|
827
|
-
// Now write workspace files that would produce different topics if parsed
|
|
828
|
-
const workspaceDir = path.join(tmp.dir, 'ws');
|
|
829
|
-
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
830
|
-
fs.writeFileSync(path.join(workspaceDir, 'USER.md'), '## Goals\n- Completely different goal\n');
|
|
831
|
-
env.A2A_WORKSPACE = workspaceDir;
|
|
832
|
-
|
|
833
|
-
// Start a minimal server for quickstart ping
|
|
834
|
-
const server = http.createServer((req, res) => {
|
|
835
|
-
if (req.method === 'GET' && req.url === '/api/a2a/ping') {
|
|
836
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
837
|
-
res.end(JSON.stringify({ pong: true }));
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
res.statusCode = 404;
|
|
841
|
-
res.end();
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
const done = new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
845
|
-
return done.then(() => {
|
|
846
|
-
const backendPort = String(server.address().port);
|
|
847
|
-
try {
|
|
848
|
-
execFileSync(process.execPath, [cliPath, 'quickstart', '--port', backendPort], {
|
|
849
|
-
env,
|
|
850
|
-
stdio: 'ignore'
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
// Verify the agent-submitted topic is preserved (not overwritten)
|
|
854
|
-
delete require.cache[require.resolve('../../src/lib/disclosure')];
|
|
855
|
-
const disc = require('../../src/lib/disclosure');
|
|
856
|
-
const manifest = disc.loadManifest();
|
|
857
|
-
assert.equal(manifest.topics.public.lead_with[0].topic, 'Agent-submitted topic');
|
|
858
|
-
} finally {
|
|
859
|
-
server.close();
|
|
860
|
-
tmp.cleanup();
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
});
|
|
864
|
-
```
|
|
865
|
-
|
|
866
|
-
**Step 2: Run tests to verify it fails**
|
|
867
|
-
|
|
868
|
-
Expected: FAIL because quickstart Step 1 still calls `generateDefaultManifest(contextFiles)` which overwrites.
|
|
869
|
-
|
|
870
|
-
Wait — actually quickstart only generates if manifest is empty: `if (!manifest || Object.keys(manifest).length === 0)`. Since we pre-saved via `--submit`, the manifest exists and quickstart should preserve it. Let me check if the `--force` flag path is the issue...
|
|
871
|
-
|
|
872
|
-
Actually this test might already pass since quickstart only regenerates on `--force` or empty manifest. Let me adjust — the key change needed is to make `--force` / `--regen-manifest` use the minimal starter instead of file parsing.
|
|
873
|
-
|
|
874
|
-
**Step 3: Update quickstart Step 1**
|
|
875
|
-
|
|
876
|
-
Replace lines ~1105-1127 in the quickstart handler:
|
|
877
|
-
|
|
878
|
-
```javascript
|
|
879
|
-
// ── Step 1: Background bootstrap (config + manifest) ─────────
|
|
880
|
-
let contextFiles = {};
|
|
881
|
-
let manifest = {};
|
|
882
|
-
try {
|
|
883
|
-
contextFiles = disc.readContextFiles(workspaceDir);
|
|
884
|
-
const forceManifest = Boolean(args.flags.force || args.flags['regen-manifest'] || args.flags.regenManifest);
|
|
885
|
-
if (forceManifest) {
|
|
886
|
-
// Force-regen uses minimal starter; agent-driven extraction is the
|
|
887
|
-
// proper way to populate topics (via `a2a onboard --submit`).
|
|
888
|
-
const generated = disc.generateDefaultManifest();
|
|
889
|
-
disc.saveManifest(generated);
|
|
890
|
-
manifest = generated;
|
|
891
|
-
} else {
|
|
892
|
-
manifest = disc.loadManifest();
|
|
893
|
-
if (!manifest || Object.keys(manifest).length === 0) {
|
|
894
|
-
const generated = disc.generateDefaultManifest();
|
|
895
|
-
disc.saveManifest(generated);
|
|
896
|
-
manifest = generated;
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
} catch (err) {
|
|
900
|
-
// Non-fatal: onboarding can proceed even if manifest fails.
|
|
901
|
-
contextFiles = {};
|
|
902
|
-
manifest = {};
|
|
903
|
-
}
|
|
904
|
-
```
|
|
905
|
-
|
|
906
|
-
The change is small: `disc.generateDefaultManifest(contextFiles)` → `disc.generateDefaultManifest()` (no context files). Since the function now ignores them anyway, this is just for clarity.
|
|
907
|
-
|
|
908
|
-
**Step 4: Run tests**
|
|
909
|
-
|
|
910
|
-
Run: `npm test`
|
|
911
|
-
Expected: All tests PASS.
|
|
912
|
-
|
|
913
|
-
**Step 5: Commit**
|
|
914
|
-
|
|
915
|
-
```bash
|
|
916
|
-
git add bin/cli.js test/integration/onboarding.test.js
|
|
917
|
-
git commit -m "fix(quickstart): stop auto-parsing workspace files into manifest
|
|
918
|
-
|
|
919
|
-
quickstart Step 1 now uses minimal starter on force-regen instead of
|
|
920
|
-
parsing workspace files. Proper topic extraction happens via
|
|
921
|
-
agent-driven 'a2a onboard --submit'."
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
---
|
|
925
|
-
|
|
926
|
-
### Task 6: Update integration tests that call `generateDefaultManifest`
|
|
927
|
-
|
|
928
|
-
**Files:**
|
|
929
|
-
- Modify: `test/integration/bramble-calls-bappybot.test.js`
|
|
930
|
-
- Modify: `test/integration/golda-calls-bappybot.test.js`
|
|
931
|
-
- Modify: `test/integration/nyx-calls-bappybot.test.js`
|
|
932
|
-
- Modify: `scripts/install-openclaw.js`
|
|
933
|
-
|
|
934
|
-
These all call `generateDefaultManifest()` with no args as a fallback. Since the function signature hasn't changed (it just ignores args now), these should already work. Verify and clean up any references to context file args.
|
|
935
|
-
|
|
936
|
-
**Step 1: Verify integration tests pass without changes**
|
|
937
|
-
|
|
938
|
-
Run: `npm test`
|
|
939
|
-
Expected: All pass. The integration tests call `disc.generateDefaultManifest()` with no args, which still returns the minimal starter.
|
|
940
|
-
|
|
941
|
-
**Step 2: Clean up `scripts/install-openclaw.js` if it passes context files**
|
|
942
|
-
|
|
943
|
-
Check if it passes `contextFiles` — if so, remove the arg since it's now ignored.
|
|
944
|
-
|
|
945
|
-
**Step 3: Commit (if any changes needed)**
|
|
946
|
-
|
|
947
|
-
```bash
|
|
948
|
-
git add scripts/install-openclaw.js
|
|
949
|
-
git commit -m "chore: remove unused context file args from generateDefaultManifest calls"
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
---
|
|
953
|
-
|
|
954
|
-
### Task 7: Final verification and cleanup
|
|
955
|
-
|
|
956
|
-
**Step 1: Run full test suite**
|
|
957
|
-
|
|
958
|
-
Run: `npm test`
|
|
959
|
-
Expected: All tests pass, no regressions.
|
|
960
|
-
|
|
961
|
-
**Step 2: Verify the new flow end-to-end manually**
|
|
962
|
-
|
|
963
|
-
```bash
|
|
964
|
-
# Generate extraction prompt
|
|
965
|
-
A2A_WORKSPACE=. node bin/cli.js onboard
|
|
966
|
-
|
|
967
|
-
# Submit valid disclosure
|
|
968
|
-
node bin/cli.js onboard --submit '{"topics":{"public":{"lead_with":[{"topic":"A2A federation","detail":"Agent-to-agent communication protocol"}],"discuss_freely":[],"deflect":[]},"friends":{"lead_with":[],"discuss_freely":[],"deflect":[]},"family":{"lead_with":[],"discuss_freely":[],"deflect":[]}},"never_disclose":["API keys"],"personality_notes":"Technical"}'
|
|
969
|
-
|
|
970
|
-
# Verify it rejects bad input
|
|
971
|
-
node bin/cli.js onboard --submit '{"bad":"data"}'
|
|
972
|
-
```
|
|
973
|
-
|
|
974
|
-
**Step 3: Commit any final fixes**
|
|
975
|
-
|
|
976
|
-
---
|
|
977
|
-
|
|
978
|
-
## Summary of changes
|
|
979
|
-
|
|
980
|
-
| File | Change |
|
|
981
|
-
|------|--------|
|
|
982
|
-
| `src/lib/disclosure.js` | Add `buildExtractionPrompt()`, `validateDisclosureSubmission()`. Strip file parsing from `generateDefaultManifest()`. Keep `isTechnicalContent()` for validation. Remove `truncateAtWord()`. |
|
|
983
|
-
| `bin/cli.js` | Rewrite `onboard` command (prompt mode + `--submit` mode). Update quickstart Step 1 to not pass context files. |
|
|
984
|
-
| `test/unit/disclosure.test.js` | Remove old parsing tests. Add validation + prompt generation tests. |
|
|
985
|
-
| `test/integration/onboarding.test.js` | Add `--submit` and prompt mode tests. |
|
|
986
|
-
| `scripts/install-openclaw.js` | Remove unused context file arg (if present). |
|