atris 3.57.3 → 3.57.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/atris/skills/engines/SKILL.md +4 -3
- package/atris/skills/x-search/SKILL.md +11 -5
- package/atris/skills/youtube/SKILL.md +6 -4
- package/ax +4 -1
- package/bin/atris.js +1 -1
- package/commands/close.js +294 -12
- package/commands/mission.js +7 -16
- package/commands/worktree.js +56 -0
- package/commands/x-search.js +231 -14
- package/commands/youtube.js +331 -33
- package/lib/apply-gate.js +13 -0
- package/lib/engine-ask.js +21 -2
- package/package.json +1 -1
package/commands/worktree.js
CHANGED
|
@@ -6,11 +6,14 @@ const { spawnSync } = require('child_process');
|
|
|
6
6
|
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
7
7
|
const { stampLatestOpenBriefForWorktree } = require('../lib/brief-ledger');
|
|
8
8
|
const { isConductorArtifact } = require('../lib/conductor-artifacts');
|
|
9
|
+
const close = require('./close');
|
|
9
10
|
|
|
10
11
|
const REGEN_ADAPTER_FILES = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
|
|
11
12
|
const COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
12
13
|
const GIT_OID_PATTERN = /^[0-9a-f]{40,64}$/;
|
|
13
14
|
const ONE_LAP_PROOF_REF_PATTERN = /^refs\/atris\/one-lap\/([0-9a-f]{40,64})$/;
|
|
15
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
const SHIP_HEALTH_SOURCE = 'ship-health:experiments-daily';
|
|
14
17
|
|
|
15
18
|
function runGit(args, { cwd = process.cwd(), check = true, timeout } = {}) {
|
|
16
19
|
const result = spawnSync('git', args, { cwd, encoding: 'utf8', timeout });
|
|
@@ -672,6 +675,56 @@ function findPrimaryRoot(root) {
|
|
|
672
675
|
return worktrees[0]?.path || root;
|
|
673
676
|
}
|
|
674
677
|
|
|
678
|
+
function shellQuote(value) {
|
|
679
|
+
const text = String(value || '');
|
|
680
|
+
if (/^[A-Za-z0-9_./:@+-]+$/.test(text)) return text;
|
|
681
|
+
return `'${text.replace(/'/g, `'"'"'`)}'`;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function experimentHealthProbe(statePath) {
|
|
685
|
+
const script = [
|
|
686
|
+
"const fs=require('fs')",
|
|
687
|
+
'const state=JSON.parse(fs.readFileSync(process.argv[1],\'utf8\'))',
|
|
688
|
+
'const last=Date.parse(state.last_run_date)',
|
|
689
|
+
`process.exit(Number.isFinite(last)&&Date.now()-last<=${DAY_MS}?0:1)`,
|
|
690
|
+
].join(';');
|
|
691
|
+
return `${shellQuote(process.execPath)} -e ${shellQuote(script)} ${shellQuote(statePath)}`;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function runShipHealthCheck(workspaceRoot, options = {}) {
|
|
695
|
+
try {
|
|
696
|
+
const now = options.now ? new Date(options.now) : new Date();
|
|
697
|
+
const statePath = path.join(workspaceRoot, '.atris', 'state', 'experiments-daily.json');
|
|
698
|
+
const state = fs.existsSync(statePath)
|
|
699
|
+
? JSON.parse(fs.readFileSync(statePath, 'utf8'))
|
|
700
|
+
: {};
|
|
701
|
+
const rawLastRunDate = state && state.last_run_date;
|
|
702
|
+
const lastRunMs = rawLastRunDate ? Date.parse(rawLastRunDate) : null;
|
|
703
|
+
if (rawLastRunDate && !Number.isFinite(lastRunMs)) {
|
|
704
|
+
throw new Error('experiment state date is unreadable');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const ageMs = Number.isFinite(lastRunMs) ? now.getTime() - lastRunMs : null;
|
|
708
|
+
if (ageMs !== null && ageMs <= DAY_MS) return { healthy: true, filed: false };
|
|
709
|
+
|
|
710
|
+
const days = ageMs === null ? 1 : Math.max(1, Math.floor(ageMs / DAY_MS));
|
|
711
|
+
console.log(`health check failed: no verified experiment in ${days} days. shipping still works, the metabolism does not.`);
|
|
712
|
+
const filed = close.upsertSourceFlag({
|
|
713
|
+
what: 'no verified experiment has run in over a day',
|
|
714
|
+
owner: 'operator',
|
|
715
|
+
lane: 'code',
|
|
716
|
+
ttlDays: 1,
|
|
717
|
+
source: SHIP_HEALTH_SOURCE,
|
|
718
|
+
closeCondition: 'a verified experiment ran within 24 hours',
|
|
719
|
+
probe: experimentHealthProbe(statePath),
|
|
720
|
+
}, workspaceRoot, { now });
|
|
721
|
+
return { healthy: false, ...filed };
|
|
722
|
+
} catch (error) {
|
|
723
|
+
console.log(`health check skipped: ${String(error && error.message || error).toLowerCase()}. shipping still works.`);
|
|
724
|
+
return { healthy: null, filed: false, error };
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
675
728
|
function shipWorktree(args) {
|
|
676
729
|
if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
|
|
677
730
|
shipHelp();
|
|
@@ -825,6 +878,7 @@ function shipWorktree(args) {
|
|
|
825
878
|
}
|
|
826
879
|
}
|
|
827
880
|
console.log('pr: skipped (local mode)');
|
|
881
|
+
if (merge && !dryRun) runShipHealthCheck(primary || root);
|
|
828
882
|
console.log('done: worktree shipped');
|
|
829
883
|
return 0;
|
|
830
884
|
}
|
|
@@ -870,6 +924,7 @@ function shipWorktree(args) {
|
|
|
870
924
|
note: `worktree ship completed into ${targetRef}`,
|
|
871
925
|
}, { worktree: root });
|
|
872
926
|
} catch {}
|
|
927
|
+
if (merge && !dryRun) runShipHealthCheck(primary || root);
|
|
873
928
|
console.log('done: worktree shipped');
|
|
874
929
|
return 0;
|
|
875
930
|
}
|
|
@@ -1219,5 +1274,6 @@ module.exports = {
|
|
|
1219
1274
|
taskTokens,
|
|
1220
1275
|
statusCounts,
|
|
1221
1276
|
swarloClaim,
|
|
1277
|
+
runShipHealthCheck,
|
|
1222
1278
|
worktreeCommand,
|
|
1223
1279
|
};
|
package/commands/x-search.js
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
3
5
|
const { apiRequestJson } = require('../utils/api');
|
|
4
6
|
const { ensureBilledCommandAuth } = require('./auth');
|
|
5
7
|
const applyGate = require('../lib/apply-gate');
|
|
8
|
+
const {
|
|
9
|
+
fileTeachExperiment,
|
|
10
|
+
extractTeachNumbers,
|
|
11
|
+
extractTeachMechanisms,
|
|
12
|
+
isThinTeachLesson,
|
|
13
|
+
TEACH_THIN_REFUSE,
|
|
14
|
+
} = require('./youtube');
|
|
6
15
|
|
|
7
16
|
const DEFAULT_TIMEOUT_MS = 120000;
|
|
8
17
|
const COST_HINT = '5 credits per search';
|
|
9
18
|
const APPLY_NEXT_MESSAGE =
|
|
10
19
|
'next: write one apply (change + receipt) for this query.';
|
|
20
|
+
const KEEP_RULE = 'keep only if measure.py moves 0→1. scores 1 only when the fixture contains the check tokens.';
|
|
11
21
|
|
|
12
22
|
function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
13
23
|
output('');
|
|
14
|
-
output(`Usage: ${commandName} "<query>" [--limit N] [--days N] [--json]`);
|
|
15
|
-
output(` ${commandName} person --name <name> [--handle <h>] [--company <c>] [--context <text>] [--json]`);
|
|
24
|
+
output(`Usage: ${commandName} "<query>" [--limit N] [--days N] [--save] [--json]`);
|
|
25
|
+
output(` ${commandName} person --name <name> [--handle <h>] [--company <c>] [--context <text>] [--save] [--json]`);
|
|
26
|
+
output(` ${commandName} unsave <query-or-source>`);
|
|
16
27
|
output('');
|
|
17
28
|
output(`Search X/Twitter via Atris (${COST_HINT}).`);
|
|
18
29
|
output('Requires login. Same auth path as atris youtube process.');
|
|
30
|
+
output('Prints to stdout. Rich ephemeral prints one apply next-step (no files).');
|
|
31
|
+
output('--save files a brief only when the result is rich.');
|
|
32
|
+
output('unsave deletes the filed brief, apply stub, and matching experiment pack (no paid calls).');
|
|
19
33
|
output('Empty or failed search refunds the credits.');
|
|
20
34
|
output('');
|
|
21
35
|
output('Options:');
|
|
22
36
|
output(' --limit <n> Max results hint (search only)');
|
|
23
37
|
output(' --days <n> Only tweets from the last N days (search only)');
|
|
38
|
+
output(' --save File brief, journal, apply; rich results mint a keep/revert experiment');
|
|
39
|
+
output(' --unsave Delete filed brief, apply stub, and matching experiment pack (no paid calls)');
|
|
24
40
|
output(' --json Print the raw JSON response');
|
|
25
41
|
output(' -h, --help This help');
|
|
26
42
|
output('');
|
|
@@ -33,7 +49,10 @@ function showXSearchHelp(output = console.log, commandName = 'atris x-search') {
|
|
|
33
49
|
output('Examples:');
|
|
34
50
|
output(` ${commandName} "MCP agents"`);
|
|
35
51
|
output(` ${commandName} "MCP agents" --limit 5 --days 2`);
|
|
52
|
+
output(` ${commandName} "MCP agents" --save`);
|
|
36
53
|
output(` ${commandName} person --name "Leah Bonvissuto" --handle leahbon`);
|
|
54
|
+
output(` ${commandName} unsave "MCP agents"`);
|
|
55
|
+
output(` ${commandName} --unsave "MCP agents"`);
|
|
37
56
|
output('');
|
|
38
57
|
}
|
|
39
58
|
|
|
@@ -58,6 +77,8 @@ function parseSearchArgs(argv = []) {
|
|
|
58
77
|
mode: 'search',
|
|
59
78
|
help: false,
|
|
60
79
|
json: false,
|
|
80
|
+
save: false,
|
|
81
|
+
unsave: false,
|
|
61
82
|
query: null,
|
|
62
83
|
limit: null,
|
|
63
84
|
daysBack: null,
|
|
@@ -75,6 +96,10 @@ function parseSearchArgs(argv = []) {
|
|
|
75
96
|
options.help = true;
|
|
76
97
|
} else if (arg === '--json') {
|
|
77
98
|
options.json = true;
|
|
99
|
+
} else if (arg === '--save') {
|
|
100
|
+
options.save = true;
|
|
101
|
+
} else if (arg === '--unsave') {
|
|
102
|
+
options.unsave = true;
|
|
78
103
|
} else if (arg === '--limit') {
|
|
79
104
|
options.limit = parsePositiveInt(readValue(args, i, arg), '--limit');
|
|
80
105
|
i++;
|
|
@@ -111,7 +136,11 @@ function parseSearchArgs(argv = []) {
|
|
|
111
136
|
}
|
|
112
137
|
|
|
113
138
|
if (options.help) return options;
|
|
114
|
-
if (!options.query)
|
|
139
|
+
if (!options.query) {
|
|
140
|
+
throw new Error(options.unsave
|
|
141
|
+
? 'usage: atris x-search unsave <query-or-source>'
|
|
142
|
+
: 'Missing query. Run "atris x-search --help".');
|
|
143
|
+
}
|
|
115
144
|
return options;
|
|
116
145
|
}
|
|
117
146
|
|
|
@@ -121,6 +150,7 @@ function parsePersonArgs(argv = []) {
|
|
|
121
150
|
mode: 'person',
|
|
122
151
|
help: false,
|
|
123
152
|
json: false,
|
|
153
|
+
save: false,
|
|
124
154
|
name: null,
|
|
125
155
|
handle: null,
|
|
126
156
|
company: null,
|
|
@@ -139,6 +169,8 @@ function parsePersonArgs(argv = []) {
|
|
|
139
169
|
options.help = true;
|
|
140
170
|
} else if (arg === '--json') {
|
|
141
171
|
options.json = true;
|
|
172
|
+
} else if (arg === '--save') {
|
|
173
|
+
options.save = true;
|
|
142
174
|
} else if (arg === '--name') {
|
|
143
175
|
options.name = readValue(args, i, arg);
|
|
144
176
|
i++;
|
|
@@ -184,11 +216,50 @@ function parsePersonArgs(argv = []) {
|
|
|
184
216
|
return options;
|
|
185
217
|
}
|
|
186
218
|
|
|
219
|
+
function parseUnsaveArgs(argv = []) {
|
|
220
|
+
const args = [...argv];
|
|
221
|
+
const options = {
|
|
222
|
+
mode: 'unsave',
|
|
223
|
+
help: false,
|
|
224
|
+
source: null,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (args.length === 0 || ['help', '--help', '-h'].includes(args[0])) {
|
|
228
|
+
if (args.length === 0) {
|
|
229
|
+
throw new Error('usage: atris x-search unsave <query-or-source>');
|
|
230
|
+
}
|
|
231
|
+
options.help = true;
|
|
232
|
+
return options;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < args.length; i++) {
|
|
236
|
+
const arg = args[i];
|
|
237
|
+
if (arg === '--help' || arg === '-h' || arg === 'help') {
|
|
238
|
+
options.help = true;
|
|
239
|
+
} else if (arg === '--unsave') {
|
|
240
|
+
continue;
|
|
241
|
+
} else if (arg.startsWith('-')) {
|
|
242
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
243
|
+
} else if (!options.source) {
|
|
244
|
+
options.source = arg;
|
|
245
|
+
} else {
|
|
246
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (options.help) return options;
|
|
251
|
+
if (!options.source) throw new Error('usage: atris x-search unsave <query-or-source>');
|
|
252
|
+
return options;
|
|
253
|
+
}
|
|
254
|
+
|
|
187
255
|
function parseXSearchArgs(argv = []) {
|
|
188
256
|
const args = [...argv];
|
|
189
257
|
if (args[0] === 'person') {
|
|
190
258
|
return parsePersonArgs(args.slice(1));
|
|
191
259
|
}
|
|
260
|
+
if (args[0] === 'unsave') {
|
|
261
|
+
return parseUnsaveArgs(args.slice(1));
|
|
262
|
+
}
|
|
192
263
|
return parseSearchArgs(args);
|
|
193
264
|
}
|
|
194
265
|
|
|
@@ -336,19 +407,138 @@ function xSearchApplyRel(source) {
|
|
|
336
407
|
return applyGate.applySidecarRel('x-search', applyGate.applySlug(source));
|
|
337
408
|
}
|
|
338
409
|
|
|
410
|
+
function xSearchExperimentSlug(source) {
|
|
411
|
+
return `x-search-${applyGate.applySlug(source)}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function xSearchExperimentRel(source) {
|
|
415
|
+
return `atris/experiments/${xSearchExperimentSlug(source)}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function xSearchBriefRel(source) {
|
|
419
|
+
return `atris/wiki/briefs/x-search-${applyGate.applySlug(source)}.md`;
|
|
420
|
+
}
|
|
421
|
+
|
|
339
422
|
function xSearchHasResults(data) {
|
|
340
423
|
return Boolean(xSearchContent(data)) || xSearchCitations(data).length > 0;
|
|
341
424
|
}
|
|
342
425
|
|
|
343
|
-
function
|
|
426
|
+
function xSearchLessonFromText(text) {
|
|
427
|
+
const body = String(text || '');
|
|
428
|
+
return {
|
|
429
|
+
numbers: extractTeachNumbers(body),
|
|
430
|
+
mechanisms: extractTeachMechanisms(body),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function dateStamp(now) {
|
|
435
|
+
if (typeof now === 'string' && /^\d{4}-\d{2}-\d{2}/.test(now)) {
|
|
436
|
+
return now.slice(0, 10);
|
|
437
|
+
}
|
|
438
|
+
const value = now instanceof Date ? now : new Date(now || Date.now());
|
|
439
|
+
if (Number.isNaN(value.getTime())) return new Date().toISOString().slice(0, 10);
|
|
440
|
+
return value.toISOString().slice(0, 10);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function fileXSearchBrief({ cwd, source, text, now } = {}) {
|
|
444
|
+
try {
|
|
445
|
+
if (!source || !cwd) return null;
|
|
446
|
+
const wikiDir = path.join(cwd, 'atris', 'wiki');
|
|
447
|
+
if (!fs.existsSync(wikiDir)) return null;
|
|
448
|
+
const rel = xSearchBriefRel(source);
|
|
449
|
+
fs.mkdirSync(path.join(cwd, 'atris', 'wiki', 'briefs'), { recursive: true });
|
|
450
|
+
const date = dateStamp(now);
|
|
451
|
+
const heading = String(source).trim().toLowerCase() || 'x search';
|
|
452
|
+
const header = [
|
|
453
|
+
heading,
|
|
454
|
+
'',
|
|
455
|
+
`date: ${date}`,
|
|
456
|
+
`source: ${source}`,
|
|
457
|
+
'rail: atris x-search',
|
|
458
|
+
].join('\n');
|
|
459
|
+
fs.writeFileSync(path.join(cwd, rel), `${header}\n\n${String(text || '').trim()}\n`);
|
|
460
|
+
|
|
461
|
+
const journalPath = path.join(cwd, 'atris', 'logs', date.slice(0, 4), `${date}.md`);
|
|
462
|
+
fs.mkdirSync(path.dirname(journalPath), { recursive: true });
|
|
463
|
+
let existing = '';
|
|
464
|
+
if (fs.existsSync(journalPath)) existing = fs.readFileSync(journalPath, 'utf8');
|
|
465
|
+
const line = `- [claimable] searched: ${heading} -> ${rel}`;
|
|
466
|
+
if (!existing.includes(line)) {
|
|
467
|
+
const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
468
|
+
fs.writeFileSync(journalPath, `${existing}${prefix}${line}\n`);
|
|
469
|
+
}
|
|
470
|
+
return rel;
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function removeUnsaveRel(cwd, rel, removed) {
|
|
477
|
+
const abs = path.join(cwd, rel);
|
|
478
|
+
try {
|
|
479
|
+
if (!fs.existsSync(abs)) return;
|
|
480
|
+
const st = fs.lstatSync(abs);
|
|
481
|
+
if (st.isDirectory()) fs.rmSync(abs, { recursive: true, force: true });
|
|
482
|
+
else fs.unlinkSync(abs);
|
|
483
|
+
removed.push(rel);
|
|
484
|
+
} catch {
|
|
485
|
+
// already gone or unreadable: do not error
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function unsaveXSearch(target, deps = {}) {
|
|
490
|
+
const output = deps.output || ((line = '') => console.log(line));
|
|
491
|
+
const cwd = deps.cwd || process.cwd();
|
|
492
|
+
const source = String(target || '').trim();
|
|
493
|
+
if (!source) {
|
|
494
|
+
output('usage: atris x-search unsave <query-or-source>');
|
|
495
|
+
return 2;
|
|
496
|
+
}
|
|
497
|
+
const briefRel = xSearchBriefRel(source);
|
|
498
|
+
const applyRel = xSearchApplyRel(source);
|
|
499
|
+
const packRel = xSearchExperimentRel(source);
|
|
500
|
+
const removed = [];
|
|
501
|
+
for (const rel of [briefRel, applyRel, packRel]) {
|
|
502
|
+
removeUnsaveRel(cwd, rel, removed);
|
|
503
|
+
}
|
|
504
|
+
if (!removed.length) {
|
|
505
|
+
output(`already gone: ${briefRel} and ${applyRel}`);
|
|
506
|
+
return 0;
|
|
507
|
+
}
|
|
508
|
+
output(`removed ${removed.join(' and ')}`);
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function saveRichXSearch({ cwd, source, text, now } = {}) {
|
|
513
|
+
const lesson = xSearchLessonFromText(text);
|
|
514
|
+
if (isThinTeachLesson(lesson)) {
|
|
515
|
+
return { thin: true, brief: null, packRel: null };
|
|
516
|
+
}
|
|
517
|
+
const brief = fileXSearchBrief({ cwd, source, text, now });
|
|
518
|
+
const packRel = fileTeachExperiment({
|
|
519
|
+
cwd,
|
|
520
|
+
lesson,
|
|
521
|
+
slug: source ? xSearchExperimentSlug(source) : null,
|
|
522
|
+
applyRel: source ? xSearchApplyRel(source) : null,
|
|
523
|
+
});
|
|
524
|
+
return { thin: false, brief, packRel };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function ensureXSearchApply({ cwd, source, packRel, now, output } = {}) {
|
|
528
|
+
const pack = packRel || (source ? xSearchExperimentRel(source) : null);
|
|
344
529
|
return applyGate.ensureApply({
|
|
345
530
|
cwd,
|
|
346
531
|
source,
|
|
347
532
|
rel: source ? xSearchApplyRel(source) : null,
|
|
348
533
|
now,
|
|
349
534
|
output,
|
|
350
|
-
incompleteMessage:
|
|
535
|
+
incompleteMessage: pack
|
|
536
|
+
? `next: apply ${pack}. keep only if measure.py moves 0→1`
|
|
537
|
+
: APPLY_NEXT_MESSAGE,
|
|
351
538
|
required: false,
|
|
539
|
+
change: pack ? `apply ${pack}` : undefined,
|
|
540
|
+
receipt: pack ? KEEP_RULE : undefined,
|
|
541
|
+
journalLine: pack ? `- [claimable] apply: ${pack}. ${KEEP_RULE}` : undefined,
|
|
352
542
|
});
|
|
353
543
|
}
|
|
354
544
|
|
|
@@ -407,6 +597,10 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
407
597
|
return 0;
|
|
408
598
|
}
|
|
409
599
|
|
|
600
|
+
if (options.mode === 'unsave' || options.unsave) {
|
|
601
|
+
return unsaveXSearch(options.source || options.query, deps);
|
|
602
|
+
}
|
|
603
|
+
|
|
410
604
|
let status = 0;
|
|
411
605
|
try {
|
|
412
606
|
const data = await runXSearch(options, deps);
|
|
@@ -416,16 +610,37 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
416
610
|
} else {
|
|
417
611
|
output(hasResults ? formatXSearchResult(data) : formatEmptyXSearchResult(data));
|
|
418
612
|
}
|
|
419
|
-
if (hasResults) {
|
|
420
|
-
|
|
421
|
-
|
|
613
|
+
if (!hasResults) {
|
|
614
|
+
status = 2;
|
|
615
|
+
} else if (options.save) {
|
|
616
|
+
const source = xSearchApplySource(options);
|
|
617
|
+
const saved = saveRichXSearch({
|
|
422
618
|
cwd: deps.cwd || process.cwd(),
|
|
423
|
-
source
|
|
424
|
-
|
|
425
|
-
|
|
619
|
+
source,
|
|
620
|
+
text: xSearchContent(data),
|
|
621
|
+
now: deps.applyNow || deps.now,
|
|
426
622
|
});
|
|
623
|
+
if (saved.thin) {
|
|
624
|
+
output(TEACH_THIN_REFUSE);
|
|
625
|
+
status = 2;
|
|
626
|
+
} else {
|
|
627
|
+
const ensureApply = deps.ensureApply || ensureXSearchApply;
|
|
628
|
+
status = ensureApply({
|
|
629
|
+
cwd: deps.cwd || process.cwd(),
|
|
630
|
+
source,
|
|
631
|
+
packRel: saved.packRel,
|
|
632
|
+
now: deps.applyNow || deps.now,
|
|
633
|
+
output,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
427
636
|
} else {
|
|
428
|
-
|
|
637
|
+
if (
|
|
638
|
+
!options.json
|
|
639
|
+
&& !isThinTeachLesson(xSearchLessonFromText(xSearchContent(data)))
|
|
640
|
+
) {
|
|
641
|
+
applyGate.hintEphemeralApply(output, 'x-search');
|
|
642
|
+
}
|
|
643
|
+
status = 0;
|
|
429
644
|
}
|
|
430
645
|
} catch (err) {
|
|
431
646
|
output(err.message);
|
|
@@ -439,12 +654,14 @@ async function xSearchCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
439
654
|
|
|
440
655
|
module.exports = {
|
|
441
656
|
DEFAULT_TIMEOUT_MS,
|
|
442
|
-
APPLY_NEXT_MESSAGE,
|
|
443
657
|
parseXSearchArgs,
|
|
444
658
|
buildSearchPayload,
|
|
445
659
|
buildPersonPayload,
|
|
446
|
-
formatXSearchResult,
|
|
447
660
|
xSearchHasResults,
|
|
448
661
|
xSearchApplyRel,
|
|
662
|
+
xSearchBriefRel,
|
|
663
|
+
xSearchExperimentSlug,
|
|
664
|
+
xSearchExperimentRel,
|
|
665
|
+
unsaveXSearch,
|
|
449
666
|
xSearchCommand,
|
|
450
667
|
};
|