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/AGENTS.md
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
# AGENTS.md
|
|
1
|
+
# AGENTS.md: Universal Agent Instructions
|
|
2
2
|
|
|
3
|
-
> **New here?** Read [`FOR_AGENTS.md`](FOR_AGENTS.md)
|
|
3
|
+
> **New here?** Read [`FOR_AGENTS.md`](FOR_AGENTS.md): open letter from an agent who tried it.
|
|
4
4
|
|
|
5
5
|
> Works with: Claude Code, Cursor, Codex, OpenClaw, Windsurf, and any AI coding agent.
|
|
6
6
|
|
|
7
|
+
## The way of talking (every reply, no exceptions)
|
|
8
|
+
|
|
9
|
+
Talk like a person. This governs every message you send the operator: boot, status, summaries, everything.
|
|
10
|
+
|
|
11
|
+
- Plain words. Say what happened and what it means for the reader, cause and effect. Not the machinery.
|
|
12
|
+
- No task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick) in the message body. If the reader needs a command, put ONE copyable line at the end.
|
|
13
|
+
- No insider terms without defining them in the same breath. A sharp non-engineer must follow it on first read.
|
|
14
|
+
- Lead with the outcome. Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies; those belong in files.
|
|
15
|
+
- Detail lives in files and receipts. Offer "want the detail?" instead of dumping it.
|
|
16
|
+
- No em dashes anywhere; use a comma, colon, or period.
|
|
17
|
+
- The test before sending: read it fried at 2am. If decoding takes work, rewrite it.
|
|
18
|
+
|
|
19
|
+
copy these shapes:
|
|
20
|
+
- **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
|
|
21
|
+
- **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
|
|
22
|
+
- **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
|
|
23
|
+
|
|
24
|
+
Canonical source: `atris/atris.md` `## voice`. This block is a copy for tools that only read `AGENTS.md`; if they differ, atris.md wins.
|
|
25
|
+
|
|
7
26
|
## Quick Start
|
|
8
27
|
|
|
9
28
|
```bash
|
|
@@ -31,7 +50,7 @@ task truth, proof, review, and backend/cloud sync all flow through Atris.
|
|
|
31
50
|
|------|---------|
|
|
32
51
|
| `atris/atris.md` | Protocol/backbone for this workspace |
|
|
33
52
|
| `atris/PERSONA.md` | Communication style (read first) |
|
|
34
|
-
| `atris task` | Current tasks, claims, dialogue, proof
|
|
53
|
+
| `atris task` | Current tasks, claims, dialogue, proof; deep dive: [`tasks.md`](tasks.md) |
|
|
35
54
|
| `.atris/state/tasks.projection.json` | Readable task projection for UIs/agents |
|
|
36
55
|
| `atris/TODO.md` | Rendered/legacy task view only |
|
|
37
56
|
| `atris/MAP.md` | Navigation (where is X?) |
|
|
@@ -125,13 +144,13 @@ member -> mission start --verify -> status --status active -> one bounded step -
|
|
|
125
144
|
- Close: if the verifier passes, run `atris mission complete <id> --proof "<receipt_path>"`; if current-agent work should keep going, repeat status -> step -> tick.
|
|
126
145
|
- Rollout: `atrisos-backend` and `atrisos-web` agents must check active missions before picking work; if no active mission exists and autonomy was requested, create one with owner, verifier, lane, and stop condition first.
|
|
127
146
|
|
|
128
|
-
## Build Craft
|
|
147
|
+
## Build Craft: what decides acceptance
|
|
129
148
|
|
|
130
149
|
Mined from this repo's receipts (803 receipts, 1711 episodes): proofs naming a
|
|
131
150
|
runnable verify command were accepted 670/674 at the gate; 24 of 28 bounces
|
|
132
151
|
named none. These rules are the difference, in priority order:
|
|
133
152
|
|
|
134
|
-
1. **Name a runnable verify command in every proof**, and run it bare
|
|
153
|
+
1. **Name a runnable verify command in every proof**, and run it bare; never
|
|
135
154
|
`test | tail` or `| grep`; a pipe replaces your exit code with the filter's.
|
|
136
155
|
2. **A task naming a spec file (`atris/features/<name>/idea.md`) is a contract.**
|
|
137
156
|
Read it before writing code; build the named slice only; use its verify
|
|
@@ -157,7 +176,7 @@ named none. These rules are the difference, in priority order:
|
|
|
157
176
|
reproduces it against the real runtime, not a mock that can stay green
|
|
158
177
|
through breakage.
|
|
159
178
|
10. **When your engine dies mid-build (credits, limits), that is a staffing
|
|
160
|
-
event, not a failure
|
|
179
|
+
event, not a failure**; leave the worktree intact with a note; the
|
|
161
180
|
conductor restaffs it.
|
|
162
181
|
|
|
163
182
|
## Rules
|
package/atris/PERSONA.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PERSONA.md
|
|
1
|
+
# PERSONA.md: Atris Agent Personality
|
|
2
2
|
|
|
3
3
|
This defines how Atris agents communicate, decide, and work.
|
|
4
4
|
|
|
@@ -28,7 +28,7 @@ This defines how Atris agents communicate, decide, and work.
|
|
|
28
28
|
|
|
29
29
|
## Core Workflow
|
|
30
30
|
|
|
31
|
-
**Read before you act.** Before planning or building, read the relevant files. Understand the current state. Your first action in any new area is always reconnaissance
|
|
31
|
+
**Read before you act.** Before planning or building, read the relevant files. Understand the current state. Your first action in any new area is always reconnaissance, not execution.
|
|
32
32
|
|
|
33
33
|
**Ask for intent when it's fuzzy or the blast radius is real.** A vague brief gets 2-3 named interpretations with a recommendation, not open questions. Small, reversible, clearly-scoped work: act, then report. Standing autonomy (autoland, push-asap) means asking permission for routine work is a failure smell, not politeness.
|
|
34
34
|
|
|
@@ -52,9 +52,13 @@ We can always add layer by layer.
|
|
|
52
52
|
|
|
53
53
|
## Communication Style
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Talk like a person.** Every message the operator reads: plain words, what happened and what it means for them, cause and effect. Not the machinery.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
**No codes in the message body.** No task ids, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick). If the reader needs a command, ONE copyable line at the end. Insider terms get defined in the same breath or cut.
|
|
58
|
+
|
|
59
|
+
**Lead with the outcome.** Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies. Detail lives in files; offer "want the detail?" instead of dumping it.
|
|
60
|
+
|
|
61
|
+
**The test before sending:** read it fried at 2am. If decoding takes work, rewrite it.
|
|
58
62
|
|
|
59
63
|
If something is slop, call it out. Optimize ruthlessly.
|
|
60
64
|
|
package/atris.md
CHANGED
|
@@ -83,6 +83,13 @@ What you ship should not read as generated. The test: if someone said "an AI mad
|
|
|
83
83
|
|
|
84
84
|
The same discipline for words. Output stays sharp no matter how bloated the context. A full context is not license to ramble.
|
|
85
85
|
|
|
86
|
+
- **The way of talking.** Chat replies to the operator carry no task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick); if the reader needs a command, it is ONE copyable line at the end. Insider terms get defined in the same breath or cut. The test before sending: read it fried at 2am; if decoding takes work, rewrite. This is the canonical statement; `AGENTS.md` and other adapters carry a copy of it, never a variant.
|
|
87
|
+
|
|
88
|
+
copy these shapes:
|
|
89
|
+
- **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
|
|
90
|
+
- **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
|
|
91
|
+
- **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
|
|
92
|
+
|
|
86
93
|
- **Lead with the move.** Answer first, support after. No preamble, no agreement reflex ("great question", "you're absolutely right").
|
|
87
94
|
- **Specific over buzzy.** Name the exact thing. If you can't, you don't understand it yet; go look, don't hedge.
|
|
88
95
|
- **Cut filler.** Drop "it's worth noting", "in order to", "leverage", "seamless", "robust", "delve", stacked hedges, and em dashes. `atris slop` flags the prose tells (em-dash, hype-copy) too.
|
package/ax
CHANGED
|
@@ -13,6 +13,7 @@ const { loadCredentials } = require('./utils/auth');
|
|
|
13
13
|
const { apiRequestJson, getApiBaseUrl } = require('./utils/api');
|
|
14
14
|
const missionRuntime = require('./lib/mission-runtime-loop');
|
|
15
15
|
const { loadOverrides, pickLane } = require('./lib/ax-auto-lane');
|
|
16
|
+
const { buildToolResultBody } = require('./lib/tool-result-encode');
|
|
16
17
|
|
|
17
18
|
const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
|
|
18
19
|
const BACKEND = {
|
|
@@ -2871,7 +2872,7 @@ async function postTurn(message, options = {}) {
|
|
|
2871
2872
|
if (options.business) {
|
|
2872
2873
|
return options.business.postToolResult(callId, result, backendBaseUrl({ route: 'cloud' }));
|
|
2873
2874
|
}
|
|
2874
|
-
return postJson(BACKEND_API_TOOL_RESULT_PATH,
|
|
2875
|
+
return postJson(BACKEND_API_TOOL_RESULT_PATH, buildToolResultBody(callId, result), {
|
|
2875
2876
|
token,
|
|
2876
2877
|
route: endpointRoute,
|
|
2877
2878
|
timeoutMs: 30000,
|
package/bin/atris.js
CHANGED
|
@@ -504,7 +504,7 @@ function showHelp() {
|
|
|
504
504
|
console.log(' activate - Load Atris context');
|
|
505
505
|
console.log(' radar - Show live agents joined with tasks, missions, and worktrees');
|
|
506
506
|
console.log(' stream - Watch the whole team work live in one terminal');
|
|
507
|
-
console.log(' team -
|
|
507
|
+
console.log(' team - One team view: members, roles, engine assignments (presence for live)');
|
|
508
508
|
console.log(' watch - Turn one sentence into an always-on background watcher');
|
|
509
509
|
console.log(' ctop - Show a process-first live agent CPU/memory view');
|
|
510
510
|
console.log(' launchpad - Show the next action from local brain, task, mission, and proof state');
|
|
@@ -520,15 +520,17 @@ function showHelp() {
|
|
|
520
520
|
console.log(' harvest - Find bugs and next actions from receipts, run logs, and thinking');
|
|
521
521
|
console.log(' verify - Validate work is done (tests, MAP.md, changes)');
|
|
522
522
|
console.log(' slop - Deterministic slop detector: frontend/prose tells, dead code (slop dead --exports)');
|
|
523
|
+
console.log(' voice - Scan binary voice tells or judge reply shape and plainness');
|
|
523
524
|
console.log(' recover - Explain stopped missions; --apply performs safe recovery only');
|
|
524
525
|
console.log(' task - Local agent task plane (atomic claims, TODO import)');
|
|
525
|
-
console.log(' golden path (
|
|
526
|
+
console.log(' golden path (one tick, by cron or by hand):');
|
|
526
527
|
console.log(' atris task delegate "fix the login bug" --to <member>');
|
|
527
528
|
console.log(' atris task claim <id> --as <member>');
|
|
528
529
|
console.log(' ... build ...');
|
|
529
530
|
console.log(' atris task ready <id> --verify');
|
|
530
531
|
console.log(' atris autoland tick # second check runs, task lands');
|
|
531
532
|
console.log(' mission - Goal + loop + member owner + verifier + receipt; --budget quick|long|deep sets bounded tiers');
|
|
533
|
+
console.log(' decide - Answer open mission asks and send the decision to the next tick');
|
|
532
534
|
console.log(' release - Tag release, bump version, create GitHub release, draft /launch');
|
|
533
535
|
console.log(' learn - Project learnings (patterns, pitfalls, preferences)');
|
|
534
536
|
console.log(' study - On-demand learning feed: ingest topic, start server, open browser');
|
|
@@ -537,6 +539,7 @@ function showHelp() {
|
|
|
537
539
|
console.log(' avail - Booking availability (/book/{username} weekly windows)');
|
|
538
540
|
console.log(' brain - Compile MAP/TODO/wiki/state into a loadable agent brain');
|
|
539
541
|
console.log(' lesson - Append a one-line lesson to atris/lessons.md (mine: distill receipts/episodes/scorecards into policy lessons)');
|
|
542
|
+
console.log(' taste - Record the operator\'s keep, kill, and more creative verdicts');
|
|
540
543
|
console.log(' teach - Turn a bad turn into a failing benchmark, then a permanent guard (red gate: no promotion without a proven failure)');
|
|
541
544
|
console.log(' ingest - Local-first wiki ingest into atris/wiki/');
|
|
542
545
|
console.log(' query - Local-first wiki query against atris/wiki/');
|
|
@@ -551,7 +554,7 @@ function showHelp() {
|
|
|
551
554
|
console.log(' worktree - Isolated Git worktrees plus guarded ship/merge for parallel agents');
|
|
552
555
|
console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
|
|
553
556
|
console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
|
|
554
|
-
console.log(
|
|
557
|
+
console.log(` autoland - Approve the policy once; ${require('../lib/autoland').certifiedWorkLandsPhrase(process.cwd())}, you keep irreversible calls`);
|
|
555
558
|
console.log(' engine - Engine registry: list/resolve roles, health flips, default engine, `engine test`, and dispatch flights');
|
|
556
559
|
console.log(' router - inspect ax lane outcomes and promote gated reflex overrides');
|
|
557
560
|
console.log(' sign - Co-author trailer on every commit in an atris workspace (on/off/status)');
|
|
@@ -588,7 +591,7 @@ function showHelp() {
|
|
|
588
591
|
console.log(' browse [query] - Discover workspace templates');
|
|
589
592
|
console.log(' fork <template> - Clone a template into a new workspace');
|
|
590
593
|
console.log(' publish - Share your workspace as a template');
|
|
591
|
-
console.log(' pack -
|
|
594
|
+
console.log(' pack - Craft, publish, share, browse, or install an Atris brain zip');
|
|
592
595
|
console.log(' sleep [business|member] [--loop id] - Pause compute or flip a member switch');
|
|
593
596
|
console.log(' wake [business|member] [--loop id] - Resume compute or flip a member switch');
|
|
594
597
|
console.log('');
|
|
@@ -597,6 +600,8 @@ function showHelp() {
|
|
|
597
600
|
console.log(' business onboard - Onboard from sparse input (--name, --website, --contact)');
|
|
598
601
|
console.log(' business add <slug> - Connect a business');
|
|
599
602
|
console.log(' business list - Show connected businesses');
|
|
603
|
+
console.log(' business room [slug] - Show the live room from the production API');
|
|
604
|
+
console.log(' business store on - Enable the room store with its current catalog');
|
|
600
605
|
console.log(' business remove <slug> - Disconnect a business');
|
|
601
606
|
console.log(' business team [slug] - Show members, roles, and admin access');
|
|
602
607
|
console.log(' business health <slug> - Health report (members, workspace, issues)');
|
|
@@ -1815,6 +1820,18 @@ if (command === 'init') {
|
|
|
1815
1820
|
} else if (command === 'router') {
|
|
1816
1821
|
const code = require('../commands/router').routerCommand(process.argv.slice(3));
|
|
1817
1822
|
if (Number.isInteger(code) && code !== 0) process.exit(code);
|
|
1823
|
+
} else if (command === 'decide') {
|
|
1824
|
+
// process.exit() can outrun a piped stdout: writes beyond the 64KB pipe
|
|
1825
|
+
// buffer are async, so large --json payloads truncate at 64KB multiples.
|
|
1826
|
+
// Queue an empty write and exit from its callback — it fires only after
|
|
1827
|
+
// every earlier buffered write has drained (BCK-1306).
|
|
1828
|
+
const exitAfterStdoutDrain = (code) => {
|
|
1829
|
+
if (process.stdout.writableLength === 0) process.exit(code);
|
|
1830
|
+
else process.stdout.write('', () => process.exit(code));
|
|
1831
|
+
};
|
|
1832
|
+
Promise.resolve(require('../commands/decide').decideCommand(process.argv.slice(3)))
|
|
1833
|
+
.then(() => exitAfterStdoutDrain(process.exitCode || 0))
|
|
1834
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitAfterStdoutDrain(1); });
|
|
1818
1835
|
} else if (command === 'mission') {
|
|
1819
1836
|
// process.exit() can outrun a piped stdout: writes beyond the 64KB pipe
|
|
1820
1837
|
// buffer are async, so large --json payloads truncate at 64KB multiples.
|
|
@@ -1962,7 +1979,7 @@ if (command === 'init') {
|
|
|
1962
1979
|
if (logsArgs.includes('--help') || logsArgs.includes('-h') || logsArgs[0] === 'help') {
|
|
1963
1980
|
console.log('Usage: atris logs [--date YYYY-MM-DD] [--json]');
|
|
1964
1981
|
console.log('');
|
|
1965
|
-
console.log(' atris logs Show today\'s
|
|
1982
|
+
console.log(' atris logs Show today\'s journal entries, completed receipts, and inbox in one view');
|
|
1966
1983
|
console.log(' atris logs --date <day> Show the digest for a specific day');
|
|
1967
1984
|
console.log(' atris logs --json Emit the digest as JSON');
|
|
1968
1985
|
process.exit(0);
|
|
@@ -2633,6 +2650,10 @@ if (command === 'init') {
|
|
|
2633
2650
|
const subcommand = process.argv[3];
|
|
2634
2651
|
const args = process.argv.slice(4);
|
|
2635
2652
|
require('../commands/lesson')(subcommand, ...args);
|
|
2653
|
+
} else if (command === 'taste') {
|
|
2654
|
+
Promise.resolve(require('../commands/taste').tasteCommand(process.argv.slice(3)))
|
|
2655
|
+
.then((code) => process.exit(typeof code === 'number' ? code : 0))
|
|
2656
|
+
.catch((err) => { console.error(`taste could not continue: ${err.message || err}.`); process.exit(1); });
|
|
2636
2657
|
} else if (command === 'teach') {
|
|
2637
2658
|
const subcommand = process.argv[3];
|
|
2638
2659
|
const args = process.argv.slice(4);
|
|
@@ -2806,6 +2827,10 @@ if (command === 'init') {
|
|
|
2806
2827
|
Promise.resolve(require('../commands/slop').slopCommand(process.argv.slice(3)))
|
|
2807
2828
|
.then((code) => process.exit(typeof code === 'number' ? code : 0))
|
|
2808
2829
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2830
|
+
} else if (command === 'voice') {
|
|
2831
|
+
Promise.resolve(require('../commands/voice').voiceCommand(process.argv.slice(3)))
|
|
2832
|
+
.then((code) => process.exit(typeof code === 'number' ? code : 0))
|
|
2833
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2809
2834
|
} else if (command === 'write') {
|
|
2810
2835
|
// Write: guided writing sessions — human types every word, atris structures + reviews (plan-do-review for prose).
|
|
2811
2836
|
Promise.resolve(require('../commands/write').writeCommand(process.argv.slice(3)))
|
package/commands/agent-spawn.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { spawnSync } = require('child_process');
|
|
5
|
+
const { hasFlag: hasSingleFlag } = require('../lib/arg-parser');
|
|
5
6
|
|
|
6
7
|
const SPAWN_SCHEMA = 'atris.agent_spawn.v1';
|
|
7
8
|
const DOGFOOD_SCHEMA = 'atris.agent_cli_dogfood.v1';
|
|
@@ -63,8 +64,9 @@ function flagValue(args, names) {
|
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
// This command accepts alias lists, while the shared helper checks one name.
|
|
68
|
+
function hasAnyFlag(args, names) {
|
|
69
|
+
return names.some(name => hasSingleFlag(args, name));
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function positionalArgs(args) {
|
|
@@ -148,11 +150,11 @@ function commandForEngine(request) {
|
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
function parseSpawnArgs(args = []) {
|
|
151
|
-
const help = args.length === 0 ||
|
|
153
|
+
const help = args.length === 0 || hasAnyFlag(args, ['--help', '-h']) || args[0] === 'help';
|
|
152
154
|
if (help) return { help: true };
|
|
153
155
|
|
|
154
|
-
const json =
|
|
155
|
-
const dryRun =
|
|
156
|
+
const json = hasAnyFlag(args, ['--json']);
|
|
157
|
+
const dryRun = hasAnyFlag(args, ['--dry-run']);
|
|
156
158
|
const roleFlag = flagValue(args, ['--role']);
|
|
157
159
|
const engine = String(flagValue(args, ['--engine']) || 'manual').toLowerCase();
|
|
158
160
|
if (!ALLOWED_ENGINES.has(engine)) throw new Error(`Unknown engine: ${engine}`);
|
|
@@ -209,10 +211,10 @@ function showSpawnHelp(output = console.log, commandName = 'atris agent spawn')
|
|
|
209
211
|
}
|
|
210
212
|
|
|
211
213
|
function parseDogfoodArgs(args = []) {
|
|
212
|
-
const help =
|
|
213
|
-
const json =
|
|
214
|
-
const live =
|
|
215
|
-
const noWrite =
|
|
214
|
+
const help = hasAnyFlag(args, ['--help', '-h']) || args[0] === 'help';
|
|
215
|
+
const json = hasAnyFlag(args, ['--json']);
|
|
216
|
+
const live = hasAnyFlag(args, ['--live']);
|
|
217
|
+
const noWrite = hasAnyFlag(args, ['--no-write']);
|
|
216
218
|
const model = flagValue(args, ['--model']) || 'glm-5.2';
|
|
217
219
|
const timeoutRaw = Number(flagValue(args, ['--timeout']) || 45);
|
|
218
220
|
const timeoutMs = Math.max(5, Math.min(300, timeoutRaw)) * 1000;
|
|
@@ -431,7 +433,7 @@ function agentSpawnCommand(args = [], deps = {}) {
|
|
|
431
433
|
function agentSpawnListCommand(args = [], deps = {}) {
|
|
432
434
|
const root = deps.root || process.cwd();
|
|
433
435
|
const output = deps.output || ((line = '') => console.log(line));
|
|
434
|
-
const json =
|
|
436
|
+
const json = hasAnyFlag(args, ['--json']);
|
|
435
437
|
const requests = loadSpawnRequests(root).reverse();
|
|
436
438
|
const payload = { ok: true, action: 'spawn_list', requests, state_path: spawnStatePath(root) };
|
|
437
439
|
if (json) {
|
|
@@ -451,7 +453,7 @@ function agentSpawnListCommand(args = [], deps = {}) {
|
|
|
451
453
|
function agentSpawnStatusCommand(args = [], deps = {}) {
|
|
452
454
|
const root = deps.root || process.cwd();
|
|
453
455
|
const output = deps.output || ((line = '') => console.log(line));
|
|
454
|
-
const json =
|
|
456
|
+
const json = hasAnyFlag(args, ['--json']);
|
|
455
457
|
const id = positionalArgs(args)[0];
|
|
456
458
|
if (!id) throw new Error('Missing spawn id. Usage: atris agent spawn-status <id>');
|
|
457
459
|
const request = loadSpawnRequests(root).find(req => req.id === id);
|
package/commands/autoland.js
CHANGED
|
@@ -6,7 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const { spawnSync } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const autoland = require('../lib/autoland');
|
|
9
|
-
const { gateForHuman } = require('../lib/voice-gate');
|
|
9
|
+
const { gateForHuman, plainLandingReason } = require('../lib/voice-gate');
|
|
10
10
|
const { evaluateAutoAccept } = require('../lib/auto-accept-certified');
|
|
11
11
|
const { operatorReady, hasAgentJargon, explainResult } = autoland;
|
|
12
12
|
const MISSION_AUTO_VERIFY_STATUSES = new Set(['planning', 'paused', 'ready']);
|
|
@@ -436,26 +436,9 @@ function protectedReviewWaiting(root, policy = {}, waiting = []) {
|
|
|
436
436
|
.filter((row) => !alreadyWaiting.has(row.ref));
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
denied_tag_deploy: 'a deploy: human decision required',
|
|
443
|
-
denied_tag_security: 'security: human decision required',
|
|
444
|
-
denied_tag_customer: 'customer-facing: human decision required',
|
|
445
|
-
denied_tag_external: 'outward-facing: human decision required',
|
|
446
|
-
denied_tag_feedback: 'customer feedback: human decision required',
|
|
447
|
-
needs_second_reviewer_or_third_pass: 'needs one more independent check first',
|
|
448
|
-
needs_independent_reviewer: 'built and judged by the same actor, needs an independent check',
|
|
449
|
-
verifier_is_builder: 'the re-check actor built this row, another actor must re-check',
|
|
450
|
-
judge_equals_worker: 'built and judged by the same actor, hand the review to someone else',
|
|
451
|
-
not_agent_certified: 'not certified yet',
|
|
452
|
-
insufficient_review_passes: 'not enough review passes yet',
|
|
453
|
-
strict_verify_missing: 'no recorded check command to re-run',
|
|
454
|
-
verify_failed: 'its check command failed on re-run',
|
|
455
|
-
proof_unmerged_or_draft_pr_boundary: 'its proof points at an unmerged draft',
|
|
456
|
-
};
|
|
457
|
-
return map[reason] || reason.replace(/_/g, ' ');
|
|
458
|
-
}
|
|
439
|
+
// One reason table for every human-facing landing refusal lives in
|
|
440
|
+
// lib/voice-gate.js (plainLandingReason); this alias keeps call sites short.
|
|
441
|
+
const plainReason = plainLandingReason;
|
|
459
442
|
|
|
460
443
|
function showStatus(root, args) {
|
|
461
444
|
const json = args.includes('--json');
|
|
@@ -475,7 +458,8 @@ function showStatus(root, args) {
|
|
|
475
458
|
// running hourly for weeks, and would keep reporting "running" for one
|
|
476
459
|
// someone deleted. A loop that cannot confirm its own liveness from
|
|
477
460
|
// evidence cannot be trusted to say it is healthy.
|
|
478
|
-
const heartbeatInstalled = heartbeatLiveness(root, policy);
|
|
461
|
+
const heartbeatInstalled = autoland.heartbeatLiveness(root, policy);
|
|
462
|
+
const heartbeatLive = autoland.heartbeatIsLive(root);
|
|
479
463
|
|
|
480
464
|
if (json) {
|
|
481
465
|
console.log(JSON.stringify({
|
|
@@ -491,7 +475,7 @@ function showStatus(root, args) {
|
|
|
491
475
|
}
|
|
492
476
|
|
|
493
477
|
console.log('');
|
|
494
|
-
console.log(
|
|
478
|
+
console.log(`autoland: ${autoland.certifiedWorkLandsPhrase(root)}; you keep the irreversible calls`);
|
|
495
479
|
console.log('');
|
|
496
480
|
const policyOwner = String(policy?.enabled_by || 'unknown').trim() || 'unknown';
|
|
497
481
|
const policyText = enabled
|
|
@@ -501,26 +485,33 @@ function showStatus(root, args) {
|
|
|
501
485
|
: 'off - everything waits for you';
|
|
502
486
|
console.log(` policy: ${policyText}`);
|
|
503
487
|
if (enabled && acceptAll) console.log(' bar: everything lands except the protected lanes (money, deploys, security, customer, outward)');
|
|
504
|
-
console.log(` heartbeat: ${heartbeatStatusText(root, policy)}`);
|
|
488
|
+
console.log(` heartbeat: ${autoland.heartbeatStatusText(root, policy)}`);
|
|
505
489
|
if (policy && policy.imessage_to) console.log(` daily message: ${policy.imessage_to} at ${policy.digest_hour ?? autoland.DEFAULT_DIGEST_HOUR}:00`);
|
|
506
490
|
const reapTrouble = autoland.readState(root).last_reap_error;
|
|
507
491
|
if (reapTrouble) console.log(` cleanup trouble: landing sweep failed on ${reapTrouble.date} (${reapTrouble.error}) - run: atris land --reap`);
|
|
508
492
|
console.log('');
|
|
509
|
-
if (readyForRecheck.length > 0) {
|
|
510
|
-
console.log(` ready for heartbeat recheck: ${readyForRecheck.length}`);
|
|
511
|
-
for (const r of readyForRecheck.slice(0, 10)) console.log(` rechecks then lands ${r.ref}`);
|
|
512
|
-
} else {
|
|
513
|
-
console.log(' nothing is ready to land on its own right now.');
|
|
514
|
-
}
|
|
515
493
|
const humanOnly = blocked.filter((r) => String(r.reason || '').startsWith('denied_tag_'));
|
|
516
494
|
const needsWork = blocked.filter((r) => !String(r.reason || '').startsWith('denied_tag_'));
|
|
517
495
|
if (humanOnly.length > 0) {
|
|
518
|
-
console.log(`
|
|
519
|
-
for (const r of humanOnly.slice(0, 10)) console.log(`
|
|
496
|
+
console.log(` yours to decide first: ${humanOnly.length} protected ${humanOnly.length === 1 ? 'review' : 'reviews'}.`);
|
|
497
|
+
for (const r of humanOnly.slice(0, 10)) console.log(` ${r.ref} waits for you; ${plainReason(r.reason)}.`);
|
|
498
|
+
}
|
|
499
|
+
if (readyForRecheck.length > 0) {
|
|
500
|
+
if (heartbeatLive) {
|
|
501
|
+
console.log(` landing on their own after the hourly recheck: ${readyForRecheck.length}.`);
|
|
502
|
+
for (const r of readyForRecheck.slice(0, 10)) console.log(` ${r.ref} passes the recheck, then lands itself.`);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(` ready to land ${autoland.STALE_HEARTBEAT_LANDING}: ${readyForRecheck.length}.`);
|
|
505
|
+
for (const r of readyForRecheck.slice(0, 10)) {
|
|
506
|
+
console.log(` ${r.ref} passes the recheck, then lands ${autoland.STALE_HEARTBEAT_LANDING}.`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
console.log(' nothing is ready to land on its own right now.');
|
|
520
511
|
}
|
|
521
512
|
if (needsWork.length > 0) {
|
|
522
|
-
console.log(`
|
|
523
|
-
for (const r of needsWork.slice(0, 10)) console.log(`
|
|
513
|
+
console.log(` still needs work before it can land: ${needsWork.length}.`);
|
|
514
|
+
for (const r of needsWork.slice(0, 10)) console.log(` ${r.ref} stays put; ${plainReason(String(r.reason || ''))}.`);
|
|
524
515
|
}
|
|
525
516
|
if (waiting.length > 0) {
|
|
526
517
|
console.log('');
|
|
@@ -699,48 +690,8 @@ function persistTickReceipt(root, receipt) {
|
|
|
699
690
|
// loud, and call out the two states that mean the loop is wedged rather than
|
|
700
691
|
// idle: work blocked behind a check the harness could not execute, and work
|
|
701
692
|
// that passed every gate and still did not land.
|
|
702
|
-
//
|
|
703
|
-
//
|
|
704
|
-
// read-only and must stay fast (a stalled crontab would block it for the full
|
|
705
|
-
// 10s read timeout — see 'autoland status never probes a stalled crontab').
|
|
706
|
-
// Tick receipts are local file reads and are stronger evidence anyway: a
|
|
707
|
-
// crontab line proves configuration, a recent receipt proves the loop ran.
|
|
708
|
-
function heartbeatLiveness(root, policy) {
|
|
709
|
-
return typeof policy?.heartbeat_installed === 'boolean' ? policy.heartbeat_installed : null;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function lastTickAgeHours(root) {
|
|
713
|
-
try {
|
|
714
|
-
const runsDir = path.join(root, 'atris', 'runs');
|
|
715
|
-
const newest = fs.readdirSync(runsDir)
|
|
716
|
-
.filter((f) => f.startsWith('autoland-tick-') && f.endsWith('.json'))
|
|
717
|
-
.sort()
|
|
718
|
-
.pop();
|
|
719
|
-
if (!newest) return null;
|
|
720
|
-
const stamp = fs.statSync(path.join(runsDir, newest)).mtimeMs;
|
|
721
|
-
return (Date.now() - stamp) / 3_600_000;
|
|
722
|
-
} catch {
|
|
723
|
-
return null;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function heartbeatStatusText(root, policy) {
|
|
728
|
-
const ageHours = lastTickAgeHours(root);
|
|
729
|
-
const ageText = ageHours === null
|
|
730
|
-
? null
|
|
731
|
-
: ageHours < 1 ? 'under an hour' : `${Math.floor(ageHours)}h`;
|
|
732
|
-
// Evidence first: a receipt from the last couple of hours proves the loop is
|
|
733
|
-
// alive no matter what the policy file remembers. The hourly cron makes two
|
|
734
|
-
// missed hours an outage rather than jitter — and a heartbeat that has gone
|
|
735
|
-
// quiet is the single most useful thing this line can say, because that is
|
|
736
|
-
// the state nobody notices.
|
|
737
|
-
if (ageHours !== null && ageHours <= 2) return `running hourly (last tick ${ageText} ago)`;
|
|
738
|
-
if (ageHours !== null) return `SILENT - last tick ${ageText} ago; run atris autoland tick`;
|
|
739
|
-
const installed = heartbeatLiveness(root, policy);
|
|
740
|
-
if (installed === false) return 'not installed - run atris autoland on';
|
|
741
|
-
if (installed === true) return 'installed, but no tick has ever run - run atris autoland tick';
|
|
742
|
-
return 'unknown - run atris autoland on to check and repair';
|
|
743
|
-
}
|
|
693
|
+
// Heartbeat liveness helpers live in lib/autoland.js so task ready copy and
|
|
694
|
+
// status share one gate (recent tick receipt within two hours).
|
|
744
695
|
|
|
745
696
|
function stuckBacklogNote(receipt) {
|
|
746
697
|
const parts = [];
|
|
@@ -1049,7 +1000,7 @@ function runTickBody(root, { json, policy, receipt }) {
|
|
|
1049
1000
|
|
|
1050
1001
|
function showHelp() {
|
|
1051
1002
|
console.log('');
|
|
1052
|
-
console.log(
|
|
1003
|
+
console.log(`atris autoland: you approve the policy once; ${autoland.certifiedWorkLandsPhrase(process.cwd())}`);
|
|
1053
1004
|
console.log('');
|
|
1054
1005
|
console.log('finished work that passed its checks and two independent reviews lands');
|
|
1055
1006
|
console.log('automatically with a receipt. money, deploys, security, customer, and');
|
package/commands/bench.js
CHANGED
|
@@ -8,12 +8,10 @@ const {
|
|
|
8
8
|
taskMetadata,
|
|
9
9
|
} = require('../lib/bench/runner');
|
|
10
10
|
const { buildBenchReport, renderBenchReportText } = require('../lib/bench/report');
|
|
11
|
+
const { hasFlag } = require('../lib/arg-parser');
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function readFlag(args, name) {
|
|
13
|
+
// Preserve the existing rule that any inline value wins over a split value.
|
|
14
|
+
function readInlineFirstFlag(args, name) {
|
|
17
15
|
const prefix = `${name}=`;
|
|
18
16
|
const inline = args.find((arg) => String(arg).startsWith(prefix));
|
|
19
17
|
if (inline) return String(inline).slice(prefix.length);
|
|
@@ -58,11 +56,11 @@ async function runCommand(args) {
|
|
|
58
56
|
const asJson = hasFlag(args, '--json');
|
|
59
57
|
try {
|
|
60
58
|
const result = await runBench({
|
|
61
|
-
pack:
|
|
62
|
-
engine:
|
|
59
|
+
pack: readInlineFirstFlag(args, '--pack') || undefined,
|
|
60
|
+
engine: readInlineFirstFlag(args, '--engine') || undefined,
|
|
63
61
|
taskIds: readRepeatedFlag(args, '--task'),
|
|
64
|
-
label:
|
|
65
|
-
experiment:
|
|
62
|
+
label: readInlineFirstFlag(args, '--label'),
|
|
63
|
+
experiment: readInlineFirstFlag(args, '--experiment'),
|
|
66
64
|
updateBaseline: hasFlag(args, '--update-baseline'),
|
|
67
65
|
stateRoot: process.cwd(),
|
|
68
66
|
});
|
|
@@ -87,7 +85,7 @@ async function runCommand(args) {
|
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
function tasksCommand(args) {
|
|
90
|
-
const pack =
|
|
88
|
+
const pack = readInlineFirstFlag(args, '--pack') || undefined;
|
|
91
89
|
const tasks = taskMetadata({ pack });
|
|
92
90
|
if (hasFlag(args, '--json')) {
|
|
93
91
|
console.log(JSON.stringify({
|
|
@@ -121,7 +119,7 @@ function packsCommand(args) {
|
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
function resultsCommand(args) {
|
|
124
|
-
const last = Number(
|
|
122
|
+
const last = Number(readInlineFirstFlag(args, '--last') || 0);
|
|
125
123
|
const results = readResultRecords({ stateRoot: process.cwd(), last });
|
|
126
124
|
if (hasFlag(args, '--json')) {
|
|
127
125
|
console.log(JSON.stringify({
|
|
@@ -143,7 +141,7 @@ function resultsCommand(args) {
|
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
function reportCommand(args) {
|
|
146
|
-
const pack =
|
|
144
|
+
const pack = readInlineFirstFlag(args, '--pack') || undefined;
|
|
147
145
|
const asJson = hasFlag(args, '--json');
|
|
148
146
|
try {
|
|
149
147
|
const report = buildBenchReport({ pack, stateRoot: process.cwd() });
|