atris 3.36.0 → 3.37.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.
- package/bin/atris.js +11 -2
- package/commands/mission.js +128 -12
- package/commands/task.js +5 -1
- package/lib/ax-auto-lane.js +48 -8
- package/package.json +1 -1
package/bin/atris.js
CHANGED
|
@@ -14,6 +14,15 @@ const os = require('os');
|
|
|
14
14
|
const crypto = require('crypto');
|
|
15
15
|
const PACKAGE_JSON_PATH = path.join(__dirname, '..', 'package.json');
|
|
16
16
|
|
|
17
|
+
// Exit without truncating piped stdout: process.exit() drops whatever is still
|
|
18
|
+
// queued in the pipe buffer (large outputs died at 512-byte boundaries). Let
|
|
19
|
+
// the event loop drain naturally; an unref'd timer forces exit if a stray
|
|
20
|
+
// handle keeps the process alive.
|
|
21
|
+
function exitWhenFlushed(code) {
|
|
22
|
+
process.exitCode = code;
|
|
23
|
+
setTimeout(() => process.exit(code), 3000).unref();
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
let CLI_VERSION = 'unknown';
|
|
18
27
|
try {
|
|
19
28
|
const pkgRaw = fs.readFileSync(PACKAGE_JSON_PATH, 'utf8');
|
|
@@ -2100,8 +2109,8 @@ if (command === 'init') {
|
|
|
2100
2109
|
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); process.exit(1); });
|
|
2101
2110
|
} else if (command === 'youtube') {
|
|
2102
2111
|
require('../commands/youtube').youtubeCommand(process.argv.slice(3))
|
|
2103
|
-
.then(() =>
|
|
2104
|
-
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`);
|
|
2112
|
+
.then(() => exitWhenFlushed(0))
|
|
2113
|
+
.catch((err) => { console.error(`\n✗ Error: ${err.message || err}`); exitWhenFlushed(1); });
|
|
2105
2114
|
} else if (command === 'run') {
|
|
2106
2115
|
const args = process.argv.slice(3);
|
|
2107
2116
|
if (args[0] === 'logs') {
|
package/commands/mission.js
CHANGED
|
@@ -1487,7 +1487,7 @@ function missionMetricLine(mission, indent = ' ') {
|
|
|
1487
1487
|
return [`${indent}metric: ${metric}${last === null ? '' : ` (last: ${last})`}`];
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
1490
|
-
function renderMemberNowMarkdown(owner, missions) {
|
|
1490
|
+
function renderMemberNowMarkdown(owner, missions, root = process.cwd()) {
|
|
1491
1491
|
const lines = [
|
|
1492
1492
|
'# Now',
|
|
1493
1493
|
'',
|
|
@@ -1505,6 +1505,8 @@ function renderMemberNowMarkdown(owner, missions) {
|
|
|
1505
1505
|
lines.push('');
|
|
1506
1506
|
lines.push(`- id: ${mission.id}`);
|
|
1507
1507
|
lines.push(`- status: ${missionHumanStatusText(mission)}`);
|
|
1508
|
+
const debtLine = missionVerificationDebtLine(missionVerificationDebt(mission, root));
|
|
1509
|
+
if (debtLine) lines.push(`- ${debtLine}`);
|
|
1508
1510
|
lines.push(`- cadence: ${mission.cadence}`);
|
|
1509
1511
|
lines.push(`- runner: ${mission.runner}${mission.model ? ` (${mission.model})` : ''}`);
|
|
1510
1512
|
lines.push(`- lane: ${mission.lane}`);
|
|
@@ -1533,7 +1535,7 @@ function renderMemberMissionState(owner, root = process.cwd()) {
|
|
|
1533
1535
|
const missions = listMissions(root).filter((mission) => mission.owner === owner);
|
|
1534
1536
|
const nowPath = path.join(dir, 'now.md');
|
|
1535
1537
|
removeLegacyGeneratedMissionViews(dir);
|
|
1536
|
-
fs.writeFileSync(nowPath, renderMemberNowMarkdown(owner, missions), 'utf8');
|
|
1538
|
+
fs.writeFileSync(nowPath, renderMemberNowMarkdown(owner, missions, root), 'utf8');
|
|
1537
1539
|
return { missionPath, nowPath };
|
|
1538
1540
|
}
|
|
1539
1541
|
|
|
@@ -2006,6 +2008,39 @@ function missionRunTrustedObjective(rawObjective, room, target) {
|
|
|
2006
2008
|
return missionRunPreflightObjective(rawObjective, room, room?.owner || 'mission-lead');
|
|
2007
2009
|
}
|
|
2008
2010
|
|
|
2011
|
+
const MISSION_RUN_SHAPING_STOPWORDS = new Set([
|
|
2012
|
+
'should', 'would', 'could', 'with', 'that', 'this', 'from', 'into',
|
|
2013
|
+
'atris', 'mission', 'room', 'just', 'work', 'works',
|
|
2014
|
+
]);
|
|
2015
|
+
|
|
2016
|
+
function missionRunSignificantTokens(value) {
|
|
2017
|
+
return String(value || '')
|
|
2018
|
+
.toLowerCase()
|
|
2019
|
+
.replace(/[^a-z]+/g, ' ')
|
|
2020
|
+
.split(/\s+/)
|
|
2021
|
+
.filter((token) => token.length >= 4 && !MISSION_RUN_SHAPING_STOPWORDS.has(token));
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
function guardMissionRunShaping(rawObjective, shapedObjective, selectedTarget) {
|
|
2025
|
+
const rawTokens = missionRunSignificantTokens(rawObjective);
|
|
2026
|
+
const shapedTokens = new Set(missionRunSignificantTokens(shapedObjective));
|
|
2027
|
+
const sharesSignificantToken = [...rawTokens].some((token) => shapedTokens.has(token));
|
|
2028
|
+
if (rawTokens.length < 4 || sharesSignificantToken) {
|
|
2029
|
+
return { shapedObjective, selectedTarget, shapingRejected: false, shapingRejectedReason: '' };
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
const rejectedTitle = missionRunConcreteTitle(selectedTarget?.title)
|
|
2033
|
+
|| missionRunConcreteTitle(shapedObjective)
|
|
2034
|
+
|| 'untitled shaping';
|
|
2035
|
+
const rejectedRef = selectedTarget?.ref || selectedTarget?.task_id || 'no ref';
|
|
2036
|
+
return {
|
|
2037
|
+
shapedObjective: rawObjective,
|
|
2038
|
+
selectedTarget: null,
|
|
2039
|
+
shapingRejected: true,
|
|
2040
|
+
shapingRejectedReason: `shaping rejected: "${rejectedTitle}" (${rejectedRef}) shares no significant tokens with the raw objective`,
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2009
2044
|
function buildMissionRunRoomPreflight(rawObjective, args = [], options = {}) {
|
|
2010
2045
|
if (!shouldMissionRunRoomPreflight(rawObjective, args)) return null;
|
|
2011
2046
|
const root = options.root || process.cwd();
|
|
@@ -2034,27 +2069,33 @@ function buildMissionRunRoomPreflight(rawObjective, args = [], options = {}) {
|
|
|
2034
2069
|
const shapedObjective = trustedRun
|
|
2035
2070
|
? missionRunTrustedObjective(rawObjective, written.room, selectedTarget)
|
|
2036
2071
|
: missionRunPreflightObjective(rawObjective, written.room, ownerResolution.owner);
|
|
2037
|
-
const
|
|
2072
|
+
const shaping = guardMissionRunShaping(rawObjective, shapedObjective, selectedTarget);
|
|
2073
|
+
const acceptedTarget = shaping.selectedTarget;
|
|
2074
|
+
const taskSpineRequired = !acceptedTarget && (explicitPreflight || signalPreflight);
|
|
2038
2075
|
return {
|
|
2039
2076
|
schema: 'atris.mission_run_preflight.v1',
|
|
2040
2077
|
source: 'mission_room',
|
|
2041
2078
|
raw_objective: rawObjective,
|
|
2042
|
-
shaped_objective: shapedObjective,
|
|
2043
|
-
visible_goal_objective: shapedObjective,
|
|
2079
|
+
shaped_objective: shaping.shapedObjective,
|
|
2080
|
+
visible_goal_objective: shaping.shapedObjective,
|
|
2044
2081
|
room_name: written.room.name,
|
|
2045
2082
|
room_receipt_path: written.relativePath,
|
|
2046
2083
|
owner: ownerResolution.owner,
|
|
2047
2084
|
owner_resolution: ownerResolution.reason,
|
|
2048
2085
|
trusted_run: trustedRun,
|
|
2049
|
-
selected_target:
|
|
2050
|
-
title:
|
|
2051
|
-
source:
|
|
2052
|
-
task_id:
|
|
2053
|
-
ref:
|
|
2054
|
-
why:
|
|
2086
|
+
selected_target: acceptedTarget ? {
|
|
2087
|
+
title: acceptedTarget.title,
|
|
2088
|
+
source: acceptedTarget.source,
|
|
2089
|
+
task_id: acceptedTarget.task_id || null,
|
|
2090
|
+
ref: acceptedTarget.ref || null,
|
|
2091
|
+
why: acceptedTarget.why || '',
|
|
2055
2092
|
} : null,
|
|
2093
|
+
...(shaping.shapingRejected ? {
|
|
2094
|
+
shaping_rejected: true,
|
|
2095
|
+
shaping_rejected_reason: shaping.shapingRejectedReason,
|
|
2096
|
+
} : {}),
|
|
2056
2097
|
task_spine_required: taskSpineRequired,
|
|
2057
|
-
next_action:
|
|
2098
|
+
next_action: acceptedTarget
|
|
2058
2099
|
? 'run one proof tick for the selected existing task'
|
|
2059
2100
|
: (taskSpineRequired ? 'attach task spine, then run one proof tick' : 'run one proof tick'),
|
|
2060
2101
|
};
|
|
@@ -4749,6 +4790,68 @@ function missionTimelineTickIndex(tick) {
|
|
|
4749
4790
|
return Number.isInteger(tickIndex) && tickIndex > 0 ? tickIndex : null;
|
|
4750
4791
|
}
|
|
4751
4792
|
|
|
4793
|
+
// A tick's verification state, made first-class so an unchecked increment reads
|
|
4794
|
+
// as a red in `atris mission report` and now.md instead of hiding inside a
|
|
4795
|
+
// receipt. A ran tick with no verifier result is the debt this surfaces: the
|
|
4796
|
+
// tick was recorded but nothing was checked, so the increment is unproven.
|
|
4797
|
+
function missionTickVerification(tick) {
|
|
4798
|
+
const ran = !!tick && typeof tick === 'object' && tick.status === 'ran';
|
|
4799
|
+
if (tick && tick.verifier_passed === true) {
|
|
4800
|
+
return { verified: true, state: 'verified', unchecked: false };
|
|
4801
|
+
}
|
|
4802
|
+
if (tick && tick.verifier_passed === false) {
|
|
4803
|
+
return { verified: false, state: 'failed', unchecked: false };
|
|
4804
|
+
}
|
|
4805
|
+
return { verified: false, state: ran ? 'unchecked' : 'skipped', unchecked: ran };
|
|
4806
|
+
}
|
|
4807
|
+
|
|
4808
|
+
// Roll every ran tick for a mission into a verification-debt tally. `unchecked`
|
|
4809
|
+
// counts ran ticks that recorded no verifier result at all — the "tick recorded
|
|
4810
|
+
// but nothing was checked" red the report and now.md rollup surface and count
|
|
4811
|
+
// against the mission.
|
|
4812
|
+
function missionVerificationDebt(mission, root = process.cwd()) {
|
|
4813
|
+
const paths = statePaths(root);
|
|
4814
|
+
let files = [];
|
|
4815
|
+
try {
|
|
4816
|
+
files = fs.readdirSync(paths.runsDir)
|
|
4817
|
+
.filter((file) => file.startsWith('mission-') && file.endsWith('.json'))
|
|
4818
|
+
.map((file) => path.join(paths.runsDir, file));
|
|
4819
|
+
} catch {
|
|
4820
|
+
files = [];
|
|
4821
|
+
}
|
|
4822
|
+
let ran = 0;
|
|
4823
|
+
let verified = 0;
|
|
4824
|
+
let unchecked = 0;
|
|
4825
|
+
const seen = new Set();
|
|
4826
|
+
for (const file of files) {
|
|
4827
|
+
let receipt = null;
|
|
4828
|
+
try {
|
|
4829
|
+
receipt = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
4830
|
+
} catch {
|
|
4831
|
+
continue;
|
|
4832
|
+
}
|
|
4833
|
+
if (!receipt || receipt.mission_id !== mission.id) continue;
|
|
4834
|
+
for (const tick of missionReceiptTicks(receipt)) {
|
|
4835
|
+
if (!tick || tick.status !== 'ran') continue;
|
|
4836
|
+
const key = `${tick.tick_index || ''}:${tick.finished_at || tick.started_at || ''}`;
|
|
4837
|
+
if (seen.has(key)) continue;
|
|
4838
|
+
seen.add(key);
|
|
4839
|
+
ran += 1;
|
|
4840
|
+
const verification = missionTickVerification(tick);
|
|
4841
|
+
if (verification.verified) verified += 1;
|
|
4842
|
+
if (verification.unchecked) unchecked += 1;
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
return { ran, verified, unchecked };
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
// One glanceable red line for a mission carrying unverified-tick debt. Empty
|
|
4849
|
+
// when every ran tick was checked, so clean missions stay quiet.
|
|
4850
|
+
function missionVerificationDebtLine(debt) {
|
|
4851
|
+
if (!debt || !debt.unchecked) return '';
|
|
4852
|
+
return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check ⚠ — treat those increments as unproven`;
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4752
4855
|
function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
|
|
4753
4856
|
const paths = statePaths(root);
|
|
4754
4857
|
let files = [];
|
|
@@ -4778,11 +4881,14 @@ function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
|
|
|
4778
4881
|
const key = `${tick.tick_index || ''}:${at}:${summary}`;
|
|
4779
4882
|
if (seen.has(key)) continue;
|
|
4780
4883
|
seen.add(key);
|
|
4884
|
+
const verification = missionTickVerification(tick);
|
|
4781
4885
|
items.push({
|
|
4782
4886
|
at,
|
|
4783
4887
|
tick_index: missionTimelineTickIndex(tick),
|
|
4784
4888
|
title: missionTimelineTitle(tick, summary),
|
|
4785
4889
|
summary,
|
|
4890
|
+
verified: verification.verified,
|
|
4891
|
+
verify_state: verification.state,
|
|
4786
4892
|
receipt_path: receiptPath,
|
|
4787
4893
|
});
|
|
4788
4894
|
}
|
|
@@ -5040,6 +5146,7 @@ function missionReportFor(mission, root = process.cwd()) {
|
|
|
5040
5146
|
const receipt = readMissionReceipt(verifierReceiptPath, root);
|
|
5041
5147
|
const explicitWorkerReceipt = readMissionReceipt(mission.worker_receipt_path, root);
|
|
5042
5148
|
const timeline = missionReportTimeline(mission, root);
|
|
5149
|
+
const verificationDebt = missionVerificationDebt(mission, root);
|
|
5043
5150
|
const workerCheckedIn = Boolean(explicitWorkerReceipt) || timeline.length > 0;
|
|
5044
5151
|
const workerReceiptPath = mission.worker_receipt_path || (receipt && verifierReceiptPath) || null;
|
|
5045
5152
|
const verifierPassed = mission.verifier_result && mission.verifier_result.passed === true;
|
|
@@ -5060,6 +5167,7 @@ function missionReportFor(mission, root = process.cwd()) {
|
|
|
5060
5167
|
: missionHumanStatusText(mission),
|
|
5061
5168
|
budget_continuation: budgetContinuation,
|
|
5062
5169
|
operator_outcome: operatorOutcome,
|
|
5170
|
+
verification: verificationDebt,
|
|
5063
5171
|
worker: mission.worker || missionWorkerLabel(mission),
|
|
5064
5172
|
worker_summary: missionWorkerSummary(mission, receipt),
|
|
5065
5173
|
timeline,
|
|
@@ -5114,6 +5222,9 @@ function reportMission(args) {
|
|
|
5114
5222
|
` What happened: ${report.operator_outcome}`,
|
|
5115
5223
|
` Worker: ${report.worker}`,
|
|
5116
5224
|
` Worker summary: ${report.worker_summary}`,
|
|
5225
|
+
...(report.verification && report.verification.unchecked ? [
|
|
5226
|
+
` ⚠ Unverified: ${report.verification.unchecked} of ${report.verification.ran} tick(s) recorded but ran no check; treat those increments as unproven.`,
|
|
5227
|
+
] : []),
|
|
5117
5228
|
...(report.timeline && report.timeline.length ? [
|
|
5118
5229
|
' Timeline:',
|
|
5119
5230
|
...report.timeline.map((item) => ` - ${report.budget_continuation ? item.summary : item.title}`),
|
|
@@ -10587,6 +10698,11 @@ module.exports = {
|
|
|
10587
10698
|
missionLandingLines,
|
|
10588
10699
|
missionVerifierCheckedText,
|
|
10589
10700
|
missionVerifierHighLevelTestText,
|
|
10701
|
+
missionTickVerification,
|
|
10702
|
+
missionVerificationDebt,
|
|
10703
|
+
missionVerificationDebtLine,
|
|
10704
|
+
missionReportFor,
|
|
10705
|
+
renderMemberNowMarkdown,
|
|
10590
10706
|
buildEngineVerifyPrompt,
|
|
10591
10707
|
engineVerifierResultFromRun,
|
|
10592
10708
|
missionFullBudgetRemainingSeconds,
|
package/commands/task.js
CHANGED
|
@@ -5533,7 +5533,11 @@ function cmdReviews(args) {
|
|
|
5533
5533
|
}
|
|
5534
5534
|
approvalItems.forEach((item, index) => {
|
|
5535
5535
|
if (index > 0) console.log('');
|
|
5536
|
-
|
|
5536
|
+
const ref = item.display_id || taskRef(item.id);
|
|
5537
|
+
const badge = item.evidence?.any_forced
|
|
5538
|
+
? ' [evidence:forced]'
|
|
5539
|
+
: item.evidence?.all_passing ? ' [evidence:passing]' : '';
|
|
5540
|
+
console.log(`${index + 1}. ${gateForHuman(item.title, { title: item.title }).text} (${ref})${badge}`);
|
|
5537
5541
|
if (item.landing) {
|
|
5538
5542
|
taskReviewLandingLines(item).forEach(line => console.log(line));
|
|
5539
5543
|
if (verbose && item.result?.saved) console.log(` saved: ${item.result.saved}`);
|
package/lib/ax-auto-lane.js
CHANGED
|
@@ -7,17 +7,35 @@ const GREETING_MAX_CHARS = 40;
|
|
|
7
7
|
const DECISION_FACTOR_MIN_COMMAS = 3;
|
|
8
8
|
const TRADEOFF_DEPTH_MIN_CHARS = 100;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// Anchored to the start: a lookup word mid-sentence is usually a subordinate
|
|
11
|
+
// clause (my fan spins when i run tests), not a lookup.
|
|
12
|
+
const LOOKUP_START_RE = /^(?:(?:please|pls|hey|hi|yo)[,: ]+)?(?:whats|whos|wheres|what|who|when|where|list|show|define)\b/i;
|
|
11
13
|
// Words that turn a lookup-shaped message into a judgment call: never fast.
|
|
12
|
-
const JUDGMENT_WORD_RE = /\b(?:should|tradeoffs?|better|best|recommend|worth)\b/i;
|
|
13
|
-
const HEAVY_REASONING_RE = /\b(?:design(?:s|ed|ing)?|architect(?:s|ed|ing)?|prov(?:e|es|ed|ing)|analy(?:ze|zes|zed|zing|se|ses|sed|sing)|compar(?:e|es|ed|ing)|plan(?:s|ned|ning)?|sketch(?:es|ed|ing)?)\b/i;
|
|
14
|
+
const JUDGMENT_WORD_RE = /\b(?:should|tradeoffs?|better|best|recommend|worth|good way)\b/i;
|
|
15
|
+
const HEAVY_REASONING_RE = /\b(?:design(?:s|ed|ing)?|architect(?:s|ed|ing)?|prov(?:e|es|ed|ing)|analy(?:ze|zes|zed|zing|se|ses|sed|sing)|compar(?:e|es|ed|ing)|plan(?:s|ned|ning)?|sketch(?:es|ed|ing)?)\b|\bwalk(?:s|ed|ing)? (?:me |us )?through\b/i;
|
|
14
16
|
const DECISION_PHRASE_RE = /\b(?:should|whether|decide|deciding)\b/i;
|
|
15
17
|
const ARGUE_BOTH_RE = /\bargue\b|\bboth sides\b|\bsteelman\b/i;
|
|
16
18
|
const CODE_EDIT_RE = /\b(?:fix(?:es|ed|ing)?|refactor(?:s|ed|ing)?|renam(?:e|es|ed|ing)|debug(?:s|ged|ging)?)\b/i;
|
|
17
19
|
const CODE_DIAGNOSIS_RE = /\bwhy\b|\bwrong\b|\bfail(?:s|ed|ing)?\b|\bresolv(?:e|es|ed|ing)?\b|\bbroken\b|\bnot work/i;
|
|
18
|
-
const CODE_CONTEXT_RE =
|
|
20
|
+
const CODE_CONTEXT_RE = /```|\bregexp?\b|(?:^|\n)\s*(?:traceback\b|(?:[a-z]+)?(?:error|exception):|at\s+\S+\s+\()/im;
|
|
19
21
|
const ERROR_EXPLAIN_RE = /\bexplain (?:this|the) error\b|\bwhat does (?:this|the) error mean\b/i;
|
|
22
|
+
// Yes/no capability checks are lookups even without a lookup keyword.
|
|
23
|
+
const YESNO_LOOKUP_RE = /^(?:does|is|are|can|do)\b/i;
|
|
24
|
+
// Tiny how-do-i asks are muscle-memory lookups; longer ones are advice.
|
|
25
|
+
const HOWTO_LOOKUP_RE = /^how (?:do|does|did) (?:i|we|you)\b/i;
|
|
26
|
+
const HOWTO_LOOKUP_MAX_CHARS = 40;
|
|
27
|
+
const YESNO_LOOKUP_MAX_CHARS = 80;
|
|
28
|
+
// Troubleshooting language means advice, not recall: keep it out of fast.
|
|
29
|
+
const TROUBLE_RE = /\bfail(?:s|ed|ing)?\b|\bcrash(?:es|ed|ing)?\b|\bbroken\b|\bnot working\b/i;
|
|
30
|
+
const WEIGHING_WORD_RE = /\b(?:consider|weigh|rank|estimate|think (?:about|through))\b/i;
|
|
31
|
+
const WEIGHING_FACTOR_MIN_COMMAS = 2;
|
|
20
32
|
const TRANSFORM_START_RE = /^(?:convert|summarize|translate)\b/i;
|
|
33
|
+
// Reshaping pasted content is editing work, not analysis; question marks
|
|
34
|
+
// inside the paste do not make it a multi-question ask.
|
|
35
|
+
const TRANSFORM_INTENT_RE = /^(?:turn|clean up|rewrite|reword|compress|tighten|polish)\b/i;
|
|
36
|
+
// Possessives point at workspace context the model must gather first.
|
|
37
|
+
const OWN_CONTEXT_RE = /\b(?:our|my)\b/i;
|
|
38
|
+
const SPANISH_LOOKUP_RE = /^(?:que|qu\u00e9|cual|cu\u00e1l|quien|qui\u00e9n|cuando|cu\u00e1ndo|donde|d\u00f3nde|como|c\u00f3mo)\b/i;
|
|
21
39
|
const GREETING_RE = /^(?:hey|hi|hello|yo|thanks|thank you|ok|okay|cool|nice|got it|great|perfect|yep|no worries)\b/i;
|
|
22
40
|
|
|
23
41
|
function threshold(value, fallback) {
|
|
@@ -35,11 +53,15 @@ function pickLane(message, opts = {}) {
|
|
|
35
53
|
if (text.length > longInputMinChars) {
|
|
36
54
|
return { lane: 'max', reason: 'max fits this long input.' };
|
|
37
55
|
}
|
|
56
|
+
if (TRANSFORM_INTENT_RE.test(text) && !HEAVY_REASONING_RE.test(text)) {
|
|
57
|
+
return { lane: 'pro', reason: 'pro fits reshaping the content you pasted.' };
|
|
58
|
+
}
|
|
38
59
|
if (questionCount > 1) {
|
|
39
60
|
return { lane: 'max', reason: 'max fits a request with multiple questions.' };
|
|
40
61
|
}
|
|
41
62
|
if (ARGUE_BOTH_RE.test(text)
|
|
42
|
-
|| (DECISION_PHRASE_RE.test(text) && commaCount >= DECISION_FACTOR_MIN_COMMAS)
|
|
63
|
+
|| (DECISION_PHRASE_RE.test(text) && commaCount >= DECISION_FACTOR_MIN_COMMAS)
|
|
64
|
+
|| (WEIGHING_WORD_RE.test(text) && commaCount >= WEIGHING_FACTOR_MIN_COMMAS)) {
|
|
43
65
|
return { lane: 'max', reason: 'max fits a decision weighing several factors.' };
|
|
44
66
|
}
|
|
45
67
|
if (HEAVY_REASONING_RE.test(text)) {
|
|
@@ -54,7 +76,12 @@ function pickLane(message, opts = {}) {
|
|
|
54
76
|
if (ERROR_EXPLAIN_RE.test(text) && !hasCodeContext) {
|
|
55
77
|
return { lane: 'fast', reason: 'fast fits a plain error explanation.' };
|
|
56
78
|
}
|
|
57
|
-
if (
|
|
79
|
+
if (SPANISH_LOOKUP_RE.test(text) && text.length <= shortLookupMaxChars
|
|
80
|
+
&& !JUDGMENT_WORD_RE.test(text) && !TROUBLE_RE.test(text)) {
|
|
81
|
+
return { lane: 'fast', reason: 'fast fits this short lookup.' };
|
|
82
|
+
}
|
|
83
|
+
if (TRANSFORM_START_RE.test(text) && text.length <= shortLookupMaxChars
|
|
84
|
+
&& !OWN_CONTEXT_RE.test(text)) {
|
|
58
85
|
return { lane: 'fast', reason: 'fast fits this small transform.' };
|
|
59
86
|
}
|
|
60
87
|
if (GREETING_RE.test(text) && text.length <= GREETING_MAX_CHARS) {
|
|
@@ -63,9 +90,22 @@ function pickLane(message, opts = {}) {
|
|
|
63
90
|
if (text.length > 0 && text.length <= TINY_MESSAGE_MAX_CHARS && !CODE_EDIT_RE.test(text)) {
|
|
64
91
|
return { lane: 'fast', reason: 'fast fits this tiny message.' };
|
|
65
92
|
}
|
|
93
|
+
if (text.length <= HOWTO_LOOKUP_MAX_CHARS
|
|
94
|
+
&& HOWTO_LOOKUP_RE.test(text)
|
|
95
|
+
&& !JUDGMENT_WORD_RE.test(text)
|
|
96
|
+
&& !TROUBLE_RE.test(text)) {
|
|
97
|
+
return { lane: 'fast', reason: 'fast fits this quick how-to.' };
|
|
98
|
+
}
|
|
99
|
+
if (text.length <= YESNO_LOOKUP_MAX_CHARS
|
|
100
|
+
&& YESNO_LOOKUP_RE.test(text)
|
|
101
|
+
&& !JUDGMENT_WORD_RE.test(text)
|
|
102
|
+
&& !TROUBLE_RE.test(text)) {
|
|
103
|
+
return { lane: 'fast', reason: 'fast fits this yes or no lookup.' };
|
|
104
|
+
}
|
|
66
105
|
if (text.length <= shortLookupMaxChars
|
|
67
|
-
&&
|
|
68
|
-
&& !JUDGMENT_WORD_RE.test(text)
|
|
106
|
+
&& LOOKUP_START_RE.test(text)
|
|
107
|
+
&& !JUDGMENT_WORD_RE.test(text)
|
|
108
|
+
&& !TROUBLE_RE.test(text)) {
|
|
69
109
|
return { lane: 'fast', reason: 'fast fits this short factual lookup.' };
|
|
70
110
|
}
|
|
71
111
|
return { lane: 'pro', reason: 'pro fits this general request.' };
|