a2acalling 0.6.9 → 0.6.11
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 +297 -108
- package/package.json +1 -1
- package/scripts/postinstall.js +62 -11
- package/docs/plans/2026-02-14-agent-driven-disclosure-extraction.md +0 -986
|
@@ -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). |
|