@humain/humain-code-contracts 0.3.2 → 0.4.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/CHANGELOG.md +25 -1
- package/README.md +10 -1
- package/dist/flow-admission.d.ts +209 -0
- package/dist/flow-admission.d.ts.map +1 -0
- package/dist/flow-admission.js +59 -0
- package/dist/flow-admission.js.map +1 -0
- package/dist/flow-common.d.ts +103 -0
- package/dist/flow-common.d.ts.map +1 -0
- package/dist/flow-common.js +282 -0
- package/dist/flow-common.js.map +1 -0
- package/dist/flow-control.d.ts +107 -0
- package/dist/flow-control.d.ts.map +1 -0
- package/dist/flow-control.js +144 -0
- package/dist/flow-control.js.map +1 -0
- package/dist/flow-events.d.ts +345 -0
- package/dist/flow-events.d.ts.map +1 -0
- package/dist/flow-events.js +147 -0
- package/dist/flow-events.js.map +1 -0
- package/dist/flow-profile.d.ts +200 -0
- package/dist/flow-profile.d.ts.map +1 -0
- package/dist/flow-profile.js +89 -0
- package/dist/flow-profile.js.map +1 -0
- package/dist/flow-sizing.d.ts +30 -0
- package/dist/flow-sizing.d.ts.map +1 -0
- package/dist/flow-sizing.js +568 -0
- package/dist/flow-sizing.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/rpc.d.ts +27 -2
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +283 -18
- package/dist/rpc.js.map +1 -1
- package/dist/trace-artifact.d.ts +1 -1
- package/dist/trace-artifact.d.ts.map +1 -1
- package/dist/trace-artifact.js +3 -1
- package/dist/trace-artifact.js.map +1 -1
- package/dist/trace-http.d.ts.map +1 -1
- package/dist/trace-http.js +3 -0
- package/dist/trace-http.js.map +1 -1
- package/dist/trace-payload.d.ts +25 -1
- package/dist/trace-payload.d.ts.map +1 -1
- package/dist/trace-payload.js +2 -0
- package/dist/trace-payload.js.map +1 -1
- package/dist/trace-records.d.ts +985 -0
- package/dist/trace-records.d.ts.map +1 -0
- package/dist/trace-records.js +535 -0
- package/dist/trace-records.js.map +1 -0
- package/dist/trace.d.ts +1 -0
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +1 -0
- package/dist/trace.js.map +1 -1
- package/package.json +29 -5
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
export const UNIFIED_CLASSIFIER_POLICY_ID = "unified-regex-v4";
|
|
2
|
+
// ─── Lane signals (moved verbatim from packages/ai/src/intent/classifier.ts) ───
|
|
3
|
+
// Lane order mirrors the router lane rubric: DEBUG is checked first, then
|
|
4
|
+
// MECHANICAL, then CREATIVE; FALLBACK catches the rest.
|
|
5
|
+
const DEBUG_SIGNALS = /\b(404|500|broken|bug|crash(?:es|ed|ing)?|debug(?:s|ged|ging)?|diagnos(?:e|es|ed|ing)|doesn'?t work|error|exception|fail(?:s|ed|ing)?|failing|flaky|null|not working|regression|root cause|stack trace|troubleshoot(?:s|ed|ing)?|undefined|unexpected|wrong (?:output|result|value))\b/i;
|
|
6
|
+
const MECHANICAL_SIGNALS = /\b(alphabetize|async\/await|bump|comment out|convert|delete unused|explain|extract (?:component|function)|inline|move (?:file|folder|component)|re-?format|re-?order|remove unused|rename|sort imports|typo|uncomment)\b/i;
|
|
7
|
+
const CREATIVE_SIGNALS = /\b(add|build|component|create|dashboard|design|enhance|feature|flow|generate|implement|improve|integrate|interface|landing|new|page|portal|redesign|screen|section|tool|view|widget|workflow)\b/i;
|
|
8
|
+
const NAMED_MODE_OVERRIDES = new Set(["production-ready", "add-tests", "code-review", "create-prd"]);
|
|
9
|
+
// ─── Depth signals (moved verbatim from apps/web/lib/ai-modes.ts) ───
|
|
10
|
+
const COMPLEX_BUILD_RE = /\b(admin|analytics|api|auth|authentication|authorization|backend|calendar|charts?|crud|dashboard|database|deploy|deployment|e2e|end[-\s]?to[-\s]?end|full[-\s]?stack|integration|kanban|multi[-\s]?page|permissions?|production|reports?|roles?|security|tests?|workflow)\b/i;
|
|
11
|
+
const EXPLICIT_THOROUGH_BUILD_RE = /\b(use\s+opus|opus[-\s]?level|thorough|deep(?:est)?\s+reasoning|deep\s+review|architecture\s+review)\b/i;
|
|
12
|
+
const HIGH_RISK_BUILD_RE = /\b(threat\s+model|security\s+review|compliance|regulated|hipaa|soc\s*2|gdpr|pci|data\s+retention|data\s+migration|schema\s+migration|tenant\s+isolation|multi[-\s]?tenant\s+authorization|authorization\s+architecture|incident\s+recovery|rollback\s+plan|payments?|billing)\b/i;
|
|
13
|
+
const SUBSTANTIVE_CHANGE_ACTION_SOURCE = "add|build|change|connect|configure|create|design|develop|enable|enhance|extend|fix|generate|implement|improve|integrate|introduce|make|redesign|refactor|remove|rename|replace|set\\s+up|support|update|wire";
|
|
14
|
+
const FOLLOW_UP_CHANGE_ACTION_SOURCE = `${SUBSTANTIVE_CHANGE_ACTION_SOURCE}|allow|automate|calculate|delete|display|export|give|handle|hide|import|invite|launch|let|localize|manage|move|permit|process|provide|rename|search|share|show|sync|track|trigger|unlock`;
|
|
15
|
+
const FOLLOW_UP_CHANGE_BOUNDARY_SOURCE = "(?:\\b(?:and|also|plus|additionally|then)\\b|[,;:.!?]|\\s(?:/|-|–|—)\\s)";
|
|
16
|
+
const FOLLOW_UP_CHANGE_BOUNDARY_RE = new RegExp(FOLLOW_UP_CHANGE_BOUNDARY_SOURCE, "i");
|
|
17
|
+
const LOCATION_REFERENCE_CONJUNCTION_RE = /^and$/i;
|
|
18
|
+
const REQUEST_DISCOURSE_MODIFIER_SOURCE = "(?:(?:just|kindly|only|please|simply)\\s+)*";
|
|
19
|
+
// Regex hardening (linear time): adjacent modifier runs are merged because `M* M*` is ambiguous, and
|
|
20
|
+
// `\s+.+\s+TAIL` spans use `spacedSpan` below. Every rewrite keeps the original language; see test/flow-sizing.test.ts.
|
|
21
|
+
const LINE_TERMINATORS = "\\n\\r\\u2028\\u2029";
|
|
22
|
+
/**
|
|
23
|
+
* Linear form of `\s+.+\s+TAIL` where TAIL starts with a non-space and `.` excludes line terminators: either the
|
|
24
|
+
* span starts and ends on non-space characters (the whitespace runs absorb everything else), or it is a single
|
|
25
|
+
* horizontal space inside one whitespace run.
|
|
26
|
+
*/
|
|
27
|
+
const spacedSpan = (tail) => `(?:\\s+\\S(?:[^${LINE_TERMINATORS}]*\\S)?\\s+|\\s[${LINE_TERMINATORS}]*[^\\S${LINE_TERMINATORS}]\\s+)${tail}`;
|
|
28
|
+
const SUBSTANTIVE_CHANGE_REQUEST_RE = new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:(?:(?:can|could|should|would|will)\\s+(?:you|we)\\s+|(?:i|we)(?:['’]d| would)?\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}like(?:\\s+you)?\\s+to\\s+|i\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}want(?:\\s+you)?\\s+to\\s+|we\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:need(?:\\s+you)?\\s+to|should|must)\\s+|let['’]?s\\s+)${REQUEST_DISCOURSE_MODIFIER_SOURCE})?(${SUBSTANTIVE_CHANGE_ACTION_SOURCE})\\b`, "i");
|
|
29
|
+
const DESIRED_STATE_REQUEST_RES = [
|
|
30
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:i|we)(?:['’]d\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}like|\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:want|need|would\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}like))\\s+(?=\\S)(.+)$`, "i"),
|
|
31
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}can\\s+(?:we|you)\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE}have\\s+(?=\\S)(.+)$`, "i"),
|
|
32
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:allow|enable|let)${spacedSpan("to\\b")}`, "i"),
|
|
33
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:give|provide)${spacedSpan("(?:a\\s+way|the\\s+ability)\\s+to\\b")}`, "i"),
|
|
34
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?!i\\b|we\\b|you\\b)(?:[\\w-]+\\s+){1,5}(?:should|must|need(?:s)?\\s+to)\\s+be\\s+able\\s+to\\b`, "i"),
|
|
35
|
+
new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:a|an|the)${spacedSpan("(?:should|must|need(?:s)?\\s+to)\\s+(?:be\\s+(?:added|built|created|implemented|integrated|introduced|supported)\\b|(?:allow|enable|have|include|provide|support)\\b)")}`, "i"),
|
|
36
|
+
];
|
|
37
|
+
const NON_IMPLEMENTATION_DESIRE_BOUNDARY_SOURCE = "(?=[.!?]*$|\\s+(?:about|of|on|regarding|whether)\\b|\\s*(?:[,;:]|\\b(?:and|also|plus|additionally|then)\\b)|\\s+(?:-|–|—)\\s+)";
|
|
38
|
+
const NON_IMPLEMENTATION_DESIRE_RE = new RegExp(`^(?:to\\s+(?:consider|decide|discuss|explore|know|learn|review|understand|wonder)\\b|help\\s+(?:with\\s+)?(?:considering|deciding|discussing|exploring|learning|reviewing|understanding)\\b|(?:advice|feedback)${NON_IMPLEMENTATION_DESIRE_BOUNDARY_SOURCE}|(?:a|an|the)\\s+(?:analysis|conversation|discussion|explanation|overview|review|summary)${NON_IMPLEMENTATION_DESIRE_BOUNDARY_SOURCE})`, "i");
|
|
39
|
+
const NON_IMPLEMENTATION_TOPIC_RE = /^(?:(?:advice|feedback)|(?:a|an|the)\s+(?:analysis|conversation|discussion|explanation|overview|review|summary))\s+(?:about|of|on|regarding|whether)\b/i;
|
|
40
|
+
const LEADING_ADVISORY_REQUEST_RE = new RegExp(`^${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:(?:can|could|would|will)\\s+(?:you|we)\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE})?(?:analy[sz]e|assess|describe|discuss|explain|explore|review|summarize|tell\\s+(?:me|us)|walk\\s+(?:me|us)\\s+through|give\\s+(?:me|us)\\s+(?:advice|feedback)|provide\\s+(?:me|us)\\s+(?:advice|feedback)|help\\s+(?:me|us)\\s+understand)\\b`, "i");
|
|
41
|
+
const ADVISORY_OUTPUT_ACTIONS = new Set(["give", "provide", "show"]);
|
|
42
|
+
const ADVISORY_TOPIC_COMPOUND_ACTION_RE = /^(?:build|change|create|design|export|import|process|search|support|update)$/;
|
|
43
|
+
const ADVISORY_TOPIC_COMPOUND_HEAD_RE = /^(?:approach|architecture|consistency|conventions?|coverage|experience|flows?|governance|management|patterns?|performance|process(?:es)?|quality|reliability|security|standards?|strategy|structure|tooling|usability|workflows?)\b/i;
|
|
44
|
+
const EXPLICIT_FOLLOW_UP_REQUEST_RE = /\b(?:just|kindly|only|please|simply|then)\b|[;:]|(?:can|could|would|will)\s+(?:you|we)\b/i;
|
|
45
|
+
const ADVISORY_OUTPUT_TARGET_RE = /^(?:(?:me|us)\s+)?(?:(?:a|an|more|some|the|your)\s+)?(?:(?:advice|analysis|assessment|conversation|discussion|explanation|feedback|findings?|next\s+steps|options?|overview|recommendations?|review|suggestions?|summary|thoughts?|trade[-\s]?offs?)(?:\s+(?:about|of|on|regarding)\s+(?=\S).+)?|(?:how|what|whether|why)\b.+)$/i;
|
|
46
|
+
const ADVISORY_OUTPUT_REQUEST_RE = /^(?:give|provide|show)\s+(?=\S)(.+)$/i;
|
|
47
|
+
const FOLLOW_UP_PREFIX_RE = /^[\s,:;.!?/–—-]*(?:(?:and|also|plus|additionally|then)\b[\s,:;.!?/–—-]*)*(?:(?:just|kindly|only|please|simply)\s+)*/i;
|
|
48
|
+
const COURTESY_ONLY_RE = /^(?:please|thanks|thank\s+you)[.!?]*$/i;
|
|
49
|
+
// Tail of a follow-up change clause, matched (sticky) where the punctuation/space run after a boundary ends.
|
|
50
|
+
const FOLLOW_UP_CHANGE_TAIL_RE = new RegExp(`${REQUEST_DISCOURSE_MODIFIER_SOURCE}(?:(?:can|could|would|will)\\s+(?:you|we)\\s+${REQUEST_DISCOURSE_MODIFIER_SOURCE})?(${FOLLOW_UP_CHANGE_ACTION_SOURCE})\\b(?=\\s+\\S)`, "iy");
|
|
51
|
+
const LEADING_SUBSTANTIVE_CHANGE_VALUE_RE = new RegExp(`^(?:${FOLLOW_UP_CHANGE_ACTION_SOURCE})\\b\\s+\\S+`, "i");
|
|
52
|
+
const BOUNDED_COPY_CONNECTOR_RE = /\b(?:and|by|or)\b/i;
|
|
53
|
+
const BOUNDED_LOCATION_PURPOSE_RE = new RegExp(`\\b(?:in\\s+order\\s+)?to\\s+(?:${FOLLOW_UP_CHANGE_ACTION_SOURCE})\\b`, "i");
|
|
54
|
+
const FUNCTIONAL_PURPOSE_CLAUSE_RE = /\b(?:so(?:\s+that)?\s+\S+(?:\s+\S+){0,2}\s+(?:can|could|may|might|will|would)\s+\S+|that\s+(?:allows?|enables?|lets?)\s+\S+\s+\S+|for\s+\S+(?:\s+\S+){0,2}\s+to\s+\S+)\b/i;
|
|
55
|
+
const POTENTIALLY_BOUNDED_ACTIONS = new Set([
|
|
56
|
+
"add",
|
|
57
|
+
"change",
|
|
58
|
+
"fix",
|
|
59
|
+
"improve",
|
|
60
|
+
"make",
|
|
61
|
+
"remove",
|
|
62
|
+
"rename",
|
|
63
|
+
"replace",
|
|
64
|
+
"update",
|
|
65
|
+
]);
|
|
66
|
+
const BOUNDED_EDIT_TARGET_SOURCE = "alignment|border|caption|color|copy|description|font|gap|heading|icon|label|link|margin|padding|placeholder|punctuation|radius|shadow|size|spacing|subtitle|text|title|tooltip|typo|weight|wording";
|
|
67
|
+
const BOUNDED_EDIT_TARGET_RE = new RegExp(`^(?:(?:the|a|an)\\s+)?((?:[\\w-]+\\s+){0,5})(${BOUNDED_EDIT_TARGET_SOURCE})\\b(.*)$`, "i");
|
|
68
|
+
const COORDINATED_BOUNDED_EDIT_TARGET_TAIL_RE = new RegExp(`(?:(?:and|also|plus|additionally)\\s+)?(?:(?:the|a|an)\\s+)?(?:[\\w-]+\\s+){0,5}(?:${BOUNDED_EDIT_TARGET_SOURCE})\\b`, "iy");
|
|
69
|
+
const BOUNDED_UI_SURFACE_MODIFIER_RE = /\b(app|application|badge|banner|breadcrumb|button|card|chip|control|dialog|drawer|dropdown|field|footer|form|header|hero|input|list|logo|menu|modal|nav|navbar|navigation|page|panel|popover|row|screen|search|section|select|shell|sidebar|switch|tab|table|tile|toast|toggle|toolbar)\b/i;
|
|
70
|
+
const CAPABILITY_CONTAINER_MODIFIER_RE = /\b(assistant|builder|center|editor|engine|feature|flow|integration|manager|module|platform|service|system|tool|workflow)\b/i;
|
|
71
|
+
const BOUNDED_PRESENTATION_ADJECTIVE_RE = /^(?:(?:a\s+(?:bit|little)|much|slightly)\s+)?(?:bolder|brighter|clearer|cooler|darker|friendlier|larger|lighter|longer|looser|rounder|sharper|shorter|smaller|softer|stronger|thicker|thinner|tighter|warmer|weaker|more\s+(?:compact|concise|descriptive|prominent|readable|specific|visible))[.!?]*$/i;
|
|
72
|
+
const BOUNDED_LOCATION_RE = /^(?:after|around|at|before|below|beside|between|by|in|inside|near|next\s+to|on|outside|right\s+next\s+to|under|within)\s+(?=\S)(.+)$/i;
|
|
73
|
+
const BOUNDED_LOCATION_BOUNDARY_RE = /\b(?:after|around|at|before|below|beside|between|by|in|inside|near|next\s+to|on|outside|right\s+next\s+to|under|within)\s+/i;
|
|
74
|
+
const BOUNDED_CONTENT_VALUE_RE = /^(?:read|say)\s+(?=\S)(.+)$/i;
|
|
75
|
+
const BOUNDED_TO_VALUE_RE = /^to\s+(?=\S)(.+)$/i;
|
|
76
|
+
const BOUNDED_FROM_TO_HEAD_RE = /^from\s+(?=\S)/i;
|
|
77
|
+
const BOUNDED_PRESENTATION_PAYLOAD_RE = /^with\s+(?=\S)(.+)$/i;
|
|
78
|
+
const BOUNDED_PASSIVE_VALUE_RE = /^(?:changed|fixed|renamed|replaced|set|updated)\s+(?:to|with)\s+(?=\S)(.+)$/i;
|
|
79
|
+
const BOUNDED_PASSIVE_FROM_TO_HEAD_RE = /^(?:changed|renamed|replaced|updated)\s+from\s+(?=\S)/i;
|
|
80
|
+
const FROM_TO_SEPARATOR_RE = /(?<=\S)\s+to\s+(?=\S)/gi;
|
|
81
|
+
const BOUNDED_PASSIVE_COMPLETED_RE = /^(?:changed|fixed|renamed|replaced|set|updated)[.!?]*$/i;
|
|
82
|
+
const BOUNDED_PASSIVE_PRESENTATION_RE = /^made\s+(?=\S)(.+)$/i;
|
|
83
|
+
const BOUNDED_PASSIVE_REMOVAL_RE = /^(?:deleted|removed)[.!?]*$/i;
|
|
84
|
+
const BUILD_CONJUNCTION_RE = /\b(and|also|plus|additionally)\b/i;
|
|
85
|
+
const LIST_SEPARATOR_RE = /[,;،]/u;
|
|
86
|
+
const NON_LATIN_RE = /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/u;
|
|
87
|
+
const ARABIC_COMPLEX_BUILD_RE = /(اختبارات|إعدادات|بيانات|تقارير|تحكم|صلاحيات|لوحة)/u;
|
|
88
|
+
const BUILD_IMPERATIVE_RES = [
|
|
89
|
+
"build",
|
|
90
|
+
"create",
|
|
91
|
+
"generate",
|
|
92
|
+
"change",
|
|
93
|
+
"make",
|
|
94
|
+
"add",
|
|
95
|
+
"remove",
|
|
96
|
+
"delete",
|
|
97
|
+
"update",
|
|
98
|
+
"move",
|
|
99
|
+
"replace",
|
|
100
|
+
"fix",
|
|
101
|
+
"implement",
|
|
102
|
+
"wire",
|
|
103
|
+
].map((value) => new RegExp(`\\b${value}\\b`, "gi"));
|
|
104
|
+
function matchSignals(prompt, pattern) {
|
|
105
|
+
const global = new RegExp(pattern.source, "gi");
|
|
106
|
+
return Array.from(new Set(Array.from(prompt.matchAll(global), (match) => match[0].toLowerCase())));
|
|
107
|
+
}
|
|
108
|
+
function classifyLane(input) {
|
|
109
|
+
const namedMode = input.mode?.trim();
|
|
110
|
+
if (namedMode && NAMED_MODE_OVERRIDES.has(namedMode)) {
|
|
111
|
+
return {
|
|
112
|
+
lane: "creative",
|
|
113
|
+
decisionSource: "mode-override",
|
|
114
|
+
matchedSignals: [`mode:${namedMode}`],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const prompt = input.prompt.trim();
|
|
118
|
+
if (!prompt)
|
|
119
|
+
return { lane: "fallback", decisionSource: "default", matchedSignals: [] };
|
|
120
|
+
const lanes = [
|
|
121
|
+
{ lane: "debug", pattern: DEBUG_SIGNALS },
|
|
122
|
+
{ lane: "mechanical", pattern: MECHANICAL_SIGNALS },
|
|
123
|
+
{ lane: "creative", pattern: CREATIVE_SIGNALS },
|
|
124
|
+
];
|
|
125
|
+
for (const { lane, pattern } of lanes) {
|
|
126
|
+
const matchedSignals = matchSignals(prompt, pattern);
|
|
127
|
+
if (matchedSignals.length > 0) {
|
|
128
|
+
return { lane, decisionSource: "keyword-classifier", matchedSignals };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { lane: "fallback", decisionSource: "default", matchedSignals: [] };
|
|
132
|
+
}
|
|
133
|
+
function imperativeCount(prompt) {
|
|
134
|
+
return BUILD_IMPERATIVE_RES.reduce((count, re) => {
|
|
135
|
+
const matches = prompt.match(re);
|
|
136
|
+
return count + (matches?.length ?? 0);
|
|
137
|
+
}, 0);
|
|
138
|
+
}
|
|
139
|
+
function hasMultiChangePrompt(prompt) {
|
|
140
|
+
if (!BUILD_CONJUNCTION_RE.test(prompt))
|
|
141
|
+
return false;
|
|
142
|
+
return imperativeCount(prompt) >= 2;
|
|
143
|
+
}
|
|
144
|
+
function hasNonLatinComplexitySignal(prompt) {
|
|
145
|
+
if (!NON_LATIN_RE.test(prompt))
|
|
146
|
+
return false;
|
|
147
|
+
if (ARABIC_COMPLEX_BUILD_RE.test(prompt))
|
|
148
|
+
return true;
|
|
149
|
+
return LIST_SEPARATOR_RE.test(prompt) && prompt.length > 48;
|
|
150
|
+
}
|
|
151
|
+
function isShortBoundedPhrase(value, maxWords) {
|
|
152
|
+
const trimmed = value.trim();
|
|
153
|
+
if (!trimmed || trimmed.length > 80 || /[\r\n;]/.test(trimmed))
|
|
154
|
+
return false;
|
|
155
|
+
const withoutTerminalPunctuation = trimmed.replace(/[.!?]+$/, "").trim();
|
|
156
|
+
if (!withoutTerminalPunctuation || /[.!?]\s/.test(withoutTerminalPunctuation))
|
|
157
|
+
return false;
|
|
158
|
+
return withoutTerminalPunctuation.split(/\s+/).length <= maxWords;
|
|
159
|
+
}
|
|
160
|
+
const QUOTE_PAIRS = {
|
|
161
|
+
'"': '"',
|
|
162
|
+
"'": "'",
|
|
163
|
+
"`": "`",
|
|
164
|
+
"“": "”",
|
|
165
|
+
"‘": "’",
|
|
166
|
+
};
|
|
167
|
+
function isWordCharacterAt(value, index) {
|
|
168
|
+
const character = value[index];
|
|
169
|
+
return character !== undefined && /[\p{L}\p{N}_]/u.test(character);
|
|
170
|
+
}
|
|
171
|
+
function maskQuotedText(value) {
|
|
172
|
+
const masked = value.split("");
|
|
173
|
+
// An opener whose scan found no closer before the end of its line proves every later opener of the same
|
|
174
|
+
// kind on that line also fails (closer validity does not depend on the opener), so it is skipped.
|
|
175
|
+
const noCloserBefore = new Map();
|
|
176
|
+
for (let openingIndex = 0; openingIndex < value.length; openingIndex += 1) {
|
|
177
|
+
const opening = value[openingIndex] ?? "";
|
|
178
|
+
const closing = QUOTE_PAIRS[opening];
|
|
179
|
+
if (!closing)
|
|
180
|
+
continue;
|
|
181
|
+
const isSingleQuote = opening === "'" || opening === "‘";
|
|
182
|
+
if (isSingleQuote &&
|
|
183
|
+
(isWordCharacterAt(value, openingIndex - 1) ||
|
|
184
|
+
!value[openingIndex + 1] ||
|
|
185
|
+
/[\s\r\n]/.test(value[openingIndex + 1] ?? ""))) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if ((noCloserBefore.get(opening) ?? -1) > openingIndex)
|
|
189
|
+
continue;
|
|
190
|
+
let closingIndex = openingIndex + 1;
|
|
191
|
+
for (; closingIndex < value.length; closingIndex += 1) {
|
|
192
|
+
const character = value[closingIndex];
|
|
193
|
+
if (character === "\r" || character === "\n")
|
|
194
|
+
break;
|
|
195
|
+
if (character !== closing)
|
|
196
|
+
continue;
|
|
197
|
+
if (isSingleQuote &&
|
|
198
|
+
isWordCharacterAt(value, closingIndex - 1) &&
|
|
199
|
+
isWordCharacterAt(value, closingIndex + 1)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
for (let index = openingIndex; index <= closingIndex; index += 1) {
|
|
203
|
+
masked[index] = " ";
|
|
204
|
+
}
|
|
205
|
+
openingIndex = closingIndex;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
if (closingIndex >= value.length || value[closingIndex] === "\r" || value[closingIndex] === "\n") {
|
|
209
|
+
noCloserBefore.set(opening, closingIndex);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return masked.join("");
|
|
213
|
+
}
|
|
214
|
+
function isEntireQuotedLiteral(value) {
|
|
215
|
+
const trimmed = value.trim();
|
|
216
|
+
const opening = trimmed[0] ?? "";
|
|
217
|
+
const closing = QUOTE_PAIRS[opening];
|
|
218
|
+
return Boolean(closing && trimmed.length >= 2 && trimmed.at(-1) === closing);
|
|
219
|
+
}
|
|
220
|
+
const FOLLOW_UP_BOUNDARY_G_RE = new RegExp(FOLLOW_UP_CHANGE_BOUNDARY_SOURCE, "gi");
|
|
221
|
+
const BOUNDARY_FILL_Y_RE = /[\s,:;.!?/–—-]*/y;
|
|
222
|
+
/**
|
|
223
|
+
* Linear form of `BOUNDARY [\s,:;.!?/–—-]* TAIL` searched leftmost-first and non-overlapping. Every boundary inside one
|
|
224
|
+
* punctuation/space run reaches the same run end, and a tail never starts inside the run except at a hyphen
|
|
225
|
+
* (where starting at the run end matches whenever starting at the hyphen does), so each run is scanned once.
|
|
226
|
+
* `onMatch` receives the boundary start, the match end and the tail match; return true to stop.
|
|
227
|
+
*/
|
|
228
|
+
function scanAfterBoundary(text, tail, onMatch) {
|
|
229
|
+
FOLLOW_UP_BOUNDARY_G_RE.lastIndex = 0;
|
|
230
|
+
let failedRunEnd = -1;
|
|
231
|
+
for (let boundary = FOLLOW_UP_BOUNDARY_G_RE.exec(text); boundary; boundary = FOLLOW_UP_BOUNDARY_G_RE.exec(text)) {
|
|
232
|
+
if (boundary.index + boundary[0].length <= failedRunEnd)
|
|
233
|
+
continue;
|
|
234
|
+
BOUNDARY_FILL_Y_RE.lastIndex = boundary.index + boundary[0].length;
|
|
235
|
+
BOUNDARY_FILL_Y_RE.exec(text);
|
|
236
|
+
const runEnd = BOUNDARY_FILL_Y_RE.lastIndex;
|
|
237
|
+
tail.lastIndex = runEnd;
|
|
238
|
+
const tailMatch = tail.exec(text);
|
|
239
|
+
if (!tailMatch) {
|
|
240
|
+
failedRunEnd = runEnd;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const end = runEnd + tailMatch[0].length;
|
|
244
|
+
if (onMatch(boundary.index, end, tailMatch))
|
|
245
|
+
return;
|
|
246
|
+
FOLLOW_UP_BOUNDARY_G_RE.lastIndex = end;
|
|
247
|
+
failedRunEnd = -1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function hasCoordinatedBoundedEditTarget(target) {
|
|
251
|
+
const maskedTarget = maskQuotedText(target);
|
|
252
|
+
const firstBoundary = FOLLOW_UP_CHANGE_BOUNDARY_RE.exec(maskedTarget);
|
|
253
|
+
const location = BOUNDED_LOCATION_BOUNDARY_RE.exec(maskedTarget);
|
|
254
|
+
if (firstBoundary &&
|
|
255
|
+
location &&
|
|
256
|
+
(location.index ?? 0) < (firstBoundary.index ?? 0) &&
|
|
257
|
+
LOCATION_REFERENCE_CONJUNCTION_RE.test(firstBoundary[0]) &&
|
|
258
|
+
isBoundedEditTarget(maskedTarget.slice(0, location.index).trim(), "change")) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
let found = false;
|
|
262
|
+
scanAfterBoundary(maskedTarget, COORDINATED_BOUNDED_EDIT_TARGET_TAIL_RE, () => {
|
|
263
|
+
found = true;
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
return found;
|
|
267
|
+
}
|
|
268
|
+
function hasFollowUpSubstantiveChange(value) {
|
|
269
|
+
let found = false;
|
|
270
|
+
scanAfterBoundary(maskQuotedText(value), FOLLOW_UP_CHANGE_TAIL_RE, () => {
|
|
271
|
+
found = true;
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
function isShortLiteralValue(value) {
|
|
277
|
+
const trimmed = value.trim();
|
|
278
|
+
if (trimmed.length <= 80 && !/[\r\n]/.test(trimmed) && isEntireQuotedLiteral(trimmed)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (!isShortBoundedPhrase(trimmed, 8) || hasFollowUpSubstantiveChange(trimmed)) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
const withoutTerminalPunctuation = trimmed.replace(/[.!?]+$/, "").trim();
|
|
285
|
+
if (/^(?:[/#@]|https?:\/\/)/i.test(withoutTerminalPunctuation))
|
|
286
|
+
return true;
|
|
287
|
+
if (/^[A-Z0-9]/.test(withoutTerminalPunctuation))
|
|
288
|
+
return true;
|
|
289
|
+
if (BOUNDED_COPY_CONNECTOR_RE.test(withoutTerminalPunctuation))
|
|
290
|
+
return true;
|
|
291
|
+
return !LEADING_SUBSTANTIVE_CHANGE_VALUE_RE.test(trimmed);
|
|
292
|
+
}
|
|
293
|
+
function isShortLocation(value) {
|
|
294
|
+
return (isShortBoundedPhrase(value, 8) &&
|
|
295
|
+
!hasFollowUpSubstantiveChange(value) &&
|
|
296
|
+
!BOUNDED_LOCATION_PURPOSE_RE.test(value) &&
|
|
297
|
+
!FUNCTIONAL_PURPOSE_CLAUSE_RE.test(value));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Linear form of `^HEAD(.+?)\s+to\s+(.+)$` on trimmed input: first separator whose left side and right side hold no line
|
|
301
|
+
* break. The regex's only extra parse (an all-whitespace left side) classifies as not-a-literal, same as no match.
|
|
302
|
+
*/
|
|
303
|
+
function matchFromTo(value, head) {
|
|
304
|
+
const headMatch = head.exec(value);
|
|
305
|
+
if (!headMatch)
|
|
306
|
+
return null;
|
|
307
|
+
const start = headMatch[0].length;
|
|
308
|
+
const relativeBreak = value.slice(start).search(/[\n\r\u2028\u2029]/);
|
|
309
|
+
const firstBreak = relativeBreak === -1 ? Number.POSITIVE_INFINITY : start + relativeBreak;
|
|
310
|
+
const lastBreak = Math.max(value.lastIndexOf("\n"), value.lastIndexOf("\r"), value.lastIndexOf("\u2028"), value.lastIndexOf("\u2029"));
|
|
311
|
+
FROM_TO_SEPARATOR_RE.lastIndex = start;
|
|
312
|
+
for (let match = FROM_TO_SEPARATOR_RE.exec(value); match; match = FROM_TO_SEPARATOR_RE.exec(value)) {
|
|
313
|
+
if (match.index > firstBreak)
|
|
314
|
+
return null;
|
|
315
|
+
const rightStart = match.index + match[0].length;
|
|
316
|
+
if (lastBreak < rightStart)
|
|
317
|
+
return [value.slice(start, match.index), value.slice(rightStart)];
|
|
318
|
+
FROM_TO_SEPARATOR_RE.lastIndex = match.index + 1;
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function isBoundedEditTarget(target, action) {
|
|
323
|
+
const match = BOUNDED_EDIT_TARGET_RE.exec(target);
|
|
324
|
+
if (!match)
|
|
325
|
+
return false;
|
|
326
|
+
const modifiers = match[1]?.trim() ?? "";
|
|
327
|
+
if ((!action || action === "add") &&
|
|
328
|
+
CAPABILITY_CONTAINER_MODIFIER_RE.test(modifiers) &&
|
|
329
|
+
!BOUNDED_UI_SURFACE_MODIFIER_RE.test(modifiers)) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const remainder = match[3]?.trim() ?? "";
|
|
333
|
+
if (hasFollowUpSubstantiveChange(remainder))
|
|
334
|
+
return false;
|
|
335
|
+
if (!remainder || /^[.!?]+$/.test(remainder))
|
|
336
|
+
return true;
|
|
337
|
+
if (BOUNDED_PRESENTATION_ADJECTIVE_RE.test(remainder))
|
|
338
|
+
return true;
|
|
339
|
+
if (BOUNDED_PASSIVE_REMOVAL_RE.test(remainder))
|
|
340
|
+
return true;
|
|
341
|
+
if (BOUNDED_PASSIVE_COMPLETED_RE.test(remainder))
|
|
342
|
+
return true;
|
|
343
|
+
const passiveFromToMatch = matchFromTo(remainder, BOUNDED_PASSIVE_FROM_TO_HEAD_RE);
|
|
344
|
+
if (passiveFromToMatch) {
|
|
345
|
+
return isShortLiteralValue(passiveFromToMatch[0]) && isShortLiteralValue(passiveFromToMatch[1]);
|
|
346
|
+
}
|
|
347
|
+
const passiveValueMatch = BOUNDED_PASSIVE_VALUE_RE.exec(remainder);
|
|
348
|
+
if (passiveValueMatch)
|
|
349
|
+
return isShortLiteralValue(passiveValueMatch[1] ?? "");
|
|
350
|
+
const passivePresentationMatch = BOUNDED_PASSIVE_PRESENTATION_RE.exec(remainder);
|
|
351
|
+
if (passivePresentationMatch) {
|
|
352
|
+
return BOUNDED_PRESENTATION_ADJECTIVE_RE.test(passivePresentationMatch[1] ?? "");
|
|
353
|
+
}
|
|
354
|
+
const fromToMatch = matchFromTo(remainder, BOUNDED_FROM_TO_HEAD_RE);
|
|
355
|
+
if (fromToMatch)
|
|
356
|
+
return isShortLiteralValue(fromToMatch[0]) && isShortLiteralValue(fromToMatch[1]);
|
|
357
|
+
const contentValueMatch = BOUNDED_CONTENT_VALUE_RE.exec(remainder);
|
|
358
|
+
if (contentValueMatch)
|
|
359
|
+
return isShortLiteralValue(contentValueMatch[1] ?? "");
|
|
360
|
+
const toValueMatch = BOUNDED_TO_VALUE_RE.exec(remainder);
|
|
361
|
+
if (toValueMatch)
|
|
362
|
+
return isShortLiteralValue(toValueMatch[1] ?? "");
|
|
363
|
+
const presentationPayloadMatch = BOUNDED_PRESENTATION_PAYLOAD_RE.exec(remainder);
|
|
364
|
+
if (presentationPayloadMatch) {
|
|
365
|
+
return isBoundedEditTarget(presentationPayloadMatch[1] ?? "", "change");
|
|
366
|
+
}
|
|
367
|
+
const locationMatch = BOUNDED_LOCATION_RE.exec(remainder);
|
|
368
|
+
return locationMatch ? isShortLocation(locationMatch[1] ?? "") : false;
|
|
369
|
+
}
|
|
370
|
+
function matchDesiredStateRequest(prompt) {
|
|
371
|
+
for (const pattern of DESIRED_STATE_REQUEST_RES) {
|
|
372
|
+
const match = pattern.exec(prompt);
|
|
373
|
+
if (match)
|
|
374
|
+
return { target: match[1]?.trimStart() ?? null };
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function firstChangeClauseTarget(target) {
|
|
379
|
+
const boundary = FOLLOW_UP_CHANGE_BOUNDARY_RE.exec(maskQuotedText(target));
|
|
380
|
+
return target.slice(0, boundary?.index ?? target.length).trim();
|
|
381
|
+
}
|
|
382
|
+
function desiredStateContinuation(target) {
|
|
383
|
+
const boundary = FOLLOW_UP_CHANGE_BOUNDARY_RE.exec(maskQuotedText(target));
|
|
384
|
+
if (!boundary)
|
|
385
|
+
return null;
|
|
386
|
+
return target
|
|
387
|
+
.slice((boundary.index ?? 0) + boundary[0].length)
|
|
388
|
+
.replace(FOLLOW_UP_PREFIX_RE, "")
|
|
389
|
+
.trim();
|
|
390
|
+
}
|
|
391
|
+
function isAdvisoryContinuation(target) {
|
|
392
|
+
if (!target || COURTESY_ONLY_RE.test(target) || ADVISORY_OUTPUT_TARGET_RE.test(target)) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
const outputRequest = ADVISORY_OUTPUT_REQUEST_RE.exec(target);
|
|
396
|
+
if (outputRequest && ADVISORY_OUTPUT_TARGET_RE.test(outputRequest[1] ?? ""))
|
|
397
|
+
return true;
|
|
398
|
+
return LEADING_ADVISORY_REQUEST_RE.test(target);
|
|
399
|
+
}
|
|
400
|
+
function classifyConjoinedChangeRequest(target, options = {}) {
|
|
401
|
+
const maskedTarget = maskQuotedText(target);
|
|
402
|
+
const matches = [];
|
|
403
|
+
scanAfterBoundary(maskedTarget, FOLLOW_UP_CHANGE_TAIL_RE, (start, end, tailMatch) => {
|
|
404
|
+
matches.push({ start, end, action: tailMatch[1] ?? "" });
|
|
405
|
+
return false;
|
|
406
|
+
});
|
|
407
|
+
let requestKind = "none";
|
|
408
|
+
for (const match of matches) {
|
|
409
|
+
const action = match.action.toLowerCase();
|
|
410
|
+
if (!action)
|
|
411
|
+
return "substantive";
|
|
412
|
+
// Masking a suffix equals the suffix of the masked text: no quote span can cross the action word.
|
|
413
|
+
const editStart = match.end + target.slice(match.end).search(/\S|$/);
|
|
414
|
+
const editTarget = target.slice(editStart);
|
|
415
|
+
const nextBoundary = FOLLOW_UP_CHANGE_BOUNDARY_RE.exec(maskedTarget.slice(editStart));
|
|
416
|
+
const clauseTarget = editTarget.slice(0, nextBoundary?.index ?? editTarget.length).trim();
|
|
417
|
+
const matchedText = maskedTarget.slice(match.start, match.end);
|
|
418
|
+
if (options.advisoryTopic &&
|
|
419
|
+
ADVISORY_TOPIC_COMPOUND_ACTION_RE.test(action) &&
|
|
420
|
+
ADVISORY_TOPIC_COMPOUND_HEAD_RE.test(clauseTarget) &&
|
|
421
|
+
!EXPLICIT_FOLLOW_UP_REQUEST_RE.test(matchedText)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (ADVISORY_OUTPUT_ACTIONS.has(action) && ADVISORY_OUTPUT_TARGET_RE.test(clauseTarget)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!POTENTIALLY_BOUNDED_ACTIONS.has(action))
|
|
428
|
+
return "substantive";
|
|
429
|
+
if (!isBoundedEditTarget(clauseTarget, action))
|
|
430
|
+
return "substantive";
|
|
431
|
+
if (hasCoordinatedBoundedEditTarget(editTarget))
|
|
432
|
+
return "substantive";
|
|
433
|
+
if (requestKind === "bounded")
|
|
434
|
+
return "substantive";
|
|
435
|
+
requestKind = "bounded";
|
|
436
|
+
}
|
|
437
|
+
return requestKind;
|
|
438
|
+
}
|
|
439
|
+
function classifyNonImplementationDesire(target) {
|
|
440
|
+
if (!NON_IMPLEMENTATION_DESIRE_RE.test(target))
|
|
441
|
+
return null;
|
|
442
|
+
const advisoryTopic = NON_IMPLEMENTATION_TOPIC_RE.test(target);
|
|
443
|
+
const conjoinedRequest = classifyConjoinedChangeRequest(target, { advisoryTopic });
|
|
444
|
+
if (conjoinedRequest !== "none")
|
|
445
|
+
return conjoinedRequest;
|
|
446
|
+
if (advisoryTopic)
|
|
447
|
+
return "none";
|
|
448
|
+
const continuation = desiredStateContinuation(target);
|
|
449
|
+
return continuation && !isAdvisoryContinuation(continuation) ? "substantive" : "none";
|
|
450
|
+
}
|
|
451
|
+
function hasNonImplementationDesire(prompt) {
|
|
452
|
+
const request = matchDesiredStateRequest(prompt);
|
|
453
|
+
if (!request?.target)
|
|
454
|
+
return false;
|
|
455
|
+
const requestKind = classifyNonImplementationDesire(request.target);
|
|
456
|
+
return requestKind !== null && requestKind !== "substantive";
|
|
457
|
+
}
|
|
458
|
+
function classifyChangeRequest(prompt) {
|
|
459
|
+
const match = SUBSTANTIVE_CHANGE_REQUEST_RE.exec(prompt);
|
|
460
|
+
if (match) {
|
|
461
|
+
const action = match[1]?.toLowerCase();
|
|
462
|
+
if (!action || !POTENTIALLY_BOUNDED_ACTIONS.has(action))
|
|
463
|
+
return "substantive";
|
|
464
|
+
const target = prompt.slice(match[0].length).trimStart();
|
|
465
|
+
const conjoinedRequest = classifyConjoinedChangeRequest(prompt);
|
|
466
|
+
if (!isBoundedEditTarget(firstChangeClauseTarget(target), action)) {
|
|
467
|
+
return action === "rename" && conjoinedRequest === "none" && !hasCoordinatedBoundedEditTarget(target)
|
|
468
|
+
? "none"
|
|
469
|
+
: "substantive";
|
|
470
|
+
}
|
|
471
|
+
if (hasCoordinatedBoundedEditTarget(target))
|
|
472
|
+
return "substantive";
|
|
473
|
+
return conjoinedRequest === "none" ? "bounded" : "substantive";
|
|
474
|
+
}
|
|
475
|
+
if (LEADING_ADVISORY_REQUEST_RE.test(prompt)) {
|
|
476
|
+
const conjoinedRequest = classifyConjoinedChangeRequest(prompt);
|
|
477
|
+
if (conjoinedRequest !== "none")
|
|
478
|
+
return conjoinedRequest;
|
|
479
|
+
}
|
|
480
|
+
const desiredStateRequest = matchDesiredStateRequest(prompt);
|
|
481
|
+
if (!desiredStateRequest)
|
|
482
|
+
return "none";
|
|
483
|
+
const { target } = desiredStateRequest;
|
|
484
|
+
if (target) {
|
|
485
|
+
const nonImplementationRequest = classifyNonImplementationDesire(target);
|
|
486
|
+
if (nonImplementationRequest !== null)
|
|
487
|
+
return nonImplementationRequest;
|
|
488
|
+
}
|
|
489
|
+
return target && isBoundedEditTarget(target) ? "bounded" : "substantive";
|
|
490
|
+
}
|
|
491
|
+
function resolvePromptExecutionDepth(trimmed, signalText, imperativeTotal, changeRequestKind) {
|
|
492
|
+
if (!trimmed)
|
|
493
|
+
return "quick";
|
|
494
|
+
if (EXPLICIT_THOROUGH_BUILD_RE.test(signalText))
|
|
495
|
+
return "thorough";
|
|
496
|
+
if (HIGH_RISK_BUILD_RE.test(signalText) && (COMPLEX_BUILD_RE.test(signalText) || imperativeTotal >= 2)) {
|
|
497
|
+
return "thorough";
|
|
498
|
+
}
|
|
499
|
+
if (signalText.length > 600 && imperativeTotal >= 3)
|
|
500
|
+
return "thorough";
|
|
501
|
+
if (hasNonImplementationDesire(trimmed))
|
|
502
|
+
return "quick";
|
|
503
|
+
if (changeRequestKind === "bounded")
|
|
504
|
+
return "quick";
|
|
505
|
+
if (COMPLEX_BUILD_RE.test(signalText))
|
|
506
|
+
return "balanced";
|
|
507
|
+
if (changeRequestKind === "substantive")
|
|
508
|
+
return "balanced";
|
|
509
|
+
if (hasNonLatinComplexitySignal(signalText))
|
|
510
|
+
return "balanced";
|
|
511
|
+
if (signalText.length > 180 && imperativeTotal >= 2)
|
|
512
|
+
return "balanced";
|
|
513
|
+
if (hasMultiChangePrompt(signalText))
|
|
514
|
+
return "balanced";
|
|
515
|
+
return "quick";
|
|
516
|
+
}
|
|
517
|
+
// Independent of the resolved depth: a prompt can carry a risk signal (e.g.
|
|
518
|
+
// a high-risk domain term) even on a turn where the overall heuristic above
|
|
519
|
+
// settles on 'balanced' because no other arm fired. Consumers that gate on
|
|
520
|
+
// risk (e.g. the review pass) rely on that independence rather than
|
|
521
|
+
// re-deriving reasons from `depth`.
|
|
522
|
+
function computeRiskReasons(trimmed, imperativeTotal) {
|
|
523
|
+
const reasons = [];
|
|
524
|
+
if (EXPLICIT_THOROUGH_BUILD_RE.test(trimmed))
|
|
525
|
+
reasons.push("explicit-thorough-request");
|
|
526
|
+
if (HIGH_RISK_BUILD_RE.test(trimmed))
|
|
527
|
+
reasons.push("high-risk-domain-term");
|
|
528
|
+
if (trimmed.length > 600 && imperativeTotal >= 3)
|
|
529
|
+
reasons.push("long-multi-imperative");
|
|
530
|
+
return reasons;
|
|
531
|
+
}
|
|
532
|
+
export function classifyPromptLanguageBucket(prompt) {
|
|
533
|
+
return NON_LATIN_RE.test(prompt) ? "non-latin" : "latin";
|
|
534
|
+
}
|
|
535
|
+
export function classifyPromptSignals(input) {
|
|
536
|
+
const trimmed = input.prompt.trim();
|
|
537
|
+
const signalText = maskQuotedText(trimmed);
|
|
538
|
+
const imperativeTotal = imperativeCount(signalText);
|
|
539
|
+
const changeRequestKind = classifyChangeRequest(trimmed);
|
|
540
|
+
const laneResult = classifyLane({
|
|
541
|
+
prompt: changeRequestKind === "bounded" ? signalText : trimmed,
|
|
542
|
+
mode: input.mode,
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
lane: laneResult.lane,
|
|
546
|
+
laneDecisionSource: laneResult.decisionSource,
|
|
547
|
+
laneMatchedSignals: laneResult.matchedSignals,
|
|
548
|
+
depth: resolvePromptExecutionDepth(trimmed, signalText, imperativeTotal, changeRequestKind),
|
|
549
|
+
riskReasons: computeRiskReasons(signalText, imperativeTotal),
|
|
550
|
+
policyId: UNIFIED_CLASSIFIER_POLICY_ID,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Maps a classifier depth to a Flow size: quick -> small, balanced -> medium, thorough -> large.
|
|
555
|
+
* When `thresholds` and a finite numeric `score` are both given, the score decides instead:
|
|
556
|
+
* score >= largeAt -> large, score >= mediumAt -> medium, else small.
|
|
557
|
+
*/
|
|
558
|
+
export function sizeFromDepth(depth, thresholds, score) {
|
|
559
|
+
if (thresholds !== undefined && typeof score === "number" && Number.isFinite(score)) {
|
|
560
|
+
if (score >= thresholds.largeAt)
|
|
561
|
+
return "large";
|
|
562
|
+
return score >= thresholds.mediumAt ? "medium" : "small";
|
|
563
|
+
}
|
|
564
|
+
if (depth === "thorough")
|
|
565
|
+
return "large";
|
|
566
|
+
return depth === "balanced" ? "medium" : "small";
|
|
567
|
+
}
|
|
568
|
+
//# sourceMappingURL=flow-sizing.js.map
|