atris 3.58.6 → 3.58.8
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 +2 -2
- package/PERSONA.md +11 -11
- package/README.md +9 -1
- package/atris/GEMINI.md +7 -6
- package/atris/PERSONA.md +4 -4
- package/atris/features/README.md +77 -6
- package/atris/policies/engineering-principles.md +129 -0
- package/atris/policies/genesis.md +112 -0
- package/atris/policies/product-design-principles.md +100 -0
- package/atris/skills/README.md +44 -15
- package/atris/skills/aeo/SKILL.md +10 -10
- package/atris/skills/atris/SKILL.md +1 -1
- package/atris/skills/design/SKILL.md +3 -1
- package/atris/skills/engines/SKILL.md +42 -50
- package/atris/skills/meta/SKILL.md +4 -4
- package/atris/skills/youtube/SKILL.md +11 -11
- package/bin/atris.js +60 -8
- package/commands/aeo.js +9 -12
- package/commands/analytics.js +8 -11
- package/commands/auth.js +8 -2
- package/commands/autopilot.js +4 -3
- package/commands/avail.js +1 -1
- package/commands/brain.js +4 -1
- package/commands/business.js +13 -8
- package/commands/close.js +2 -1
- package/commands/computer.js +15 -10
- package/commands/context-sync.js +3 -3
- package/commands/design.js +362 -0
- package/commands/doc-health.js +377 -0
- package/commands/drive.js +32 -0
- package/commands/experiments.js +13 -4
- package/commands/improve.js +68 -2
- package/commands/integrations.js +2 -5
- package/commands/land.js +144 -4
- package/commands/learn.js +7 -2
- package/commands/member.js +239 -58
- package/commands/mission.js +94 -18
- package/commands/pack.js +2 -2
- package/commands/probe.js +11 -5
- package/commands/pull.js +1 -1
- package/commands/pulse.js +38 -0
- package/commands/push.js +1 -1
- package/commands/rainmaker.js +3 -16
- package/commands/rsi.js +156 -0
- package/commands/spaceship.js +1 -1
- package/commands/stream.js +27 -2
- package/commands/study.js +15 -9
- package/commands/sync.js +1 -1
- package/commands/task.js +54 -3
- package/commands/workflow.js +17 -7
- package/commands/youtube.js +76 -8
- package/lib/apply-gate.js +22 -4
- package/lib/codex-flight.js +28 -5
- package/lib/daily-log.js +88 -0
- package/lib/design-api.js +130 -0
- package/lib/engine-ask.js +1 -1
- package/lib/first-minute.js +1 -6
- package/lib/fleet.js +4 -1
- package/lib/improve-vitals-html.js +3 -2
- package/lib/known-commands.js +3 -3
- package/lib/lesson-contradiction.js +2 -2
- package/lib/map-refs.js +183 -0
- package/lib/member-context.js +42 -0
- package/lib/mission-room.js +4 -4
- package/lib/noninteractive.js +15 -4
- package/lib/policy-lessons.js +1 -1
- package/lib/receipt-block.js +137 -28
- package/lib/rsi-record.js +335 -0
- package/lib/runner-command.js +1 -1
- package/lib/scorecard.js +4 -4
- package/lib/sleep-sync.js +11 -0
- package/lib/state-detection.js +36 -17
- package/lib/task-db.js +74 -53
- package/lib/task-list-keeper.js +192 -0
- package/lib/todo-fallback.js +10 -4
- package/lib/todo.js +22 -10
- package/lib/wish-delegate.js +19 -5
- package/mcp/atris-mcp/index.mjs +227 -0
- package/package.json +4 -2
- package/scripts/det/det.js +2 -2
- package/scripts/det/pr-description.js +2 -2
- package/scripts/det/test.js +2 -2
- package/scripts/det/ytnotes +43 -8
- package/utils/api.js +1 -1
- package/utils/auth.js +79 -14
- package/utils/backend-root.js +31 -0
- package/utils/config.js +5 -4
- package/utils/owner-identity.js +61 -0
- package/atris/policies/LESSONS.md +0 -13
- package/atris/skills/autopilot/atris-autopilot.md +0 -71
package/commands/land.js
CHANGED
|
@@ -58,6 +58,102 @@ function worktreeWithinReapGrace(worktreePath, now = Date.now()) {
|
|
|
58
58
|
return typeof mtime === 'number' && now - mtime < WORKTREE_REAP_GRACE_MS;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function canonicalPath(p) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.realpathSync(p);
|
|
64
|
+
} catch {
|
|
65
|
+
return path.resolve(p);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function gitCommonDir(root) {
|
|
70
|
+
for (const args of [['rev-parse', '--path-format=absolute', '--git-common-dir'], ['rev-parse', '--git-common-dir']]) {
|
|
71
|
+
const res = runGit(args, { cwd: root, check: false });
|
|
72
|
+
if (res.status === 0 && res.stdout.trim()) return path.resolve(root, res.stdout.trim());
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function headRefName(headFile) {
|
|
78
|
+
try {
|
|
79
|
+
const m = /^ref: refs\/heads\/(.+)$/.exec(fs.readFileSync(headFile, 'utf8').trim());
|
|
80
|
+
return m ? m[1] : '';
|
|
81
|
+
} catch {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Branch checkouts straight from .git/worktrees/<id>/HEAD plus the main
|
|
87
|
+
// checkout's HEAD — the live truth, not the board snapshot. A `worktree add`
|
|
88
|
+
// still in flight (or a half-registered entry) can leave a branch checked out
|
|
89
|
+
// in a directory the snapshot never saw, and git's own `branch -D` refusal
|
|
90
|
+
// only consults registrations whose gitdir link is already written.
|
|
91
|
+
// pendingFresh marks a fresh registration whose HEAD file does not exist yet:
|
|
92
|
+
// an add mid-flight that has not named its branch.
|
|
93
|
+
function liveCheckouts(root, now = Date.now()) {
|
|
94
|
+
const bound = new Map();
|
|
95
|
+
let pendingFresh = false;
|
|
96
|
+
const common = gitCommonDir(root);
|
|
97
|
+
if (!common) return { bound, pendingFresh };
|
|
98
|
+
if (path.basename(common) === '.git') {
|
|
99
|
+
const name = headRefName(path.join(common, 'HEAD'));
|
|
100
|
+
const mainPath = path.dirname(common);
|
|
101
|
+
if (name && fs.existsSync(mainPath)) bound.set(name, { path: mainPath, adminDir: common });
|
|
102
|
+
}
|
|
103
|
+
let ids = [];
|
|
104
|
+
try {
|
|
105
|
+
ids = fs.readdirSync(path.join(common, 'worktrees'));
|
|
106
|
+
} catch {
|
|
107
|
+
return { bound, pendingFresh };
|
|
108
|
+
}
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
const adminDir = path.join(common, 'worktrees', id);
|
|
111
|
+
let stat;
|
|
112
|
+
try {
|
|
113
|
+
stat = fs.statSync(adminDir);
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!stat.isDirectory()) continue;
|
|
118
|
+
const headFile = path.join(adminDir, 'HEAD');
|
|
119
|
+
const name = headRefName(headFile);
|
|
120
|
+
if (!name) {
|
|
121
|
+
if (!fs.existsSync(headFile) && now - stat.mtimeMs < WORKTREE_REAP_GRACE_MS) pendingFresh = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let wtPath = null;
|
|
125
|
+
try {
|
|
126
|
+
wtPath = path.dirname(fs.readFileSync(path.join(adminDir, 'gitdir'), 'utf8').trim());
|
|
127
|
+
} catch {
|
|
128
|
+
// gitdir unwritten or lost: cannot prove which directory is the
|
|
129
|
+
// checkout, so the branch stays claimed — fail safe, prune's job.
|
|
130
|
+
}
|
|
131
|
+
if (wtPath && !fs.existsSync(wtPath)) continue;
|
|
132
|
+
bound.set(name, { path: wtPath, adminDir });
|
|
133
|
+
}
|
|
134
|
+
return { bound, pendingFresh };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The branch's own reflog records when `worktree add -b` (or `git branch`)
|
|
138
|
+
// created it. While an add is in flight a just-created target branch is very
|
|
139
|
+
// likely the checkout being wired up right now — keep it for this pass.
|
|
140
|
+
function branchCreatedWithinGrace(root, name, now = Date.now()) {
|
|
141
|
+
const res = runGit(['reflog', 'show', '--date=unix', name], { cwd: root, check: false });
|
|
142
|
+
if (res.status !== 0) return false;
|
|
143
|
+
const lines = res.stdout.split(/\r?\n/).filter(Boolean);
|
|
144
|
+
const m = /@\{(\d+)\}/.exec(lines[lines.length - 1] || '');
|
|
145
|
+
return Boolean(m) && now - Number(m[1]) * 1000 < WORKTREE_REAP_GRACE_MS;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// One receipt line for a branch kept because a live checkout claims it:
|
|
149
|
+
// fresh checkouts name the grace, older ones name where the branch is in use.
|
|
150
|
+
function checkoutKeepLine(name, bound, now) {
|
|
151
|
+
if (worktreeWithinReapGrace(bound.path || bound.adminDir, now)) {
|
|
152
|
+
return bound.path ? `${bound.path} (fresh_worktree_grace)` : `branch ${name} (fresh_worktree_grace)`;
|
|
153
|
+
}
|
|
154
|
+
return `branch ${name} (checked out in ${bound.path || 'a worktree'})`;
|
|
155
|
+
}
|
|
156
|
+
|
|
61
157
|
function listBranches(root, base = '') {
|
|
62
158
|
// With a base, ask git for ahead counts in the same single spawn
|
|
63
159
|
// (%(ahead-behind:) needs git >= 2.41; on failure we retry without it and
|
|
@@ -151,7 +247,12 @@ function collectBoard(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_S
|
|
|
151
247
|
const worktrees = [];
|
|
152
248
|
const all = listWorktrees(root);
|
|
153
249
|
for (const wt of all.slice(1)) {
|
|
154
|
-
const
|
|
250
|
+
const rawBranch = (wt.branch || '').replace(/^refs\/heads\//, '');
|
|
251
|
+
// 'detached' is a marker, not a name: left as-is it reads as a real branch
|
|
252
|
+
// name (hiding the worktree's own commits and, in reap, pushing the word
|
|
253
|
+
// "detached" into the delete list). Treat it as no branch so the worktree
|
|
254
|
+
// is asked directly what it holds.
|
|
255
|
+
const branch = rawBranch === 'detached' ? '' : rawBranch;
|
|
155
256
|
// light mode skips the full `git status` per worktree, the banner summary
|
|
156
257
|
// never reads dirty counts, only worktree mtimes for staleness.
|
|
157
258
|
const counts = (light ? null : statusCounts(wt.path)) || { staged: 0, unstaged: 0, untracked: 0 };
|
|
@@ -376,8 +477,25 @@ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOU
|
|
|
376
477
|
};
|
|
377
478
|
if (targetNames.size === 0 && worktreeTargets.length === 0) return receipt;
|
|
378
479
|
if (dryRun) {
|
|
480
|
+
// The preview must match the real pass: a branch still checked out in a
|
|
481
|
+
// worktree the board missed is kept, not listed as deleted. A checkout
|
|
482
|
+
// that is itself a removal target would be gone first, so it still reads
|
|
483
|
+
// as deletable here.
|
|
484
|
+
const live = liveCheckouts(root, now);
|
|
485
|
+
const targetPaths = new Set(worktreeTargets.map((w) => canonicalPath(w.path)));
|
|
379
486
|
receipt.removedWorktrees = worktreeTargets.map((w) => w.path);
|
|
380
|
-
receipt.deletedBranches = [...targetNames]
|
|
487
|
+
receipt.deletedBranches = [...targetNames].filter((name) => {
|
|
488
|
+
const bound = live.bound.get(name);
|
|
489
|
+
if (bound && !(bound.path && targetPaths.has(canonicalPath(bound.path)))) {
|
|
490
|
+
receipt.keptWorktrees.push(checkoutKeepLine(name, bound, now));
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
if (!bound && live.pendingFresh && branchCreatedWithinGrace(root, name, now)) {
|
|
494
|
+
receipt.keptWorktrees.push(`branch ${name} (fresh_worktree_grace)`);
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
return true;
|
|
498
|
+
});
|
|
381
499
|
return receipt;
|
|
382
500
|
}
|
|
383
501
|
|
|
@@ -406,9 +524,17 @@ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOU
|
|
|
406
524
|
(w) => targetNames.has(w.branch) || (includeDetached && w.state === 'detached')
|
|
407
525
|
);
|
|
408
526
|
for (const w of survivingWorktreeTargets) {
|
|
527
|
+
// The grace check ran at scan time, and a bundle build can sit between it
|
|
528
|
+
// and removal — an engine that booted mid-sweep refreshes the directory
|
|
529
|
+
// mtime in that gap. Stat once more at removal time, the cheapest place
|
|
530
|
+
// to never be wrong about freshness.
|
|
531
|
+
if (worktreeWithinReapGrace(w.path)) {
|
|
532
|
+
receipt.keptWorktrees.push(`${w.path} (fresh_worktree_grace)`);
|
|
533
|
+
if (w.branch) targetNames.delete(w.branch);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
409
536
|
// Salvage-then-remove, never keep-because-dirty: patches + untracked
|
|
410
|
-
// copies bank everything force-remove would destroy.
|
|
411
|
-
// grace was already applied when candidates were selected.
|
|
537
|
+
// copies bank everything force-remove would destroy.
|
|
412
538
|
if (w.dirty > 0 && !salvageWorktree(w, dir, receipt)) {
|
|
413
539
|
// could not fully back up what force-remove would destroy, keep it,
|
|
414
540
|
// and say why: a bare path in the receipt reads as an unexplained
|
|
@@ -430,7 +556,21 @@ function reap(root, { ttlDays = DEFAULT_TTL_DAYS, staleHours = DEFAULT_STALE_HOU
|
|
|
430
556
|
}
|
|
431
557
|
}
|
|
432
558
|
|
|
559
|
+
// Re-scan after removals: worktrees just removed freed their branches, and
|
|
560
|
+
// anything still bound is checked out in a directory that exists — the
|
|
561
|
+
// board snapshot may have missed it entirely (a `worktree add` mid-flight),
|
|
562
|
+
// and git's own -D refusal only reads fully wired registrations.
|
|
563
|
+
const live = liveCheckouts(root, now);
|
|
433
564
|
for (const name of targetNames) {
|
|
565
|
+
const bound = live.bound.get(name);
|
|
566
|
+
if (bound) {
|
|
567
|
+
receipt.keptWorktrees.push(checkoutKeepLine(name, bound, now));
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (live.pendingFresh && branchCreatedWithinGrace(root, name, now)) {
|
|
571
|
+
receipt.keptWorktrees.push(`branch ${name} (fresh_worktree_grace)`);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
434
574
|
const entry = board.branches.find((b) => b.name === name);
|
|
435
575
|
// the board is a snapshot; an agent may have committed since it was
|
|
436
576
|
// taken. A branch that moved is left alone, the next reap sees the
|
package/commands/learn.js
CHANGED
|
@@ -420,6 +420,10 @@ function logDirect(jsonStr, deps = {}) {
|
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
function leftoverClaimableInsight(insight) {
|
|
424
|
+
return /^\[claimable\]/i.test(String(insight || '').trim());
|
|
425
|
+
}
|
|
426
|
+
|
|
423
427
|
/**
|
|
424
428
|
* Harvest learnings from journal Notes sections.
|
|
425
429
|
* Scans recent journals for lines that look like insights.
|
|
@@ -450,13 +454,14 @@ function harvestFromJournals(deps = {}) {
|
|
|
450
454
|
// Scan last 7 journals for Notes section entries
|
|
451
455
|
const candidates = [];
|
|
452
456
|
for (const logPath of allLogs.slice(0, 7)) {
|
|
453
|
-
const content = fs.readFileSync(logPath, 'utf8');
|
|
457
|
+
const content = fs.readFileSync(logPath, 'utf8').replace(/\r\n/g, '\n');
|
|
454
458
|
const notesMatch = content.match(/## Notes\n([\s\S]*?)(?=\n## |$)/);
|
|
455
459
|
if (notesMatch && notesMatch[1].trim()) {
|
|
456
460
|
const lines = notesMatch[1].trim().split('\n').filter(l => l.startsWith('- '));
|
|
457
461
|
for (const line of lines) {
|
|
458
462
|
// Strip bullet and optional timestamp prefix
|
|
459
|
-
const insight = line.replace(/^- (\d{2}:\d{2}
|
|
463
|
+
const insight = line.replace(/^- (\d{2}:\d{2} [-\u2014] )?/, '').trim();
|
|
464
|
+
if (leftoverClaimableInsight(insight)) continue;
|
|
460
465
|
if (insight.length > 10) {
|
|
461
466
|
candidates.push({ insight, source: path.basename(logPath) });
|
|
462
467
|
}
|