atris 3.37.1 → 3.40.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 +25 -6
- package/README.md +2 -0
- package/atris/PERSONA.md +11 -11
- package/atris.md +17 -2
- package/ax +160 -34
- package/bin/atris.js +78 -6
- package/commands/activate.js +16 -3
- package/commands/agent-spawn.js +13 -18
- package/commands/autoland.js +76 -47
- package/commands/autopilot-front.js +0 -4
- package/commands/autopilot.js +0 -12
- package/commands/bench.js +10 -12
- package/commands/brain.js +0 -7
- package/commands/brief.js +0 -2
- package/commands/business-sync.js +0 -1
- package/commands/business.js +452 -12
- package/commands/chat-scan.js +5 -7
- package/commands/clean.js +31 -2
- package/commands/close.js +46 -11
- package/commands/cloud.js +0 -1
- package/commands/codex-goal.js +8 -12
- package/commands/computer.js +6 -5
- package/commands/console.js +64 -11
- package/commands/decide.js +166 -0
- package/commands/deck.js +30 -12
- package/commands/dream.js +0 -7
- package/commands/drill.js +14 -25
- package/commands/engine.js +206 -26
- package/commands/experiments.js +0 -4
- package/commands/fleet-report.js +12 -4
- package/commands/game.js +261 -0
- package/commands/gm.js +17 -7
- package/commands/goal.js +0 -6
- package/commands/harvest.js +1 -5
- package/commands/improve.js +29 -8
- package/commands/init.js +41 -37
- package/commands/integrations.js +0 -7
- package/commands/land.js +77 -20
- package/commands/launchpad.js +1 -16
- package/commands/lesson.js +17 -7
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/loop-front.js +0 -2
- package/commands/loops.js +6 -5
- package/commands/meet.js +0 -2
- package/commands/member.js +622 -573
- package/commands/mission.js +790 -314
- package/commands/next.js +0 -1
- package/commands/now.js +0 -12
- package/commands/one-lap.js +8 -8
- package/commands/orb.js +0 -4
- package/commands/pack-craft.js +0 -1
- package/commands/pack.js +3599 -424
- package/commands/play.js +6 -6
- package/commands/probe.js +12 -6
- package/commands/pulse.js +88 -64
- package/commands/push.js +0 -4
- package/commands/radar.js +14 -11
- package/commands/recover.js +340 -0
- package/commands/release.js +10 -9
- package/commands/report.js +0 -1
- package/commands/router.js +320 -0
- package/commands/run-front.js +0 -2
- package/commands/search.js +0 -3
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/skill.js +7 -2
- package/commands/slop.js +237 -7
- package/commands/status.js +6 -0
- package/commands/stream.js +4 -20
- package/commands/study.js +0 -3
- package/commands/sync-checkout.js +51 -0
- package/commands/sync.js +11 -40
- package/commands/task.js +999 -595
- package/commands/taste.js +101 -0
- package/commands/teach.js +86 -0
- package/commands/team.js +83 -3
- package/commands/unknowns.js +0 -10
- package/commands/update.js +0 -1
- package/commands/vercel.js +4 -2
- package/commands/visualize.js +0 -5
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -28
- package/commands/wiki.js +42 -15
- package/commands/wish.js +0 -1
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +88 -28
- package/commands/xp.js +28 -30
- package/commands/youtube.js +0 -2
- package/lib/accept-verify-gate.js +126 -0
- package/lib/activity-stream.js +0 -2
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +261 -3
- package/lib/autoland.js +66 -3
- package/lib/ax-auto-lane.js +68 -7
- package/lib/bench/context.js +0 -2
- package/lib/bench/report.js +0 -1
- package/lib/bench/runner.js +18 -11
- package/lib/brief-ledger.js +0 -6
- package/lib/business-simulate.js +382 -0
- package/lib/chat-log-scan.js +0 -5
- package/lib/checkout-sync.js +52 -0
- package/lib/claude-boot-block.js +48 -0
- package/lib/cloud-mission.js +0 -6
- package/lib/codex-flight.js +0 -1
- package/lib/conductor-artifacts.js +25 -0
- package/lib/context-gatherer.js +7 -2
- package/lib/deck-compose.js +0 -1
- package/lib/deck-history.js +0 -1
- package/lib/deck-layout.js +0 -5
- package/lib/deck-review.js +0 -7
- package/lib/deck-schema.js +0 -1
- package/lib/default-verifier.js +0 -2
- package/lib/engine-registry.js +157 -28
- package/lib/experiments/daily.js +0 -6
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +275 -42
- package/lib/functional-owner.js +0 -3
- package/lib/git-spawn.js +15 -0
- package/lib/inspect-fields.js +0 -1
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/learnings.js +0 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +11 -19
- package/lib/manifest.js +0 -2
- package/lib/member-alive.js +0 -1
- package/lib/member-switches.js +0 -5
- package/lib/mission-artifact.js +0 -1
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +296 -0
- package/lib/mission-room.js +0 -2
- package/lib/mission-runtime-loop.js +0 -1
- package/lib/next-moves.js +0 -18
- package/lib/official-cli-integration.js +47 -6
- package/lib/orb-context.js +14 -4
- package/lib/orb-scorecard.js +0 -1
- package/lib/outbound-send-gate.js +0 -6
- package/lib/pack-capabilities.js +685 -0
- package/lib/permission-grants.js +0 -6
- package/lib/policy-lessons.js +0 -2
- package/lib/pulse.js +68 -15
- package/lib/review-integrity.js +0 -2
- package/lib/router-brain.js +51 -3
- package/lib/runner-command.js +0 -10
- package/lib/runtime-bootstrap.js +0 -1
- package/lib/scorecard.js +0 -1
- package/lib/self-drive.js +45 -14
- package/lib/state-detection.js +0 -6
- package/lib/task-db.js +137 -7
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/team-presence.js +0 -1
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +129 -2
- package/lib/wiki.js +152 -9
- package/lib/wish-audit.js +21 -7
- package/lib/wish-delegate.js +27 -21
- package/lib/wish-stats.js +0 -5
- package/lib/wish-store.js +0 -1
- package/lib/workspace-safety.js +0 -2
- package/lib/zip.js +95 -8
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/commands/land.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const {
|
|
4
|
-
|
|
3
|
+
const { runGit } = require('../lib/git-spawn');
|
|
5
4
|
const { listWorktrees, statusCounts } = require('./worktree');
|
|
6
5
|
|
|
7
6
|
const DEFAULT_TTL_DAYS = 7;
|
|
@@ -9,12 +8,9 @@ const DEFAULT_STALE_HOURS = 48;
|
|
|
9
8
|
const WORKTREE_REAP_GRACE_MS = 60 * 60 * 1000;
|
|
10
9
|
const PROTECTED_BRANCHES = new Set(['main', 'master']);
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw new Error(`git ${args.join(' ')} failed: ${(result.stderr || result.stdout || '').trim()}`);
|
|
16
|
-
}
|
|
17
|
-
return result;
|
|
11
|
+
// Pluralize a count + noun ("1 change", "2 changes").
|
|
12
|
+
function countLabel(n, word) {
|
|
13
|
+
return `${n} ${word}${n === 1 ? '' : 's'}`;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
function repoRoot(cwd = process.cwd()) {
|
|
@@ -89,6 +85,14 @@ function aheadCount(root, base, ref) {
|
|
|
89
85
|
return Number(result.stdout.trim()) || 0;
|
|
90
86
|
}
|
|
91
87
|
|
|
88
|
+
// The subject lines of the commits a ref carries beyond base, oldest last —
|
|
89
|
+
// what a stranded worktree is actually holding, so surfacing it names the work.
|
|
90
|
+
function commitSubjects(cwd, base, ref, limit = 6) {
|
|
91
|
+
const result = runGit(['log', '--format=%s', `${base}..${ref}`], { cwd, check: false });
|
|
92
|
+
if (result.status !== 0) return [];
|
|
93
|
+
return result.stdout.split(/\r?\n/).filter(Boolean).slice(0, limit);
|
|
94
|
+
}
|
|
95
|
+
|
|
92
96
|
function cherryStats(root, base, ref) {
|
|
93
97
|
const result = runGit(['cherry', base, ref], { cwd: root, check: false });
|
|
94
98
|
if (result.status !== 0) return { landedElsewhere: 0, unique: 0 };
|
|
@@ -144,11 +148,36 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
|
|
|
144
148
|
const counts = (light ? null : statusCounts(wt.path)) || { staged: 0, unstaged: 0, untracked: 0 };
|
|
145
149
|
const dirty = counts.staged + counts.unstaged + counts.untracked;
|
|
146
150
|
const entry = branches.find((b) => b.name === branch) || null;
|
|
151
|
+
// Committed-but-unlanded work is invisible in the dirty count — a clean
|
|
152
|
+
// worktree can still hold commits ahead of base that silently never land.
|
|
153
|
+
// Carry the ahead/unique counts (and, off the light path, the subjects)
|
|
154
|
+
// so a stranded worktree is surfaced by what it is actually holding.
|
|
155
|
+
let ahead = entry ? entry.ahead : 0;
|
|
156
|
+
let unlandedCommits = entry ? entry.uniqueChanges : 0;
|
|
157
|
+
let ageDaysVal = entry ? entry.ageDays : null;
|
|
158
|
+
let subjects = [];
|
|
159
|
+
if (!branch) {
|
|
160
|
+
// Detached HEAD has no row in the branch table — ask the worktree itself.
|
|
161
|
+
const cnt = runGit(['rev-list', '--count', `${base}..HEAD`], { cwd: wt.path, check: false });
|
|
162
|
+
ahead = cnt.status === 0 ? Number(cnt.stdout.trim()) || 0 : 0;
|
|
163
|
+
if (ahead > 0) {
|
|
164
|
+
unlandedCommits = cherryStats(wt.path, base, 'HEAD').unique;
|
|
165
|
+
const ts = runGit(['log', '-1', '--format=%ct', 'HEAD'], { cwd: wt.path, check: false });
|
|
166
|
+
if (ts.status === 0) ageDaysVal = ageDays(Number(ts.stdout.trim()), now);
|
|
167
|
+
if (!light) subjects = commitSubjects(wt.path, base, 'HEAD');
|
|
168
|
+
}
|
|
169
|
+
} else if (!light && ahead > 0) {
|
|
170
|
+
subjects = commitSubjects(root, base, branch);
|
|
171
|
+
}
|
|
147
172
|
worktrees.push({
|
|
148
173
|
path: wt.path,
|
|
149
174
|
branch,
|
|
150
175
|
dirty,
|
|
151
|
-
|
|
176
|
+
ahead,
|
|
177
|
+
unlandedCommits,
|
|
178
|
+
subjects,
|
|
179
|
+
stranded: unlandedCommits > 0,
|
|
180
|
+
ageDays: ageDaysVal,
|
|
152
181
|
ageHours: entry ? entry.ageHours : null,
|
|
153
182
|
state: entry ? entry.state : 'detached',
|
|
154
183
|
});
|
|
@@ -174,12 +203,14 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
|
|
|
174
203
|
const landed = branches.filter((b) => b.state === 'landed');
|
|
175
204
|
const active = branches.filter((b) => b.state === 'active');
|
|
176
205
|
const stale = active.filter((b) => b.stale);
|
|
206
|
+
const strandedWorktrees = worktrees.filter((w) => w.stranded);
|
|
177
207
|
return {
|
|
178
208
|
base,
|
|
179
209
|
ttlDays,
|
|
180
210
|
staleHours,
|
|
181
211
|
branches,
|
|
182
212
|
worktrees,
|
|
213
|
+
strandedWorktrees,
|
|
183
214
|
summary: {
|
|
184
215
|
unlanded: branches.length,
|
|
185
216
|
active: active.length,
|
|
@@ -187,6 +218,7 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
|
|
|
187
218
|
landed: landed.length,
|
|
188
219
|
stale: stale.length,
|
|
189
220
|
worktrees: worktrees.length,
|
|
221
|
+
stranded: strandedWorktrees.length,
|
|
190
222
|
},
|
|
191
223
|
};
|
|
192
224
|
}
|
|
@@ -269,12 +301,12 @@ function salvageWorktree(w, dir, receipt) {
|
|
|
269
301
|
// includeDetached: detached-HEAD worktrees have no branch, so the salvage
|
|
270
302
|
// bundle cannot cover their commits — unattended reaps (autoland) pass false
|
|
271
303
|
// and leave them for a human reap.
|
|
272
|
-
function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOURS, base: baseOverride = '', dryRun = false, remote = true, includeDetached = true, protectCurrent = true, now = Date.now() } = {}) {
|
|
304
|
+
function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOURS, base: baseOverride = '', dryRun = false, remote = true, includeDetached = true, protectCurrent = true, force = false, now = Date.now() } = {}) {
|
|
273
305
|
const board = collectBoard(root, { ttlDays, staleHours, base: baseOverride, now });
|
|
274
306
|
const targets = board.branches.filter((b) => b.state === 'landed' || b.state === 'due');
|
|
275
307
|
const targetNames = new Set(targets.map((b) => b.name));
|
|
276
308
|
const candidateWorktrees = board.worktrees.filter(
|
|
277
|
-
(w) => targetNames.has(w.branch) || (includeDetached && w.state === 'detached') || (typeof w.ageDays === 'number' && w.ageDays > ttlDays)
|
|
309
|
+
(w) => targetNames.has(w.branch) || (includeDetached && w.state === 'detached') || (typeof w.ageDays === 'number' && w.ageDays > ttlDays) || w.unlandedCommits > 0
|
|
278
310
|
);
|
|
279
311
|
const protectedWorktrees = [];
|
|
280
312
|
const worktreeTargets = [];
|
|
@@ -293,6 +325,18 @@ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOU
|
|
|
293
325
|
protectedWorktrees.push({ ...w, reason: 'fresh_worktree_grace' });
|
|
294
326
|
continue;
|
|
295
327
|
}
|
|
328
|
+
// Unlanded commits that no branch salvage bundle will cover are lost by a
|
|
329
|
+
// force-remove: a detached worktree's commits ride no branch, and an active
|
|
330
|
+
// branch worktree is not a reap target so its commits are never bundled.
|
|
331
|
+
// Keep the worktree (and name what it holds) unless the operator forces it.
|
|
332
|
+
// A due/landed branch worktree still reaps normally — its commits ride the
|
|
333
|
+
// branch bundle, so removal is loss-free.
|
|
334
|
+
const bundleCovered = w.branch && targetNames.has(w.branch);
|
|
335
|
+
if (w.unlandedCommits > 0 && !force && !bundleCovered) {
|
|
336
|
+
const oldest = typeof w.ageDays === 'number' ? `, oldest ${w.ageDays}d` : '';
|
|
337
|
+
protectedWorktrees.push({ ...w, reason: `unlanded commits: ${w.unlandedCommits} never landed${oldest}, needs landing` });
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
296
340
|
worktreeTargets.push(w);
|
|
297
341
|
}
|
|
298
342
|
for (const w of protectedWorktrees) {
|
|
@@ -472,7 +516,7 @@ function printStory(story) {
|
|
|
472
516
|
const who = [...new Set(story.changes.map((c) => c.author))].join(', ');
|
|
473
517
|
const when = story.changes[story.changes.length - 1].date;
|
|
474
518
|
const last = story.changes[0].date;
|
|
475
|
-
const age = story.ageDays === null ? '' : ` (${story.ageDays}
|
|
519
|
+
const age = story.ageDays === null ? '' : ` (${countLabel(story.ageDays, 'day')} ago)`;
|
|
476
520
|
console.log(` started ${when}, last touched ${last}${age}, by ${who}.`);
|
|
477
521
|
console.log('');
|
|
478
522
|
console.log(' what it tried to do:');
|
|
@@ -488,7 +532,7 @@ function printStory(story) {
|
|
|
488
532
|
console.log('');
|
|
489
533
|
}
|
|
490
534
|
if (story.uniqueChanges === 0) {
|
|
491
|
-
console.log(` bottom line: all ${story.changes.length}
|
|
535
|
+
console.log(` bottom line: all ${countLabel(story.changes.length, 'change')} made it into ${story.base}`);
|
|
492
536
|
console.log(' some other way. nothing here would be lost by clearing it.');
|
|
493
537
|
} else if (story.landedElsewhere > 0) {
|
|
494
538
|
console.log(` bottom line: ${story.landedElsewhere} of ${story.changes.length} changes made it into ${story.base} another`);
|
|
@@ -522,8 +566,15 @@ function printBoard(board) {
|
|
|
522
566
|
console.log(` ${label.padEnd(11)} ${String(b.ageDays).padStart(3)}d ${changes} ${b.name}`);
|
|
523
567
|
}
|
|
524
568
|
for (const w of board.worktrees) {
|
|
525
|
-
const
|
|
526
|
-
|
|
569
|
+
const bits = [];
|
|
570
|
+
if (w.stranded) bits.push(w.unlandedCommits === 1 ? '1 commit never landed' : `${w.unlandedCommits} commits never landed`);
|
|
571
|
+
if (w.dirty > 0) bits.push(w.dirty === 1 ? '1 unsaved edit' : `${w.dirty} unsaved edits`);
|
|
572
|
+
const age = typeof w.ageDays === 'number' ? `, ${countLabel(w.ageDays, 'day')} old` : '';
|
|
573
|
+
const detail = bits.length ? ` (${bits.join(', ')}${age})` : '';
|
|
574
|
+
console.log(` side copy ${w.path}${detail}`);
|
|
575
|
+
if (w.stranded) {
|
|
576
|
+
for (const s of w.subjects) console.log(` - ${s}`);
|
|
577
|
+
}
|
|
527
578
|
}
|
|
528
579
|
console.log('');
|
|
529
580
|
const s = board.summary;
|
|
@@ -554,7 +605,9 @@ function printReceipt(receipt) {
|
|
|
554
605
|
// fresh-grace keeps are healthy active work, not a call to action —
|
|
555
606
|
// "needs a human" on those trained readers to ignore the real ones.
|
|
556
607
|
for (const k of receipt.keptWorktrees || []) {
|
|
608
|
+
// The receipt keeps the raw codes; the human line speaks in sentences.
|
|
557
609
|
if (String(k).includes('(fresh_worktree_grace)')) console.log(` in use, left alone: ${k.replace(' (fresh_worktree_grace)', '')}`);
|
|
610
|
+
else if (String(k).includes('(current_checkout)')) console.log(` you are standing in it, left alone: ${k.replace(' (current_checkout)', '')}`);
|
|
558
611
|
else console.log(` ✋ kept, needs a human: ${k}`);
|
|
559
612
|
}
|
|
560
613
|
for (const m of receipt.keptMovedBranches || []) console.log(` moved since scan, left alone: ${m}`);
|
|
@@ -564,7 +617,8 @@ function printReceipt(receipt) {
|
|
|
564
617
|
console.log('');
|
|
565
618
|
}
|
|
566
619
|
|
|
567
|
-
|
|
620
|
+
// Preserve split-only parsing and its fallback for missing or falsey values.
|
|
621
|
+
function readFollowingFlag(args, name, fallback = '') {
|
|
568
622
|
const idx = args.indexOf(name);
|
|
569
623
|
if (idx === -1) return fallback;
|
|
570
624
|
return args[idx + 1] || fallback;
|
|
@@ -586,6 +640,8 @@ function showHelp() {
|
|
|
586
640
|
console.log(' atris land --reap back up, then clear anything overdue');
|
|
587
641
|
console.log(' or already landed (here and on github)');
|
|
588
642
|
console.log(' atris land --reap --dry-run preview what would be cleared');
|
|
643
|
+
console.log(' atris land --reap --force also clear side copies holding');
|
|
644
|
+
console.log(' commits that never landed (backs up first)');
|
|
589
645
|
console.log(' atris land --ttl <days> change the 7-day overdue line');
|
|
590
646
|
console.log(' atris land --json machine-readable output');
|
|
591
647
|
console.log('');
|
|
@@ -603,21 +659,21 @@ function landCommand(args = []) {
|
|
|
603
659
|
console.error('not a git repository');
|
|
604
660
|
return 1;
|
|
605
661
|
}
|
|
606
|
-
const ttlRaw =
|
|
662
|
+
const ttlRaw = readFollowingFlag(args, '--ttl', '');
|
|
607
663
|
const ttlParsed = Number(ttlRaw);
|
|
608
664
|
const ttlDays = ttlRaw !== '' && Number.isFinite(ttlParsed) && ttlParsed >= 0 ? ttlParsed : DEFAULT_TTL_DAYS;
|
|
609
|
-
const base =
|
|
665
|
+
const base = readFollowingFlag(args, '--base', '');
|
|
610
666
|
const json = args.includes('--json');
|
|
611
667
|
|
|
612
668
|
if (args.includes('--reap')) {
|
|
613
|
-
const receipt = reap(root, { ttlDays, base, dryRun: args.includes('--dry-run'), remote: !args.includes('--no-remote') });
|
|
669
|
+
const receipt = reap(root, { ttlDays, base, dryRun: args.includes('--dry-run'), remote: !args.includes('--no-remote'), force: args.includes('--force') });
|
|
614
670
|
if (json) console.log(JSON.stringify(receipt, null, 2));
|
|
615
671
|
else printReceipt(receipt);
|
|
616
672
|
return 0;
|
|
617
673
|
}
|
|
618
674
|
|
|
619
675
|
const lookupArgs = args[0] === 'status' ? args.slice(1) : args;
|
|
620
|
-
const name = lookupArgs.find((a) => !a.startsWith('-') && a !==
|
|
676
|
+
const name = lookupArgs.find((a) => !a.startsWith('-') && a !== readFollowingFlag(lookupArgs, '--ttl') && a !== readFollowingFlag(lookupArgs, '--base'));
|
|
621
677
|
if (name) {
|
|
622
678
|
const story = collectStory(root, name, { base });
|
|
623
679
|
if (!story) {
|
|
@@ -637,6 +693,7 @@ function landCommand(args = []) {
|
|
|
637
693
|
|
|
638
694
|
module.exports = {
|
|
639
695
|
collectBoard,
|
|
696
|
+
countLabel,
|
|
640
697
|
landCommand,
|
|
641
698
|
landSummary,
|
|
642
699
|
reap,
|
package/commands/launchpad.js
CHANGED
|
@@ -2,23 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { hasFlag, readFlag } = require('../lib/arg-parser');
|
|
5
6
|
|
|
6
7
|
const ACTIVE_MISSION_STATUSES = new Set(['planning', 'running', 'ready']);
|
|
7
8
|
|
|
8
|
-
function hasFlag(args, name) {
|
|
9
|
-
return args.includes(name);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readFlag(args, name, fallback = '') {
|
|
13
|
-
const prefix = `${name}=`;
|
|
14
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
15
|
-
const arg = String(args[i]);
|
|
16
|
-
if (arg === name && args[i + 1] && !String(args[i + 1]).startsWith('--')) return String(args[i + 1]);
|
|
17
|
-
if (arg.startsWith(prefix)) return arg.slice(prefix.length);
|
|
18
|
-
}
|
|
19
|
-
return fallback;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
9
|
function readJson(file) {
|
|
23
10
|
try {
|
|
24
11
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -632,7 +619,5 @@ function launchpadCommand(args = []) {
|
|
|
632
619
|
}
|
|
633
620
|
|
|
634
621
|
module.exports = {
|
|
635
|
-
collectLaunchpad,
|
|
636
622
|
launchpadCommand,
|
|
637
|
-
renderLaunchpad,
|
|
638
623
|
};
|
package/commands/lesson.js
CHANGED
|
@@ -213,7 +213,23 @@ function mineLessons(args) {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
function printLessonUsage() {
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
|
|
219
|
+
console.log(' atris lesson mine [--json] [--dry-run]');
|
|
220
|
+
console.log(' atris lesson sweep [--json] [--dry-run]');
|
|
221
|
+
console.log(' atris lesson resolve [--json] [--dry-run]');
|
|
222
|
+
console.log('');
|
|
223
|
+
}
|
|
224
|
+
|
|
216
225
|
function lessonAtris(subcommand, ...args) {
|
|
226
|
+
// -h/--help is an explicit help request: print usage and exit 0, before any
|
|
227
|
+
// filesystem check, so `atris lesson --help` works everywhere.
|
|
228
|
+
if (subcommand === '-h' || subcommand === '--help') {
|
|
229
|
+
printLessonUsage();
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
|
|
217
233
|
const atrisDir = path.join(process.cwd(), 'atris');
|
|
218
234
|
if (!fs.existsSync(atrisDir)) {
|
|
219
235
|
console.error(' ✗ atris/ folder not found. Run "atris init" first.');
|
|
@@ -236,12 +252,7 @@ function lessonAtris(subcommand, ...args) {
|
|
|
236
252
|
}
|
|
237
253
|
|
|
238
254
|
if (subcommand !== 'add') {
|
|
239
|
-
|
|
240
|
-
console.log(' Usage: atris lesson add <slug> <pass|fail> "<text>"');
|
|
241
|
-
console.log(' atris lesson mine [--json] [--dry-run]');
|
|
242
|
-
console.log(' atris lesson sweep [--json] [--dry-run]');
|
|
243
|
-
console.log(' atris lesson resolve [--json] [--dry-run]');
|
|
244
|
-
console.log('');
|
|
255
|
+
printLessonUsage();
|
|
245
256
|
process.exit(subcommand ? 1 : 0);
|
|
246
257
|
}
|
|
247
258
|
|
|
@@ -269,4 +280,3 @@ function lessonAtris(subcommand, ...args) {
|
|
|
269
280
|
|
|
270
281
|
module.exports = lessonAtris;
|
|
271
282
|
module.exports.autoResolveLessons = autoResolveLessons;
|
|
272
|
-
module.exports.tagLessonResolvedInMd = tagLessonResolvedInMd;
|
package/commands/lifecycle.js
CHANGED
|
@@ -168,7 +168,7 @@ async function sleepAtris() {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
* Wake a sleeping workspace
|
|
171
|
+
* Wake a sleeping workspace: remote wake request, or local wake flag.
|
|
172
172
|
* @returns {Promise<void>}
|
|
173
173
|
*/
|
|
174
174
|
async function wakeAtris() {
|
|
@@ -179,7 +179,7 @@ async function wakeAtris() {
|
|
|
179
179
|
if (!slug || slug === '--help' || slug === '-h' || slug === 'help') {
|
|
180
180
|
console.log('Usage: atris wake [business|member] [--loop <loop-id>]');
|
|
181
181
|
console.log('');
|
|
182
|
-
console.log(' Wake a sleeping workspace.
|
|
182
|
+
console.log(' Wake a sleeping workspace. Wake signal sent; loops pick it up on their next pass.');
|
|
183
183
|
console.log(' Or flip a local member / member-loop switch awake.');
|
|
184
184
|
process.exit(0);
|
|
185
185
|
}
|
|
@@ -207,13 +207,13 @@ async function wakeAtris() {
|
|
|
207
207
|
console.log(`Waking business computer '${business.name || slug}'...`);
|
|
208
208
|
|
|
209
209
|
if (result.data && result.data.status === 'running' && result.data.endpoint) {
|
|
210
|
-
console.log(`Business computer '${business.name || slug}' is alive.
|
|
210
|
+
console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const running = await waitForBusinessComputer(creds.token, business);
|
|
215
215
|
if (running) {
|
|
216
|
-
console.log(`Business computer '${business.name || slug}' is alive.
|
|
216
|
+
console.log(`Business computer '${business.name || slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -246,7 +246,7 @@ async function wakeAtris() {
|
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
if (status.ok && status.data && status.data.status === 'running') {
|
|
249
|
-
console.log(`Workspace '${slug}' is alive.
|
|
249
|
+
console.log(`Workspace '${slug}' is alive. Wake signal sent; loops pick it up on their next pass.`);
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
}
|
package/commands/log.js
CHANGED
|
@@ -97,6 +97,56 @@ function readDigestSection(root, title, relativePath, maxLines = null) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Splits a journal body into its ## groups so the digest can show
|
|
101
|
+
// completed / inbox / notes as short labeled blocks instead of a raw dump.
|
|
102
|
+
function journalGroups(lines) {
|
|
103
|
+
const groups = [];
|
|
104
|
+
let current = null;
|
|
105
|
+
for (const line of lines) {
|
|
106
|
+
const heading = line.match(/^##\s+(.+?)\s*$/);
|
|
107
|
+
if (heading) {
|
|
108
|
+
current = { heading: heading[1], lines: [] };
|
|
109
|
+
groups.push(current);
|
|
110
|
+
} else if (current) {
|
|
111
|
+
current.lines.push(line);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return groups;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function bulletCount(group) {
|
|
118
|
+
return group.lines.filter((line) => /^\s*-\s+/.test(line)).length;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function digestSummaryLine(date, sections) {
|
|
122
|
+
let completed = 0;
|
|
123
|
+
let inbox = 0;
|
|
124
|
+
for (const section of sections) {
|
|
125
|
+
for (const group of journalGroups(section.lines)) {
|
|
126
|
+
if (/^completed/i.test(group.heading)) completed += bulletCount(group);
|
|
127
|
+
else if (/^inbox/i.test(group.heading)) inbox += bulletCount(group);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const parts = [`${sections.length} log${sections.length === 1 ? '' : 's'}`];
|
|
131
|
+
if (completed) parts.push(`${completed} completed`);
|
|
132
|
+
if (inbox) parts.push(`${inbox} inbox`);
|
|
133
|
+
return `${date}: ${parts.join(', ')}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderDigestSection(section) {
|
|
137
|
+
const groups = journalGroups(section.lines);
|
|
138
|
+
if (!groups.length) {
|
|
139
|
+
return [`=== ${section.title} ===`, section.path, ...section.lines].join('\n');
|
|
140
|
+
}
|
|
141
|
+
const out = [`=== ${section.title} ===`, section.path];
|
|
142
|
+
for (const group of groups) {
|
|
143
|
+
const body = group.lines.filter((line) => line.trim() && line.trim() !== '---');
|
|
144
|
+
if (!body.length) continue;
|
|
145
|
+
out.push('', `${group.heading.toLowerCase()} (${bulletCount(group)})`, ...body);
|
|
146
|
+
}
|
|
147
|
+
return out.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
100
150
|
function logsDigest(args = [], options = {}) {
|
|
101
151
|
const root = options.cwd || process.cwd();
|
|
102
152
|
const date = digestDate(args);
|
|
@@ -125,11 +175,11 @@ function logsDigest(args = [], options = {}) {
|
|
|
125
175
|
} else if (sections.length === 0) {
|
|
126
176
|
console.log(`No logs for ${date} yet.`);
|
|
127
177
|
} else {
|
|
128
|
-
console.log(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
...
|
|
132
|
-
].join('\n'))
|
|
178
|
+
console.log([
|
|
179
|
+
digestSummaryLine(date, sections),
|
|
180
|
+
'',
|
|
181
|
+
...sections.map(renderDigestSection),
|
|
182
|
+
].join('\n'));
|
|
133
183
|
}
|
|
134
184
|
|
|
135
185
|
return payload;
|
package/commands/loop-front.js
CHANGED
|
@@ -396,11 +396,9 @@ function loopFront(argv = []) {
|
|
|
396
396
|
module.exports = {
|
|
397
397
|
routeLoop,
|
|
398
398
|
renderLoopHome,
|
|
399
|
-
printLoopHome,
|
|
400
399
|
startLocalOptions,
|
|
401
400
|
localRunSummary,
|
|
402
401
|
loopStatusJson,
|
|
403
|
-
loopSeedMove,
|
|
404
402
|
createNextLoopTask,
|
|
405
403
|
loopReport,
|
|
406
404
|
renderLoopReport,
|
package/commands/loops.js
CHANGED
|
@@ -382,6 +382,12 @@ function showLoopsHelp() {
|
|
|
382
382
|
}
|
|
383
383
|
|
|
384
384
|
function loopsCommand(subcommand, ...args) {
|
|
385
|
+
// `loops <sub> --help` is a help request, not a run — tick/start/stop/init
|
|
386
|
+
// mutate loop state, so never execute a subcommand just to show usage.
|
|
387
|
+
if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help'
|
|
388
|
+
|| args.includes('--help') || args.includes('-h')) {
|
|
389
|
+
return showLoopsHelp();
|
|
390
|
+
}
|
|
385
391
|
switch (subcommand) {
|
|
386
392
|
case undefined:
|
|
387
393
|
case 'audit':
|
|
@@ -408,9 +414,4 @@ function loopsCommand(subcommand, ...args) {
|
|
|
408
414
|
|
|
409
415
|
module.exports = {
|
|
410
416
|
loopsCommand,
|
|
411
|
-
showBoard,
|
|
412
|
-
initLoops,
|
|
413
|
-
auditLoops,
|
|
414
|
-
tickLoops,
|
|
415
|
-
structuralProblems,
|
|
416
417
|
};
|