atris 3.57.4 → 3.58.5
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/README.md +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +55 -12
- package/atris/skills/x-search/SKILL.md +8 -6
- package/atris/skills/youtube/SKILL.md +37 -8
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +153 -23
- package/bin/atris.js +78 -30
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +7 -7
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +3 -3
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +12 -12
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +90 -82
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +97 -40
- package/commands/worktree.js +1 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +89 -17
- package/commands/xp.js +6 -3
- package/commands/youtube.js +601 -48
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +45 -7
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +0 -3
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/skill.js
CHANGED
|
@@ -33,7 +33,7 @@ function parseFrontmatter(content) {
|
|
|
33
33
|
currentKey = kvMatch[1];
|
|
34
34
|
const val = kvMatch[2].trim();
|
|
35
35
|
if (val === '') {
|
|
36
|
-
// Could be start of a list or nested object
|
|
36
|
+
// Could be start of a list or nested object, leave empty, next lines fill it
|
|
37
37
|
result[currentKey] = {};
|
|
38
38
|
} else if (val.startsWith('[') && val.endsWith(']')) {
|
|
39
39
|
// Inline array: [a, b, "c d"]
|
|
@@ -317,7 +317,7 @@ function autoFix(skill, dryRun) {
|
|
|
317
317
|
|
|
318
318
|
let newFmLines = rawFm.split('\n');
|
|
319
319
|
|
|
320
|
-
// Fix 1: name mismatch
|
|
320
|
+
// Fix 1: name mismatch, replace name value with folder name
|
|
321
321
|
if (fm.name && fm.name !== skill.leafFolder) {
|
|
322
322
|
const idx = newFmLines.findIndex(l => l.match(/^name:\s/));
|
|
323
323
|
if (idx !== -1) {
|
|
@@ -718,7 +718,7 @@ function skillCreate(nameArg, ...flags) {
|
|
|
718
718
|
const descFlag = flags.find(f => f.startsWith('--description='));
|
|
719
719
|
const description = descFlag ? descFlag.split('=').slice(1).join('=').replace(/^["']|["']$/g, '') : '';
|
|
720
720
|
|
|
721
|
-
// Parse name
|
|
721
|
+
// Parse name, supports "customer/skill-name" format
|
|
722
722
|
let skillDir, skillName, customerName;
|
|
723
723
|
if (nameArg.includes('/')) {
|
|
724
724
|
const parts = nameArg.split('/');
|
|
@@ -798,7 +798,7 @@ function skillCreate(nameArg, ...flags) {
|
|
|
798
798
|
}
|
|
799
799
|
}
|
|
800
800
|
if (linked.length > 0) {
|
|
801
|
-
console.log(`✓ Linked to ${linked.join(', ')} (system-level
|
|
801
|
+
console.log(`✓ Linked to ${linked.join(', ')} (system-level, all tools)`);
|
|
802
802
|
}
|
|
803
803
|
}
|
|
804
804
|
|
|
@@ -923,7 +923,7 @@ function skillDelete(name) {
|
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
925
|
|
|
926
|
-
// Remove symlinks
|
|
926
|
+
// Remove symlinks, use unlinkSync for symlinks, rmSync for directories
|
|
927
927
|
function removeLink(linkPath, label) {
|
|
928
928
|
try {
|
|
929
929
|
const stat = fs.lstatSync(linkPath);
|
|
@@ -939,7 +939,7 @@ function skillDelete(name) {
|
|
|
939
939
|
// Project-level
|
|
940
940
|
removeLink(path.join(process.cwd(), '.claude', 'skills', name), `.claude/skills/${name}`);
|
|
941
941
|
|
|
942
|
-
// System-level
|
|
942
|
+
// System-level, all tool directories
|
|
943
943
|
const toolDirs = [
|
|
944
944
|
{ dir: path.join(home, '.claude', 'skills', name), label: '~/.claude' },
|
|
945
945
|
{ dir: path.join(home, '.codex', 'skills', name), label: '~/.codex' },
|
package/commands/slop.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// atris slop
|
|
1
|
+
// atris slop, deterministic frontend-slop detector (no LLM).
|
|
2
2
|
//
|
|
3
3
|
// Steal-from-Impeccable, the Atris way: makes "looks AI-generated" concrete and
|
|
4
|
-
// CHECKABLE. A failure is a fact (file:line + rule), not a taste opinion
|
|
4
|
+
// CHECKABLE. A failure is a fact (file:line + rule), not a taste opinion, so it
|
|
5
5
|
// drops straight into the autopilot/review verification gate and CI. Each finding
|
|
6
6
|
// is the seed of a typed lesson; the ruleset is meant to GROW from lessons.md
|
|
7
7
|
// rather than be hand-curated forever.
|
|
8
8
|
//
|
|
9
|
-
// Zero external deps (Node built-ins only)
|
|
9
|
+
// Zero external deps (Node built-ins only), repo contract.
|
|
10
10
|
//
|
|
11
11
|
// Usage:
|
|
12
12
|
// atris slop detect [path...] # scan one or more files or dirs (default: .)
|
|
@@ -25,7 +25,7 @@ const SCAN_EXTS = new Set(['.css', '.scss', '.sass', '.less', '.tsx', '.jsx', '.
|
|
|
25
25
|
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.astro', 'coverage', '.cache', 'out', 'vendor']);
|
|
26
26
|
|
|
27
27
|
// Each rule is deterministic: a regex + a one-line why. severity drives the icon.
|
|
28
|
-
// Kept high-precision on purpose
|
|
28
|
+
// Kept high-precision on purpose, a noisy gate gets muted, and a muted gate is dead.
|
|
29
29
|
const RULES = [
|
|
30
30
|
{ id: 'ai-gradient-text', sev: 'error',
|
|
31
31
|
re: /(text-transparent[^"'`]{0,40}bg-clip-text|bg-clip-text[^"'`]{0,40}text-transparent|-webkit-text-fill-color:\s*transparent|background-clip:\s*text)/i,
|
|
@@ -79,8 +79,8 @@ const RULES = [
|
|
|
79
79
|
re: /[✨\u{1F680}\u{1F4A1}\u{1F525}\u{1F389}⚡\u{1F31F}\u{1FA84}\u{1F4AB}\u{1F44B}]/u,
|
|
80
80
|
why: 'decorative emoji in UI copy' },
|
|
81
81
|
{ id: 'em-dash', sev: 'warn',
|
|
82
|
-
re:
|
|
83
|
-
fix: (s) => s.replace(/\s
|
|
82
|
+
re: /\u2014/,
|
|
83
|
+
fix: (s) => s.replace(/\s*\u2014\s*/g, ', '), // safe deterministic repair (prose)
|
|
84
84
|
why: 'em dash: a top AI-writing tell; use a comma, colon, or period' },
|
|
85
85
|
{ id: 'hype-copy', sev: 'error',
|
|
86
86
|
re: /\b(boost your productivity|supercharge|unleash|game[- ]?chang(?:er|ing)|seamlessly|effortlessly|revolutioniz(?:e|ing)|take your .{1,30} to the next level|elevate your|cutting[- ]edge|powered by ai|next[- ]generation)\b/i,
|
|
@@ -321,7 +321,7 @@ function detect(argv) {
|
|
|
321
321
|
const rel = (f) => path.relative(process.cwd(), f);
|
|
322
322
|
if (!quiet) {
|
|
323
323
|
if (!findings.length) {
|
|
324
|
-
console.log(`\n ✓ clean
|
|
324
|
+
console.log(`\n ✓ clean, no slop tells in ${files.length} file${files.length === 1 ? '' : 's'}`);
|
|
325
325
|
} else {
|
|
326
326
|
console.log('');
|
|
327
327
|
const w = Math.max(...findings.map((f) => `${rel(f.file)}:${f.line}`.length));
|
|
@@ -440,8 +440,8 @@ function findDeadCode(root = process.cwd(), opts = {}) {
|
|
|
440
440
|
|
|
441
441
|
// 3) string-mention safety net: basename (sans ext) named as a string literal
|
|
442
442
|
// outside itself. Split by scope: a mention in a NON-test file (prod code, a
|
|
443
|
-
// bin entry like `ax`) means the file is dynamically dispatched
|
|
444
|
-
// require(path.join(__dirname, 'lib', 'permission-grants.js'))
|
|
443
|
+
// bin entry like `ax`) means the file is dynamically dispatched, e.g.
|
|
444
|
+
// require(path.join(__dirname, 'lib', 'permission-grants.js')), and is
|
|
445
445
|
// genuinely reachable even though the static edge parser and the test BFS
|
|
446
446
|
// can't see the edge. That must outrank the test-only classification, or a
|
|
447
447
|
// live feature imported by both `ax` and a test reads as "feature gone".
|
|
@@ -462,9 +462,9 @@ function findDeadCode(root = process.cwd(), opts = {}) {
|
|
|
462
462
|
const dead = [], testOnly = [];
|
|
463
463
|
for (const c of candidates) {
|
|
464
464
|
if (reached.has(c)) continue;
|
|
465
|
-
if (mentionedInProd(c)) continue; // dynamically dispatched from prod (e.g. the `ax` bin)
|
|
465
|
+
if (mentionedInProd(c)) continue; // dynamically dispatched from prod (e.g. the `ax` bin), reachable
|
|
466
466
|
if (testReached.has(c)) { testOnly.push(c); continue; }
|
|
467
|
-
if (mentioned(c)) continue; // docs-driven / other dynamic dispatch
|
|
467
|
+
if (mentioned(c)) continue; // docs-driven / other dynamic dispatch, not provably dead
|
|
468
468
|
dead.push(c);
|
|
469
469
|
}
|
|
470
470
|
return { root, candidates: candidates.length, dead: dead.sort(), testOnly: testOnly.sort() };
|
|
@@ -476,7 +476,7 @@ function findDeadCode(root = process.cwd(), opts = {}) {
|
|
|
476
476
|
function exportedNames(file) {
|
|
477
477
|
let text; try { text = fs.readFileSync(file, 'utf8'); } catch { return []; }
|
|
478
478
|
const names = new Set();
|
|
479
|
-
// module.exports = { a, b: c, ... }
|
|
479
|
+
// module.exports = { a, b: c, ... }, shorthand entries and keys
|
|
480
480
|
const block = text.match(/module\.exports\s*=\s*\{([\s\S]*?)\n\}/);
|
|
481
481
|
if (block) {
|
|
482
482
|
for (const line of block[1].split('\n')) {
|
|
@@ -543,13 +543,13 @@ function deadCommand(argv) {
|
|
|
543
543
|
return bad ? 1 : 0;
|
|
544
544
|
}
|
|
545
545
|
if (!bad && !res.testOnly.length) {
|
|
546
|
-
console.log(`\n ✓ no dead code
|
|
546
|
+
console.log(`\n ✓ no dead code, all ${res.candidates} files reachable from the entrypoints\n`);
|
|
547
547
|
return 0;
|
|
548
548
|
}
|
|
549
549
|
console.log('');
|
|
550
|
-
for (const f of res.dead) console.log(` ✗ ${rel(f).padEnd(40)} unreachable and unreferenced
|
|
551
|
-
for (const f of res.testOnly) console.log(` ⚠ ${rel(f).padEnd(40)} only tests import it
|
|
552
|
-
if (orphans) for (const o of orphans) console.log(` ⚠ ${`${rel(o.file)} → ${o.name}`.padEnd(40)} exported but nothing anywhere names it
|
|
550
|
+
for (const f of res.dead) console.log(` ✗ ${rel(f).padEnd(40)} unreachable and unreferenced, delete it`);
|
|
551
|
+
for (const f of res.testOnly) console.log(` ⚠ ${rel(f).padEnd(40)} only tests import it, feature gone, test lingers?`);
|
|
552
|
+
if (orphans) for (const o of orphans) console.log(` ⚠ ${`${rel(o.file)} → ${o.name}`.padEnd(40)} exported but nothing anywhere names it, drop the export entry`);
|
|
553
553
|
const parts = [`${res.dead.length} dead`, `${res.testOnly.length} test-only`];
|
|
554
554
|
if (orphans) parts.push(`${orphans.length} orphaned export${orphans.length === 1 ? '' : 's'}`);
|
|
555
555
|
console.log(`\n ${parts.join(', ')} of ${res.candidates} files · exit ${bad ? 1 : 0}\n`);
|
|
@@ -588,7 +588,7 @@ function slopCommand(argv) {
|
|
|
588
588
|
return 0;
|
|
589
589
|
}
|
|
590
590
|
const project = loadProjectRules();
|
|
591
|
-
console.log('\n atris slop
|
|
591
|
+
console.log('\n atris slop, deterministic rules:\n');
|
|
592
592
|
for (const r of RULES) console.log(` ${ICON[r.sev]} ${r.id.padEnd(20)} ${r.why}`);
|
|
593
593
|
for (const r of project) console.log(` ${ICON[r.sev]} ${r.id.padEnd(20)} ${r.why} (project)`);
|
|
594
594
|
console.log(`\n ${RULES.length} built-in${project.length ? ` + ${project.length} project` : ''} rule${RULES.length + project.length === 1 ? '' : 's'}\n`);
|
|
@@ -596,7 +596,7 @@ function slopCommand(argv) {
|
|
|
596
596
|
}
|
|
597
597
|
// help
|
|
598
598
|
console.log(`
|
|
599
|
-
atris slop
|
|
599
|
+
atris slop, deterministic slop detector + repairer (no LLM)
|
|
600
600
|
|
|
601
601
|
atris slop detect [path...] scan one or more files or dirs (default: .)
|
|
602
602
|
atris slop detect --diff scan only changed lines (commit/PR gate)
|
package/commands/social.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Social commands for Atris CLI
|
|
2
|
+
* Social commands for Atris CLI, find people, add friends, send messages.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* atris social My social overview (stats, unread, suggestions)
|
|
@@ -413,7 +413,7 @@ async function join(code, rest = []) {
|
|
|
413
413
|
console.log('');
|
|
414
414
|
if (!inv.can_claim) fail('This invite has no claims left.');
|
|
415
415
|
|
|
416
|
-
// No account yet? Create one right here
|
|
416
|
+
// No account yet? Create one right here, signup is browser-free
|
|
417
417
|
// (commands/signup.js), so a headless terminal can claim in one run.
|
|
418
418
|
const creds = loadCredentials();
|
|
419
419
|
if (!creds || !creds.token) {
|
|
@@ -488,7 +488,7 @@ async function join(code, rest = []) {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
function printHelp() {
|
|
491
|
-
console.log(`atris social
|
|
491
|
+
console.log(`atris social, people, friends, and messages on Atris
|
|
492
492
|
|
|
493
493
|
atris social Your overview
|
|
494
494
|
atris people <query> Find people
|
package/commands/soul.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Atris Soul
|
|
2
|
+
* Atris Soul, The persona as a living artifact
|
|
3
3
|
*
|
|
4
4
|
* Every atris project has a soul: persona + policies + learnings + context.
|
|
5
5
|
* This command lets you see it, evolve it, fork it.
|
|
6
6
|
*
|
|
7
|
-
* atris soul
|
|
8
|
-
* atris soul snapshot
|
|
9
|
-
* atris soul distill
|
|
10
|
-
* atris soul fork <target
|
|
7
|
+
* atris soul , Show your project's soul state
|
|
8
|
+
* atris soul snapshot , Export full soul to JSON
|
|
9
|
+
* atris soul distill , Compress learnings into persona
|
|
10
|
+
* atris soul fork <target>, Copy soul to another project
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
@@ -59,7 +59,7 @@ function snapshotSoul(atrisDir) {
|
|
|
59
59
|
soul.identity.team = members;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// Knowledge
|
|
62
|
+
// Knowledge, features
|
|
63
63
|
const featuresDir = path.join(atrisDir, 'features');
|
|
64
64
|
if (fs.existsSync(featuresDir)) {
|
|
65
65
|
const features = fs.readdirSync(featuresDir).filter(f => {
|
|
@@ -69,31 +69,31 @@ function snapshotSoul(atrisDir) {
|
|
|
69
69
|
soul.knowledge.feature_count = features.length;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Knowledge
|
|
72
|
+
// Knowledge, research
|
|
73
73
|
const researchDir = path.join(atrisDir, 'research');
|
|
74
74
|
if (fs.existsSync(researchDir)) {
|
|
75
75
|
soul.knowledge.research_files = countFiles(researchDir);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// Knowledge
|
|
78
|
+
// Knowledge, refs
|
|
79
79
|
const refsDir = path.join(atrisDir, 'refs');
|
|
80
80
|
if (fs.existsSync(refsDir)) {
|
|
81
81
|
soul.knowledge.refs = fs.readdirSync(refsDir).filter(f => f.endsWith('.md'));
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// Learned
|
|
84
|
+
// Learned, policies
|
|
85
85
|
const policiesDir = path.join(atrisDir, 'policies');
|
|
86
86
|
if (fs.existsSync(policiesDir)) {
|
|
87
87
|
soul.learned.policies = fs.readdirSync(policiesDir).filter(f => f.endsWith('.md'));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Learned
|
|
90
|
+
// Learned, logs (journal depth)
|
|
91
91
|
const logsDir = path.join(atrisDir, 'logs');
|
|
92
92
|
if (fs.existsSync(logsDir)) {
|
|
93
93
|
soul.learned.journal_entries = countFiles(logsDir);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
// Learned
|
|
96
|
+
// Learned, lessons
|
|
97
97
|
const lessons = readFile(path.join(atrisDir, 'lessons.md'));
|
|
98
98
|
if (lessons) {
|
|
99
99
|
const lessonCount = (lessons.match(/^-/gm) || []).length;
|
|
@@ -298,7 +298,7 @@ function distillSoul(atrisDir) {
|
|
|
298
298
|
|
|
299
299
|
function showSoulHelp() {
|
|
300
300
|
console.log('');
|
|
301
|
-
console.log(' atris soul
|
|
301
|
+
console.log(' atris soul, see what your project has learned');
|
|
302
302
|
console.log('');
|
|
303
303
|
console.log(' soul show identity, knowledge, learnings');
|
|
304
304
|
console.log(' soul snapshot export full soul to JSON (auto-gitignored)');
|
|
@@ -338,11 +338,11 @@ async function soul(args = []) {
|
|
|
338
338
|
try {
|
|
339
339
|
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
340
340
|
if (!existing.includes('soul-snapshot.json')) {
|
|
341
|
-
fs.appendFileSync(gitignorePath, '\n# Atris soul
|
|
341
|
+
fs.appendFileSync(gitignorePath, '\n# Atris soul, private, never commit\natris/soul-snapshot.json\n');
|
|
342
342
|
}
|
|
343
343
|
} catch {}
|
|
344
344
|
console.log(`✓ Soul snapshot saved to ${outPath}`);
|
|
345
|
-
console.log(` (auto-added to .gitignore
|
|
345
|
+
console.log(` (auto-added to .gitignore, this stays private)`);
|
|
346
346
|
break;
|
|
347
347
|
}
|
|
348
348
|
case 'fork': {
|
package/commands/spaceship.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* atris spaceship
|
|
4
|
+
* atris spaceship, bounded, self-reporting overnight runner.
|
|
5
5
|
*
|
|
6
6
|
* Thin wrapper over scripts/spaceship.sh. The script is the implementation
|
|
7
7
|
* (a supervised loop that survives bad ticks and emails Keshav on every
|
package/commands/status.js
CHANGED
|
@@ -234,7 +234,7 @@ function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
|
|
|
234
234
|
const teamActivity = getTeamActivity(targetDir);
|
|
235
235
|
const checkoutWarning = checkoutBehindMessage(process.cwd());
|
|
236
236
|
|
|
237
|
-
// JSON mode
|
|
237
|
+
// JSON mode: structured output for scripting
|
|
238
238
|
if (jsonMode) {
|
|
239
239
|
const output = {
|
|
240
240
|
date: dateFormatted,
|
|
@@ -324,7 +324,7 @@ function statusAtris(isQuick = false, jsonMode = false, verbose = false) {
|
|
|
324
324
|
|
|
325
325
|
o('');
|
|
326
326
|
o(`┌─${'─'.repeat(W)}─┐`);
|
|
327
|
-
o(`│ ${pad(`TASK BOARD
|
|
327
|
+
o(`│ ${pad(`TASK BOARD, ${dateFormatted}`)} │`);
|
|
328
328
|
o(`├─${'─'.repeat(W)}─┤`);
|
|
329
329
|
|
|
330
330
|
// Backlog
|
package/commands/strings.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// atris strings
|
|
1
|
+
// atris strings, a content design system built from live codebase content (no LLM).
|
|
2
2
|
//
|
|
3
3
|
// The missing pillar next to `atris slop`: slop catches HOW copy is written (tells,
|
|
4
4
|
// hype, em-dashes); strings governs WHAT words you ship. It scans the repo for
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// enforces preferred terms at the commit/PR gate so you rename "live" -> "active"
|
|
8
8
|
// once and it holds everywhere.
|
|
9
9
|
//
|
|
10
|
-
// Zero external deps (Node built-ins only)
|
|
10
|
+
// Zero external deps (Node built-ins only), repo contract. Deterministic: a finding
|
|
11
11
|
// is a fact (file:line + term), not a taste opinion, so it drops into CI + the gate.
|
|
12
12
|
//
|
|
13
13
|
// Usage:
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const fs = require('fs');
|
|
23
23
|
const path = require('path');
|
|
24
|
-
const { gitChangedLines } = require('./slop'); // reuse the diff parser
|
|
24
|
+
const { gitChangedLines } = require('./slop'); // reuse the diff parser, DRY
|
|
25
25
|
const escapeRegExp = require('../lib/escape-regexp');
|
|
26
26
|
|
|
27
27
|
const CODE_EXTS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs', '.vue', '.svelte', '.astro', '.html']);
|
|
@@ -160,7 +160,7 @@ function scan(argv) {
|
|
|
160
160
|
}
|
|
161
161
|
console.log(`\n scanned ${files.length} file${files.length === 1 ? '' : 's'} -> ${reg.strings.length} unique string${reg.strings.length === 1 ? '' : 's'}`);
|
|
162
162
|
console.log(` registry: ${path.relative(process.cwd(), file)}`);
|
|
163
|
-
if (clusters.length) console.log(`\n ⚠ ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'} (same string, different casing/punctuation)
|
|
163
|
+
if (clusters.length) console.log(`\n ⚠ ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'} (same string, different casing/punctuation), run: atris strings variants`);
|
|
164
164
|
else console.log(`\n ✓ no inconsistent variants`);
|
|
165
165
|
console.log('');
|
|
166
166
|
return 0;
|
|
@@ -169,11 +169,11 @@ function scan(argv) {
|
|
|
169
169
|
function variants(argv) {
|
|
170
170
|
const json = argv.includes('--json');
|
|
171
171
|
const reg = loadRegistry();
|
|
172
|
-
if (!reg.strings.length) { console.error(' no registry yet
|
|
172
|
+
if (!reg.strings.length) { console.error(' no registry yet, run: atris strings scan'); return 2; }
|
|
173
173
|
const clusters = variantClusters(reg.strings);
|
|
174
174
|
if (json) { console.log(JSON.stringify({ ok: clusters.length === 0, clusters }, null, 2)); return clusters.length ? 1 : 0; }
|
|
175
|
-
if (!clusters.length) { console.log(`\n ✓ clean
|
|
176
|
-
console.log(`\n ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'}
|
|
175
|
+
if (!clusters.length) { console.log(`\n ✓ clean, every string is written one way\n`); return 0; }
|
|
176
|
+
console.log(`\n ${clusters.length} variant cluster${clusters.length === 1 ? '' : 's'}, same string, inconsistent surface form:\n`);
|
|
177
177
|
for (const c of clusters) {
|
|
178
178
|
console.log(` ⚠ ${c.surfaces.map((s) => JSON.stringify(s)).join(' vs ')} (${c.count}×)`);
|
|
179
179
|
}
|
|
@@ -188,7 +188,7 @@ function term(argv) {
|
|
|
188
188
|
const why = get('--why') || '';
|
|
189
189
|
if (argv.includes('--list') || (!ban && !argv.includes('--remove'))) {
|
|
190
190
|
const reg = loadRegistry();
|
|
191
|
-
if (!reg.terms.length) { console.log('\n no terms yet
|
|
191
|
+
if (!reg.terms.length) { console.log('\n no terms yet, add one: atris strings term --ban "live" --prefer "active"\n'); return 0; }
|
|
192
192
|
console.log('\n preferred terms (enforced by: atris strings check):\n');
|
|
193
193
|
for (const t of reg.terms) console.log(` ✗ "${t.ban}" → "${t.prefer}"${t.why ? ` (${t.why})` : ''}`);
|
|
194
194
|
console.log('');
|
|
@@ -219,7 +219,7 @@ function check(argv) {
|
|
|
219
219
|
const reg = loadRegistry();
|
|
220
220
|
if (!reg.terms.length) {
|
|
221
221
|
if (json) { console.log(JSON.stringify({ ok: true, terms: 0, findings: [] }, null, 2)); }
|
|
222
|
-
else if (!quiet) console.log('\n no terms to enforce
|
|
222
|
+
else if (!quiet) console.log('\n no terms to enforce, add one: atris strings term --ban "live" --prefer "active"\n');
|
|
223
223
|
return 0;
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -248,7 +248,7 @@ function check(argv) {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
if (json) { console.log(JSON.stringify({ ok: findings.length === 0, scanned: files.length, terms: reg.terms.length, findings }, null, 2)); return findings.length ? 1 : 0; }
|
|
251
|
-
if (!findings.length) { if (!quiet) console.log(`\n ✓ clean
|
|
251
|
+
if (!findings.length) { if (!quiet) console.log(`\n ✓ clean, no banned terms in ${files.length} file${files.length === 1 ? '' : 's'}\n`); else console.log(' ✓ clean · exit 0'); return 0; }
|
|
252
252
|
if (!quiet) {
|
|
253
253
|
console.log('');
|
|
254
254
|
const w = Math.max(...findings.map((f) => `${f.file}:${f.line}`.length));
|
|
@@ -262,7 +262,7 @@ function list(argv) {
|
|
|
262
262
|
const json = argv.includes('--json');
|
|
263
263
|
const reg = loadRegistry();
|
|
264
264
|
if (json) { console.log(JSON.stringify(reg, null, 2)); return 0; }
|
|
265
|
-
if (!reg.strings.length) { console.error(' no registry yet
|
|
265
|
+
if (!reg.strings.length) { console.error(' no registry yet, run: atris strings scan'); return 2; }
|
|
266
266
|
const top = Number((argv[argv.indexOf('--top') + 1]) || 30);
|
|
267
267
|
console.log(`\n ${reg.strings.length} strings (scanned ${reg.scannedAt || '?'}), top ${Math.min(top, reg.strings.length)} by use:\n`);
|
|
268
268
|
for (const s of reg.strings.slice(0, top)) console.log(` ${String(s.count).padStart(3)}× ${JSON.stringify(s.text)}`);
|
|
@@ -279,7 +279,7 @@ function stringsCommand(argv) {
|
|
|
279
279
|
if (sub === 'check' || sub === 'gate') return check(rest);
|
|
280
280
|
if (sub === 'list' || sub === 'ls') return list(rest);
|
|
281
281
|
console.log(`
|
|
282
|
-
atris strings
|
|
282
|
+
atris strings, a content design system from your live codebase (no LLM)
|
|
283
283
|
|
|
284
284
|
atris strings scan [path] extract user-facing strings -> .atris/strings.json
|
|
285
285
|
atris strings variants the same string written N different ways (pick one)
|
|
@@ -289,7 +289,7 @@ function stringsCommand(argv) {
|
|
|
289
289
|
atris strings list [--top N] the registry, most-used first
|
|
290
290
|
add --json to scan/variants/check/list for machine output
|
|
291
291
|
|
|
292
|
-
Pairs with 'atris slop' (how copy reads)
|
|
292
|
+
Pairs with 'atris slop' (how copy reads), strings governs what words you ship.
|
|
293
293
|
The registry lives in .atris/strings.json. Wire 'check --staged' into the pre-commit gate.
|
|
294
294
|
`);
|
|
295
295
|
return 0;
|
package/commands/study.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* atris study
|
|
4
|
+
* atris study, on-demand learning feed + tutor-loop routing + waiting-room cards.
|
|
5
5
|
*
|
|
6
6
|
* atris study <topic…> [--personal]
|
|
7
7
|
* atris study --while "<cmd>" [topic…] [--personal]
|
|
@@ -33,7 +33,7 @@ const LANGUAGE_TOPICS = new Set([
|
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
35
|
function showHelp() {
|
|
36
|
-
console.log(`\n atris study
|
|
36
|
+
console.log(`\n atris study, learn a topic on demand (learning feed + tutor loop)\n
|
|
37
37
|
Usage:
|
|
38
38
|
atris study <topic…> [--personal]
|
|
39
39
|
atris study --while "<cmd>" [topic…] [--personal]
|
|
@@ -48,7 +48,7 @@ function showHelp() {
|
|
|
48
48
|
Space advances. q stops cards and live-streams build output.
|
|
49
49
|
Ctrl+C kills the child and replays output so far (exit 130).
|
|
50
50
|
Non-TTY stdin = plain passthrough (no cards). If the feed
|
|
51
|
-
server is down, the command still runs
|
|
51
|
+
server is down, the command still runs, cards are skipped.
|
|
52
52
|
|
|
53
53
|
--personal also scan workspace briefs for personal-edge cards
|
|
54
54
|
|
|
@@ -146,7 +146,7 @@ function printTutorLoopBlock(backendRoot, language) {
|
|
|
146
146
|
console.log(' venv/bin/python experiments/tutor-loop/tutor.py scan');
|
|
147
147
|
|
|
148
148
|
if (language !== 'spanish') {
|
|
149
|
-
console.log('\n curriculum not yet built
|
|
149
|
+
console.log('\n curriculum not yet built, spanish ships today');
|
|
150
150
|
}
|
|
151
151
|
console.log('');
|
|
152
152
|
}
|
package/commands/sync.js
CHANGED
|
@@ -372,7 +372,7 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
372
372
|
|
|
373
373
|
// Skills: sync the canonical skill set from atris-cli package into the
|
|
374
374
|
// customer workspace. Business-starter template ships skill infra (README,
|
|
375
|
-
// folders) but skill files live in atris-cli/atris/skills
|
|
375
|
+
// folders) but skill files live in atris-cli/atris/skills/, single source
|
|
376
376
|
// of truth. Any new skill (e.g. AEO) auto-propagates to every customer.
|
|
377
377
|
const skillsUpdated = syncPackageSkills(targetAtrisDir, { verbose: false, dryRun });
|
|
378
378
|
|
|
@@ -730,8 +730,8 @@ function syncRecursiveCount(src, dest, label, silent) {
|
|
|
730
730
|
|
|
731
731
|
/**
|
|
732
732
|
* Lightweight skill-only sync. Syncs skills from the npm package to:
|
|
733
|
-
* 1. Global skill dirs (~/.claude/skills/, ~/.codex/skills/)
|
|
734
|
-
* 2. Project-level (atris/skills/ + .claude/skills/ symlinks)
|
|
733
|
+
* 1. Global skill dirs (~/.claude/skills/, ~/.codex/skills/), always, if they exist
|
|
734
|
+
* 2. Project-level (atris/skills/ + .claude/skills/ symlinks), if in a project
|
|
735
735
|
*
|
|
736
736
|
* Global = baseline truth. Project = optional override.
|
|
737
737
|
* Returns number of files updated (0 = already current).
|
|
@@ -776,7 +776,7 @@ function syncSkills({ silent = false } = {}) {
|
|
|
776
776
|
// contain ONLY business-specific custom skills in atris/skills/.
|
|
777
777
|
const businessJson = path.join(process.cwd(), '.atris', 'business.json');
|
|
778
778
|
if (fs.existsSync(businessJson)) {
|
|
779
|
-
// We're inside a business workspace
|
|
779
|
+
// We're inside a business workspace, skip project-level skill sync.
|
|
780
780
|
return updated;
|
|
781
781
|
}
|
|
782
782
|
|
|
@@ -867,7 +867,7 @@ const SYNC_ALL_FILES = [
|
|
|
867
867
|
* No console output, no writes. Returns { projects, plan } for inspection.
|
|
868
868
|
* Plan entries: { projectRoot, isBusiness, isCustomized, changes }.
|
|
869
869
|
*
|
|
870
|
-
* Exported for tests
|
|
870
|
+
* Exported for tests, the production syncAtrisAll wraps this.
|
|
871
871
|
*/
|
|
872
872
|
function buildSyncAllPlan({ root, pkgRoot, filesToSync = SYNC_ALL_FILES } = {}) {
|
|
873
873
|
const projects = _findAtrisProjects(root);
|
|
@@ -935,16 +935,16 @@ function syncAtrisAll({ dryRun = false, force = false, yes = false } = {}) {
|
|
|
935
935
|
for (const p of plan) {
|
|
936
936
|
const rel = path.relative(root, p.projectRoot) || '.';
|
|
937
937
|
if (p.isBusiness) {
|
|
938
|
-
console.log(` ⏭ ${rel} (business workspace
|
|
938
|
+
console.log(` ⏭ ${rel} (business workspace, run "atris update" in that dir)`);
|
|
939
939
|
skipped++;
|
|
940
940
|
} else if (p.isCustomized) {
|
|
941
|
-
console.log(` ⏭ ${rel} (customized atris.md
|
|
941
|
+
console.log(` ⏭ ${rel} (customized atris.md, review manually)`);
|
|
942
942
|
skipped++;
|
|
943
943
|
} else if (p.changes.length === 0) {
|
|
944
944
|
console.log(` · ${rel} (up to date)`);
|
|
945
945
|
unchanged++;
|
|
946
946
|
} else {
|
|
947
|
-
console.log(` → ${rel}
|
|
947
|
+
console.log(` → ${rel}: ${p.changes.length} ${p.changes.length === 1 ? 'file' : 'files'}: ${p.changes.join(', ')}`);
|
|
948
948
|
wouldUpdate++;
|
|
949
949
|
}
|
|
950
950
|
}
|