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/zip.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const zlib = require('zlib');
|
|
6
|
+
|
|
7
|
+
const LOCAL_FILE_HEADER = 0x04034b50;
|
|
8
|
+
const CENTRAL_DIRECTORY_HEADER = 0x02014b50;
|
|
9
|
+
const END_OF_CENTRAL_DIRECTORY = 0x06054b50;
|
|
10
|
+
const UTF8_FLAG = 0x0800;
|
|
11
|
+
const DEFLATE = 8;
|
|
12
|
+
const STORE = 0;
|
|
13
|
+
|
|
14
|
+
let crcTable = null;
|
|
15
|
+
|
|
16
|
+
function getCrcTable() {
|
|
17
|
+
if (crcTable) return crcTable;
|
|
18
|
+
crcTable = new Uint32Array(256);
|
|
19
|
+
for (let i = 0; i < 256; i += 1) {
|
|
20
|
+
let c = i;
|
|
21
|
+
for (let j = 0; j < 8; j += 1) {
|
|
22
|
+
c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
|
|
23
|
+
}
|
|
24
|
+
crcTable[i] = c >>> 0;
|
|
25
|
+
}
|
|
26
|
+
return crcTable;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function crc32(data) {
|
|
30
|
+
const table = getCrcTable();
|
|
31
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data || '');
|
|
32
|
+
let crc = 0xffffffff;
|
|
33
|
+
for (const byte of buffer) {
|
|
34
|
+
crc = table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
35
|
+
}
|
|
36
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function dosDateTime(dateValue) {
|
|
40
|
+
const date = dateValue instanceof Date && Number.isFinite(dateValue.getTime())
|
|
41
|
+
? dateValue
|
|
42
|
+
: new Date();
|
|
43
|
+
const year = Math.max(1980, Math.min(2107, date.getFullYear()));
|
|
44
|
+
const month = date.getMonth() + 1;
|
|
45
|
+
const day = date.getDate();
|
|
46
|
+
const hours = date.getHours();
|
|
47
|
+
const minutes = date.getMinutes();
|
|
48
|
+
const seconds = Math.floor(date.getSeconds() / 2);
|
|
49
|
+
return {
|
|
50
|
+
date: ((year - 1980) << 9) | (month << 5) | day,
|
|
51
|
+
time: (hours << 11) | (minutes << 5) | seconds,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeEntryName(name) {
|
|
56
|
+
return String(name || '').replace(/\\/g, '/');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function assertUInt32(value, label) {
|
|
60
|
+
if (!Number.isFinite(value) || value < 0 || value > 0xffffffff) {
|
|
61
|
+
throw new Error(`zip ${label} is too large`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createZipBuffer(entries) {
|
|
66
|
+
const localParts = [];
|
|
67
|
+
const centralParts = [];
|
|
68
|
+
let offset = 0;
|
|
69
|
+
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const name = normalizeEntryName(entry.name);
|
|
72
|
+
if (!name) throw new Error('zip entry name is required');
|
|
73
|
+
const nameBuffer = Buffer.from(name, 'utf8');
|
|
74
|
+
const data = Buffer.isBuffer(entry.data) ? entry.data : Buffer.from(entry.data || '');
|
|
75
|
+
const compressed = zlib.deflateRawSync(data);
|
|
76
|
+
const crc = crc32(data);
|
|
77
|
+
const { date, time } = dosDateTime(entry.mtime);
|
|
78
|
+
|
|
79
|
+
assertUInt32(data.length, 'entry');
|
|
80
|
+
assertUInt32(compressed.length, 'compressed entry');
|
|
81
|
+
assertUInt32(offset, 'offset');
|
|
82
|
+
|
|
83
|
+
const localHeader = Buffer.alloc(30);
|
|
84
|
+
localHeader.writeUInt32LE(LOCAL_FILE_HEADER, 0);
|
|
85
|
+
localHeader.writeUInt16LE(20, 4);
|
|
86
|
+
localHeader.writeUInt16LE(UTF8_FLAG, 6);
|
|
87
|
+
localHeader.writeUInt16LE(DEFLATE, 8);
|
|
88
|
+
localHeader.writeUInt16LE(time, 10);
|
|
89
|
+
localHeader.writeUInt16LE(date, 12);
|
|
90
|
+
localHeader.writeUInt32LE(crc, 14);
|
|
91
|
+
localHeader.writeUInt32LE(compressed.length, 18);
|
|
92
|
+
localHeader.writeUInt32LE(data.length, 22);
|
|
93
|
+
localHeader.writeUInt16LE(nameBuffer.length, 26);
|
|
94
|
+
localHeader.writeUInt16LE(0, 28);
|
|
95
|
+
localParts.push(localHeader, nameBuffer, compressed);
|
|
96
|
+
|
|
97
|
+
const centralHeader = Buffer.alloc(46);
|
|
98
|
+
centralHeader.writeUInt32LE(CENTRAL_DIRECTORY_HEADER, 0);
|
|
99
|
+
centralHeader.writeUInt16LE(20, 4);
|
|
100
|
+
centralHeader.writeUInt16LE(20, 6);
|
|
101
|
+
centralHeader.writeUInt16LE(UTF8_FLAG, 8);
|
|
102
|
+
centralHeader.writeUInt16LE(DEFLATE, 10);
|
|
103
|
+
centralHeader.writeUInt16LE(time, 12);
|
|
104
|
+
centralHeader.writeUInt16LE(date, 14);
|
|
105
|
+
centralHeader.writeUInt32LE(crc, 16);
|
|
106
|
+
centralHeader.writeUInt32LE(compressed.length, 20);
|
|
107
|
+
centralHeader.writeUInt32LE(data.length, 24);
|
|
108
|
+
centralHeader.writeUInt16LE(nameBuffer.length, 28);
|
|
109
|
+
centralHeader.writeUInt16LE(0, 30);
|
|
110
|
+
centralHeader.writeUInt16LE(0, 32);
|
|
111
|
+
centralHeader.writeUInt16LE(0, 34);
|
|
112
|
+
centralHeader.writeUInt16LE(0, 36);
|
|
113
|
+
centralHeader.writeUInt32LE(0, 38);
|
|
114
|
+
centralHeader.writeUInt32LE(offset, 42);
|
|
115
|
+
centralParts.push(centralHeader, nameBuffer);
|
|
116
|
+
|
|
117
|
+
offset += localHeader.length + nameBuffer.length + compressed.length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const centralOffset = offset;
|
|
121
|
+
const centralSize = centralParts.reduce((sum, part) => sum + part.length, 0);
|
|
122
|
+
assertUInt32(centralOffset, 'central directory offset');
|
|
123
|
+
assertUInt32(centralSize, 'central directory');
|
|
124
|
+
if (entries.length > 0xffff) throw new Error('zip has too many entries');
|
|
125
|
+
|
|
126
|
+
const end = Buffer.alloc(22);
|
|
127
|
+
end.writeUInt32LE(END_OF_CENTRAL_DIRECTORY, 0);
|
|
128
|
+
end.writeUInt16LE(0, 4);
|
|
129
|
+
end.writeUInt16LE(0, 6);
|
|
130
|
+
end.writeUInt16LE(entries.length, 8);
|
|
131
|
+
end.writeUInt16LE(entries.length, 10);
|
|
132
|
+
end.writeUInt32LE(centralSize, 12);
|
|
133
|
+
end.writeUInt32LE(centralOffset, 16);
|
|
134
|
+
end.writeUInt16LE(0, 20);
|
|
135
|
+
|
|
136
|
+
return Buffer.concat([...localParts, ...centralParts, end]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function writeZipFile(filePath, entries) {
|
|
140
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
141
|
+
fs.writeFileSync(filePath, createZipBuffer(entries));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function findEndOfCentralDirectory(buffer) {
|
|
145
|
+
const start = Math.max(0, buffer.length - 22 - 0xffff);
|
|
146
|
+
for (let i = buffer.length - 22; i >= start; i -= 1) {
|
|
147
|
+
if (buffer.readUInt32LE(i) === END_OF_CENTRAL_DIRECTORY) return i;
|
|
148
|
+
}
|
|
149
|
+
throw new Error('invalid zip: missing central directory');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function assertReadable(buffer, offset, length, label) {
|
|
153
|
+
if (offset < 0 || length < 0 || offset + length > buffer.length) {
|
|
154
|
+
throw new Error(`invalid zip: truncated ${label}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readZipBuffer(input) {
|
|
159
|
+
const buffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
160
|
+
const endOffset = findEndOfCentralDirectory(buffer);
|
|
161
|
+
const entryCount = buffer.readUInt16LE(endOffset + 10);
|
|
162
|
+
const centralSize = buffer.readUInt32LE(endOffset + 12);
|
|
163
|
+
const centralOffset = buffer.readUInt32LE(endOffset + 16);
|
|
164
|
+
assertReadable(buffer, centralOffset, centralSize, 'central directory');
|
|
165
|
+
|
|
166
|
+
const entries = [];
|
|
167
|
+
let cursor = centralOffset;
|
|
168
|
+
for (let i = 0; i < entryCount; i += 1) {
|
|
169
|
+
assertReadable(buffer, cursor, 46, 'central directory entry');
|
|
170
|
+
if (buffer.readUInt32LE(cursor) !== CENTRAL_DIRECTORY_HEADER) {
|
|
171
|
+
throw new Error('invalid zip: malformed central directory');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const flags = buffer.readUInt16LE(cursor + 8);
|
|
175
|
+
const method = buffer.readUInt16LE(cursor + 10);
|
|
176
|
+
const compressedSize = buffer.readUInt32LE(cursor + 20);
|
|
177
|
+
const uncompressedSize = buffer.readUInt32LE(cursor + 24);
|
|
178
|
+
const nameLength = buffer.readUInt16LE(cursor + 28);
|
|
179
|
+
const extraLength = buffer.readUInt16LE(cursor + 30);
|
|
180
|
+
const commentLength = buffer.readUInt16LE(cursor + 32);
|
|
181
|
+
const localOffset = buffer.readUInt32LE(cursor + 42);
|
|
182
|
+
const nameStart = cursor + 46;
|
|
183
|
+
assertReadable(buffer, nameStart, nameLength + extraLength + commentLength, 'central directory name');
|
|
184
|
+
const name = buffer.slice(nameStart, nameStart + nameLength).toString('utf8');
|
|
185
|
+
cursor = nameStart + nameLength + extraLength + commentLength;
|
|
186
|
+
|
|
187
|
+
if (flags & 1) throw new Error(`invalid zip: encrypted entry ${name} is not supported`);
|
|
188
|
+
if (![STORE, DEFLATE].includes(method)) {
|
|
189
|
+
throw new Error(`invalid zip: unsupported compression method ${method} for ${name}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
assertReadable(buffer, localOffset, 30, 'local file header');
|
|
193
|
+
if (buffer.readUInt32LE(localOffset) !== LOCAL_FILE_HEADER) {
|
|
194
|
+
throw new Error(`invalid zip: missing local header for ${name}`);
|
|
195
|
+
}
|
|
196
|
+
const localNameLength = buffer.readUInt16LE(localOffset + 26);
|
|
197
|
+
const localExtraLength = buffer.readUInt16LE(localOffset + 28);
|
|
198
|
+
const dataStart = localOffset + 30 + localNameLength + localExtraLength;
|
|
199
|
+
assertReadable(buffer, dataStart, compressedSize, `file data for ${name}`);
|
|
200
|
+
const compressed = buffer.slice(dataStart, dataStart + compressedSize);
|
|
201
|
+
const data = method === STORE ? Buffer.from(compressed) : zlib.inflateRawSync(compressed);
|
|
202
|
+
if (data.length !== uncompressedSize) {
|
|
203
|
+
throw new Error(`invalid zip: size mismatch for ${name}`);
|
|
204
|
+
}
|
|
205
|
+
entries.push({ name, data });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return entries;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function readZipFile(filePath) {
|
|
212
|
+
return readZipBuffer(fs.readFileSync(filePath));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
createZipBuffer,
|
|
217
|
+
writeZipFile,
|
|
218
|
+
readZipBuffer,
|
|
219
|
+
readZipFile,
|
|
220
|
+
crc32,
|
|
221
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.37.0",
|
|
4
|
+
"description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
|
|
4
5
|
"main": "bin/atris.js",
|
|
5
6
|
"bin": {
|
|
6
7
|
"atris": "bin/atris.js",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
],
|
|
55
56
|
"scripts": {
|
|
56
57
|
"test": "node --test",
|
|
58
|
+
"test:fast": "xargs node --test < test/fast-tests.txt",
|
|
57
59
|
"lint:decks": "node scripts/lint-all-decks.js",
|
|
58
60
|
"publish:release": "node scripts/publish-atris-release.js",
|
|
59
61
|
"prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && cp scripts/commit-msg .git/hooks/commit-msg 2>/dev/null && cp scripts/prepare-commit-msg .git/hooks/prepare-commit-msg 2>/dev/null && chmod +x .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/prepare-commit-msg || true"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# The Loops
|
|
2
|
+
|
|
3
|
+
**Boot phrase:** "atris loops" or "run the loops" - audits every loop file live,
|
|
4
|
+
routes every open loop back to an owner, and keeps the product improving from proof
|
|
5
|
+
instead of memory.
|
|
6
|
+
|
|
7
|
+
Missions are point-in-time. Loops are forever. A loop owns one thing that must always
|
|
8
|
+
work and always improve, names the signal that proves it, and feeds the loops
|
|
9
|
+
downstream of it. Every mission dispatched in this project should name the loop it
|
|
10
|
+
serves; a mission that serves no loop is probably make-work.
|
|
11
|
+
|
|
12
|
+
Each loop file follows the same contract, enforced by `atris loops audit`:
|
|
13
|
+
|
|
14
|
+
- **Owner** - the `team/<member>` accountable for the loop.
|
|
15
|
+
- **Wiki** - the knowledge page that keeps the loop context alive across sessions.
|
|
16
|
+
- **Runner** - the named thing that executes the signal: script, CI, mission, tick.
|
|
17
|
+
- **Protects** - the user-felt thing that must never break.
|
|
18
|
+
- **Signal** - the receipt that proves it. Green is an exit code or a number, not vibes.
|
|
19
|
+
- **Check** - optional shell command run by `atris loops audit`.
|
|
20
|
+
- **Cadence** - when the loop runs: per-commit, per-tick, nightly, weekly.
|
|
21
|
+
- **Feeds / Fed by** - the loop graph. No loop is an island.
|
|
22
|
+
|
|
23
|
+
## The Starter Loops
|
|
24
|
+
|
|
25
|
+
| # | Loop | Protects | Signal |
|
|
26
|
+
|---|------|----------|--------|
|
|
27
|
+
| 1 | [feedback](feedback.md) | user pain becomes tracked work | TODO: intake count, queue check, or triage command |
|
|
28
|
+
| 2 | [quality](quality.md) | changes do not regress trust | TODO: test, lint, audit, or review gate |
|
|
29
|
+
|
|
30
|
+
## The Graph
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
users
|
|
34
|
+
|
|
|
35
|
+
v
|
|
36
|
+
[feedback] ---- pain becomes tasks ----+
|
|
37
|
+
| |
|
|
38
|
+
v v
|
|
39
|
+
[quality] <--------- fixes, reviews, receipts
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Reading the graph: feedback is the intake. Quality is the ratchet. Feedback discovers
|
|
43
|
+
breaks and turns them into work; quality proves the work made the project tighter.
|
|
44
|
+
|
|
45
|
+
## Learning Rule
|
|
46
|
+
|
|
47
|
+
Every error that reaches a loop exits as a dated line in that loop's **Log** before the
|
|
48
|
+
work closes. The loop file is the owner member's training data: a class of error is
|
|
49
|
+
allowed to happen once per lesson.
|
|
50
|
+
|
|
51
|
+
## Operating Rule
|
|
52
|
+
|
|
53
|
+
One tick = run `atris loops audit`, pick the first open loop, run or fix its signal,
|
|
54
|
+
re-run the audit, and append a dated Log line with the receipt. When every loop is
|
|
55
|
+
green, raise one bar instead of idling.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The Tick - loop protocol for ANY model
|
|
2
|
+
|
|
3
|
+
This is the minimum viable evolutionary state of the project. Any agent can run this
|
|
4
|
+
exactly. No step is optional, and no step requires guessing from memory.
|
|
5
|
+
|
|
6
|
+
## The Tick
|
|
7
|
+
|
|
8
|
+
1. `atris loops audit` - this is the source of truth. Never assess loop health from
|
|
9
|
+
prose, memory, or vibes.
|
|
10
|
+
2. Pick the first open loop. If several are open, take the first listed.
|
|
11
|
+
3. Do exactly what the loop's signal or Check says. Stay inside that loop's surface;
|
|
12
|
+
do not refactor unrelated files.
|
|
13
|
+
4. `atris loops audit` again. The selected loop must improve or the failure must be
|
|
14
|
+
made explicit.
|
|
15
|
+
5. Append one dated line to `## Log` in `atris/loops/<loop>.md`: what was open, what
|
|
16
|
+
changed, and the receipt command plus exit code.
|
|
17
|
+
6. Move the owning task to proof-ready, or land according to this repo's policy.
|
|
18
|
+
|
|
19
|
+
## Truth Rules
|
|
20
|
+
|
|
21
|
+
- Claim green only by pasting the re-run audit output.
|
|
22
|
+
- Never edit loop files, baselines, or checks to hide a failing signal.
|
|
23
|
+
- If you cannot fix the first open loop, write why as a dated Log line and stop.
|
|
24
|
+
- If all loops are green, tighten one signal so the system keeps improving.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Loop - Feedback
|
|
2
|
+
|
|
3
|
+
**Owner:** `team/TODO-feedback-owner`
|
|
4
|
+
**Wiki:** [systems/loops.md](../wiki/systems/loops.md) TODO: replace or extend with the owner context page.
|
|
5
|
+
**Runner:** TODO: name the intake command, CI job, mission, or wake tick that owns this loop.
|
|
6
|
+
|
|
7
|
+
**Protects:** TODO: every user pain, bug report, or operator note becomes tracked work
|
|
8
|
+
or an explicit no-op decision.
|
|
9
|
+
|
|
10
|
+
**Signal (green =):** TODO: define a numeric or exit-code signal, for example open
|
|
11
|
+
feedback count, stale inbox count, or a triage command that exits 0.
|
|
12
|
+
|
|
13
|
+
**Check:** `true`
|
|
14
|
+
|
|
15
|
+
**Cadence:** TODO: per tick, daily, weekly, or per release.
|
|
16
|
+
|
|
17
|
+
**Feeds:** quality.
|
|
18
|
+
**Fed by:** users, support, operators, telemetry.
|
|
19
|
+
|
|
20
|
+
## Log
|
|
21
|
+
|
|
22
|
+
- {{DATE}}: TODO: first loop scaffold created; replace this line with the first real receipt.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Loop - Quality
|
|
2
|
+
|
|
3
|
+
**Owner:** `team/TODO-quality-owner`
|
|
4
|
+
**Wiki:** [systems/loops.md](../wiki/systems/loops.md) TODO: replace or extend with the owner context page.
|
|
5
|
+
**Runner:** TODO: name the test, audit, review lane, CI job, or mission that owns this loop.
|
|
6
|
+
|
|
7
|
+
**Protects:** TODO: shipped changes do not make the project harder to trust, operate,
|
|
8
|
+
or understand.
|
|
9
|
+
|
|
10
|
+
**Signal (green =):** TODO: define the receipt, for example a focused test, lint,
|
|
11
|
+
review gate, smoke check, or quality score with a threshold.
|
|
12
|
+
|
|
13
|
+
**Check:** `true`
|
|
14
|
+
|
|
15
|
+
**Cadence:** TODO: per commit, per PR, daily, or before release.
|
|
16
|
+
|
|
17
|
+
**Feeds:** feedback, release confidence, owner trust.
|
|
18
|
+
**Fed by:** feedback, reviews, incidents, failed checks.
|
|
19
|
+
|
|
20
|
+
## Log
|
|
21
|
+
|
|
22
|
+
- {{DATE}}: TODO: first loop scaffold created; replace this line with the first real receipt.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# The Loop System - how this project improves itself
|
|
2
|
+
|
|
3
|
+
A loop owns one user-felt thing that must never break, names the receipt that proves it,
|
|
4
|
+
has a team member accountable for it, and feeds the loops downstream. Missions are
|
|
5
|
+
point-in-time; loops are forever. Source of truth: `atris/loops/LOOPS.md`.
|
|
6
|
+
|
|
7
|
+
## The Closure Contract
|
|
8
|
+
|
|
9
|
+
A loop is **closed** when all five hold; a project where every loop is closed is
|
|
10
|
+
**self-improving**: breaks discover themselves, route to an owner, and land as fixes
|
|
11
|
+
without waiting for a human to notice first.
|
|
12
|
+
|
|
13
|
+
1. **Owner** - a `team/<member>/` directory exists. Someone wakes for it.
|
|
14
|
+
2. **Wiki** - a knowledge page resolves, so context survives sessions.
|
|
15
|
+
3. **Runner** - the named thing executes the signal: script, CI job, mission, or tick.
|
|
16
|
+
4. **Signal** - green is an exit code or number, never vibes.
|
|
17
|
+
5. **Fresh log** - a dated receipt in the loop file is newer than 14 days.
|
|
18
|
+
|
|
19
|
+
`atris loops audit` enforces this contract and prints the self-improving verdict. If a
|
|
20
|
+
loop file has `**Check:** \`<command>\``, the audit runs that command from the project
|
|
21
|
+
root and reports its exit code.
|
|
22
|
+
|
|
23
|
+
## Starter Owner Map
|
|
24
|
+
|
|
25
|
+
| # | Loop | Owner | Runner class |
|
|
26
|
+
|---|------|-------|--------------|
|
|
27
|
+
| 1 | feedback | TODO-feedback-owner | TODO intake command, mission, or wake tick |
|
|
28
|
+
| 2 | quality | TODO-quality-owner | TODO test, audit, review lane, or CI job |
|
|
29
|
+
|
|
30
|
+
## Known Open Edges
|
|
31
|
+
|
|
32
|
+
- Replace TODO owners with real `team/<member>/` directories.
|
|
33
|
+
- Replace TODO runners with real commands or missions.
|
|
34
|
+
- Replace `true` starter checks with project-native proof commands.
|
|
35
|
+
- Add loops only when they protect a real user-felt thing.
|
|
36
|
+
|
|
37
|
+
## Learning Rule
|
|
38
|
+
|
|
39
|
+
An error closes only by becoming a dated Log line in its owning loop, with a receipt.
|
|
40
|
+
Members wake with their loop file, this wiki, and recent receipts in context. Each
|
|
41
|
+
lesson removes a class of error; the loop audit makes the next tick cheap.
|
package/utils/api.js
CHANGED
|
@@ -25,7 +25,11 @@ const DEFAULT_USER_AGENT = `${DEFAULT_CLIENT_ID} (node ${process.version}; ${os.
|
|
|
25
25
|
* @returns {string} API base URL
|
|
26
26
|
*/
|
|
27
27
|
function getApiBaseUrl() {
|
|
28
|
-
const
|
|
28
|
+
const backend = process.env.ATRIS_BACKEND_URL
|
|
29
|
+
? process.env.ATRIS_BACKEND_URL.replace(/\/+$/, '')
|
|
30
|
+
: '';
|
|
31
|
+
const raw = process.env.ATRIS_API_URL
|
|
32
|
+
|| (backend ? (backend.endsWith('/api') ? backend : `${backend}/api`) : 'https://api.atris.ai/api');
|
|
29
33
|
return raw.replace(/\/$/, '');
|
|
30
34
|
}
|
|
31
35
|
|
package/utils/auth.js
CHANGED
|
@@ -139,13 +139,25 @@ function shouldRefreshToken(token, bufferSeconds = TOKEN_REFRESH_BUFFER_SECONDS)
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Credentials management
|
|
142
|
+
|
|
143
|
+
// Create a directory owner-only (0o700). Under the default umask (0o022) a bare
|
|
144
|
+
// recursive mkdir yields 0o755, leaving ~/.atris and its profiles/sessions
|
|
145
|
+
// world-traversable so any local user can enumerate tokens and workspace ids.
|
|
146
|
+
function mkPrivateDir(dir) {
|
|
147
|
+
if (!fs.existsSync(dir)) {
|
|
148
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
fs.chmodSync(dir, 0o700);
|
|
152
|
+
} catch {
|
|
153
|
+
// Best effort: permissions may be unsupported on this platform.
|
|
154
|
+
}
|
|
155
|
+
return dir;
|
|
156
|
+
}
|
|
157
|
+
|
|
142
158
|
function getAtrisDir() {
|
|
143
159
|
const homeDir = os.homedir();
|
|
144
|
-
|
|
145
|
-
if (!fs.existsSync(atrisDir)) {
|
|
146
|
-
fs.mkdirSync(atrisDir, { recursive: true });
|
|
147
|
-
}
|
|
148
|
-
return atrisDir;
|
|
160
|
+
return mkPrivateDir(path.join(homeDir, '.atris'));
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
function getCredentialsPath() {
|
|
@@ -153,11 +165,7 @@ function getCredentialsPath() {
|
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
function getSessionsDir() {
|
|
156
|
-
|
|
157
|
-
if (!fs.existsSync(dir)) {
|
|
158
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
159
|
-
}
|
|
160
|
-
return dir;
|
|
168
|
+
return mkPrivateDir(path.join(getAtrisDir(), 'sessions'));
|
|
161
169
|
}
|
|
162
170
|
|
|
163
171
|
function getTerminalSessionId() {
|
|
@@ -248,11 +256,7 @@ function cleanStaleSessions() {
|
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
function getProfilesDir() {
|
|
251
|
-
|
|
252
|
-
if (!fs.existsSync(dir)) {
|
|
253
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
254
|
-
}
|
|
255
|
-
return dir;
|
|
259
|
+
return mkPrivateDir(path.join(getAtrisDir(), 'profiles'));
|
|
256
260
|
}
|
|
257
261
|
|
|
258
262
|
function profileNameFromEmail(email) {
|
|
@@ -342,7 +346,7 @@ function loadCredentials() {
|
|
|
342
346
|
const profileOverride = process.env.ATRIS_PROFILE;
|
|
343
347
|
if (profileOverride) {
|
|
344
348
|
const profile = loadProfile(profileOverride);
|
|
345
|
-
if (profile) return profile;
|
|
349
|
+
if (profile) return { ...profile, source_profile: profileOverride };
|
|
346
350
|
const profiles = listProfiles();
|
|
347
351
|
const q = profileOverride.toLowerCase();
|
|
348
352
|
const match = profiles.find(p => p.toLowerCase() === q)
|
|
@@ -350,7 +354,7 @@ function loadCredentials() {
|
|
|
350
354
|
|| profiles.find(p => p.toLowerCase().includes(q));
|
|
351
355
|
if (match) {
|
|
352
356
|
const matched = loadProfile(match);
|
|
353
|
-
if (matched) return matched;
|
|
357
|
+
if (matched) return { ...matched, source_profile: match };
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
|
|
@@ -358,7 +362,7 @@ function loadCredentials() {
|
|
|
358
362
|
const sessionProfile = getSessionProfile();
|
|
359
363
|
if (sessionProfile) {
|
|
360
364
|
const profile = loadProfile(sessionProfile);
|
|
361
|
-
if (profile) return profile;
|
|
365
|
+
if (profile) return { ...profile, source_profile: sessionProfile };
|
|
362
366
|
}
|
|
363
367
|
|
|
364
368
|
// 3. Global credentials.json
|
|
@@ -438,7 +442,27 @@ async function performTokenRefresh(credentials, apiRequestJson) {
|
|
|
438
442
|
const email = refreshUser?.email || credentials.email;
|
|
439
443
|
const userId = refreshUser?.id || credentials.user_id;
|
|
440
444
|
|
|
441
|
-
|
|
445
|
+
// Refreshed tokens must land in the file they were loaded from. Writing a
|
|
446
|
+
// profile's refresh to the global credentials.json leaves the profile token
|
|
447
|
+
// to expire permanently (every ATRIS_PROFILE session then 401s forever).
|
|
448
|
+
const persistRefreshed = () => {
|
|
449
|
+
if (credentials.source_profile) {
|
|
450
|
+
const { source_profile, source, ...rest } = { ...credentials };
|
|
451
|
+
saveProfile(credentials.source_profile, {
|
|
452
|
+
...rest,
|
|
453
|
+
token: accessToken,
|
|
454
|
+
refresh_token: newRefreshToken,
|
|
455
|
+
email,
|
|
456
|
+
user_id: userId,
|
|
457
|
+
provider,
|
|
458
|
+
saved_at: new Date().toISOString(),
|
|
459
|
+
});
|
|
460
|
+
} else {
|
|
461
|
+
saveCredentials(accessToken, newRefreshToken, email, userId, provider);
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
persistRefreshed();
|
|
442
466
|
let latestCreds = loadCredentials();
|
|
443
467
|
|
|
444
468
|
const validation = await validateAccessToken(accessToken, apiRequestJson);
|
|
@@ -456,7 +480,20 @@ async function performTokenRefresh(credentials, apiRequestJson) {
|
|
|
456
480
|
updatedProvider !== latestCreds.provider ||
|
|
457
481
|
updatedUserId !== latestCreds.user_id
|
|
458
482
|
) {
|
|
459
|
-
|
|
483
|
+
if (credentials.source_profile) {
|
|
484
|
+
const { source_profile, source, ...rest } = { ...credentials };
|
|
485
|
+
saveProfile(credentials.source_profile, {
|
|
486
|
+
...rest,
|
|
487
|
+
token: accessToken,
|
|
488
|
+
refresh_token: newRefreshToken,
|
|
489
|
+
email: updatedEmail,
|
|
490
|
+
user_id: updatedUserId,
|
|
491
|
+
provider: updatedProvider,
|
|
492
|
+
saved_at: new Date().toISOString(),
|
|
493
|
+
});
|
|
494
|
+
} else {
|
|
495
|
+
saveCredentials(accessToken, newRefreshToken, updatedEmail, updatedUserId, updatedProvider);
|
|
496
|
+
}
|
|
460
497
|
latestCreds = loadCredentials();
|
|
461
498
|
}
|
|
462
499
|
}
|
|
@@ -568,7 +605,6 @@ async function displayAccountSummary(apiRequestJson) {
|
|
|
568
605
|
console.log(`User ID: ${userId}`);
|
|
569
606
|
console.log(`Provider: ${provider}`);
|
|
570
607
|
console.log(`Credentials saved: ${savedAt}`);
|
|
571
|
-
console.log(`Credential file: ${getCredentialsPath()}`);
|
|
572
608
|
|
|
573
609
|
try {
|
|
574
610
|
const agentsResponse = await fetchMyAgents(credentials.token, apiRequestJson);
|