atris 3.2.0 → 3.5.0
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/GETTING_STARTED.md +65 -131
- package/README.md +18 -2
- package/atris/GETTING_STARTED.md +65 -131
- package/atris/PERSONA.md +5 -1
- package/atris/atris.md +122 -153
- package/atris/skills/aeo/SKILL.md +117 -0
- package/atris/skills/atris/SKILL.md +49 -25
- package/atris/skills/create-member/SKILL.md +29 -9
- package/atris/skills/endgame/SKILL.md +9 -0
- package/atris/skills/research-search/SKILL.md +167 -0
- package/atris/skills/research-search/arxiv_search.py +157 -0
- package/atris/skills/research-search/program.md +48 -0
- package/atris/skills/research-search/results.tsv +6 -0
- package/atris/skills/research-search/scholar_search.py +154 -0
- package/atris/skills/tidy/SKILL.md +36 -21
- package/atris/team/_template/MEMBER.md +2 -0
- package/atris/team/validator/MEMBER.md +35 -1
- package/atris.md +118 -178
- package/bin/atris.js +30 -5
- package/cli/__pycache__/atris_code.cpython-314.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-312.pyc +0 -0
- package/cli/__pycache__/runtime_guard.cpython-314.pyc +0 -0
- package/cli/atris_code.py +889 -0
- package/cli/runtime_guard.py +693 -0
- package/commands/align.js +15 -0
- package/commands/app.js +316 -0
- package/commands/autopilot.js +390 -7
- package/commands/business.js +677 -2
- package/commands/computer.js +1979 -43
- package/commands/context-sync.js +5 -0
- package/commands/lifecycle.js +12 -0
- package/commands/plugin.js +24 -0
- package/commands/pull.js +40 -1
- package/commands/push.js +44 -0
- package/commands/serve.js +1 -0
- package/commands/sync.js +272 -76
- package/commands/verify.js +50 -1
- package/commands/wiki.js +27 -2
- package/lib/file-ops.js +13 -1
- package/lib/journal.js +23 -0
- package/lib/scorecard.js +42 -4
- package/lib/sync-telemetry.js +59 -0
- package/lib/todo.js +6 -0
- package/lib/wiki.js +150 -6
- package/package.json +2 -1
- package/utils/api.js +19 -0
- package/utils/auth.js +25 -1
- package/utils/config.js +24 -0
- package/utils/update-check.js +16 -0
package/commands/business.js
CHANGED
|
@@ -3,7 +3,8 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { loadCredentials } = require('../utils/auth');
|
|
5
5
|
const { apiRequestJson } = require('../utils/api');
|
|
6
|
-
const { syncBusinessCanonical } = require('./sync');
|
|
6
|
+
const { syncBusinessCanonical, ensureWorkspaceStateFiles } = require('./sync');
|
|
7
|
+
const { ensureContextScaffold, writeWikiStatus, appendWikiLog } = require('../lib/wiki');
|
|
7
8
|
|
|
8
9
|
function getBusinessConfigPath() {
|
|
9
10
|
const home = require('os').homedir();
|
|
@@ -104,6 +105,663 @@ function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
|
|
|
104
105
|
return { targetRoot, businessJsonPath, workspaceTemplate };
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
function parseRecordFlags(args, cwd = process.cwd()) {
|
|
109
|
+
const options = {
|
|
110
|
+
cwd,
|
|
111
|
+
reportPath: null,
|
|
112
|
+
summary: '',
|
|
113
|
+
metric: '',
|
|
114
|
+
outcome: 'recorded',
|
|
115
|
+
reward: null,
|
|
116
|
+
loop: 'manual',
|
|
117
|
+
actor: 'operator',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < args.length; i++) {
|
|
121
|
+
const arg = args[i];
|
|
122
|
+
const next = args[i + 1];
|
|
123
|
+
|
|
124
|
+
if ((arg === '--summary' || arg === '-s') && next) {
|
|
125
|
+
options.summary = next;
|
|
126
|
+
i++;
|
|
127
|
+
} else if ((arg === '--metric' || arg === '-m') && next) {
|
|
128
|
+
options.metric = next;
|
|
129
|
+
i++;
|
|
130
|
+
} else if ((arg === '--outcome' || arg === '-o') && next) {
|
|
131
|
+
options.outcome = next;
|
|
132
|
+
i++;
|
|
133
|
+
} else if ((arg === '--reward' || arg === '-r') && next) {
|
|
134
|
+
options.reward = next;
|
|
135
|
+
i++;
|
|
136
|
+
} else if (arg === '--loop' && next) {
|
|
137
|
+
options.loop = next;
|
|
138
|
+
i++;
|
|
139
|
+
} else if (arg === '--actor' && next) {
|
|
140
|
+
options.actor = next;
|
|
141
|
+
i++;
|
|
142
|
+
} else if (!arg.startsWith('-') && !options.reportPath) {
|
|
143
|
+
options.reportPath = arg;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return options;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseOnboardFlags(args, cwd = process.cwd()) {
|
|
151
|
+
const options = {
|
|
152
|
+
cwd,
|
|
153
|
+
name: '',
|
|
154
|
+
website: '',
|
|
155
|
+
links: [],
|
|
156
|
+
notes: [],
|
|
157
|
+
sources: [],
|
|
158
|
+
contactName: '',
|
|
159
|
+
contactEmail: '',
|
|
160
|
+
contactRole: '',
|
|
161
|
+
};
|
|
162
|
+
const freeform = [];
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < args.length; i++) {
|
|
165
|
+
const arg = args[i];
|
|
166
|
+
const next = args[i + 1];
|
|
167
|
+
|
|
168
|
+
if ((arg === '--name' || arg === '--business') && next) {
|
|
169
|
+
options.name = next;
|
|
170
|
+
i++;
|
|
171
|
+
} else if ((arg === '--website' || arg === '--site') && next) {
|
|
172
|
+
options.website = next;
|
|
173
|
+
i++;
|
|
174
|
+
} else if ((arg === '--link' || arg === '--url') && next) {
|
|
175
|
+
options.links.push(next);
|
|
176
|
+
i++;
|
|
177
|
+
} else if ((arg === '--from' || arg === '--source') && next) {
|
|
178
|
+
options.sources.push(next);
|
|
179
|
+
i++;
|
|
180
|
+
} else if ((arg === '--note' || arg === '--notes') && next) {
|
|
181
|
+
options.notes.push(next);
|
|
182
|
+
i++;
|
|
183
|
+
} else if ((arg === '--contact' || arg === '--person') && next) {
|
|
184
|
+
options.contactName = next;
|
|
185
|
+
i++;
|
|
186
|
+
} else if (arg === '--email' && next) {
|
|
187
|
+
options.contactEmail = next;
|
|
188
|
+
i++;
|
|
189
|
+
} else if (arg === '--role' && next) {
|
|
190
|
+
options.contactRole = next;
|
|
191
|
+
i++;
|
|
192
|
+
} else if (!arg.startsWith('-')) {
|
|
193
|
+
const resolved = path.resolve(cwd, arg);
|
|
194
|
+
if (/^https?:\/\//i.test(arg)) {
|
|
195
|
+
if (!options.website) options.website = arg;
|
|
196
|
+
else options.links.push(arg);
|
|
197
|
+
} else if (fs.existsSync(resolved)) {
|
|
198
|
+
options.sources.push(arg);
|
|
199
|
+
} else {
|
|
200
|
+
freeform.push(arg);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (freeform.length > 0) {
|
|
206
|
+
options.notes.push(freeform.join(' '));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return options;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function readWorkspaceBusinessMeta(cwd = process.cwd()) {
|
|
213
|
+
const bizFile = path.join(cwd, '.atris', 'business.json');
|
|
214
|
+
if (!fs.existsSync(bizFile)) {
|
|
215
|
+
throw new Error('Run this command inside a business environment with .atris/business.json.');
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
return JSON.parse(fs.readFileSync(bizFile, 'utf8'));
|
|
219
|
+
} catch (error) {
|
|
220
|
+
throw new Error(`Failed to read .atris/business.json: ${error.message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveWorkspaceReport(cwd, reportPath) {
|
|
225
|
+
if (!reportPath) {
|
|
226
|
+
throw new Error('Usage: atris business record <report-path> [--summary "text"] [--metric name] [--outcome positive|mixed|negative] [--reward N]');
|
|
227
|
+
}
|
|
228
|
+
const absPath = path.resolve(cwd, reportPath);
|
|
229
|
+
if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) {
|
|
230
|
+
throw new Error(`Report not found: ${reportPath}`);
|
|
231
|
+
}
|
|
232
|
+
const relPath = path.relative(cwd, absPath).replace(/\\/g, '/');
|
|
233
|
+
return { absPath, relPath };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function extractReportTitle(content, absPath) {
|
|
237
|
+
const heading = String(content || '').match(/^#\s+(.+)$/m);
|
|
238
|
+
if (heading) return heading[1].trim();
|
|
239
|
+
return path.basename(absPath, path.extname(absPath));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function normalizeOutcome(value) {
|
|
243
|
+
const normalized = String(value || 'recorded').trim().toLowerCase();
|
|
244
|
+
if (['positive', 'win', 'success', 'improved'].includes(normalized)) return 'positive';
|
|
245
|
+
if (['negative', 'loss', 'failed', 'regressed'].includes(normalized)) return 'negative';
|
|
246
|
+
if (['mixed', 'partial', 'unclear'].includes(normalized)) return 'mixed';
|
|
247
|
+
return 'recorded';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function defaultRewardForOutcome(outcome) {
|
|
251
|
+
if (outcome === 'positive') return 5;
|
|
252
|
+
if (outcome === 'negative') return -3;
|
|
253
|
+
if (outcome === 'mixed') return 1;
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function appendJsonl(filePath, record) {
|
|
258
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
259
|
+
fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, 'utf8');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function slugifyName(value) {
|
|
263
|
+
return String(value || '')
|
|
264
|
+
.toLowerCase()
|
|
265
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
266
|
+
.replace(/^-+|-+$/g, '') || 'item';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function upsertIndexEntry(indexPath, sectionName, relativePath, description) {
|
|
270
|
+
const normalizedPath = relativePath.replace(/\\/g, '/');
|
|
271
|
+
const entryLine = `- [[${normalizedPath}]] - ${description}`;
|
|
272
|
+
let lines = fs.readFileSync(indexPath, 'utf8').split('\n');
|
|
273
|
+
const existingIndex = lines.findIndex((line) => line.includes(`[[${normalizedPath}]]`));
|
|
274
|
+
if (existingIndex >= 0) {
|
|
275
|
+
lines[existingIndex] = entryLine;
|
|
276
|
+
fs.writeFileSync(indexPath, `${lines.join('\n').replace(/\n*$/, '\n')}`, 'utf8');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const header = `## ${sectionName}`;
|
|
281
|
+
const sectionIndex = lines.findIndex((line) => line.trim() === header);
|
|
282
|
+
if (sectionIndex === -1) return;
|
|
283
|
+
|
|
284
|
+
let insertAt = sectionIndex + 1;
|
|
285
|
+
while (insertAt < lines.length && !/^##\s+/.test(lines[insertAt])) {
|
|
286
|
+
insertAt++;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
lines.splice(insertAt, 0, entryLine);
|
|
290
|
+
fs.writeFileSync(indexPath, `${lines.join('\n').replace(/\n*$/, '\n')}`, 'utf8');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function writeMarkdownWithFrontmatter(filePath, frontmatter, body) {
|
|
294
|
+
const yaml = Object.entries(frontmatter).map(([key, value]) => {
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
return `${key}:\n${value.map((item) => ` - ${item}`).join('\n')}`;
|
|
297
|
+
}
|
|
298
|
+
return `${key}: ${value}`;
|
|
299
|
+
}).join('\n');
|
|
300
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
301
|
+
fs.writeFileSync(filePath, `---\n${yaml}\n---\n\n${body.trim()}\n`, 'utf8');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function walkOnboardingFiles(dir, options = {}) {
|
|
305
|
+
const skipDirs = new Set(['.git', '.atris', 'atris', '_ingest', 'node_modules', 'dist', 'build', 'coverage', '.next']);
|
|
306
|
+
const allowedExt = new Set(['.md', '.txt', '.pdf', '.csv', '.json', '.html', '.htm', '.docx', '.xlsx', '.png', '.jpg', '.jpeg']);
|
|
307
|
+
const maxFiles = options.maxFiles || 25;
|
|
308
|
+
const output = [];
|
|
309
|
+
|
|
310
|
+
function walk(currentDir) {
|
|
311
|
+
if (!fs.existsSync(currentDir) || output.length >= maxFiles) return;
|
|
312
|
+
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
313
|
+
if (output.length >= maxFiles) break;
|
|
314
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
315
|
+
if (entry.isDirectory()) {
|
|
316
|
+
if (skipDirs.has(entry.name)) continue;
|
|
317
|
+
walk(fullPath);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (!entry.isFile()) continue;
|
|
321
|
+
if (entry.name.startsWith('.')) continue;
|
|
322
|
+
if (!allowedExt.has(path.extname(entry.name).toLowerCase())) continue;
|
|
323
|
+
output.push(fullPath);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
walk(dir);
|
|
328
|
+
return output;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function extractUrlsFromText(text) {
|
|
332
|
+
return Array.from(new Set((String(text || '').match(/https?:\/\/[^\s)<>"']+/g) || []).map((item) => item.replace(/[.,]$/, ''))));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function isTextLike(filePath) {
|
|
336
|
+
return new Set(['.md', '.txt', '.json', '.csv', '.html', '.htm']).has(path.extname(filePath).toLowerCase());
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function readSmallText(filePath, maxBytes = 200000) {
|
|
340
|
+
try {
|
|
341
|
+
const stat = fs.statSync(filePath);
|
|
342
|
+
if (stat.size > maxBytes || !isTextLike(filePath)) return null;
|
|
343
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function discoverOnboardingSignals(cwd, options = {}) {
|
|
350
|
+
const explicitSourcePaths = (options.sources || [])
|
|
351
|
+
.map((value) => path.resolve(cwd, value))
|
|
352
|
+
.filter((fullPath) => fs.existsSync(fullPath));
|
|
353
|
+
|
|
354
|
+
const rootCandidates = walkOnboardingFiles(cwd, { maxFiles: 20 })
|
|
355
|
+
.filter((fullPath) => {
|
|
356
|
+
const relative = path.relative(cwd, fullPath).replace(/\\/g, '/');
|
|
357
|
+
return !relative.startsWith('atris/') && !relative.startsWith('.atris/');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const contextDir = path.join(cwd, 'atris', 'context');
|
|
361
|
+
const contextCandidates = walkOnboardingFiles(contextDir, { maxFiles: 20 })
|
|
362
|
+
.filter((fullPath) => {
|
|
363
|
+
const relative = path.relative(contextDir, fullPath).replace(/\\/g, '/');
|
|
364
|
+
return !relative.startsWith('_ingest/') && relative !== 'README.md' && relative !== 'live-workspace.md';
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const sourcePaths = Array.from(new Set([...explicitSourcePaths, ...rootCandidates, ...contextCandidates]));
|
|
368
|
+
const urls = new Set([options.website, ...(options.links || [])].filter(Boolean));
|
|
369
|
+
|
|
370
|
+
for (const note of options.notes || []) {
|
|
371
|
+
for (const url of extractUrlsFromText(note)) urls.add(url);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
for (const sourcePath of sourcePaths) {
|
|
375
|
+
const text = readSmallText(sourcePath);
|
|
376
|
+
if (!text) continue;
|
|
377
|
+
for (const url of extractUrlsFromText(text)) urls.add(url);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
website: options.website || Array.from(urls)[0] || '',
|
|
382
|
+
urls: Array.from(urls),
|
|
383
|
+
sourcePaths,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function stageOnboardingSources(cwd, packDir, sourcePaths = []) {
|
|
388
|
+
const stagedDir = path.join(packDir, 'sources');
|
|
389
|
+
fs.mkdirSync(stagedDir, { recursive: true });
|
|
390
|
+
const stagedEntries = [];
|
|
391
|
+
let counter = 0;
|
|
392
|
+
|
|
393
|
+
for (const sourcePath of sourcePaths) {
|
|
394
|
+
if (!fs.existsSync(sourcePath)) continue;
|
|
395
|
+
counter += 1;
|
|
396
|
+
const baseName = path.basename(sourcePath);
|
|
397
|
+
const targetPath = path.join(stagedDir, `${String(counter).padStart(2, '0')}-${baseName}`);
|
|
398
|
+
const stat = fs.statSync(sourcePath);
|
|
399
|
+
if (stat.isDirectory()) {
|
|
400
|
+
fs.cpSync(sourcePath, targetPath, { recursive: true });
|
|
401
|
+
} else {
|
|
402
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
403
|
+
}
|
|
404
|
+
stagedEntries.push({
|
|
405
|
+
original: path.relative(cwd, sourcePath).replace(/\\/g, '/'),
|
|
406
|
+
staged: path.relative(cwd, targetPath).replace(/\\/g, '/'),
|
|
407
|
+
kind: stat.isDirectory() ? 'directory' : 'file',
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return stagedEntries;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function suggestStarterAction(signals) {
|
|
415
|
+
if (signals.contactEmail && signals.website) {
|
|
416
|
+
return {
|
|
417
|
+
title: 'Draft a founder-context note',
|
|
418
|
+
action: `Write a short note to ${signals.contactName || 'the contact'} that reflects the website, asks for the current priority, and proposes one concrete first loop.`,
|
|
419
|
+
why: 'This is the shortest safe path to real feedback from a named human.',
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (signals.website) {
|
|
423
|
+
return {
|
|
424
|
+
title: 'Map the offer into one loop',
|
|
425
|
+
action: 'Read the website and turn it into one measurable workflow with a clear reward signal.',
|
|
426
|
+
why: 'A website is enough to define a first useful business loop without waiting for perfect intake.',
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if ((signals.sourceEntries || []).length > 0) {
|
|
430
|
+
return {
|
|
431
|
+
title: 'Extract the first workflow from local evidence',
|
|
432
|
+
action: 'Read the strongest local source, summarize what the company does, and choose one workflow worth operationalizing first.',
|
|
433
|
+
why: 'Local evidence is already better than a blank template and can anchor the first action.',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
title: 'Collect one anchor signal',
|
|
438
|
+
action: 'Get one website, one named human, or one source doc so the environment can stop guessing.',
|
|
439
|
+
why: 'The system can work from partial input, but it still needs one concrete anchor.',
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function onboardBusiness(...flags) {
|
|
444
|
+
const options = parseOnboardFlags(flags, process.cwd());
|
|
445
|
+
const cwd = options.cwd || process.cwd();
|
|
446
|
+
|
|
447
|
+
const bizFile = path.join(cwd, '.atris', 'business.json');
|
|
448
|
+
if (!fs.existsSync(bizFile) && options.name) {
|
|
449
|
+
const slug = slugifyName(options.name);
|
|
450
|
+
createCanonicalBusinessWorkspace(cwd, {
|
|
451
|
+
business_id: '',
|
|
452
|
+
workspace_id: '',
|
|
453
|
+
name: options.name,
|
|
454
|
+
slug,
|
|
455
|
+
owner_email: '',
|
|
456
|
+
workspace_template: 'business',
|
|
457
|
+
}, { here: true });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const bizMeta = readWorkspaceBusinessMeta(cwd);
|
|
461
|
+
|
|
462
|
+
ensureWorkspaceStateFiles(cwd, {
|
|
463
|
+
slug: bizMeta.slug || 'business',
|
|
464
|
+
business_id: bizMeta.business_id || '',
|
|
465
|
+
workspace_id: bizMeta.workspace_id || '',
|
|
466
|
+
workspace_template: bizMeta.workspace_template || 'business',
|
|
467
|
+
}, { dryRun: false });
|
|
468
|
+
|
|
469
|
+
const contextDir = ensureContextScaffold(cwd, 'public');
|
|
470
|
+
const stamp = new Date().toISOString().replace(/[:]/g, '-').slice(0, 16);
|
|
471
|
+
const packDir = path.join(contextDir, '_ingest', `${stamp}-onboarding`);
|
|
472
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
473
|
+
|
|
474
|
+
const discovered = discoverOnboardingSignals(cwd, options);
|
|
475
|
+
const stagedSources = stageOnboardingSources(cwd, packDir, discovered.sourcePaths);
|
|
476
|
+
const links = Array.from(new Set([discovered.website, ...discovered.urls].filter(Boolean)));
|
|
477
|
+
const starterAction = suggestStarterAction({
|
|
478
|
+
website: discovered.website,
|
|
479
|
+
contactName: options.contactName,
|
|
480
|
+
contactEmail: options.contactEmail,
|
|
481
|
+
sourceEntries: stagedSources,
|
|
482
|
+
});
|
|
483
|
+
const intakeLines = [
|
|
484
|
+
`# ${bizMeta.name} Onboarding Intake`,
|
|
485
|
+
'',
|
|
486
|
+
`- Business: ${bizMeta.name}`,
|
|
487
|
+
`- Slug: ${bizMeta.slug}`,
|
|
488
|
+
discovered.website ? `- Website: ${discovered.website}` : null,
|
|
489
|
+
options.contactName ? `- Contact: ${options.contactName}` : null,
|
|
490
|
+
options.contactRole ? `- Contact role: ${options.contactRole}` : null,
|
|
491
|
+
options.contactEmail ? `- Contact email: ${options.contactEmail}` : null,
|
|
492
|
+
'',
|
|
493
|
+
'## Notes',
|
|
494
|
+
...(options.notes.length > 0 ? options.notes.map((note) => `- ${note}`) : ['- No notes captured yet.']),
|
|
495
|
+
'',
|
|
496
|
+
'## Discovered Sources',
|
|
497
|
+
...(stagedSources.length > 0 ? stagedSources.map((entry) => `- ${entry.original} -> ${entry.staged}`) : ['- No local files discovered yet.']),
|
|
498
|
+
'',
|
|
499
|
+
'## Links',
|
|
500
|
+
...(links.length > 0 ? links.map((link) => `- ${link}`) : ['- No links captured yet.']),
|
|
501
|
+
].filter(Boolean);
|
|
502
|
+
const intakePath = path.join(packDir, 'intake.md');
|
|
503
|
+
fs.writeFileSync(intakePath, `${intakeLines.join('\n')}\n`, 'utf8');
|
|
504
|
+
|
|
505
|
+
const linksPath = path.join(packDir, 'links.txt');
|
|
506
|
+
fs.writeFileSync(linksPath, `${links.join('\n')}${links.length > 0 ? '\n' : ''}`, 'utf8');
|
|
507
|
+
const sourcesPath = path.join(packDir, 'sources.txt');
|
|
508
|
+
fs.writeFileSync(
|
|
509
|
+
sourcesPath,
|
|
510
|
+
`${stagedSources.map((entry) => `${entry.original} -> ${entry.staged}`).join('\n')}${stagedSources.length > 0 ? '\n' : ''}`,
|
|
511
|
+
'utf8'
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
const intakeRel = path.relative(cwd, intakePath).replace(/\\/g, '/');
|
|
515
|
+
const linksRel = path.relative(cwd, linksPath).replace(/\\/g, '/');
|
|
516
|
+
const sourcesRel = path.relative(cwd, sourcesPath).replace(/\\/g, '/');
|
|
517
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
518
|
+
|
|
519
|
+
const briefSlug = `${bizMeta.slug}-starter-brief`;
|
|
520
|
+
const briefPath = path.join(cwd, 'atris', 'wiki', 'briefs', `${briefSlug}.md`);
|
|
521
|
+
writeMarkdownWithFrontmatter(briefPath, {
|
|
522
|
+
type: 'brief',
|
|
523
|
+
slug: briefSlug,
|
|
524
|
+
title: `${bizMeta.name} Starter Brief`,
|
|
525
|
+
sources: [intakeRel, linksRel, sourcesRel],
|
|
526
|
+
last_compiled: today,
|
|
527
|
+
created: today,
|
|
528
|
+
updated: today,
|
|
529
|
+
tags: ['business', 'onboarding', 'starter'],
|
|
530
|
+
}, `
|
|
531
|
+
# ${bizMeta.name} Starter Brief
|
|
532
|
+
|
|
533
|
+
## What We Know
|
|
534
|
+
|
|
535
|
+
- Website: ${discovered.website || 'unknown'}
|
|
536
|
+
- Contact: ${options.contactName || 'unknown'}
|
|
537
|
+
- Contact role: ${options.contactRole || 'unknown'}
|
|
538
|
+
- Contact email: ${options.contactEmail || 'unknown'}
|
|
539
|
+
${options.notes.map((note) => `- Note: ${note}`).join('\n') || '- Notes: none captured yet'}
|
|
540
|
+
${stagedSources.length > 0 ? `- Local sources discovered: ${stagedSources.length}` : '- Local sources discovered: 0'}
|
|
541
|
+
|
|
542
|
+
## Unknowns
|
|
543
|
+
|
|
544
|
+
- Primary customer or audience
|
|
545
|
+
- Revenue model and buying motion
|
|
546
|
+
- Main operator inside the business
|
|
547
|
+
- Tool stack and source systems
|
|
548
|
+
- First measurable operating loop
|
|
549
|
+
|
|
550
|
+
## Next Moves
|
|
551
|
+
|
|
552
|
+
- Read the staged intake in \`${intakeRel}\`
|
|
553
|
+
- ${starterAction.action}
|
|
554
|
+
- Turn the first real interaction into a recap, then run \`atris business record ...\`
|
|
555
|
+
`);
|
|
556
|
+
upsertIndexEntry(path.join(cwd, 'atris', 'wiki', 'index.md'), 'Briefs', path.relative(cwd, briefPath), 'Starter business brief from onboarding intake');
|
|
557
|
+
|
|
558
|
+
let personRelativePath = null;
|
|
559
|
+
if (options.contactName) {
|
|
560
|
+
const personSlug = slugifyName(options.contactName);
|
|
561
|
+
const personPath = path.join(cwd, 'atris', 'wiki', 'people', `${personSlug}.md`);
|
|
562
|
+
writeMarkdownWithFrontmatter(personPath, {
|
|
563
|
+
type: 'person',
|
|
564
|
+
slug: personSlug,
|
|
565
|
+
title: options.contactName,
|
|
566
|
+
sources: [intakeRel, sourcesRel],
|
|
567
|
+
last_compiled: today,
|
|
568
|
+
created: today,
|
|
569
|
+
updated: today,
|
|
570
|
+
tags: ['person', 'contact', 'onboarding'],
|
|
571
|
+
}, `
|
|
572
|
+
# ${options.contactName}
|
|
573
|
+
|
|
574
|
+
## Known
|
|
575
|
+
|
|
576
|
+
- Business: ${bizMeta.name}
|
|
577
|
+
- Role: ${options.contactRole || 'unknown'}
|
|
578
|
+
- Email: ${options.contactEmail || 'unknown'}
|
|
579
|
+
|
|
580
|
+
## Unknown
|
|
581
|
+
|
|
582
|
+
- Decision authority
|
|
583
|
+
- Preferred communication rhythm
|
|
584
|
+
- Main business pain
|
|
585
|
+
|
|
586
|
+
## Cross-References
|
|
587
|
+
|
|
588
|
+
- [[atris/wiki/briefs/${path.basename(briefPath)}]] - starter brief
|
|
589
|
+
`);
|
|
590
|
+
personRelativePath = path.relative(cwd, personPath).replace(/\\/g, '/');
|
|
591
|
+
upsertIndexEntry(path.join(cwd, 'atris', 'wiki', 'index.md'), 'People', personRelativePath, `Seed contact for ${bizMeta.name}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const conceptSlug = `${bizMeta.slug}-first-loop`;
|
|
595
|
+
const conceptPath = path.join(cwd, 'atris', 'wiki', 'concepts', `${conceptSlug}.md`);
|
|
596
|
+
writeMarkdownWithFrontmatter(conceptPath, {
|
|
597
|
+
type: 'concept',
|
|
598
|
+
slug: conceptSlug,
|
|
599
|
+
title: `${bizMeta.name} First Loop`,
|
|
600
|
+
sources: [intakeRel, linksRel, sourcesRel],
|
|
601
|
+
last_compiled: today,
|
|
602
|
+
created: today,
|
|
603
|
+
updated: today,
|
|
604
|
+
tags: ['concept', 'loop', 'onboarding'],
|
|
605
|
+
}, `
|
|
606
|
+
# ${bizMeta.name} First Loop
|
|
607
|
+
|
|
608
|
+
## Candidate Loop
|
|
609
|
+
|
|
610
|
+
- Trigger: a new lead, meeting, client request, or operator handoff
|
|
611
|
+
- Action: summarize context, propose the next move, and draft one concrete output
|
|
612
|
+
- Reward: operator approval, reply, booked meeting, or visible pipeline progress
|
|
613
|
+
|
|
614
|
+
## Known Signals
|
|
615
|
+
|
|
616
|
+
${links.map((link) => `- ${link}`).join('\n') || '- No external links captured yet'}
|
|
617
|
+
${stagedSources.length > 0 ? `- Local evidence files: ${stagedSources.length}` : ''}
|
|
618
|
+
|
|
619
|
+
## Unknowns
|
|
620
|
+
|
|
621
|
+
- Best first workflow to automate
|
|
622
|
+
- Exact reward signal
|
|
623
|
+
- Required integrations
|
|
624
|
+
`);
|
|
625
|
+
upsertIndexEntry(path.join(cwd, 'atris', 'wiki', 'index.md'), 'Concepts', path.relative(cwd, conceptPath), 'Seed first-loop hypothesis from onboarding intake');
|
|
626
|
+
|
|
627
|
+
const cheatSheetPath = path.join(cwd, 'atris', 'reports', `${today}-${bizMeta.slug}-onboarding-cheat-sheet.md`);
|
|
628
|
+
const onePagerPath = path.join(cwd, 'atris', 'reports', `${today}-${bizMeta.slug}-operator-one-pager.md`);
|
|
629
|
+
const operatorSummary = [
|
|
630
|
+
`# ${bizMeta.name} Onboarding Cheat Sheet`,
|
|
631
|
+
'',
|
|
632
|
+
'## What Exists',
|
|
633
|
+
`- Starter brief: ${path.relative(cwd, briefPath).replace(/\\/g, '/')}`,
|
|
634
|
+
personRelativePath ? `- Contact page: ${personRelativePath}` : null,
|
|
635
|
+
`- First loop page: ${path.relative(cwd, conceptPath).replace(/\\/g, '/')}`,
|
|
636
|
+
`- Raw intake: ${intakeRel}`,
|
|
637
|
+
`- Source list: ${sourcesRel}`,
|
|
638
|
+
stagedSources.length > 0 ? `- Staged sources: ${stagedSources.length}` : '- Staged sources: 0',
|
|
639
|
+
'',
|
|
640
|
+
'## Best Next Action',
|
|
641
|
+
`- ${starterAction.title}`,
|
|
642
|
+
`- Action: ${starterAction.action}`,
|
|
643
|
+
`- Why: ${starterAction.why}`,
|
|
644
|
+
'- Swarlo join: placeholder preserved for the next live join step.',
|
|
645
|
+
'',
|
|
646
|
+
'## Next 3 Moves',
|
|
647
|
+
'- Open the starter brief and correct anything false.',
|
|
648
|
+
`- ${starterAction.action}`,
|
|
649
|
+
'- After the first real run, write a recap and record it with `atris business record ...`.',
|
|
650
|
+
].filter(Boolean).join('\n') + '\n';
|
|
651
|
+
fs.writeFileSync(cheatSheetPath, operatorSummary, 'utf8');
|
|
652
|
+
fs.writeFileSync(onePagerPath, operatorSummary.replace('# ', '# One Pager — '), 'utf8');
|
|
653
|
+
|
|
654
|
+
const todoPath = path.join(cwd, 'atris', 'TODO.md');
|
|
655
|
+
if (fs.existsSync(todoPath)) {
|
|
656
|
+
let todoContent = fs.readFileSync(todoPath, 'utf8');
|
|
657
|
+
const taskLine = `- **Onboard:** ${starterAction.title} — ${starterAction.action} [execute]\n`;
|
|
658
|
+
const backlogMatch = todoContent.match(/^## Backlog\s*$/m);
|
|
659
|
+
if (backlogMatch) {
|
|
660
|
+
const insertAt = backlogMatch.index + backlogMatch[0].length;
|
|
661
|
+
todoContent = todoContent.slice(0, insertAt) + '\n' + taskLine + todoContent.slice(insertAt);
|
|
662
|
+
} else {
|
|
663
|
+
todoContent += '\n## Backlog\n\n' + taskLine;
|
|
664
|
+
}
|
|
665
|
+
fs.writeFileSync(todoPath, todoContent, 'utf8');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
writeWikiStatus(cwd, {
|
|
669
|
+
health: `starter onboarding compiled from ${intakeRel}`,
|
|
670
|
+
nextMove: `review ${path.relative(cwd, briefPath).replace(/\\/g, '/')} and tighten the first loop`,
|
|
671
|
+
}, 'public', { lastIngest: `${today} ${new Date().toTimeString().slice(0, 5)}` });
|
|
672
|
+
appendWikiLog(cwd, `starter onboarding compiled for ${bizMeta.slug}`, [
|
|
673
|
+
`intake ${intakeRel}`,
|
|
674
|
+
`sources ${sourcesRel}`,
|
|
675
|
+
`brief ${path.relative(cwd, briefPath).replace(/\\/g, '/')}`,
|
|
676
|
+
personRelativePath ? `person ${personRelativePath}` : null,
|
|
677
|
+
`concept ${path.relative(cwd, conceptPath).replace(/\\/g, '/')}`,
|
|
678
|
+
`cheat sheet ${path.relative(cwd, cheatSheetPath).replace(/\\/g, '/')}`,
|
|
679
|
+
`one pager ${path.relative(cwd, onePagerPath).replace(/\\/g, '/')}`,
|
|
680
|
+
].filter(Boolean), 'public', 'ONBOARD');
|
|
681
|
+
|
|
682
|
+
console.log('');
|
|
683
|
+
console.log(`Onboarded ${bizMeta.name}.`);
|
|
684
|
+
console.log(` Intake: ${intakeRel}`);
|
|
685
|
+
console.log(` Sources: ${sourcesRel}`);
|
|
686
|
+
console.log(` Brief: ${path.relative(cwd, briefPath).replace(/\\/g, '/')}`);
|
|
687
|
+
if (personRelativePath) console.log(` Contact: ${personRelativePath}`);
|
|
688
|
+
console.log(` First loop: ${path.relative(cwd, conceptPath).replace(/\\/g, '/')}`);
|
|
689
|
+
console.log(` Cheat sheet: ${path.relative(cwd, cheatSheetPath).replace(/\\/g, '/')}`);
|
|
690
|
+
console.log(` One pager: ${path.relative(cwd, onePagerPath).replace(/\\/g, '/')}`);
|
|
691
|
+
console.log(` Next action: ${starterAction.title}`);
|
|
692
|
+
console.log('');
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
async function recordBusinessRun(reportArg, ...flags) {
|
|
696
|
+
const options = parseRecordFlags([reportArg, ...flags], process.cwd());
|
|
697
|
+
const cwd = options.cwd || process.cwd();
|
|
698
|
+
const bizMeta = readWorkspaceBusinessMeta(cwd);
|
|
699
|
+
const { absPath, relPath } = resolveWorkspaceReport(cwd, options.reportPath);
|
|
700
|
+
|
|
701
|
+
ensureWorkspaceStateFiles(cwd, {
|
|
702
|
+
slug: bizMeta.slug || 'business',
|
|
703
|
+
business_id: bizMeta.business_id || '',
|
|
704
|
+
workspace_id: bizMeta.workspace_id || '',
|
|
705
|
+
workspace_template: bizMeta.workspace_template || 'business',
|
|
706
|
+
}, { dryRun: false });
|
|
707
|
+
|
|
708
|
+
const reportContent = fs.readFileSync(absPath, 'utf8');
|
|
709
|
+
const title = extractReportTitle(reportContent, absPath);
|
|
710
|
+
const outcome = normalizeOutcome(options.outcome);
|
|
711
|
+
const reward = options.reward != null ? Number(options.reward) : defaultRewardForOutcome(outcome);
|
|
712
|
+
if (!Number.isFinite(reward)) {
|
|
713
|
+
throw new Error(`Invalid reward: ${options.reward}`);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const recordedAt = new Date().toISOString();
|
|
717
|
+
const summary = options.summary || title;
|
|
718
|
+
const metric = options.metric || null;
|
|
719
|
+
const loop = options.loop || 'manual';
|
|
720
|
+
const actor = options.actor || 'operator';
|
|
721
|
+
const stateDir = path.join(cwd, '.atris', 'state');
|
|
722
|
+
|
|
723
|
+
const shared = {
|
|
724
|
+
recorded_at: recordedAt,
|
|
725
|
+
business_slug: bizMeta.slug || null,
|
|
726
|
+
business_name: bizMeta.name || null,
|
|
727
|
+
business_id: bizMeta.business_id || null,
|
|
728
|
+
workspace_id: bizMeta.workspace_id || null,
|
|
729
|
+
workspace_template: bizMeta.workspace_template || 'business',
|
|
730
|
+
report_path: relPath,
|
|
731
|
+
report_title: title,
|
|
732
|
+
summary,
|
|
733
|
+
metric,
|
|
734
|
+
outcome,
|
|
735
|
+
reward,
|
|
736
|
+
loop,
|
|
737
|
+
actor,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
appendJsonl(path.join(stateDir, 'events.jsonl'), {
|
|
741
|
+
...shared,
|
|
742
|
+
type: 'report_recorded',
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
appendJsonl(path.join(stateDir, 'episodes.jsonl'), {
|
|
746
|
+
...shared,
|
|
747
|
+
type: 'episode',
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
appendJsonl(path.join(stateDir, 'scorecards.jsonl'), {
|
|
751
|
+
...shared,
|
|
752
|
+
type: 'scorecard',
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
console.log('');
|
|
756
|
+
console.log(`Recorded recap for ${bizMeta.name || bizMeta.slug || 'workspace'}.`);
|
|
757
|
+
console.log(` Report: ${relPath}`);
|
|
758
|
+
console.log(` Outcome: ${outcome}`);
|
|
759
|
+
console.log(` Reward: ${reward}`);
|
|
760
|
+
if (metric) console.log(` Metric: ${metric}`);
|
|
761
|
+
console.log(' State: .atris/state/events.jsonl, episodes.jsonl, scorecards.jsonl');
|
|
762
|
+
console.log('');
|
|
763
|
+
}
|
|
764
|
+
|
|
107
765
|
function detectBusinessSlug(explicitSlug) {
|
|
108
766
|
if (explicitSlug) return explicitSlug;
|
|
109
767
|
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
@@ -1154,12 +1812,18 @@ async function quickstart() {
|
|
|
1154
1812
|
2. Open the local workspace:
|
|
1155
1813
|
cd ~/arena/atris-business/my-company
|
|
1156
1814
|
|
|
1157
|
-
3.
|
|
1815
|
+
3. Seed onboarding context:
|
|
1816
|
+
atris business onboard --website https://example.com --contact "Founder Name" --note "what they do"
|
|
1817
|
+
|
|
1818
|
+
4. Push local state to cloud:
|
|
1158
1819
|
atris align --fix
|
|
1159
1820
|
|
|
1160
1821
|
Then open atris/TODO.md and work the starter queue:
|
|
1161
1822
|
define the first loop -> add named humans -> write the first recap
|
|
1162
1823
|
|
|
1824
|
+
After the first recap lands:
|
|
1825
|
+
atris business record atris/reports/YYYY-MM-DD-your-recap.md --outcome mixed --metric "operator speed"
|
|
1826
|
+
|
|
1163
1827
|
Optional:
|
|
1164
1828
|
atris business connect slack --business my-company
|
|
1165
1829
|
atris business connect github --business my-company
|
|
@@ -1230,6 +1894,13 @@ async function businessCommand(subcommand, ...args) {
|
|
|
1230
1894
|
case 'push':
|
|
1231
1895
|
await deployBusiness(args[0]);
|
|
1232
1896
|
break;
|
|
1897
|
+
case 'record':
|
|
1898
|
+
case 'record-recap':
|
|
1899
|
+
await recordBusinessRun(args[0], ...args.slice(1));
|
|
1900
|
+
break;
|
|
1901
|
+
case 'onboard':
|
|
1902
|
+
await onboardBusiness(...args);
|
|
1903
|
+
break;
|
|
1233
1904
|
case 'quickstart':
|
|
1234
1905
|
case 'start':
|
|
1235
1906
|
case 'guide':
|
|
@@ -1252,6 +1923,8 @@ async function businessCommand(subcommand, ...args) {
|
|
|
1252
1923
|
console.log(' connect <service> Connect a skill/integration');
|
|
1253
1924
|
console.log(' notify <mode> Set notification mode (digest/silent/push)');
|
|
1254
1925
|
console.log(' deploy <slug> Push local business to cloud');
|
|
1926
|
+
console.log(' onboard Seed brief, person, first loop, safe next action, and one-pager from sparse input');
|
|
1927
|
+
console.log(' record <report> Append recap state into events, episodes, and scorecards');
|
|
1255
1928
|
console.log(' remove <slug> Unregister locally');
|
|
1256
1929
|
}
|
|
1257
1930
|
}
|
|
@@ -1266,4 +1939,6 @@ module.exports = {
|
|
|
1266
1939
|
getBusinessConfigPath,
|
|
1267
1940
|
createCanonicalBusinessWorkspace,
|
|
1268
1941
|
initBusinessWorkspace,
|
|
1942
|
+
onboardBusiness,
|
|
1943
|
+
recordBusinessRun,
|
|
1269
1944
|
};
|