atris 3.38.0 → 3.40.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 +25 -6
- package/atris/PERSONA.md +8 -4
- package/atris.md +7 -0
- package/ax +2 -1
- package/bin/atris.js +30 -5
- package/commands/agent-spawn.js +13 -11
- package/commands/autoland.js +28 -77
- package/commands/bench.js +10 -12
- package/commands/business.js +345 -0
- package/commands/chat-scan.js +5 -7
- package/commands/codex-goal.js +8 -10
- package/commands/console.js +19 -3
- package/commands/decide.js +166 -0
- package/commands/deck.js +1 -4
- package/commands/drill.js +14 -24
- package/commands/engine.js +196 -8
- package/commands/gm.js +8 -6
- package/commands/harvest.js +1 -4
- package/commands/init.js +23 -3
- package/commands/land.js +8 -14
- package/commands/launchpad.js +1 -14
- package/commands/lifecycle.js +5 -5
- package/commands/log.js +55 -5
- package/commands/member.js +558 -574
- package/commands/mission.js +456 -237
- package/commands/pack.js +2746 -164
- package/commands/play.js +6 -4
- package/commands/probe.js +2 -2
- package/commands/pulse.js +15 -16
- package/commands/release.js +10 -9
- package/commands/router.js +5 -4
- package/commands/site-deploy.js +870 -0
- package/commands/site.js +11 -2
- package/commands/slop.js +14 -2
- package/commands/stream.js +4 -18
- package/commands/task.js +880 -558
- package/commands/taste.js +101 -0
- package/commands/team.js +83 -3
- package/commands/vercel.js +4 -2
- package/commands/voice.js +195 -0
- package/commands/watch.js +1 -22
- package/commands/wiki.js +1 -4
- package/commands/workflow.js +2 -2
- package/commands/worktree.js +1 -14
- package/commands/xp.js +27 -24
- package/lib/accept-verify-gate.js +5 -1
- package/lib/arg-parser.js +41 -0
- package/lib/auto-accept-certified.js +116 -1
- package/lib/autoland.js +66 -0
- package/lib/bench/runner.js +19 -1
- package/lib/context-gatherer.js +7 -1
- package/lib/engine-registry.js +141 -20
- package/lib/falsifier-probe.js +84 -0
- package/lib/fleet.js +65 -15
- package/lib/git-spawn.js +15 -0
- package/lib/json-file.js +37 -0
- package/lib/known-commands.js +2 -2
- package/lib/lesson-preflight.js +146 -0
- package/lib/loop-doctor.js +0 -2
- package/lib/mission-human-asks.js +28 -0
- package/lib/mission-protected-lane.js +4 -1
- package/lib/official-cli-integration.js +47 -2
- package/lib/orb-context.js +8 -1
- package/lib/pack-capabilities.js +685 -0
- package/lib/router-brain.js +51 -1
- package/lib/runner-command.js +0 -6
- package/lib/self-drive.js +44 -13
- package/lib/task-db.js +137 -3
- package/lib/task-decision.js +50 -0
- package/lib/taste-lessons.js +153 -0
- package/lib/tool-result-encode.js +17 -1
- package/lib/voice-gate.js +66 -0
- package/lib/wish-audit.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/zip.js +95 -7
- package/package.json +2 -1
- package/templates/business-starter/persona.md +9 -0
package/lib/voice-gate.js
CHANGED
|
@@ -215,10 +215,76 @@ function isRetiredFillerReason(text) {
|
|
|
215
215
|
return RETIRED_FILLER_REASON.test(String(text || '').replace(/\s+/g, ' ').trim());
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// One table turns every landing/refusal reason code into a plain sentence a
|
|
219
|
+
// fried reader can act on: what happened, and what unblocks it. JSON output
|
|
220
|
+
// keeps the raw codes; only human text goes through here. Fragments carry no
|
|
221
|
+
// trailing period because callers stitch them into their own sentences.
|
|
222
|
+
const LANDING_REASON_SENTENCES = {
|
|
223
|
+
accept_all_but_protected: 'the accept-everything policy covers it, so it lands unless a protected lane applies',
|
|
224
|
+
already_auto_accepted: 'it already landed on its own earlier, nothing left to do',
|
|
225
|
+
autoland_policy_missing_owner: 'the self-landing policy has no owner recorded, turn it on again to set one',
|
|
226
|
+
autoland_policy_off: 'self-landing is off, so everything waits for you',
|
|
227
|
+
certified_independent_review: 'an independent reviewer certified it, so it can land',
|
|
228
|
+
certified_strict_verify: 'it is certified and its recorded check passed, so it can land',
|
|
229
|
+
dead_exports: 'the change leaves behind code nothing calls, delete what the hygiene check names and re-certify',
|
|
230
|
+
declared_protected_lane: 'it touches a protected lane, so it waits for your decision',
|
|
231
|
+
forced_completion_needs_human: 'it was pushed to done without proof, so only a human can accept it',
|
|
232
|
+
insufficient_review_passes: 'it has not been reviewed enough times yet, one more pass unblocks it',
|
|
233
|
+
judge_equals_worker: 'built and judged by the same actor, hand the review to someone else',
|
|
234
|
+
mission_xp_requires_end_to_end_receipt: 'the proof does not show the whole flow working start to finish, attach that receipt or move it back to do',
|
|
235
|
+
needs_independent_reviewer: 'built and judged by the same actor, a review from someone else unblocks it',
|
|
236
|
+
needs_second_actor_review: 'the same actor built and reviewed it, someone else has to look before it lands',
|
|
237
|
+
needs_second_reviewer_or_third_pass: 'it needs one more independent check first',
|
|
238
|
+
no_verify_command: 'no check command was given, add one so the work can prove itself',
|
|
239
|
+
not_agent_certified: 'no reviewer has certified it yet, a second review unblocks it',
|
|
240
|
+
not_in_review: 'it is not up for review yet, finish the work and move it to review first',
|
|
241
|
+
probation_needs_review: 'the builder is still earning trust, so a human look is required before it lands',
|
|
242
|
+
proof_not_executed: 'the proof was written but never actually run, execute the check and record what it showed',
|
|
243
|
+
proof_required: 'no proof was given, say what was run and what it showed',
|
|
244
|
+
proof_unmerged_or_draft_pr_boundary: 'its proof points at an unmerged draft, merge it or prove the work another way',
|
|
245
|
+
receipt_verifier_failed: 'a saved receipt failed its re-check, fix what it names and re-certify',
|
|
246
|
+
strict_verify_missing: 'no recorded check command to re-run, add one and re-certify',
|
|
247
|
+
untagged_protected_lane_text: 'the description reads like protected-lane work without the tag, tag it or decide it yourself',
|
|
248
|
+
verification_pending: 'the recorded check has not been re-run yet, the next hourly pass runs it',
|
|
249
|
+
verifier_is_builder: 'the re-check actor built this row, another actor must re-check',
|
|
250
|
+
verify_command_not_allowed: 'its recorded check is not on the list this machine may run alone, run it yourself to unblock',
|
|
251
|
+
verify_failed: 'its check command failed on re-run, fix the failure and re-certify',
|
|
252
|
+
verify_passed: 'its check command passed on re-run',
|
|
253
|
+
verify_unrunnable: 'its check command could not even start, fix the command and re-certify',
|
|
254
|
+
verify_workdir_missing: 'the folder its check was recorded in is gone, re-run the check from a real folder',
|
|
255
|
+
verify_worktree_missing: 'the folder its check needs is gone, rebuild it or run the check by hand',
|
|
256
|
+
weak_proof: 'the proof is too thin to trust, cite a command, receipt, or approval',
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const DENIED_TAG_SENTENCES = {
|
|
260
|
+
billing: 'it touches money, so it is your decision',
|
|
261
|
+
deploy: 'it is a deploy, so it is your decision',
|
|
262
|
+
security: 'it touches security, so it is your decision',
|
|
263
|
+
customer: 'it is customer-facing, so it is your decision',
|
|
264
|
+
external: 'it is outward-facing, so it is your decision',
|
|
265
|
+
feedback: 'it answers customer feedback, so it is your decision',
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
function plainLandingReason(reason) {
|
|
269
|
+
const code = String(reason || '').trim();
|
|
270
|
+
if (!code) return 'no reason was recorded, re-run the review to get one';
|
|
271
|
+
if (LANDING_REASON_SENTENCES[code]) return LANDING_REASON_SENTENCES[code];
|
|
272
|
+
const denied = code.match(/^denied_tag_(.+)$/);
|
|
273
|
+
if (denied) {
|
|
274
|
+
return DENIED_TAG_SENTENCES[denied[1]]
|
|
275
|
+
|| `it is tagged ${denied[1].replace(/_/g, ' ')}, a protected lane, so it is your decision`;
|
|
276
|
+
}
|
|
277
|
+
const approval = code.match(/^approval_(.+)$/);
|
|
278
|
+
if (approval) return `a human already marked it ${approval[1].replace(/_/g, ' ')}, so it will not land on its own`;
|
|
279
|
+
// Unknown codes still never leak snake_case to a human.
|
|
280
|
+
return code.replace(/_/g, ' ');
|
|
281
|
+
}
|
|
282
|
+
|
|
218
283
|
module.exports = {
|
|
219
284
|
gateForHuman,
|
|
220
285
|
isRetiredFillerReason,
|
|
221
286
|
landingWhyClause,
|
|
222
287
|
numberWord,
|
|
288
|
+
plainLandingReason,
|
|
223
289
|
scanText,
|
|
224
290
|
};
|
package/lib/wish-audit.js
CHANGED
|
@@ -167,7 +167,7 @@ const BUDGET_LABELS = {
|
|
|
167
167
|
const WISH_SWEEP_LIMIT = 3;
|
|
168
168
|
const WISH_MISSION_RUNNER = 'claude';
|
|
169
169
|
const TEST_VERIFY_COMMAND = 'node --test';
|
|
170
|
-
const JOURNAL_RESULT_TEXT = '
|
|
170
|
+
const JOURNAL_RESULT_TEXT = 'the wish is recorded and the result comes back for review';
|
|
171
171
|
const WAITING_INPUT_STATUSES = new Set(['needs_input', 'waiting_input']);
|
|
172
172
|
const PATH_SLOT_QUESTION = 'Where should I find the file or folder you named?';
|
|
173
173
|
const AUDIENCE_WORDS = /\b(?:for|user|users|customer|customers|operator|operators|agent|agents|developer|developers|admin|admins|member|members|team|teams|visitor|visitors|client|clients|student|students|founder|founders|newcomer|newcomers)\b/i;
|
package/lib/wish-delegate.js
CHANGED
|
@@ -726,7 +726,7 @@ function reviewWish(positionals, root = process.cwd(), options = {}) {
|
|
|
726
726
|
review_score: score.value === null ? reviewWordScore(reviewText) : score.value,
|
|
727
727
|
reviewed_by: actorName(),
|
|
728
728
|
});
|
|
729
|
-
console.log(`Review saved for ${quoteText(target.wish.text || target.wish.id)}. It
|
|
729
|
+
console.log(`Review saved for ${quoteText(target.wish.text || target.wish.id)}. It shapes future runs once distilled into lessons.`);
|
|
730
730
|
return 0;
|
|
731
731
|
}
|
|
732
732
|
|
package/lib/zip.js
CHANGED
|
@@ -11,6 +11,13 @@ const UTF8_FLAG = 0x0800;
|
|
|
11
11
|
const DEFLATE = 8;
|
|
12
12
|
const STORE = 0;
|
|
13
13
|
|
|
14
|
+
const ZIP_LIMITS = Object.freeze({
|
|
15
|
+
maxZipBytes: 5 * 1024 * 1024,
|
|
16
|
+
maxUnpackedBytes: 20 * 1024 * 1024,
|
|
17
|
+
maxEntryBytes: 20 * 1024 * 1024,
|
|
18
|
+
maxEntries: 500,
|
|
19
|
+
});
|
|
20
|
+
|
|
14
21
|
let crcTable = null;
|
|
15
22
|
|
|
16
23
|
function getCrcTable() {
|
|
@@ -155,24 +162,56 @@ function assertReadable(buffer, offset, length, label) {
|
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
function
|
|
165
|
+
function readLimits(options = {}) {
|
|
166
|
+
return {
|
|
167
|
+
maxZipBytes: options.maxZipBytes ?? ZIP_LIMITS.maxZipBytes,
|
|
168
|
+
maxUnpackedBytes: options.maxUnpackedBytes ?? ZIP_LIMITS.maxUnpackedBytes,
|
|
169
|
+
maxEntryBytes: options.maxEntryBytes ?? ZIP_LIMITS.maxEntryBytes,
|
|
170
|
+
maxEntries: options.maxEntries ?? ZIP_LIMITS.maxEntries,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function assertWithinLimit(value, limit, message) {
|
|
175
|
+
if (!Number.isFinite(limit) || limit < 0) {
|
|
176
|
+
throw new Error('invalid zip read limit');
|
|
177
|
+
}
|
|
178
|
+
if (value > limit) throw new Error(message);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function readZipBuffer(input, options = {}) {
|
|
159
182
|
const buffer = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
183
|
+
const limits = readLimits(options);
|
|
184
|
+
assertWithinLimit(
|
|
185
|
+
buffer.length,
|
|
186
|
+
limits.maxZipBytes,
|
|
187
|
+
`refusing zip: archive size ${buffer.length} bytes exceeds ${limits.maxZipBytes} byte limit`,
|
|
188
|
+
);
|
|
160
189
|
const endOffset = findEndOfCentralDirectory(buffer);
|
|
161
190
|
const entryCount = buffer.readUInt16LE(endOffset + 10);
|
|
162
191
|
const centralSize = buffer.readUInt32LE(endOffset + 12);
|
|
163
192
|
const centralOffset = buffer.readUInt32LE(endOffset + 16);
|
|
193
|
+
assertWithinLimit(
|
|
194
|
+
entryCount,
|
|
195
|
+
limits.maxEntries,
|
|
196
|
+
`refusing zip: ${entryCount} entries exceeds ${limits.maxEntries} entry limit`,
|
|
197
|
+
);
|
|
164
198
|
assertReadable(buffer, centralOffset, centralSize, 'central directory');
|
|
165
199
|
|
|
166
|
-
const
|
|
200
|
+
const metadata = [];
|
|
201
|
+
const centralEnd = centralOffset + centralSize;
|
|
202
|
+
let declaredUnpackedBytes = 0;
|
|
167
203
|
let cursor = centralOffset;
|
|
168
204
|
for (let i = 0; i < entryCount; i += 1) {
|
|
169
|
-
|
|
205
|
+
if (cursor + 46 > centralEnd) {
|
|
206
|
+
throw new Error('invalid zip: truncated central directory entry');
|
|
207
|
+
}
|
|
170
208
|
if (buffer.readUInt32LE(cursor) !== CENTRAL_DIRECTORY_HEADER) {
|
|
171
209
|
throw new Error('invalid zip: malformed central directory');
|
|
172
210
|
}
|
|
173
211
|
|
|
174
212
|
const flags = buffer.readUInt16LE(cursor + 8);
|
|
175
213
|
const method = buffer.readUInt16LE(cursor + 10);
|
|
214
|
+
const expectedCrc = buffer.readUInt32LE(cursor + 16);
|
|
176
215
|
const compressedSize = buffer.readUInt32LE(cursor + 20);
|
|
177
216
|
const uncompressedSize = buffer.readUInt32LE(cursor + 24);
|
|
178
217
|
const nameLength = buffer.readUInt16LE(cursor + 28);
|
|
@@ -180,7 +219,9 @@ function readZipBuffer(input) {
|
|
|
180
219
|
const commentLength = buffer.readUInt16LE(cursor + 32);
|
|
181
220
|
const localOffset = buffer.readUInt32LE(cursor + 42);
|
|
182
221
|
const nameStart = cursor + 46;
|
|
183
|
-
|
|
222
|
+
if (nameStart + nameLength + extraLength + commentLength > centralEnd) {
|
|
223
|
+
throw new Error('invalid zip: truncated central directory name');
|
|
224
|
+
}
|
|
184
225
|
const name = buffer.slice(nameStart, nameStart + nameLength).toString('utf8');
|
|
185
226
|
cursor = nameStart + nameLength + extraLength + commentLength;
|
|
186
227
|
|
|
@@ -189,6 +230,39 @@ function readZipBuffer(input) {
|
|
|
189
230
|
throw new Error(`invalid zip: unsupported compression method ${method} for ${name}`);
|
|
190
231
|
}
|
|
191
232
|
|
|
233
|
+
assertWithinLimit(
|
|
234
|
+
uncompressedSize,
|
|
235
|
+
limits.maxEntryBytes,
|
|
236
|
+
`refusing zip: entry ${name} declares ${uncompressedSize} unpacked bytes, exceeding ${limits.maxEntryBytes} byte entry limit`,
|
|
237
|
+
);
|
|
238
|
+
declaredUnpackedBytes += uncompressedSize;
|
|
239
|
+
assertWithinLimit(
|
|
240
|
+
declaredUnpackedBytes,
|
|
241
|
+
limits.maxUnpackedBytes,
|
|
242
|
+
`refusing zip: declared unpacked size ${declaredUnpackedBytes} bytes exceeds ${limits.maxUnpackedBytes} byte limit`,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
metadata.push({
|
|
246
|
+
name,
|
|
247
|
+
flags,
|
|
248
|
+
method,
|
|
249
|
+
expectedCrc,
|
|
250
|
+
compressedSize,
|
|
251
|
+
uncompressedSize,
|
|
252
|
+
localOffset,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const entries = [];
|
|
257
|
+
for (const entry of metadata) {
|
|
258
|
+
const {
|
|
259
|
+
name,
|
|
260
|
+
method,
|
|
261
|
+
expectedCrc,
|
|
262
|
+
compressedSize,
|
|
263
|
+
uncompressedSize,
|
|
264
|
+
localOffset,
|
|
265
|
+
} = entry;
|
|
192
266
|
assertReadable(buffer, localOffset, 30, 'local file header');
|
|
193
267
|
if (buffer.readUInt32LE(localOffset) !== LOCAL_FILE_HEADER) {
|
|
194
268
|
throw new Error(`invalid zip: missing local header for ${name}`);
|
|
@@ -198,18 +272,31 @@ function readZipBuffer(input) {
|
|
|
198
272
|
const dataStart = localOffset + 30 + localNameLength + localExtraLength;
|
|
199
273
|
assertReadable(buffer, dataStart, compressedSize, `file data for ${name}`);
|
|
200
274
|
const compressed = buffer.slice(dataStart, dataStart + compressedSize);
|
|
201
|
-
|
|
275
|
+
let data;
|
|
276
|
+
try {
|
|
277
|
+
data = method === STORE
|
|
278
|
+
? Buffer.from(compressed)
|
|
279
|
+
: zlib.inflateRawSync(compressed, { maxOutputLength: Math.max(1, uncompressedSize) });
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error && error.code === 'ERR_BUFFER_TOO_LARGE') {
|
|
282
|
+
throw new Error(`invalid zip: inflated data exceeds declared size for ${name}`);
|
|
283
|
+
}
|
|
284
|
+
throw new Error(`invalid zip: could not inflate ${name}: ${error.message}`);
|
|
285
|
+
}
|
|
202
286
|
if (data.length !== uncompressedSize) {
|
|
203
287
|
throw new Error(`invalid zip: size mismatch for ${name}`);
|
|
204
288
|
}
|
|
289
|
+
if (crc32(data) !== expectedCrc) {
|
|
290
|
+
throw new Error(`invalid zip: checksum mismatch for ${name}`);
|
|
291
|
+
}
|
|
205
292
|
entries.push({ name, data });
|
|
206
293
|
}
|
|
207
294
|
|
|
208
295
|
return entries;
|
|
209
296
|
}
|
|
210
297
|
|
|
211
|
-
function readZipFile(filePath) {
|
|
212
|
-
return readZipBuffer(fs.readFileSync(filePath));
|
|
298
|
+
function readZipFile(filePath, options = {}) {
|
|
299
|
+
return readZipBuffer(fs.readFileSync(filePath), options);
|
|
213
300
|
}
|
|
214
301
|
|
|
215
302
|
module.exports = {
|
|
@@ -217,4 +304,5 @@ module.exports = {
|
|
|
217
304
|
writeZipFile,
|
|
218
305
|
readZipBuffer,
|
|
219
306
|
readZipFile,
|
|
307
|
+
ZIP_LIMITS,
|
|
220
308
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atris",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.40.0",
|
|
4
4
|
"description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
|
|
5
5
|
"main": "bin/atris.js",
|
|
6
6
|
"bin": {
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"test": "node --test",
|
|
58
58
|
"test:fast": "xargs node --test < test/fast-tests.txt",
|
|
59
59
|
"lint:decks": "node scripts/lint-all-decks.js",
|
|
60
|
+
"audit:markdown-whitespace": "node scripts/audit-markdown-whitespace.js",
|
|
60
61
|
"publish:release": "node scripts/publish-atris-release.js",
|
|
61
62
|
"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"
|
|
62
63
|
},
|
|
@@ -18,6 +18,15 @@ Keep this file aligned with `atris/persona.md`.
|
|
|
18
18
|
- Concrete examples over abstractions
|
|
19
19
|
- Cite the source when answering domain questions
|
|
20
20
|
|
|
21
|
+
## The Way Of Talking (every message a human reads)
|
|
22
|
+
|
|
23
|
+
- Plain words. Say what happened and what it means for the reader — cause and effect, not machinery.
|
|
24
|
+
- No task codes, commit hashes, or internal system names in the message body. If the reader needs a code to act, put ONE copyable command at the end.
|
|
25
|
+
- No insider terms without defining them in the same breath.
|
|
26
|
+
- One idea per line. Outcome first. Detail lives in files, not the message.
|
|
27
|
+
- Lists: spell the numbers as words (One. Two. Three.), each item its own paragraph with a blank line between. Never markdown numbered lists — they render cramped.
|
|
28
|
+
- The test: a sharp, tired non-engineer follows it on first read. If not, rewrite.
|
|
29
|
+
|
|
21
30
|
## Anti-patterns
|
|
22
31
|
|
|
23
32
|
- No em dashes in outbound copy
|