atris 3.34.0 → 3.36.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/AGENTS.md +35 -0
- package/FOR_AGENTS.md +5 -3
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +35 -8
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/render-cli/SKILL.md +88 -0
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +6 -2
- package/ax +617 -31
- package/bin/atris.js +719 -266
- package/commands/activate.js +194 -88
- package/commands/aeo.js +52 -0
- package/commands/agents.js +166 -0
- package/commands/autoland.js +718 -72
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/business.js +91 -8
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/codex-goal.js +26 -2
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/drive.js +187 -0
- package/commands/engine.js +1061 -29
- package/commands/experiments.js +28 -0
- package/commands/feed.js +202 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/github.js +38 -0
- package/commands/gm.js +285 -3
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +83 -43
- package/commands/integrations.js +39 -11
- package/commands/interview.js +209 -0
- package/commands/land.js +253 -52
- package/commands/lesson.js +112 -1
- package/commands/lifecycle.js +39 -3
- package/commands/linear.js +38 -0
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +899 -66
- package/commands/mission.js +3512 -332
- package/commands/next.js +137 -0
- package/commands/now.js +240 -21
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/radar.js +259 -14
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/serve.js +54 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/status.js +50 -5
- package/commands/stream.js +861 -0
- package/commands/stripe.js +38 -0
- package/commands/study.js +693 -0
- package/commands/supabase.js +39 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +2275 -182
- package/commands/team.js +73 -0
- package/commands/truth.js +29 -3
- package/commands/unknowns.js +627 -0
- package/commands/update.js +44 -0
- package/commands/vercel.js +38 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +299 -20
- package/commands/write.js +399 -0
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +391 -43
- package/lib/autoland.js +353 -52
- package/lib/ax-auto-lane.js +79 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2431 -38
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/memory-view.js +14 -5
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/mission-runtime-loop.js +7 -0
- package/lib/next-moves.js +327 -10
- package/lib/official-cli-integration.js +174 -0
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/outbound-send-gate.js +165 -0
- package/lib/permission-grants.js +293 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/review-integrity.js +147 -0
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +33 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +319 -27
- package/lib/task-proof.js +43 -1
- package/lib/task-receipt.js +93 -0
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
- package/utils/update-check.js +27 -6
- package/atris/learnings.jsonl +0 -1
package/commands/verify.js
CHANGED
|
@@ -531,9 +531,105 @@ function verifyRubric(slug, section, opts = {}) {
|
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
+
// Words too common to count as objective coverage signal.
|
|
535
|
+
const OBJECTIVE_STOPWORDS = new Set([
|
|
536
|
+
'the', 'and', 'for', 'with', 'that', 'this', 'from', 'into', 'each', 'their',
|
|
537
|
+
'have', 'has', 'are', 'was', 'were', 'will', 'would', 'should', 'could',
|
|
538
|
+
'about', 'them', 'then', 'than', 'when', 'what', 'where', 'which', 'while',
|
|
539
|
+
'your', 'yours', 'ours', 'over', 'under', 'after', 'before', 'between',
|
|
540
|
+
'make', 'made', 'making', 'work', 'working', 'draft', 'write', 'written',
|
|
541
|
+
'save', 'saved', 'file', 'files', 'become', 'becomes', 'gets', 'also',
|
|
542
|
+
]);
|
|
543
|
+
|
|
544
|
+
const PLACEHOLDER_LINE_RE = /\b(todo|tbd|fixme|xxx|lorem ipsum|placeholder|fill (?:this|me) in|coming soon)\b/i;
|
|
545
|
+
const HEADING_OR_DIVIDER_RE = /^(#{1,6}\s|[-=*_]{3,}\s*$|\s*[|+][-\s|+]*$)/;
|
|
546
|
+
|
|
547
|
+
function objectiveKeywords(objective) {
|
|
548
|
+
const words = String(objective || '').toLowerCase().match(/[a-z][a-z0-9-]{3,}/g) || [];
|
|
549
|
+
return [...new Set(words.filter((word) => !OBJECTIVE_STOPWORDS.has(word)))];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* atris verify artifact <path> - deterministic substance checks for a
|
|
554
|
+
* mission artifact. This is a pre-filter, not a quality judgment: it kills
|
|
555
|
+
* empty, skeleton, and placeholder-dominated artifacts that `test -s` lets
|
|
556
|
+
* through, and (optionally) checks the artifact mentions enough of the
|
|
557
|
+
* mission objective's vocabulary to plausibly be about it.
|
|
558
|
+
*/
|
|
559
|
+
function verifyArtifact(target, opts = {}) {
|
|
560
|
+
const cwd = opts.cwd || process.cwd();
|
|
561
|
+
const minLines = Number.isFinite(opts.minLines) ? opts.minLines : 10;
|
|
562
|
+
const checks = [];
|
|
563
|
+
const fail = (name, detail) => checks.push({ name, passed: false, detail });
|
|
564
|
+
const pass = (name, detail) => checks.push({ name, passed: true, detail });
|
|
565
|
+
|
|
566
|
+
const resolved = path.resolve(cwd, String(target || ''));
|
|
567
|
+
let content = null;
|
|
568
|
+
try {
|
|
569
|
+
content = fs.readFileSync(resolved, 'utf8');
|
|
570
|
+
pass('exists', path.relative(cwd, resolved));
|
|
571
|
+
} catch {
|
|
572
|
+
fail('exists', `cannot read ${target}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (content !== null) {
|
|
576
|
+
const lines = content.split('\n');
|
|
577
|
+
const substantive = lines.filter((line) => line.trim() && !HEADING_OR_DIVIDER_RE.test(line.trim()));
|
|
578
|
+
if (substantive.length >= minLines) {
|
|
579
|
+
pass('substance', `${substantive.length} substantive lines (min ${minLines})`);
|
|
580
|
+
} else {
|
|
581
|
+
fail('substance', `${substantive.length} substantive lines, need ${minLines}; headings and dividers do not count`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const placeholderLines = substantive.filter((line) => PLACEHOLDER_LINE_RE.test(line));
|
|
585
|
+
const placeholderShare = substantive.length ? placeholderLines.length / substantive.length : 1;
|
|
586
|
+
if (placeholderShare <= 0.3) {
|
|
587
|
+
pass('not_placeholder', `${placeholderLines.length}/${substantive.length} placeholder lines`);
|
|
588
|
+
} else {
|
|
589
|
+
fail('not_placeholder', `${placeholderLines.length}/${substantive.length} lines are TODO/TBD/placeholder`);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (opts.objective) {
|
|
593
|
+
const keywords = objectiveKeywords(opts.objective);
|
|
594
|
+
const haystack = content.toLowerCase();
|
|
595
|
+
const hit = keywords.filter((word) => haystack.includes(word));
|
|
596
|
+
const coverage = keywords.length ? hit.length / keywords.length : 1;
|
|
597
|
+
if (coverage >= 0.4) {
|
|
598
|
+
pass('objective_coverage', `${hit.length}/${keywords.length} objective keywords present`);
|
|
599
|
+
} else {
|
|
600
|
+
fail('objective_coverage', `only ${hit.length}/${keywords.length} objective keywords present (need 40%): missing ${keywords.filter((w) => !hit.includes(w)).slice(0, 8).join(', ')}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (Number.isFinite(opts.maxAgeHours)) {
|
|
605
|
+
const ageHours = (Date.now() - fs.statSync(resolved).mtimeMs) / 3600000;
|
|
606
|
+
if (ageHours <= opts.maxAgeHours) {
|
|
607
|
+
pass('freshness', `modified ${ageHours.toFixed(1)}h ago (max ${opts.maxAgeHours}h)`);
|
|
608
|
+
} else {
|
|
609
|
+
fail('freshness', `modified ${ageHours.toFixed(1)}h ago, older than ${opts.maxAgeHours}h window`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const passed = checks.length > 0 && checks.every((check) => check.passed);
|
|
615
|
+
const result = { ok: passed, artifact: target, checks };
|
|
616
|
+
if (opts.json) {
|
|
617
|
+
console.log(JSON.stringify(result, null, 2));
|
|
618
|
+
} else {
|
|
619
|
+
for (const check of checks) {
|
|
620
|
+
console.log(`${check.passed ? '✓' : '✗'} ${check.name}: ${check.detail}`);
|
|
621
|
+
}
|
|
622
|
+
console.log(passed
|
|
623
|
+
? `artifact passes the substance pre-filter (not a quality judgment)`
|
|
624
|
+
: `artifact FAILS the substance pre-filter`);
|
|
625
|
+
}
|
|
626
|
+
return passed ? 0 : 1;
|
|
627
|
+
}
|
|
628
|
+
|
|
534
629
|
module.exports = {
|
|
535
630
|
verifyAtris,
|
|
536
631
|
verifyRubric,
|
|
632
|
+
verifyArtifact,
|
|
537
633
|
findTaskInContent,
|
|
538
634
|
escapeRegExp
|
|
539
635
|
};
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { startMission, listMissions } = require('./mission');
|
|
7
|
+
|
|
8
|
+
const CLI_PATH = path.join(__dirname, '..', 'bin', 'atris.js');
|
|
9
|
+
const VALID_EVERY = new Set(['15m', '1h', '4h']);
|
|
10
|
+
const VALID_NOTIFY = new Set(['journal', 'imessage']);
|
|
11
|
+
const TERMINAL_STATUSES = new Set(['stopped', 'complete']);
|
|
12
|
+
const DEFAULT_EVERY = '1h';
|
|
13
|
+
const DEFAULT_NOTIFY = 'journal';
|
|
14
|
+
const WATCH_OWNER = 'watcher';
|
|
15
|
+
const WATCH_LANE = 'watch';
|
|
16
|
+
|
|
17
|
+
function slugify(value) {
|
|
18
|
+
return String(value || 'watch')
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
21
|
+
.replace(/^-+|-+$/g, '')
|
|
22
|
+
.slice(0, 48) || 'watch';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function hasFlag(args, name) {
|
|
26
|
+
return args.includes(name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function unquote(value) {
|
|
30
|
+
const text = String(value);
|
|
31
|
+
if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
|
|
32
|
+
return text.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readFlag(args, name, fallback = '') {
|
|
38
|
+
const prefix = `${name}=`;
|
|
39
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
40
|
+
const arg = String(args[i]);
|
|
41
|
+
if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return unquote(args[i + 1]);
|
|
42
|
+
if (arg.startsWith(prefix)) return unquote(arg.slice(prefix.length));
|
|
43
|
+
}
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseSentenceArgs(args) {
|
|
48
|
+
const sentenceParts = [];
|
|
49
|
+
let index = 0;
|
|
50
|
+
while (index < args.length) {
|
|
51
|
+
const arg = String(args[index]);
|
|
52
|
+
if (arg.startsWith('--')) break;
|
|
53
|
+
sentenceParts.push(arg);
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
sentence: sentenceParts.join(' ').trim(),
|
|
58
|
+
rest: args.slice(index),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function watchStatePath(slug) {
|
|
63
|
+
return `.atris/state/watch/${slug}.json`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildWatchObjective(sentence, slug) {
|
|
67
|
+
const statePath = watchStatePath(slug);
|
|
68
|
+
return `Each tick: check ${sentence}, compare to last tick state under ${statePath}, report only meaningful changes`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function notifyLabel(notify) {
|
|
72
|
+
if (notify === 'imessage') return 'imessage when the pulse picks it up';
|
|
73
|
+
return "today's journal under ## Notes";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function activeWatchMissions(root = process.cwd()) {
|
|
77
|
+
return listMissions(root).filter((mission) => (
|
|
78
|
+
mission.lane === WATCH_LANE && !TERMINAL_STATUSES.has(mission.status)
|
|
79
|
+
));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function watchName(mission) {
|
|
83
|
+
return mission.metadata?.watch_slug || mission.slug || mission.id;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveWatchMission(ref, root = process.cwd()) {
|
|
87
|
+
const slug = slugify(ref);
|
|
88
|
+
return activeWatchMissions(root).find((mission) => (
|
|
89
|
+
watchName(mission) === slug || mission.slug === slug || mission.id === ref
|
|
90
|
+
)) || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatLastTick(mission) {
|
|
94
|
+
if (!mission.last_tick_at) return 'never';
|
|
95
|
+
const ageMs = Date.now() - Date.parse(mission.last_tick_at);
|
|
96
|
+
if (!Number.isFinite(ageMs) || ageMs < 0) return mission.last_tick_at;
|
|
97
|
+
const minutes = Math.floor(ageMs / 60000);
|
|
98
|
+
if (minutes < 1) return 'just now';
|
|
99
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
100
|
+
const hours = Math.floor(minutes / 60);
|
|
101
|
+
if (hours < 48) return `${hours}h ago`;
|
|
102
|
+
const days = Math.floor(hours / 24);
|
|
103
|
+
return `${days}d ago`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function formatLastReport(mission) {
|
|
107
|
+
const line = String(mission.last_tick_reason || mission.metadata?.last_report || '').trim();
|
|
108
|
+
return line || 'none yet';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function padCell(value, width) {
|
|
112
|
+
const text = String(value);
|
|
113
|
+
if (text.length <= width) return text.padEnd(width);
|
|
114
|
+
if (width <= 1) return text.slice(0, width);
|
|
115
|
+
return `${text.slice(0, width - 1)}…`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function printWatchTable(missions) {
|
|
119
|
+
const rows = missions.map((mission) => ({
|
|
120
|
+
name: watchName(mission),
|
|
121
|
+
cadence: mission.cadence || DEFAULT_EVERY,
|
|
122
|
+
lastTick: formatLastTick(mission),
|
|
123
|
+
lastReport: formatLastReport(mission),
|
|
124
|
+
}));
|
|
125
|
+
const widths = {
|
|
126
|
+
name: Math.max(4, ...rows.map((row) => row.name.length)),
|
|
127
|
+
cadence: Math.max(7, ...rows.map((row) => row.cadence.length)),
|
|
128
|
+
lastTick: Math.max(9, ...rows.map((row) => row.lastTick.length)),
|
|
129
|
+
lastReport: Math.max(11, ...rows.map((row) => row.lastReport.length)),
|
|
130
|
+
};
|
|
131
|
+
console.log([
|
|
132
|
+
padCell('name', widths.name),
|
|
133
|
+
padCell('cadence', widths.cadence),
|
|
134
|
+
padCell('last tick', widths.lastTick),
|
|
135
|
+
padCell('last report', widths.lastReport),
|
|
136
|
+
].join(' '));
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
console.log([
|
|
139
|
+
padCell(row.name, widths.name),
|
|
140
|
+
padCell(row.cadence, widths.cadence),
|
|
141
|
+
padCell(row.lastTick, widths.lastTick),
|
|
142
|
+
padCell(row.lastReport, widths.lastReport),
|
|
143
|
+
].join(' '));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function showWatchHelp() {
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log('usage: atris watch "<sentence>" [--every 15m|1h|4h] [--name <slug>] [--notify imessage|journal]');
|
|
150
|
+
console.log(' atris watch list');
|
|
151
|
+
console.log(' atris watch stop <slug>');
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log('turn one sentence into an always-on claude watch mission.');
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('options:');
|
|
156
|
+
console.log(' --every cadence how often to check (default 1h)');
|
|
157
|
+
console.log(' --name slug stable watch name (default derived from the sentence)');
|
|
158
|
+
console.log(' --notify channel journal (default) or imessage preference for the pulse');
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log('looking for the old file-sync watcher? use: atris sync --watch');
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createWatch(args, cwd = process.cwd()) {
|
|
165
|
+
const { sentence, rest } = parseSentenceArgs(args);
|
|
166
|
+
if (!sentence) {
|
|
167
|
+
console.error('usage: atris watch "<sentence>" [--every 15m|1h|4h] [--name <slug>] [--notify imessage|journal]');
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const slug = slugify(readFlag(rest, '--name', '') || sentence);
|
|
172
|
+
const every = readFlag(rest, '--every', DEFAULT_EVERY) || DEFAULT_EVERY;
|
|
173
|
+
const notify = readFlag(rest, '--notify', DEFAULT_NOTIFY) || DEFAULT_NOTIFY;
|
|
174
|
+
|
|
175
|
+
if (!VALID_EVERY.has(every)) {
|
|
176
|
+
console.error(`--every must be one of: ${Array.from(VALID_EVERY).join(', ')}`);
|
|
177
|
+
return 2;
|
|
178
|
+
}
|
|
179
|
+
if (!VALID_NOTIFY.has(notify)) {
|
|
180
|
+
console.error(`--notify must be one of: ${Array.from(VALID_NOTIFY).join(', ')}`);
|
|
181
|
+
return 2;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const stateRel = watchStatePath(slug);
|
|
185
|
+
fs.mkdirSync(path.join(cwd, path.dirname(stateRel)), { recursive: true });
|
|
186
|
+
|
|
187
|
+
const objective = buildWatchObjective(sentence, slug);
|
|
188
|
+
const startArgs = [
|
|
189
|
+
objective,
|
|
190
|
+
'--owner', WATCH_OWNER,
|
|
191
|
+
'--lane', WATCH_LANE,
|
|
192
|
+
'--runner', 'claude',
|
|
193
|
+
'--cadence', every,
|
|
194
|
+
'--always-on',
|
|
195
|
+
'--no-verify',
|
|
196
|
+
'--stop', 'owner stops the watch',
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const previousCwd = process.cwd();
|
|
200
|
+
process.chdir(cwd);
|
|
201
|
+
try {
|
|
202
|
+
startMission(startArgs, {
|
|
203
|
+
silent: true,
|
|
204
|
+
missionPatch: {
|
|
205
|
+
slug,
|
|
206
|
+
metadata: {
|
|
207
|
+
watch: true,
|
|
208
|
+
watch_slug: slug,
|
|
209
|
+
watch_sentence: sentence,
|
|
210
|
+
watch_state_path: stateRel,
|
|
211
|
+
notify,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
} finally {
|
|
216
|
+
process.chdir(previousCwd);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log(`watching ${sentence} every ${every}.`);
|
|
220
|
+
console.log(`reports land in ${notifyLabel(notify)}.`);
|
|
221
|
+
console.log(`state file: ${stateRel}`);
|
|
222
|
+
console.log(`stop with: atris watch stop ${slug}`);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function listWatch(cwd = process.cwd()) {
|
|
227
|
+
const missions = activeWatchMissions(cwd);
|
|
228
|
+
if (!missions.length) {
|
|
229
|
+
console.log('no active watches.');
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
printWatchTable(missions);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function stopWatch(ref, cwd = process.cwd()) {
|
|
237
|
+
const slug = slugify(ref);
|
|
238
|
+
if (!slug) {
|
|
239
|
+
console.error('usage: atris watch stop <slug>');
|
|
240
|
+
return 2;
|
|
241
|
+
}
|
|
242
|
+
const mission = resolveWatchMission(slug, cwd);
|
|
243
|
+
if (!mission) {
|
|
244
|
+
console.error(`no active watch named "${slug}".`);
|
|
245
|
+
return 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = spawnSync(process.execPath, [
|
|
249
|
+
CLI_PATH,
|
|
250
|
+
'mission',
|
|
251
|
+
'stop',
|
|
252
|
+
mission.id,
|
|
253
|
+
'--reason',
|
|
254
|
+
'owner stops the watch',
|
|
255
|
+
'--json',
|
|
256
|
+
], {
|
|
257
|
+
cwd,
|
|
258
|
+
encoding: 'utf8',
|
|
259
|
+
env: {
|
|
260
|
+
...process.env,
|
|
261
|
+
ATRIS_SKIP_UPDATE_CHECK: '1',
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (result.error) throw result.error;
|
|
266
|
+
if (result.status !== 0) {
|
|
267
|
+
const message = String(result.stderr || result.stdout || '').trim();
|
|
268
|
+
console.error(message || `failed to stop watch "${slug}".`);
|
|
269
|
+
return result.status || 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(`stopped watch ${slug}.`);
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function watchCommand(args = [], cwd = process.cwd()) {
|
|
277
|
+
if (hasFlag(args, '--help') || hasFlag(args, '-h') || args[0] === 'help') {
|
|
278
|
+
showWatchHelp();
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const subcommand = String(args[0] || '').trim().toLowerCase();
|
|
283
|
+
if (subcommand === 'list') return listWatch(cwd);
|
|
284
|
+
if (subcommand === 'stop') return stopWatch(args[1], cwd);
|
|
285
|
+
|
|
286
|
+
return createWatch(args, cwd);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function watchAtris() {
|
|
290
|
+
const code = watchCommand(process.argv.slice(3), process.cwd());
|
|
291
|
+
process.exit(code);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = {
|
|
295
|
+
watchAtris,
|
|
296
|
+
watchCommand,
|
|
297
|
+
showWatchHelp,
|
|
298
|
+
slugify,
|
|
299
|
+
buildWatchObjective,
|
|
300
|
+
activeWatchMissions,
|
|
301
|
+
resolveWatchMission,
|
|
302
|
+
watchStatePath,
|
|
303
|
+
};
|