atris 3.79.0 → 3.80.1

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.
@@ -5,6 +5,8 @@
5
5
  // marker is needs-human (CLI-879); decision is the same hold under a clearer
6
6
  // name. Autonomous pickers must refuse these; list renderers must show them.
7
7
 
8
+ const { decisionChoiceText } = require('./task-explanation');
9
+
8
10
  const DECISION_HOLD_TAGS = new Set([
9
11
  'needs-human',
10
12
  'needshuman',
@@ -41,10 +43,74 @@ function decisionMarkerFor(task) {
41
43
  return isDecisionTask(task) ? DECISION_MARKER : '';
42
44
  }
43
45
 
46
+ function decisionRefFor(task) {
47
+ if (!task || typeof task !== 'object') return 'TASK';
48
+ const ref = task.display_id || task.legacy_ref || task.id || '';
49
+ const text = String(ref || '').trim();
50
+ if (!text) return 'TASK';
51
+ if (task.display_id || task.legacy_ref) return text;
52
+ return text.replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 8) || 'TASK';
53
+ }
54
+
55
+ function decisionShowCommand(task) {
56
+ return `atris task show ${decisionRefFor(task)}`;
57
+ }
58
+
59
+ function summarizeDecisionTask(task) {
60
+ const ref = decisionRefFor(task);
61
+ const title = String((task && task.title) || '').replace(/\s+/g, ' ').trim();
62
+ const choice = decisionChoiceText(task);
63
+ const command = `atris task show ${ref}`;
64
+ return {
65
+ id: (task && task.id) || null,
66
+ task_id: (task && task.id) || null,
67
+ ref,
68
+ display_id: (task && task.display_id) || ref,
69
+ title,
70
+ choice,
71
+ command,
72
+ show_command: command,
73
+ };
74
+ }
75
+
76
+ function summarizeSkippedDecisions(tasks = []) {
77
+ const list = (Array.isArray(tasks) ? tasks : [])
78
+ .filter(Boolean)
79
+ .map(summarizeDecisionTask);
80
+ const first = list[0] || null;
81
+ return {
82
+ reason: DECISION_REFUSE_REASON,
83
+ skipped_decision_count: list.length,
84
+ skipped_decision_refs: list.map((item) => item.ref),
85
+ skipped_decisions: list,
86
+ decisions: list,
87
+ choice: first ? first.choice : null,
88
+ command: first ? first.command : null,
89
+ show_command: first ? first.command : null,
90
+ next_command: first ? first.command : null,
91
+ };
92
+ }
93
+
94
+ function formatDecisionRefusalLines(tasks = []) {
95
+ const summary = summarizeSkippedDecisions(tasks);
96
+ const lines = [DECISION_REFUSE_REASON];
97
+ for (const item of summary.skipped_decisions) {
98
+ lines.push(` ${item.ref}: ${item.title}`);
99
+ lines.push(` choice: ${item.choice}`);
100
+ lines.push(` show: ${item.command}`);
101
+ }
102
+ if (summary.command) {
103
+ lines.push(`next: ${summary.command}`);
104
+ }
105
+ return lines;
106
+ }
107
+
44
108
  module.exports = {
45
109
  DECISION_REFUSE_REASON,
46
110
  taskTagTokens,
47
111
  isDecisionHoldTag,
48
112
  isDecisionTask,
49
113
  decisionMarkerFor,
114
+ summarizeSkippedDecisions,
115
+ formatDecisionRefusalLines,
50
116
  };
@@ -213,6 +213,25 @@ function taskApprovalControls({
213
213
  };
214
214
  }
215
215
 
216
+ function decisionChoiceText(task) {
217
+ const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
218
+ const explicitChoice = cleanText(
219
+ (task && task.choice)
220
+ || metadata.choice
221
+ || metadata.decision
222
+ || metadata.question
223
+ );
224
+ if (explicitChoice) return endSentence(plainText(explicitChoice));
225
+ const explicit = explicitExplanationFields(metadata);
226
+ if (explicit.what_changes) return endSentence(plainText(explicit.what_changes));
227
+ if (explicit.why_it_matters) return endSentence(plainText(explicit.why_it_matters));
228
+ const goal = cleanText(explanationGoalText(task));
229
+ if (goal) return endSentence(plainText(goal));
230
+ const title = cleanText(task && task.title);
231
+ if (title) return endSentence(plainText(title));
232
+ return 'Decide whether to approve this work, revise it, or remove the decision hold.';
233
+ }
234
+
216
235
  function approvalLines(approval, { indent = '' } = {}) {
217
236
  const lines = [`${indent}${approval.question}`];
218
237
  if (approval.approve.enabled) lines.push(`${indent}Approve: ${approval.approve.command}`);
@@ -228,6 +247,7 @@ module.exports = {
228
247
  explicitDoneLooksLike,
229
248
  plainText,
230
249
  taskExplanation,
250
+ decisionChoiceText,
231
251
  explanationFieldsFromInput,
232
252
  explanationLines,
233
253
  explanationMarkdownLines,
package/lib/yes-lock.js CHANGED
@@ -593,7 +593,7 @@ const ATRIS_INTERNAL = new Set([
593
593
  'version', 'help', 'next', 'atris', 'clean', 'close', 'harvest', 'verify', 'recover', 'search', 'scout', 'skill', 'member', 'codex-goal', 'learn', 'lesson', 'taste', 'teach',
594
594
  'experiments', 'bench', 'router', 'tree', 'receipt', 'proof', 'openclaw', 'pull', 'watch', 'live', 'align', 'terminal', 'computer', 'diff', 'youtube', 'x-search',
595
595
  'ingest', 'query', 'lint', 'loop', 'pulse', 'task', 'mission', 'campaign', 'agents', 'probe', 'worktree', 'caretaker', 'aeo', 'slop', 'voice', 'strings', 'write',
596
- 'security-review', 'secure', 'theme', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'recap', 'report', 'clarity', 'interview', 'moves', 'unknowns', 'avail',
596
+ 'security-review', 'secure', 'sentinel', 'theme', 'improve', 'study', 'rainmaker', 'xp', 'play', 'gm', 'game', 'recap', 'report', 'clarity', 'interview', 'moves', 'unknowns', 'avail',
597
597
  'scoreboard', 'sync-checkout', 'zen', 'ci', 'balance', 'usage', 'agent-client', 'transactions', 'hires', 'design', 'mcp', 'integrations', 'setup', 'clean-workspace', 'cw',
598
598
  'browse', 'sleep', 'wake', 'errors', 'wiki', 'code-review', 'cr', 'soul', 'fleet', 'fleet-report', 'loops', 'self-improve', 'compile', 'spaceship', 'truth', 'engine',
599
599
  'engines', 'link', 'playbook', 'brief', 'inbox', 'friends', 'rsi',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.79.0",
3
+ "version": "3.80.1",
4
4
  "description": "you say what you want in plain words. atris builds it, checks it, and shows you proof.",
5
5
  "mcpName": "io.github.atrislabs/atris",
6
6
  "main": "bin/atris.js",
@@ -91,7 +91,14 @@
91
91
  "developer-tools",
92
92
  "cli",
93
93
  "codebase-navigation",
94
- "ai-agents"
94
+ "ai-agents",
95
+ "agent-memory",
96
+ "ai-memory",
97
+ "llm-memory",
98
+ "project-memory",
99
+ "context-graph",
100
+ "knowledge-graph",
101
+ "memory"
95
102
  ],
96
103
  "author": "Keshav Rao (atrislabs)",
97
104
  "license": "MIT",
package/utils/auth.js CHANGED
@@ -40,9 +40,11 @@ let inputIndex = 0;
40
40
  /**
41
41
  * Prompt the user for input, handling both TTY and piped input.
42
42
  * @param {string} question - Prompt text to display
43
+ * @param {{secret?: boolean}} [opts] - secret: never print the answer back
43
44
  * @returns {Promise<string>} User's input
44
45
  */
45
- function promptUser(question) {
46
+ function promptUser(question, opts = {}) {
47
+ const shown = (answer) => (opts.secret ? '' : answer);
46
48
  // If stdin is not a TTY (piped input), read all lines upfront
47
49
  if (!process.stdin.isTTY && inputLines.length === 0 && !sharedRl) {
48
50
  return new Promise((resolve) => {
@@ -57,7 +59,7 @@ function promptUser(question) {
57
59
  inputLines = data.trim().split('\n');
58
60
  process.stdout.write(question);
59
61
  const answer = inputLines[inputIndex++] || '';
60
- console.log(answer);
62
+ console.log(shown(answer));
61
63
  resolve(answer.trim());
62
64
  });
63
65
  process.stdin.resume();
@@ -69,22 +71,34 @@ function promptUser(question) {
69
71
  return new Promise((resolve) => {
70
72
  process.stdout.write(question);
71
73
  const answer = inputLines[inputIndex++] || '';
72
- console.log(answer);
74
+ console.log(shown(answer));
73
75
  resolve(answer.trim());
74
76
  });
75
77
  }
76
78
 
77
79
  // Interactive TTY mode - create readline per prompt
80
+ // A secret typed at a terminal must stay hidden even when stdout is a file:
81
+ // terminal mode turns off the terminal's own echo, and the prompt goes to
82
+ // whichever stream is still the screen.
83
+ const secretOut = opts.secret && !process.stdout.isTTY && process.stderr.isTTY ? process.stderr : process.stdout;
78
84
  const rl = readline.createInterface({
79
85
  input: process.stdin,
80
- output: process.stdout
86
+ output: secretOut,
87
+ ...(opts.secret ? { terminal: Boolean(process.stdin.isTTY) } : {}),
81
88
  });
82
89
 
90
+ // Secret answers: let the prompt through, then swallow the typed echo.
91
+ let muted = false;
92
+ if (opts.secret) {
93
+ rl._writeToOutput = (text) => { if (!muted) rl.output.write(text); };
94
+ }
83
95
  return new Promise((resolve) => {
84
96
  rl.question(question, (answer) => {
97
+ if (muted) rl.output.write('\n');
85
98
  rl.close();
86
99
  resolve(answer.trim());
87
100
  });
101
+ muted = Boolean(opts.secret);
88
102
  });
89
103
  }
90
104