atris 3.16.1 → 3.17.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.
Files changed (58) hide show
  1. package/README.md +32 -7
  2. package/atris/skills/atris/SKILL.md +15 -2
  3. package/atris/skills/atris-feedback/SKILL.md +7 -0
  4. package/atris/skills/design/SKILL.md +29 -2
  5. package/atris/skills/engines/SKILL.md +44 -0
  6. package/atris/skills/flow/SKILL.md +1 -1
  7. package/atris/skills/wake/SKILL.md +37 -0
  8. package/atris/skills/youtube/SKILL.md +13 -39
  9. package/atris/team/validator/MEMBER.md +1 -0
  10. package/atris/wiki/concepts/agent-activation-contract.md +3 -3
  11. package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
  12. package/atris/wiki/index.md +1 -0
  13. package/atris.md +43 -19
  14. package/bin/atris.js +400 -30
  15. package/commands/agent-spawn.js +480 -0
  16. package/commands/analytics.js +6 -3
  17. package/commands/apps.js +11 -0
  18. package/commands/autopilot.js +42 -18
  19. package/commands/brain.js +74 -7
  20. package/commands/brainstorm.js +9 -58
  21. package/commands/clean.js +1 -4
  22. package/commands/compile.js +9 -4
  23. package/commands/console.js +8 -3
  24. package/commands/deck.js +135 -0
  25. package/commands/init.js +22 -11
  26. package/commands/lesson.js +76 -0
  27. package/commands/member.js +252 -48
  28. package/commands/mission.js +405 -13
  29. package/commands/now.js +4 -2
  30. package/commands/probe.js +105 -27
  31. package/commands/pulse.js +504 -0
  32. package/commands/radar.js +1 -0
  33. package/commands/recap.js +55 -25
  34. package/commands/run.js +615 -22
  35. package/commands/slop.js +173 -0
  36. package/commands/spaceship.js +39 -0
  37. package/commands/sync.js +0 -2
  38. package/commands/task.js +429 -37
  39. package/commands/verify.js +7 -3
  40. package/lib/activity-stream.js +166 -0
  41. package/lib/auto-accept-certified.js +23 -1
  42. package/lib/context-gatherer.js +170 -0
  43. package/lib/escape-regexp.js +13 -0
  44. package/lib/file-ops.js +6 -3
  45. package/lib/journal.js +1 -1
  46. package/lib/lesson-contradiction.js +113 -0
  47. package/lib/policy-lessons.js +3 -2
  48. package/lib/pulse.js +401 -0
  49. package/lib/runner-command.js +156 -0
  50. package/lib/slides-deck.js +236 -0
  51. package/lib/state-detection.js +1 -4
  52. package/lib/task-db.js +101 -4
  53. package/lib/task-proof.js +1 -1
  54. package/lib/todo-fallback.js +2 -1
  55. package/lib/todo-sections.js +33 -0
  56. package/package.json +1 -2
  57. package/utils/api.js +14 -2
  58. package/atris/atrisDev.md +0 -717
