atris 3.74.0 → 3.74.2
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/bin/atris.js +85 -82
- package/commands/aeo.js +2 -2
- package/commands/align.js +10 -19
- package/commands/auth.js +1 -1
- package/commands/autopilot.js +9 -10
- package/commands/avail.js +36 -7
- package/commands/clean.js +2 -1
- package/commands/close.js +7 -4
- package/commands/computer.js +3 -1
- package/commands/console.js +1 -1
- package/commands/context-sync.js +1 -60
- package/commands/experiments.js +4 -1
- package/commands/fleet.js +0 -1
- package/commands/improve.js +3 -1
- package/commands/interview.js +13 -5
- package/commands/land.js +5 -1
- package/commands/lifecycle.js +17 -6
- package/commands/log-sync.js +1 -1
- package/commands/loops.js +5 -2
- package/commands/member.js +17 -11
- package/commands/mission.js +1 -4
- package/commands/now.js +0 -7
- package/commands/play.js +3 -1
- package/commands/plugin.js +1 -1
- package/commands/pull.js +5 -3
- package/commands/push.js +0 -1
- package/commands/radar.js +0 -4
- package/commands/recap.js +25 -1
- package/commands/report.js +0 -4
- package/commands/review.js +1 -1
- package/commands/security-review.js +4 -1
- package/commands/setup.js +6 -2
- package/commands/soul.js +2 -1
- package/commands/sync.js +8 -2
- package/commands/task.js +0 -522
- package/commands/terminal.js +1 -1
- package/commands/x-search.js +5 -3
- package/commands/youtube.js +9 -7
- package/commands/zen.js +4 -4
- package/lib/agent-today.js +412 -0
- package/lib/apply-gate.js +5 -3
- package/lib/autoland.js +0 -8
- package/lib/bench/runner.js +0 -4
- package/lib/chat-log-scan.js +0 -28
- package/lib/cli-scope.js +4 -2
- package/lib/engine-registry.js +0 -4
- package/lib/experiments/daily.js +2 -1
- package/lib/file-ops.js +6 -1
- package/lib/first-minute.js +2 -0
- package/lib/gm-bet.js +73 -9
- package/lib/gm-phone-page.js +42 -5
- package/lib/gm-phone.js +3 -0
- package/lib/local-day.js +10 -0
- package/lib/manifest.js +0 -10
- package/lib/member-switches.js +0 -11
- package/lib/review-integrity.js +0 -4
- package/lib/scorecard.js +7 -6
- package/lib/wiki.js +4 -1
- package/package.json +1 -1
package/commands/task.js
CHANGED
|
@@ -94,10 +94,6 @@ const PROOF_BOUNDARY_BLOCKED_REASON = 'proof_boundary_blocked_requires_revision'
|
|
|
94
94
|
const MISSION_XP_END_TO_END_REASON = 'mission_xp_requires_end_to_end_receipt';
|
|
95
95
|
const MISSION_XP_END_TO_END_DETAIL = 'mission XP proof must name a zero-papercut end-to-end fresh-laptop pass through install, init, first mission, and first self-landed task; generic mission/tick receipts are not enough';
|
|
96
96
|
const READY_RESULT_TEACHING = 'ready needs --result: one plain sentence someone new to the project can understand. say what someone can do now and why it matters. no ids, no paths, no commands. example: operators can now read the whole team day on one page instead of scrolling raw logs.';
|
|
97
|
-
const REVIEW_AUTO_ACCEPT_ACTOR = 'auto (certified, small)';
|
|
98
|
-
const REVIEW_AUTO_ACCEPT_POLICY = 'review_autoaccept_certified_small';
|
|
99
|
-
const REVIEW_AUTO_ACCEPT_FILE_LIMIT = 10;
|
|
100
|
-
const REVIEW_AUTO_ACCEPT_LINE_LIMIT = 300;
|
|
101
97
|
|
|
102
98
|
const STATUS_PLAN_TAGS = new Set([
|
|
103
99
|
'agent',
|
|
@@ -5010,69 +5006,6 @@ function cmdReviewLaneRun(args) {
|
|
|
5010
5006
|
if (!result.ok) process.exit(1);
|
|
5011
5007
|
}
|
|
5012
5008
|
|
|
5013
|
-
function configValueDisabled(value) {
|
|
5014
|
-
if (value === false) return true;
|
|
5015
|
-
const text = String(value === undefined || value === null ? '' : value).trim().toLowerCase();
|
|
5016
|
-
return ['0', 'false', 'off', 'no'].includes(text);
|
|
5017
|
-
}
|
|
5018
|
-
|
|
5019
|
-
function reviewAutoAcceptEnabled() {
|
|
5020
|
-
try {
|
|
5021
|
-
const { loadConfig } = require('../utils/config');
|
|
5022
|
-
const config = loadConfig();
|
|
5023
|
-
const value = Object.prototype.hasOwnProperty.call(config, 'autoaccept')
|
|
5024
|
-
? config.autoaccept
|
|
5025
|
-
: Object.prototype.hasOwnProperty.call(config, 'review_autoaccept')
|
|
5026
|
-
? config.review_autoaccept
|
|
5027
|
-
: config.reviewAutoaccept;
|
|
5028
|
-
return !configValueDisabled(value);
|
|
5029
|
-
} catch {
|
|
5030
|
-
return true;
|
|
5031
|
-
}
|
|
5032
|
-
}
|
|
5033
|
-
|
|
5034
|
-
function reviewAutoAcceptStatePath(root = process.cwd()) {
|
|
5035
|
-
return path.join(root, '.atris', 'state', 'review-autoaccept.json');
|
|
5036
|
-
}
|
|
5037
|
-
|
|
5038
|
-
function readReviewAutoAcceptState(root = process.cwd()) {
|
|
5039
|
-
const file = reviewAutoAcceptStatePath(root);
|
|
5040
|
-
try {
|
|
5041
|
-
if (!fs.existsSync(file)) return {};
|
|
5042
|
-
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
5043
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
5044
|
-
} catch {
|
|
5045
|
-
return {};
|
|
5046
|
-
}
|
|
5047
|
-
}
|
|
5048
|
-
|
|
5049
|
-
function writeReviewAutoAcceptState(root = process.cwd(), state = {}) {
|
|
5050
|
-
const file = reviewAutoAcceptStatePath(root);
|
|
5051
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
5052
|
-
fs.writeFileSync(file, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
5053
|
-
}
|
|
5054
|
-
|
|
5055
|
-
function finiteNumber(value) {
|
|
5056
|
-
const number = Number(value);
|
|
5057
|
-
return Number.isFinite(number) ? number : null;
|
|
5058
|
-
}
|
|
5059
|
-
|
|
5060
|
-
function firstNumberFromObject(source, keys) {
|
|
5061
|
-
if (!source || typeof source !== 'object') return null;
|
|
5062
|
-
for (const key of keys) {
|
|
5063
|
-
if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
|
|
5064
|
-
const value = source[key];
|
|
5065
|
-
if (Array.isArray(value)) return value.length;
|
|
5066
|
-
if (typeof value === 'string' && value.includes(',')) {
|
|
5067
|
-
const parts = value.split(',').map(part => part.trim()).filter(Boolean);
|
|
5068
|
-
if (parts.length > 1) return parts.length;
|
|
5069
|
-
}
|
|
5070
|
-
const number = finiteNumber(value);
|
|
5071
|
-
if (number !== null) return number;
|
|
5072
|
-
}
|
|
5073
|
-
return null;
|
|
5074
|
-
}
|
|
5075
|
-
|
|
5076
5009
|
function arrayFromPathValue(value) {
|
|
5077
5010
|
if (!value) return [];
|
|
5078
5011
|
if (Array.isArray(value)) return value.flatMap(arrayFromPathValue);
|
|
@@ -5088,448 +5021,6 @@ function arrayFromPathValue(value) {
|
|
|
5088
5021
|
.filter(Boolean);
|
|
5089
5022
|
}
|
|
5090
5023
|
|
|
5091
|
-
function collectRecordedDiffPaths(source) {
|
|
5092
|
-
if (!source || typeof source !== 'object') return [];
|
|
5093
|
-
const keys = [
|
|
5094
|
-
'files',
|
|
5095
|
-
'paths',
|
|
5096
|
-
'touched_files',
|
|
5097
|
-
'touchedFiles',
|
|
5098
|
-
'changed_files',
|
|
5099
|
-
'changedFiles',
|
|
5100
|
-
'modified_files',
|
|
5101
|
-
'modifiedFiles',
|
|
5102
|
-
];
|
|
5103
|
-
const out = [];
|
|
5104
|
-
for (const key of keys) out.push(...arrayFromPathValue(source[key]));
|
|
5105
|
-
return out;
|
|
5106
|
-
}
|
|
5107
|
-
|
|
5108
|
-
function normalizeRecordedDiffStats(source, label = 'recorded') {
|
|
5109
|
-
if (!source || typeof source !== 'object') return null;
|
|
5110
|
-
const fileKeys = [
|
|
5111
|
-
'files_touched',
|
|
5112
|
-
'filesTouched',
|
|
5113
|
-
'changed_files_count',
|
|
5114
|
-
'changedFilesCount',
|
|
5115
|
-
'files_changed',
|
|
5116
|
-
'filesChanged',
|
|
5117
|
-
'file_count',
|
|
5118
|
-
'fileCount',
|
|
5119
|
-
'files',
|
|
5120
|
-
'changed_files',
|
|
5121
|
-
'changedFiles',
|
|
5122
|
-
];
|
|
5123
|
-
const changedLineKeys = [
|
|
5124
|
-
'changed_lines',
|
|
5125
|
-
'changedLines',
|
|
5126
|
-
'lines_changed',
|
|
5127
|
-
'linesChanged',
|
|
5128
|
-
'changed_line_count',
|
|
5129
|
-
'changedLineCount',
|
|
5130
|
-
'total_changed_lines',
|
|
5131
|
-
'totalChangedLines',
|
|
5132
|
-
'line_count',
|
|
5133
|
-
'lineCount',
|
|
5134
|
-
];
|
|
5135
|
-
const insertions = firstNumberFromObject(source, ['insertions', 'added', 'additions', 'lines_added', 'linesAdded']);
|
|
5136
|
-
const deletions = firstNumberFromObject(source, ['deletions', 'deleted', 'removals', 'lines_deleted', 'linesDeleted']);
|
|
5137
|
-
const paths = collectRecordedDiffPaths(source);
|
|
5138
|
-
const filesTouched = firstNumberFromObject(source, fileKeys) ?? (paths.length ? paths.length : null);
|
|
5139
|
-
const changedLines = firstNumberFromObject(source, changedLineKeys)
|
|
5140
|
-
?? (insertions !== null || deletions !== null ? Number(insertions || 0) + Number(deletions || 0) : null);
|
|
5141
|
-
if (filesTouched === null && changedLines === null && !paths.length) return null;
|
|
5142
|
-
return {
|
|
5143
|
-
source: label,
|
|
5144
|
-
files_touched: filesTouched,
|
|
5145
|
-
changed_lines: changedLines,
|
|
5146
|
-
paths,
|
|
5147
|
-
};
|
|
5148
|
-
}
|
|
5149
|
-
|
|
5150
|
-
function recordedDiffStatsForTask(task) {
|
|
5151
|
-
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5152
|
-
const candidates = [
|
|
5153
|
-
metadata.diff_stats,
|
|
5154
|
-
metadata.diffStats,
|
|
5155
|
-
metadata.git_diff_stats,
|
|
5156
|
-
metadata.gitDiffStats,
|
|
5157
|
-
metadata.change_stats,
|
|
5158
|
-
metadata.changeStats,
|
|
5159
|
-
metadata.stats,
|
|
5160
|
-
metadata,
|
|
5161
|
-
];
|
|
5162
|
-
for (const candidate of candidates) {
|
|
5163
|
-
const stats = normalizeRecordedDiffStats(candidate, 'recorded');
|
|
5164
|
-
if (stats) return stats;
|
|
5165
|
-
}
|
|
5166
|
-
return null;
|
|
5167
|
-
}
|
|
5168
|
-
|
|
5169
|
-
function proofDiffStats(proof) {
|
|
5170
|
-
const text = String(proof || '').replace(/\s+/g, ' ').trim();
|
|
5171
|
-
if (!text) return null;
|
|
5172
|
-
let filesTouched = null;
|
|
5173
|
-
let changedLines = null;
|
|
5174
|
-
const gitStat = text.match(/(\d+)\s+files?\s+changed(?:,\s*(\d+)\s+insertions?\(\+\))?(?:,\s*(\d+)\s+deletions?\(-\))?/i);
|
|
5175
|
-
if (gitStat) {
|
|
5176
|
-
filesTouched = Number(gitStat[1]);
|
|
5177
|
-
const insertions = gitStat[2] ? Number(gitStat[2]) : 0;
|
|
5178
|
-
const deletions = gitStat[3] ? Number(gitStat[3]) : 0;
|
|
5179
|
-
if (gitStat[2] || gitStat[3]) changedLines = insertions + deletions;
|
|
5180
|
-
}
|
|
5181
|
-
const filePatterns = [
|
|
5182
|
-
/files?\s+(?:touched|changed|modified)\s*[:=]\s*(\d+)/i,
|
|
5183
|
-
/(\d+)\s+files?\s+(?:touched|changed|modified)/i,
|
|
5184
|
-
];
|
|
5185
|
-
for (const pattern of filePatterns) {
|
|
5186
|
-
const match = text.match(pattern);
|
|
5187
|
-
if (match) filesTouched = Number(match[1]);
|
|
5188
|
-
}
|
|
5189
|
-
const linePatterns = [
|
|
5190
|
-
/changed\s+lines?\s*[:=]\s*(\d+)/i,
|
|
5191
|
-
/(\d+)\s+changed\s+lines?/i,
|
|
5192
|
-
/lines?\s+changed\s*[:=]\s*(\d+)/i,
|
|
5193
|
-
];
|
|
5194
|
-
for (const pattern of linePatterns) {
|
|
5195
|
-
const match = text.match(pattern);
|
|
5196
|
-
if (match) changedLines = Number(match[1]);
|
|
5197
|
-
}
|
|
5198
|
-
if (filesTouched === null && changedLines === null) return null;
|
|
5199
|
-
return {
|
|
5200
|
-
source: 'proof',
|
|
5201
|
-
files_touched: filesTouched,
|
|
5202
|
-
changed_lines: changedLines,
|
|
5203
|
-
paths: [],
|
|
5204
|
-
};
|
|
5205
|
-
}
|
|
5206
|
-
|
|
5207
|
-
function safeGitRef(value) {
|
|
5208
|
-
const text = String(value || '').trim();
|
|
5209
|
-
if (!text || text.startsWith('-') || text.includes('..')) return null;
|
|
5210
|
-
if (!/^[a-zA-Z0-9_./@=+~^-]+$/.test(text)) return null;
|
|
5211
|
-
return text;
|
|
5212
|
-
}
|
|
5213
|
-
|
|
5214
|
-
function runGitForAutoAccept(root, args) {
|
|
5215
|
-
try {
|
|
5216
|
-
const { spawnSync } = require('child_process');
|
|
5217
|
-
return spawnSync('git', args, {
|
|
5218
|
-
cwd: root || process.cwd(),
|
|
5219
|
-
encoding: 'utf8',
|
|
5220
|
-
timeout: 10000,
|
|
5221
|
-
});
|
|
5222
|
-
} catch {
|
|
5223
|
-
return { status: 1, stdout: '', stderr: '' };
|
|
5224
|
-
}
|
|
5225
|
-
}
|
|
5226
|
-
|
|
5227
|
-
function gitRefExists(root, ref) {
|
|
5228
|
-
const result = runGitForAutoAccept(root, ['rev-parse', '--verify', `${ref}^{commit}`]);
|
|
5229
|
-
return result.status === 0;
|
|
5230
|
-
}
|
|
5231
|
-
|
|
5232
|
-
function defaultDiffBase(root) {
|
|
5233
|
-
for (const ref of ['origin/master', 'origin/main', 'master', 'main']) {
|
|
5234
|
-
if (gitRefExists(root, ref)) return ref;
|
|
5235
|
-
}
|
|
5236
|
-
return null;
|
|
5237
|
-
}
|
|
5238
|
-
|
|
5239
|
-
function branchDiffStatsForTask(task, root) {
|
|
5240
|
-
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5241
|
-
const branch = safeGitRef(
|
|
5242
|
-
metadata.branch
|
|
5243
|
-
|| metadata.worktree_branch
|
|
5244
|
-
|| metadata.git_branch
|
|
5245
|
-
|| metadata.pr_branch
|
|
5246
|
-
|| metadata.head_branch
|
|
5247
|
-
|| metadata.worktree?.branch
|
|
5248
|
-
);
|
|
5249
|
-
if (!branch) return null;
|
|
5250
|
-
const base = safeGitRef(
|
|
5251
|
-
metadata.base
|
|
5252
|
-
|| metadata.base_ref
|
|
5253
|
-
|| metadata.target_ref
|
|
5254
|
-
|| metadata.target_branch
|
|
5255
|
-
|| metadata.worktree?.base
|
|
5256
|
-
) || defaultDiffBase(root);
|
|
5257
|
-
if (!base) return null;
|
|
5258
|
-
const result = runGitForAutoAccept(root, ['diff', '--numstat', `${base}...${branch}`]);
|
|
5259
|
-
if (result.status !== 0) return null;
|
|
5260
|
-
const lines = String(result.stdout || '').split(/\r?\n/).filter(Boolean);
|
|
5261
|
-
if (!lines.length) return { source: 'branch', files_touched: 0, changed_lines: 0, paths: [] };
|
|
5262
|
-
let changedLines = 0;
|
|
5263
|
-
let unknownLines = false;
|
|
5264
|
-
const paths = [];
|
|
5265
|
-
for (const line of lines) {
|
|
5266
|
-
const parts = line.split(/\t+/);
|
|
5267
|
-
const added = Number(parts[0]);
|
|
5268
|
-
const deleted = Number(parts[1]);
|
|
5269
|
-
if (!Number.isFinite(added) || !Number.isFinite(deleted)) unknownLines = true;
|
|
5270
|
-
else changedLines += added + deleted;
|
|
5271
|
-
if (parts[2]) paths.push(parts.slice(2).join('\t'));
|
|
5272
|
-
}
|
|
5273
|
-
return {
|
|
5274
|
-
source: 'branch',
|
|
5275
|
-
files_touched: lines.length,
|
|
5276
|
-
changed_lines: unknownLines ? null : changedLines,
|
|
5277
|
-
paths,
|
|
5278
|
-
};
|
|
5279
|
-
}
|
|
5280
|
-
|
|
5281
|
-
function reviewAutoAcceptDiffStats(task, root) {
|
|
5282
|
-
const recorded = recordedDiffStatsForTask(task);
|
|
5283
|
-
if (recorded) return recorded;
|
|
5284
|
-
const branch = branchDiffStatsForTask(task, root);
|
|
5285
|
-
if (branch) return branch;
|
|
5286
|
-
const proof = String(task?.review?.proof || task?.metadata?.latest_agent_proof || '');
|
|
5287
|
-
return proofDiffStats(proof);
|
|
5288
|
-
}
|
|
5289
|
-
|
|
5290
|
-
function reviewAutoAcceptBigTitle(task) {
|
|
5291
|
-
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5292
|
-
const text = [
|
|
5293
|
-
task && task.title,
|
|
5294
|
-
metadata.kind,
|
|
5295
|
-
metadata.type,
|
|
5296
|
-
metadata.category,
|
|
5297
|
-
metadata.report_type,
|
|
5298
|
-
].map(value => String(value || '')).join(' ').toLowerCase();
|
|
5299
|
-
const match = text.match(/\b(daily\s+update|summary|report|digest|retro|retrospective)\b/i);
|
|
5300
|
-
if (!match) return null;
|
|
5301
|
-
return {
|
|
5302
|
-
ok: false,
|
|
5303
|
-
reason: `big_title_${match[1].toLowerCase().replace(/\s+/g, '_')}`,
|
|
5304
|
-
};
|
|
5305
|
-
}
|
|
5306
|
-
|
|
5307
|
-
function reviewAutoAcceptSizeGate(task, root) {
|
|
5308
|
-
const titleGate = reviewAutoAcceptBigTitle(task);
|
|
5309
|
-
if (titleGate) return titleGate;
|
|
5310
|
-
const stats = reviewAutoAcceptDiffStats(task, root);
|
|
5311
|
-
if (!stats) return { ok: false, reason: 'size_unknown', stats: null };
|
|
5312
|
-
const filesTouched = finiteNumber(stats.files_touched);
|
|
5313
|
-
const changedLines = finiteNumber(stats.changed_lines);
|
|
5314
|
-
if (filesTouched !== null && filesTouched > REVIEW_AUTO_ACCEPT_FILE_LIMIT) {
|
|
5315
|
-
return { ok: false, reason: 'big_files', stats };
|
|
5316
|
-
}
|
|
5317
|
-
if (changedLines !== null && changedLines > REVIEW_AUTO_ACCEPT_LINE_LIMIT) {
|
|
5318
|
-
return { ok: false, reason: 'big_changed_lines', stats };
|
|
5319
|
-
}
|
|
5320
|
-
if (filesTouched === null || changedLines === null) {
|
|
5321
|
-
return { ok: false, reason: 'size_unknown', stats };
|
|
5322
|
-
}
|
|
5323
|
-
return { ok: true, reason: 'small_change', stats };
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
function reviewAutoAcceptMetadataText(task) {
|
|
5327
|
-
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5328
|
-
return [
|
|
5329
|
-
task && task.title,
|
|
5330
|
-
task && task.tag,
|
|
5331
|
-
task && task.source_key,
|
|
5332
|
-
metadata.kind,
|
|
5333
|
-
metadata.type,
|
|
5334
|
-
metadata.category,
|
|
5335
|
-
metadata.lane,
|
|
5336
|
-
metadata.stage,
|
|
5337
|
-
metadata.area,
|
|
5338
|
-
JSON.stringify(metadata),
|
|
5339
|
-
].map(value => String(value || '')).join(' ').toLowerCase();
|
|
5340
|
-
}
|
|
5341
|
-
|
|
5342
|
-
function reviewAutoAcceptTouchedPaths(task) {
|
|
5343
|
-
const metadata = task && task.metadata && typeof task.metadata === 'object' ? task.metadata : {};
|
|
5344
|
-
const stats = recordedDiffStatsForTask(task);
|
|
5345
|
-
const proof = String(task?.review?.proof || metadata.latest_agent_proof || '');
|
|
5346
|
-
return [
|
|
5347
|
-
...(stats && stats.paths ? stats.paths : []),
|
|
5348
|
-
...collectRecordedDiffPaths(metadata),
|
|
5349
|
-
...taskReviewEvidencePaths(proof, 50),
|
|
5350
|
-
].map(value => String(value || '').trim()).filter(Boolean);
|
|
5351
|
-
}
|
|
5352
|
-
|
|
5353
|
-
function reviewProtectedMatch(task) {
|
|
5354
|
-
const text = reviewAutoAcceptMetadataText(task);
|
|
5355
|
-
const paths = reviewAutoAcceptTouchedPaths(task).join(' ').toLowerCase();
|
|
5356
|
-
const combined = `${text} ${paths}`;
|
|
5357
|
-
const checks = [
|
|
5358
|
-
['auth', /\b(auth|authentication|authorization|oauth|login|session)\b/i],
|
|
5359
|
-
['credentials', /\b(credentials?|secrets?|passwords?|api[-_ ]?keys?|tokens?)\b/i],
|
|
5360
|
-
['csp', /\b(csp|content[-_ ]security[-_ ]policy)\b/i],
|
|
5361
|
-
['sandbox', /\b(sandbox|allow-scripts|allow-same-origin)\b/i],
|
|
5362
|
-
['billing', /\b(billing|invoice|invoices|subscription|subscriptions)\b/i],
|
|
5363
|
-
['payments', /\b(payments?|stripe|checkout|refunds?)\b/i],
|
|
5364
|
-
['outbound_sends', /\b(outbound|send|sending|email|sms|webhook|notification|notifications)\b/i],
|
|
5365
|
-
];
|
|
5366
|
-
for (const [key, pattern] of checks) {
|
|
5367
|
-
if (pattern.test(combined)) return { ok: false, reason: `protected_${key}` };
|
|
5368
|
-
}
|
|
5369
|
-
return { ok: true };
|
|
5370
|
-
}
|
|
5371
|
-
|
|
5372
|
-
function evaluateReviewAutoAccept(task, root) {
|
|
5373
|
-
const ref = taskRef(task);
|
|
5374
|
-
if (!task) return { eligible: false, ref, reason: 'task_not_found' };
|
|
5375
|
-
const protectedGate = reviewProtectedMatch(task);
|
|
5376
|
-
if (!protectedGate.ok) return { eligible: false, ref, reason: protectedGate.reason };
|
|
5377
|
-
const sizeGate = reviewAutoAcceptSizeGate(task, root);
|
|
5378
|
-
if (!sizeGate.ok) return { eligible: false, ref, reason: sizeGate.reason, size: sizeGate.stats };
|
|
5379
|
-
// executeVerify: false, this is a READ path. The verdict comes from
|
|
5380
|
-
// metadata.verify_cache, stamped by the two lanes allowed to spawn
|
|
5381
|
-
// checks (certify-verified, autoland landing re-check). Spawning here is
|
|
5382
|
-
// what turned `atris task reviews` into a fork-bomb multiplier.
|
|
5383
|
-
const evaluation = evaluateAutoAccept(task, { strictVerify: true, executeVerify: false });
|
|
5384
|
-
if (!evaluation.eligible) {
|
|
5385
|
-
return {
|
|
5386
|
-
...evaluation,
|
|
5387
|
-
size: sizeGate.stats,
|
|
5388
|
-
};
|
|
5389
|
-
}
|
|
5390
|
-
if (evaluation.verification_pending) {
|
|
5391
|
-
const verdict = cachedVerifyVerdict(task, root);
|
|
5392
|
-
if (!verdict.fresh) {
|
|
5393
|
-
return {
|
|
5394
|
-
eligible: false,
|
|
5395
|
-
ref,
|
|
5396
|
-
reason: 'verification_pending',
|
|
5397
|
-
verify: verdict.command || null,
|
|
5398
|
-
next_action: 'no fresh executed verify on record; `atris task certify-verified` or the autoland tick runs the stored check and stamps metadata.verify_cache',
|
|
5399
|
-
size: sizeGate.stats,
|
|
5400
|
-
};
|
|
5401
|
-
}
|
|
5402
|
-
if (!verdict.ok) {
|
|
5403
|
-
return {
|
|
5404
|
-
eligible: false,
|
|
5405
|
-
ref,
|
|
5406
|
-
reason: verdict.reason || 'verify_failed',
|
|
5407
|
-
verify: verdict.command,
|
|
5408
|
-
verify_cache: verdict,
|
|
5409
|
-
size: sizeGate.stats,
|
|
5410
|
-
};
|
|
5411
|
-
}
|
|
5412
|
-
return {
|
|
5413
|
-
...evaluation,
|
|
5414
|
-
verification_pending: false,
|
|
5415
|
-
verify_cache: verdict,
|
|
5416
|
-
eligible: true,
|
|
5417
|
-
reason: 'certified_small',
|
|
5418
|
-
policy: REVIEW_AUTO_ACCEPT_POLICY,
|
|
5419
|
-
size: sizeGate.stats,
|
|
5420
|
-
};
|
|
5421
|
-
}
|
|
5422
|
-
return {
|
|
5423
|
-
...evaluation,
|
|
5424
|
-
eligible: true,
|
|
5425
|
-
reason: 'certified_small',
|
|
5426
|
-
policy: REVIEW_AUTO_ACCEPT_POLICY,
|
|
5427
|
-
size: sizeGate.stats,
|
|
5428
|
-
};
|
|
5429
|
-
}
|
|
5430
|
-
|
|
5431
|
-
function autoAcceptCertifiedSmallReviews(taskDb, db, projection) {
|
|
5432
|
-
const enabled = reviewAutoAcceptEnabled();
|
|
5433
|
-
const root = projection.workspace_root || process.cwd();
|
|
5434
|
-
const results = [];
|
|
5435
|
-
if (!enabled) {
|
|
5436
|
-
return {
|
|
5437
|
-
enabled,
|
|
5438
|
-
scanned: 0,
|
|
5439
|
-
accepted: 0,
|
|
5440
|
-
changed: false,
|
|
5441
|
-
results,
|
|
5442
|
-
};
|
|
5443
|
-
}
|
|
5444
|
-
const certified = certifiedPendingReviewTasks(projection);
|
|
5445
|
-
for (const item of certified) {
|
|
5446
|
-
const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
|
|
5447
|
-
const task = fullProjection.tasks[0] || null;
|
|
5448
|
-
const evaluation = evaluateReviewAutoAccept(task, root);
|
|
5449
|
-
if (!evaluation.eligible) {
|
|
5450
|
-
results.push({ ...evaluation, action: 'queued', task_id: task?.id || item.id || null });
|
|
5451
|
-
continue;
|
|
5452
|
-
}
|
|
5453
|
-
const policyGate = candidatePolicyGate(task, { executeDetectors: true });
|
|
5454
|
-
if (!policyGate.ok) {
|
|
5455
|
-
results.push({ ...policyGate, action: 'queued', task_id: task?.id || item.id || null });
|
|
5456
|
-
continue;
|
|
5457
|
-
}
|
|
5458
|
-
evaluation.candidate_gate = policyGate.gate;
|
|
5459
|
-
const accepted = acceptReviewTask(taskDb, db, task.id, {
|
|
5460
|
-
actor: REVIEW_AUTO_ACCEPT_ACTOR,
|
|
5461
|
-
proof: evaluation.proof,
|
|
5462
|
-
reward: 1,
|
|
5463
|
-
lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
|
|
5464
|
-
nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
|
|
5465
|
-
autoAccepted: true,
|
|
5466
|
-
});
|
|
5467
|
-
if (!accepted.ok) {
|
|
5468
|
-
results.push({ ...evaluation, action: 'accept_failed', task_id: task.id, reason: accepted.reason });
|
|
5469
|
-
continue;
|
|
5470
|
-
}
|
|
5471
|
-
const landing = stampAutoAcceptMetadata(taskDb, db, task, REVIEW_AUTO_ACCEPT_ACTOR, REVIEW_AUTO_ACCEPT_POLICY);
|
|
5472
|
-
refreshCareerXpAfterReview(accepted.reviewed);
|
|
5473
|
-
results.push({
|
|
5474
|
-
...evaluation,
|
|
5475
|
-
action: 'accepted',
|
|
5476
|
-
landing,
|
|
5477
|
-
task_id: task.id,
|
|
5478
|
-
reward: accepted.reviewed.episode.reward.value,
|
|
5479
|
-
});
|
|
5480
|
-
}
|
|
5481
|
-
const acceptedCount = results.filter(row => row.action === 'accepted').length;
|
|
5482
|
-
return {
|
|
5483
|
-
enabled,
|
|
5484
|
-
scanned: certified.length,
|
|
5485
|
-
accepted: acceptedCount,
|
|
5486
|
-
changed: acceptedCount > 0,
|
|
5487
|
-
results,
|
|
5488
|
-
};
|
|
5489
|
-
}
|
|
5490
|
-
|
|
5491
|
-
function autoAcceptedReviewRowsSince(taskDb, db, workspaceRoot, sinceIso, acceptedNowIds = []) {
|
|
5492
|
-
const sinceMs = Date.parse(sinceIso || '');
|
|
5493
|
-
const rows = taskDb.withTaskDisplayRefs(taskDb.listTasks(db, { workspaceRoot }));
|
|
5494
|
-
const acceptedNow = new Set(acceptedNowIds.filter(Boolean));
|
|
5495
|
-
const seen = new Set();
|
|
5496
|
-
const out = [];
|
|
5497
|
-
for (const row of rows) {
|
|
5498
|
-
const metadata = row.metadata && typeof row.metadata === 'object' ? row.metadata : {};
|
|
5499
|
-
const acceptedAt = Date.parse(metadata.auto_accepted_at || metadata.accepted_at || '');
|
|
5500
|
-
const policyMatch = metadata.auto_accept_policy === REVIEW_AUTO_ACCEPT_POLICY
|
|
5501
|
-
|| metadata.auto_accepted_by === REVIEW_AUTO_ACCEPT_ACTOR
|
|
5502
|
-
|| metadata.accepted_by === REVIEW_AUTO_ACCEPT_ACTOR;
|
|
5503
|
-
const isCurrent = acceptedNow.has(row.id);
|
|
5504
|
-
const isSince = Number.isFinite(sinceMs) && Number.isFinite(acceptedAt) && acceptedAt > sinceMs;
|
|
5505
|
-
const firstLook = !Number.isFinite(sinceMs) && Number.isFinite(acceptedAt);
|
|
5506
|
-
if (row.status !== 'done' || !policyMatch || (!isCurrent && !isSince && !firstLook)) continue;
|
|
5507
|
-
if (seen.has(row.id)) continue;
|
|
5508
|
-
seen.add(row.id);
|
|
5509
|
-
out.push({
|
|
5510
|
-
id: row.id,
|
|
5511
|
-
ref: row.display_id || taskRef(row),
|
|
5512
|
-
title: row.title,
|
|
5513
|
-
accepted_at: metadata.auto_accepted_at || metadata.accepted_at || null,
|
|
5514
|
-
});
|
|
5515
|
-
}
|
|
5516
|
-
out.sort((a, b) => String(b.accepted_at || '').localeCompare(String(a.accepted_at || '')));
|
|
5517
|
-
return out;
|
|
5518
|
-
}
|
|
5519
|
-
|
|
5520
|
-
function reviewAutoAcceptRollup(taskDb, db, workspaceRoot, previousState, autoAcceptRun) {
|
|
5521
|
-
const acceptedNowIds = (autoAcceptRun.results || [])
|
|
5522
|
-
.filter(row => row.action === 'accepted')
|
|
5523
|
-
.map(row => row.task_id)
|
|
5524
|
-
.filter(Boolean);
|
|
5525
|
-
const rows = autoAcceptedReviewRowsSince(taskDb, db, workspaceRoot, previousState.last_look_at, acceptedNowIds);
|
|
5526
|
-
return {
|
|
5527
|
-
count: rows.length,
|
|
5528
|
-
items: rows,
|
|
5529
|
-
since: previousState.last_look_at || null,
|
|
5530
|
-
};
|
|
5531
|
-
}
|
|
5532
|
-
|
|
5533
5024
|
function reviewQueueLimit(args, total) {
|
|
5534
5025
|
if (hasFlag(args, '--all')) return total;
|
|
5535
5026
|
const raw = flag(args, '--limit');
|
|
@@ -10674,19 +10165,6 @@ function failedOnSameCode(task, verify, codeSha) {
|
|
|
10674
10165
|
&& cache.command === verify && codeSha && cache.code_sha === codeSha);
|
|
10675
10166
|
}
|
|
10676
10167
|
|
|
10677
|
-
function cachedVerifyVerdict(task, root) {
|
|
10678
|
-
const metadata = (task && task.metadata) || {};
|
|
10679
|
-
const cache = metadata.verify_cache;
|
|
10680
|
-
if (!cache || typeof cache !== 'object') return { fresh: false, command: metadata.verify || null };
|
|
10681
|
-
// A check run on the job's own code stays fresh until that code changes.
|
|
10682
|
-
const currentCode = cache.code_source && cache.code_source !== 'checkout'
|
|
10683
|
-
? checkedCodeSha(task, root)
|
|
10684
|
-
: currentHeadSha(task.workspace_root || root);
|
|
10685
|
-
const fresh = cache.command === metadata.verify
|
|
10686
|
-
&& (!cache.code_sha || cache.code_sha === currentCode);
|
|
10687
|
-
return { ...cache, fresh: Boolean(fresh) };
|
|
10688
|
-
}
|
|
10689
|
-
|
|
10690
10168
|
// `atris task ready --verify "<cmd>"` already runs the command live and
|
|
10691
10169
|
// gates on exit 0, but that alone leaves no machine re-runnable trace once
|
|
10692
10170
|
// the proof text scrolls out of easy reach: storing the exact command on
|
package/commands/terminal.js
CHANGED
|
@@ -188,7 +188,7 @@ async function terminalAtris() {
|
|
|
188
188
|
// Auto-wake (the rule)
|
|
189
189
|
const awake = await ensureAwake(creds.token, biz.businessId);
|
|
190
190
|
if (!awake) {
|
|
191
|
-
console.error(
|
|
191
|
+
console.error(` The cloud computer for "${slug}" did not wake up in time, so nothing ran. Wait a minute, then run the same command again.`);
|
|
192
192
|
process.exit(1);
|
|
193
193
|
}
|
|
194
194
|
|
package/commands/x-search.js
CHANGED
|
@@ -472,9 +472,11 @@ function dateStamp(now) {
|
|
|
472
472
|
if (typeof now === 'string' && /^\d{4}-\d{2}-\d{2}/.test(now)) {
|
|
473
473
|
return now.slice(0, 10);
|
|
474
474
|
}
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
475
|
+
const parsed = now instanceof Date ? now : new Date(now || Date.now());
|
|
476
|
+
const value = Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
|
477
|
+
// Local calendar day. toISOString() is the UTC day, which is already
|
|
478
|
+
// tomorrow in the evening anywhere west of Greenwich.
|
|
479
|
+
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
|
|
478
480
|
}
|
|
479
481
|
|
|
480
482
|
function fileXSearchBrief({ cwd, source, text, now } = {}) {
|
package/commands/youtube.js
CHANGED
|
@@ -262,10 +262,6 @@ function captionTrackCandidates(info = {}) {
|
|
|
262
262
|
return candidates;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
function chooseCaptionTrack(info = {}) {
|
|
266
|
-
return captionTrackCandidates(info)[0] || null;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
265
|
function formatTimestampFromMs(ms) {
|
|
270
266
|
const totalSeconds = Math.max(0, Math.floor(Number(ms || 0) / 1000));
|
|
271
267
|
const hours = Math.floor(totalSeconds / 3600);
|
|
@@ -772,8 +768,10 @@ function dateStamp(now) {
|
|
|
772
768
|
return now.slice(0, 10);
|
|
773
769
|
}
|
|
774
770
|
const value = now instanceof Date ? now : new Date(now || Date.now());
|
|
775
|
-
|
|
776
|
-
|
|
771
|
+
// Local calendar day, the same journal file every other writer uses;
|
|
772
|
+
// toISOString is UTC and files tonight's notes under tomorrow west of Greenwich.
|
|
773
|
+
const day = Number.isNaN(value.getTime()) ? new Date() : value;
|
|
774
|
+
return `${day.getFullYear()}-${String(day.getMonth() + 1).padStart(2, '0')}-${String(day.getDate()).padStart(2, '0')}`;
|
|
777
775
|
}
|
|
778
776
|
|
|
779
777
|
function firstHeading(notes) {
|
|
@@ -2532,9 +2530,12 @@ function searchApplyNow(now) {
|
|
|
2532
2530
|
const value = now();
|
|
2533
2531
|
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}/.test(value)) return value;
|
|
2534
2532
|
const ms = Number(value);
|
|
2535
|
-
if (Number.isFinite(ms) && ms > 0) return
|
|
2533
|
+
if (Number.isFinite(ms) && ms > 0) return dateStamp(ms);
|
|
2536
2534
|
return undefined;
|
|
2537
2535
|
}
|
|
2536
|
+
// The apply stub is dated by the local day, like the notes it follows;
|
|
2537
|
+
// left to itself the apply gate would stamp the UTC day.
|
|
2538
|
+
if (now === undefined || now === null || now instanceof Date || typeof now === 'number') return dateStamp(now);
|
|
2538
2539
|
return now;
|
|
2539
2540
|
}
|
|
2540
2541
|
|
|
@@ -3966,6 +3967,7 @@ async function youtubeCommand(argv = process.argv.slice(3), deps = {}) {
|
|
|
3966
3967
|
|
|
3967
3968
|
module.exports = {
|
|
3968
3969
|
DEFAULT_QUERY,
|
|
3970
|
+
searchApplyNow,
|
|
3969
3971
|
DEFAULT_TIMEOUT_MS,
|
|
3970
3972
|
parseYoutubeArgs,
|
|
3971
3973
|
buildYoutubePayload,
|
package/commands/zen.js
CHANGED
|
@@ -58,8 +58,8 @@ function makeOut(calm) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function teamLine(n) {
|
|
61
|
-
if (!n) return 'no team notes
|
|
62
|
-
return n === 1 ? '1 team note
|
|
61
|
+
if (!n) return 'no unsaved team notes.';
|
|
62
|
+
return n === 1 ? '1 unsaved team note left in place.' : `${n} unsaved team notes left in place.`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function printYours(yours, line, dim) {
|
|
@@ -69,8 +69,8 @@ function printYours(yours, line, dim) {
|
|
|
69
69
|
|
|
70
70
|
function printCheck(result, workspace) {
|
|
71
71
|
const kept = result.team_files
|
|
72
|
-
? (result.team_files === 1 ? '1 team file
|
|
73
|
-
: 'no team files
|
|
72
|
+
? (result.team_files === 1 ? '1 unsaved team file left in place' : `${result.team_files} unsaved team files left in place`)
|
|
73
|
+
: 'no unsaved team files';
|
|
74
74
|
const stale = result.fetched === false ? ' (could not reach origin, using last fetch)' : '';
|
|
75
75
|
const tidy = workspace?.todo.length || 0;
|
|
76
76
|
if (result.zen && tidy) {
|