atris 3.35.0 → 3.37.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 +37 -0
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +22 -4
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +2 -0
- package/ax +147 -19
- package/bin/atris.js +576 -267
- package/commands/activate.js +194 -88
- package/commands/agents.js +166 -0
- package/commands/autoland.js +459 -107
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/engine.js +993 -32
- package/commands/experiments.js +28 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/gm.js +23 -0
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +72 -44
- package/commands/interview.js +67 -1
- package/commands/land.js +152 -52
- package/commands/lifecycle.js +39 -3
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +511 -34
- package/commands/mission.js +3144 -338
- package/commands/next.js +137 -0
- package/commands/now.js +220 -25
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/stream.js +861 -0
- package/commands/study.js +693 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +1346 -117
- package/commands/team.js +73 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +234 -13
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +331 -34
- package/lib/autoland.js +319 -54
- package/lib/ax-auto-lane.js +96 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2219 -67
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/next-moves.js +327 -10
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +10 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +108 -29
- package/lib/task-proof.js +23 -1
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
package/lib/usage.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { knownCommands } = require('./known-commands');
|
|
6
|
+
|
|
7
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
const USAGE_RELATIVE_PATH = path.join('.atris', 'state', 'usage.jsonl');
|
|
9
|
+
const USAGE_MAX_BYTES = 5 * 1024 * 1024;
|
|
10
|
+
|
|
11
|
+
// Usage telemetry used to write to `<cwd>/.atris/state/usage.jsonl`, so a
|
|
12
|
+
// command run from a subdirectory that already had a nested .atris (e.g.
|
|
13
|
+
// backend/) split its usage away from the workspace root's store — the same
|
|
14
|
+
// footgun the mission and task stores hit. Resolve to the shared workspace
|
|
15
|
+
// root (spine-first -> git toplevel -> cwd) so usage lands with the rest of the
|
|
16
|
+
// state, whichever subdir the caller stood in. Falls back to the raw cwd if the
|
|
17
|
+
// shared resolver can't load; telemetry must never break the command path.
|
|
18
|
+
function resolveUsageRoot(cwd = process.cwd()) {
|
|
19
|
+
try {
|
|
20
|
+
const { workspaceRoot } = require('./task-db');
|
|
21
|
+
const root = workspaceRoot(cwd);
|
|
22
|
+
if (root) return root;
|
|
23
|
+
} catch {}
|
|
24
|
+
return cwd;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function usagePath(cwd = process.cwd()) {
|
|
28
|
+
return path.join(resolveUsageRoot(cwd), USAGE_RELATIVE_PATH);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseUsageLine(line) {
|
|
32
|
+
try {
|
|
33
|
+
const entry = JSON.parse(line);
|
|
34
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
35
|
+
if (typeof entry.at !== 'string' || typeof entry.cmd !== 'string') return null;
|
|
36
|
+
return entry;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readAllUsage(cwd = process.cwd()) {
|
|
43
|
+
const file = usagePath(cwd);
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(file)) return [];
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const out = [];
|
|
50
|
+
try {
|
|
51
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed) continue;
|
|
54
|
+
const entry = parseUsageLine(trimmed);
|
|
55
|
+
if (entry) out.push(entry);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readUsage(cwd = process.cwd(), options = {}) {
|
|
64
|
+
const entries = readAllUsage(cwd);
|
|
65
|
+
const sinceDays = Number(options.sinceDays);
|
|
66
|
+
if (!Number.isFinite(sinceDays) || sinceDays <= 0) return entries;
|
|
67
|
+
const now = options.now ? new Date(options.now).getTime() : Date.now();
|
|
68
|
+
const cutoff = now - sinceDays * DAY_MS;
|
|
69
|
+
return entries.filter((entry) => {
|
|
70
|
+
const at = Date.parse(entry.at);
|
|
71
|
+
return Number.isFinite(at) && at >= cutoff;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function trimUsageFile(cwd = process.cwd()) {
|
|
76
|
+
const file = usagePath(cwd);
|
|
77
|
+
const stat = fs.statSync(file);
|
|
78
|
+
if (stat.size <= USAGE_MAX_BYTES) return;
|
|
79
|
+
const cutoff = Date.now() - 30 * DAY_MS;
|
|
80
|
+
const kept = readAllUsage(cwd).filter((entry) => {
|
|
81
|
+
const at = Date.parse(entry.at);
|
|
82
|
+
return Number.isFinite(at) && at >= cutoff;
|
|
83
|
+
});
|
|
84
|
+
fs.writeFileSync(file, kept.map((entry) => JSON.stringify(entry)).join('\n') + (kept.length ? '\n' : ''), 'utf8');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function recordUsage(command, cwd = process.cwd()) {
|
|
88
|
+
try {
|
|
89
|
+
if (!command || !knownCommands.includes(command)) return;
|
|
90
|
+
const root = resolveUsageRoot(cwd);
|
|
91
|
+
// The sensor observes workspaces, it never creates them: recording into a
|
|
92
|
+
// resolved root with no .atris/ would litter pristine cwds (even `--help`).
|
|
93
|
+
if (!fs.existsSync(path.join(root, '.atris'))) return;
|
|
94
|
+
const file = usagePath(root);
|
|
95
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
96
|
+
fs.appendFileSync(file, `${JSON.stringify({ at: new Date().toISOString(), cmd: String(command) })}\n`, 'utf8');
|
|
97
|
+
trimUsageFile(root);
|
|
98
|
+
} catch {
|
|
99
|
+
// Usage telemetry must never affect the command path.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
usagePath,
|
|
105
|
+
recordUsage,
|
|
106
|
+
readUsage,
|
|
107
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
dejargon,
|
|
5
|
+
hasAgentJargon,
|
|
6
|
+
operatorReady,
|
|
7
|
+
voicePatterns,
|
|
8
|
+
} = require('./autoland');
|
|
9
|
+
|
|
10
|
+
const EM_DASH = /\u2014/;
|
|
11
|
+
const NUMBER_WORDS = [
|
|
12
|
+
'no', 'one', 'two', 'three', 'four', 'five', 'six',
|
|
13
|
+
'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function numberWord(n) {
|
|
17
|
+
return NUMBER_WORDS[n] || String(n);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function globalPattern(pattern) {
|
|
21
|
+
const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
|
|
22
|
+
return new RegExp(pattern.source, flags);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findingsFor(text, pattern, rule, why) {
|
|
26
|
+
const findings = [];
|
|
27
|
+
for (const match of String(text || '').matchAll(globalPattern(pattern))) {
|
|
28
|
+
findings.push({ rule, why, snippet: match[0] });
|
|
29
|
+
}
|
|
30
|
+
return findings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scanText(text) {
|
|
34
|
+
const value = String(text || '');
|
|
35
|
+
const findings = findingsFor(value, EM_DASH, 'em-dash', 'use plain punctuation');
|
|
36
|
+
if (hasAgentJargon(value)) {
|
|
37
|
+
findings.push(...findingsFor(
|
|
38
|
+
value,
|
|
39
|
+
voicePatterns.agentJargon,
|
|
40
|
+
'agent-jargon',
|
|
41
|
+
'replace internal syntax with plain words',
|
|
42
|
+
));
|
|
43
|
+
}
|
|
44
|
+
findings.push(...findingsFor(
|
|
45
|
+
value,
|
|
46
|
+
voicePatterns.rawUlid,
|
|
47
|
+
'raw-ulid',
|
|
48
|
+
'name the item instead of showing its database id',
|
|
49
|
+
));
|
|
50
|
+
findings.push(...findingsFor(
|
|
51
|
+
value,
|
|
52
|
+
voicePatterns.filePath,
|
|
53
|
+
'file-path',
|
|
54
|
+
'describe the proof instead of exposing an internal file location',
|
|
55
|
+
));
|
|
56
|
+
findings.push(...findingsFor(
|
|
57
|
+
value,
|
|
58
|
+
voicePatterns.shellCommand,
|
|
59
|
+
'shell-command',
|
|
60
|
+
'turn command syntax into a human action',
|
|
61
|
+
));
|
|
62
|
+
return findings;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cleanEmDashes(text) {
|
|
66
|
+
return String(text || '')
|
|
67
|
+
.replace(/\s*\u2014\s*/g, ', ')
|
|
68
|
+
.replace(/^,\s*/, '')
|
|
69
|
+
.replace(/,\s*$/, '.')
|
|
70
|
+
.replace(/([.!?])\s*,\s*/g, '$1 ')
|
|
71
|
+
.replace(/,\s*([,.;:!?])/g, '$1');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cleanAfterRemoval(text) {
|
|
75
|
+
return String(text || '')
|
|
76
|
+
.replace(/\(\s*\)/g, '')
|
|
77
|
+
.replace(/\s+([,.;:!?])/g, '$1')
|
|
78
|
+
.replace(/([:;,])\s*([.!?])/g, '$2')
|
|
79
|
+
.replace(/\s{2,}/g, ' ')
|
|
80
|
+
.trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function titleCanReplaceId(title) {
|
|
84
|
+
const value = String(title || '').trim();
|
|
85
|
+
return Boolean(value) && (operatorReady(value) || !hasAgentJargon(value));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function gateForHuman(text, opts = {}) {
|
|
89
|
+
let cleaned = cleanEmDashes(text);
|
|
90
|
+
cleaned = dejargon(cleaned, { preserveTickets: true });
|
|
91
|
+
if (titleCanReplaceId(opts.title)) {
|
|
92
|
+
cleaned = cleaned.replace(globalPattern(voicePatterns.rawUlid), '');
|
|
93
|
+
}
|
|
94
|
+
cleaned = cleanAfterRemoval(cleaned);
|
|
95
|
+
const issues = scanText(cleaned);
|
|
96
|
+
return { ok: issues.length === 0, text: cleaned, issues };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const LANDING_PARTICIPLE_WHY = {
|
|
100
|
+
avoiding: 'it avoids',
|
|
101
|
+
cutting: 'it cuts',
|
|
102
|
+
eliminating: 'it eliminates',
|
|
103
|
+
giving: 'it gives',
|
|
104
|
+
keeping: 'it keeps',
|
|
105
|
+
making: 'it makes',
|
|
106
|
+
preventing: 'it prevents',
|
|
107
|
+
reducing: 'it reduces',
|
|
108
|
+
removing: 'it removes',
|
|
109
|
+
saving: 'it saves',
|
|
110
|
+
stopping: 'it stops',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// A landing sentence written for a human already carries its own why
|
|
114
|
+
// ("..., so operators keep deciding instead of waiting"). Split that clause
|
|
115
|
+
// out so "why it matters" quotes the work instead of a canned line; return
|
|
116
|
+
// null when the sentence has no real why.
|
|
117
|
+
function landingWhyClause(sentence) {
|
|
118
|
+
const text = String(sentence || '').replace(/\s+/g, ' ').trim();
|
|
119
|
+
const finish = (change, why) => {
|
|
120
|
+
const cleanChange = String(change || '').replace(/[,;:]+$/, '').trim();
|
|
121
|
+
const cleanWhy = String(why || '').trim();
|
|
122
|
+
if (!cleanChange || !cleanWhy) return null;
|
|
123
|
+
return {
|
|
124
|
+
change: /[.!?]$/.test(cleanChange) ? cleanChange : `${cleanChange}.`,
|
|
125
|
+
why: /[.!?]$/.test(cleanWhy) ? cleanWhy : `${cleanWhy}.`,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const connective = text.match(/^(.{12,}?),?\s+(?:so(?:\s+that)?|because|which means)\s+(.{8,}?)[.!?]?$/i);
|
|
129
|
+
if (connective) return finish(connective[1], connective[2]);
|
|
130
|
+
const participles = Object.keys(LANDING_PARTICIPLE_WHY).join('|');
|
|
131
|
+
const participial = text.match(new RegExp(`^(.{12,}?),\\s+(${participles})\\s+(.{4,}?)[.!?]?$`, 'i'));
|
|
132
|
+
if (participial) {
|
|
133
|
+
return finish(participial[1], `${LANDING_PARTICIPLE_WHY[participial[2].toLowerCase()]} ${participial[3]}`);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Canned reasons older composers wrote into durable receipts. Treat them as
|
|
139
|
+
// absent so the reader sees the work's own why or nothing at all.
|
|
140
|
+
const RETIRED_FILLER_REASON = new RegExp([
|
|
141
|
+
'^It makes the result understandable before a human accepts or rejects it\\.$',
|
|
142
|
+
'^It proves the workflow works in the place people actually use it\\.$',
|
|
143
|
+
'^It turns the mission into a concrete result a human can accept, reject, or run again\\.$',
|
|
144
|
+
'^It turns the task title into a concrete result the human can approve\\.$',
|
|
145
|
+
'^It gives the human a repeatable check before approval\\.$',
|
|
146
|
+
'^It keeps real-world side effects behind a clear human decision\\.$',
|
|
147
|
+
'^It keeps private data out of the fast human decision screen\\.$',
|
|
148
|
+
'^It lets the operator see the next command without hunting\\.$',
|
|
149
|
+
'^It stops old approvals from running after their context has gone stale\\.$',
|
|
150
|
+
'^This makes the work easier to judge\\.$',
|
|
151
|
+
].join('|'));
|
|
152
|
+
|
|
153
|
+
function isRetiredFillerReason(text) {
|
|
154
|
+
return RETIRED_FILLER_REASON.test(String(text || '').replace(/\s+/g, ' ').trim());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
gateForHuman,
|
|
159
|
+
isRetiredFillerReason,
|
|
160
|
+
landingWhyClause,
|
|
161
|
+
numberWord,
|
|
162
|
+
scanText,
|
|
163
|
+
};
|