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.
- package/README.md +32 -7
- package/atris/skills/atris/SKILL.md +15 -2
- package/atris/skills/atris-feedback/SKILL.md +7 -0
- package/atris/skills/design/SKILL.md +29 -2
- package/atris/skills/engines/SKILL.md +44 -0
- package/atris/skills/flow/SKILL.md +1 -1
- package/atris/skills/wake/SKILL.md +37 -0
- package/atris/skills/youtube/SKILL.md +13 -39
- package/atris/team/validator/MEMBER.md +1 -0
- package/atris/wiki/concepts/agent-activation-contract.md +3 -3
- package/atris/wiki/concepts/workspace-initialization-contract.md +3 -3
- package/atris/wiki/index.md +1 -0
- package/atris.md +43 -19
- package/bin/atris.js +400 -30
- package/commands/agent-spawn.js +480 -0
- package/commands/analytics.js +6 -3
- package/commands/apps.js +11 -0
- package/commands/autopilot.js +42 -18
- package/commands/brain.js +74 -7
- package/commands/brainstorm.js +9 -58
- package/commands/clean.js +1 -4
- package/commands/compile.js +9 -4
- package/commands/console.js +8 -3
- package/commands/deck.js +135 -0
- package/commands/init.js +22 -11
- package/commands/lesson.js +76 -0
- package/commands/member.js +252 -48
- package/commands/mission.js +405 -13
- package/commands/now.js +4 -2
- package/commands/probe.js +105 -27
- package/commands/pulse.js +504 -0
- package/commands/radar.js +1 -0
- package/commands/recap.js +55 -25
- package/commands/run.js +615 -22
- package/commands/slop.js +173 -0
- package/commands/spaceship.js +39 -0
- package/commands/sync.js +0 -2
- package/commands/task.js +429 -37
- package/commands/verify.js +7 -3
- package/lib/activity-stream.js +166 -0
- package/lib/auto-accept-certified.js +23 -1
- package/lib/context-gatherer.js +170 -0
- package/lib/escape-regexp.js +13 -0
- package/lib/file-ops.js +6 -3
- package/lib/journal.js +1 -1
- package/lib/lesson-contradiction.js +113 -0
- package/lib/policy-lessons.js +3 -2
- package/lib/pulse.js +401 -0
- package/lib/runner-command.js +156 -0
- package/lib/slides-deck.js +236 -0
- package/lib/state-detection.js +1 -4
- package/lib/task-db.js +101 -4
- package/lib/task-proof.js +1 -1
- package/lib/todo-fallback.js +2 -1
- package/lib/todo-sections.js +33 -0
- package/package.json +1 -2
- package/utils/api.js +14 -2
- package/atris/atrisDev.md +0 -717
package/commands/slop.js
ADDED
|
@@ -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' },
|