@@ -0,0 +1,173 @@
1
+ // atris slop — deterministic frontend-slop detector (no LLM).
2
+ //
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 — so it
5
+ // drops straight into the autopilot/review verification gate and CI. Each finding
6
+ // is the seed of a typed lesson; the ruleset is meant to GROW from lessons.md
7
+ // rather than be hand-curated forever.
8
+ //
9
+ // Zero external deps (Node built-ins only) — repo contract.
10
+ //
11
+ // Usage:
12
+ // atris slop detect [path] # scan a file or dir (default: .)
13
+ // atris slop detect src/ --json # machine output for CI / the loop
14
+ // atris slop detect src/ --quiet # only print the summary line
15
+ //
16
+ // Exit code: 0 = clean, 1 = slop found, 2 = bad usage. CI/PR gates read this.
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const SCAN_EXTS = new Set(['.css', '.scss', '.sass', '.less', '.tsx', '.jsx', '.ts', '.js', '.mjs', '.html', '.vue', '.svelte', '.astro',
22
+ '.md', '.mdx', '.txt']); // prose too: the voice doctrine (em-dash, hype-copy) is enforceable, not just advice
23
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.astro', 'coverage', '.cache', 'out', 'vendor']);
24
+
25
+ // Each rule is deterministic: a regex + a one-line why. severity drives the icon.
26
+ // Kept high-precision on purpose — a noisy gate gets muted, and a muted gate is dead.
27
+ const RULES = [
28
+ { id: 'ai-gradient-text', sev: 'error',
29
+ 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,
30
+ why: 'gradient-filled text headline: the #1 generated-look tell' },
31
+ { id: 'ai-purple-gradient', sev: 'error',
32
+ re: /((from|via|to)-(purple|violet|indigo|fuchsia)-\d{2,3}\b|linear-gradient\([^)]*(#6366f1|#8b5cf6|#a855f7|#7c3aed|#4f46e5|\bpurple\b|\bviolet\b|\bindigo\b))/i,
33
+ why: 'purple/indigo gradient: default "AI startup" palette' },
34
+ { id: 'ai-indigo-brand', sev: 'warn',
35
+ re: /(#6366f1|#4f46e5|#4338ca|(?:bg|text|border|from|to|ring)-indigo-(?:500|600|700)\b)/i,
36
+ why: 'canonical AI indigo used as brand color' },
37
+ { id: 'glassmorphism', sev: 'warn',
38
+ re: /(backdrop-blur(?:-\w+)?\b|backdrop-filter:\s*blur)/i,
39
+ why: 'glassmorphism (frosted blur): overused default' },
40
+ { id: 'over-rounding', sev: 'warn',
41
+ re: /(rounded-(?:3xl|\[(?:[2-9]\d?|1\d\d)(?:px|rem)\])|border-radius:\s*(?:2[4-9]|[3-9]\d|\d{3})px|border-radius:\s*(?:[2-9](?:\.\d+)?)rem)/i,
42
+ why: 'over-rounded corners (>=24px / rounded-3xl)' },
43
+ { id: 'mega-shadow', sev: 'warn',
44
+ re: /(shadow-2xl\b|box-shadow:\s*0\s+\d{2,}px)/i,
45
+ why: 'oversized generic drop shadow (depth-by-blur)' },
46
+ { id: 'side-stripe-card', sev: 'warn',
47
+ re: /border-(?:left|l)-(?:4|8|\[\d+px\])\b|border-left:\s*[3-9]px\s+solid/i,
48
+ why: 'accent side-stripe on a card: generated layout reflex' },
49
+ { id: 'transition-all', sev: 'warn',
50
+ re: /\btransition-all\b|transition:\s*all\b/i,
51
+ why: 'transition-all: animate-everything laziness, not intent' },
52
+ { id: 'pulse-animation', sev: 'warn',
53
+ re: /animation:[^;]*\binfinite\b|@keyframes\s+(pulse|ping|blink|glow|throb)\b|\banimate-(pulse|ping|bounce)\b/i,
54
+ why: 'looping pulse/ping/glow animation: distracting live-status reflex' },
55
+ { id: 'eyebrow-caps', sev: 'warn',
56
+ re: /text-transform:\s*uppercase\b|\buppercase\b[^"'`]{0,30}tracking-|tracking-[^"'`]{0,30}\buppercase\b/i,
57
+ why: 'tracked all-caps eyebrow/label: dated reflex; use sentence case' },
58
+ { id: 'decorative-emoji', sev: 'warn',
59
+ re: /[✨\u{1F680}\u{1F4A1}\u{1F525}\u{1F389}⚡\u{1F31F}\u{1FA84}\u{1F4AB}\u{1F44B}]/u,
60
+ why: 'decorative emoji in UI copy' },
61
+ { id: 'em-dash', sev: 'warn',
62
+ re: /—/,
63
+ why: 'em dash: a top AI-writing tell; use a comma, colon, or period' },
64
+ { id: 'hype-copy', sev: 'error',
65
+ 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,
66
+ why: 'hype/marketing slop phrase: say the specific thing instead' },
67
+ ];
68
+
69
+ const ICON = { error: '✗', warn: '⚠' }; // ✗ ⚠
70
+
71
+ function walk(target, out) {
72
+ let stat;
73
+ try { stat = fs.statSync(target); } catch { return out; }
74
+ if (stat.isFile()) {
75
+ if (SCAN_EXTS.has(path.extname(target))) out.push(target);
76
+ return out;
77
+ }
78
+ if (stat.isDirectory()) {
79
+ if (SKIP_DIRS.has(path.basename(target))) return out;
80
+ for (const name of fs.readdirSync(target)) {
81
+ if (name.startsWith('.') && name !== '.') continue;
82
+ walk(path.join(target, name), out);
83
+ }
84
+ }
85
+ return out;
86
+ }
87
+
88
+ function scanFile(file) {
89
+ const findings = [];
90
+ let text;
91
+ try { text = fs.readFileSync(file, 'utf8'); } catch { return findings; }
92
+ const lines = text.split('\n');
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i];
95
+ for (const rule of RULES) {
96
+ const m = rule.re.exec(line);
97
+ if (m) {
98
+ findings.push({
99
+ file, line: i + 1, rule: rule.id, sev: rule.sev, why: rule.why,
100
+ snippet: m[0].trim().slice(0, 48),
101
+ });
102
+ }
103
+ }
104
+ }
105
+ return findings;
106
+ }
107
+
108
+ function detect(argv) {
109
+ const json = argv.includes('--json');
110
+ const quiet = argv.includes('--quiet');
111
+ const target = argv.find((a) => !a.startsWith('-')) || '.';
112
+
113
+ const files = walk(path.resolve(target), []);
114
+ const findings = files.flatMap(scanFile);
115
+ const errors = findings.filter((f) => f.sev === 'error').length;
116
+
117
+ if (json) {
118
+ console.log(JSON.stringify({
119
+ ok: findings.length === 0, scanned: files.length,
120
+ slop: findings.length, errors,
121
+ findings: findings.map((f) => ({ ...f, file: path.relative(process.cwd(), f.file) })),
122
+ }, null, 2));
123
+ return findings.length ? 1 : 0;
124
+ }
125
+
126
+ const rel = (f) => path.relative(process.cwd(), f);
127
+ if (!quiet) {
128
+ if (!findings.length) {
129
+ console.log(`\n ✓ clean — no slop tells in ${files.length} file${files.length === 1 ? '' : 's'}`);
130
+ } else {
131
+ console.log('');
132
+ const w = Math.max(...findings.map((f) => `${rel(f.file)}:${f.line}`.length));
133
+ for (const f of findings) {
134
+ const loc = `${rel(f.file)}:${f.line}`.padEnd(w);
135
+ console.log(` ${ICON[f.sev]} ${loc} ${f.rule.padEnd(20)} ${f.why}`);
136
+ }
137
+ }
138
+ }
139
+ if (findings.length) {
140
+ console.log(`\n ${findings.length} slop tell${findings.length === 1 ? '' : 's'} (${errors} error) · exit 1\n`);
141
+ } else if (quiet) {
142
+ console.log(` ✓ clean · exit 0`);
143
+ }
144
+ return findings.length ? 1 : 0;
145
+ }
146
+
147
+ function slopCommand(argv) {
148
+ const sub = argv[0];
149
+ if (!sub || sub === 'detect' || sub.startsWith('-') || !['detect', 'rules', 'help'].includes(sub)) {
150
+ // default + `detect`: scan. Bare `atris slop` scans cwd too.
151
+ const rest = sub === 'detect' ? argv.slice(1) : argv;
152
+ return detect(rest);
153
+ }
154
+ if (sub === 'rules') {
155
+ console.log('\n atris slop — deterministic rules:\n');
156
+ for (const r of RULES) console.log(` ${ICON[r.sev]} ${r.id.padEnd(20)} ${r.why}`);
157
+ console.log('');
158
+ return 0;
159
+ }
160
+ // help
161
+ console.log(`
162
+ atris slop — deterministic frontend-slop detector (no LLM)
163
+
164
+ atris slop detect [path] scan a file or dir (default: .)
165
+ atris slop detect src --json machine output for CI / the loop
166
+ atris slop rules list the active rules
167
+
168
+ exit 0 = clean, 1 = slop found. Wire into PR checks and the autopilot gate.
169
+ `);
170
+ return 0;
171
+ }
172
+
173
+ module.exports = { slopCommand, detect, scanFile, RULES };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * atris spaceship — bounded, self-reporting overnight runner.
3
+ *
4
+ * Thin wrapper over scripts/spaceship.sh. The script is the implementation
5
+ * (a supervised loop that survives bad ticks and emails Keshav on every
6
+ * meaningful state change); this module just makes it reachable as
7
+ * `atris spaceship ...` and integrates with the CLI's command dispatch.
8
+ *
9
+ * Examples:
10
+ * atris spaceship --hours 4
11
+ * atris spaceship --hours 4 --repo /path/to/repo --interval 780
12
+ * atris spaceship --hours 0.01 --tick-cmd /tmp/stub.sh --no-email # test
13
+ */
14
+
15
+ const path = require('path');
16
+ const fs = require('fs');
17
+ const { spawn } = require('child_process');
18
+
19
+ const SCRIPT = path.join(__dirname, '..', 'scripts', 'spaceship.sh');
20
+
21
+ function spaceship(args = []) {
22
+ return new Promise((resolve, reject) => {
23
+ if (!fs.existsSync(SCRIPT)) {
24
+ reject(new Error(`spaceship.sh not found at ${SCRIPT}`));
25
+ return;
26
+ }
27
+ const child = spawn('bash', [SCRIPT, ...args], {
28
+ stdio: 'inherit',
29
+ env: process.env,
30
+ });
31
+ child.on('error', reject);
32
+ child.on('close', (code) => {
33
+ if (code === 0) resolve({ success: true });
34
+ else reject(new Error(`spaceship exited with code ${code}`));
35
+ });
36
+ });
37
+ }
38
+
39
+ module.exports = { spaceship, SCRIPT };
package/commands/sync.js CHANGED
@@ -463,7 +463,6 @@ function syncAtris() {
463
463
 
464
464
  const filesToSync = [
465
465
  { source: 'atris.md', target: 'atris.md' },
466
- { source: 'atris/atrisDev.md', target: 'atrisDev.md' },
467
466
  { source: 'PERSONA.md', target: 'PERSONA.md' },
468
467
  { source: 'GETTING_STARTED.md', target: 'GETTING_STARTED.md' },
469
468
  { source: 'atris/CLAUDE.md', target: 'CLAUDE.md' },
@@ -808,7 +807,6 @@ function _findAtrisProjects(rootDir, maxDepth = 8) {
808
807
  // Canonical files shipped from the package root. Must match syncAtris's filesToSync.
809
808
  const SYNC_ALL_FILES = [
810
809
  { source: 'atris.md', target: 'atris.md' },
811
- { source: 'atris/atrisDev.md', target: 'atrisDev.md' },
812
810
  { source: 'PERSONA.md', target: 'PERSONA.md' },
813
811
  { source: 'GETTING_STARTED.md', target: 'GETTING_STARTED.md' },
814
812
  { source: 'atris/CLAUDE.md', target: 'CLAUDE.md' },