myshell-tools 2.8.0 → 2.13.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 +38 -0
- package/README.md +34 -3
- package/dist/cli.js +35 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts +36 -1
- package/dist/commands/doctor.js +130 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/install.d.ts +5 -0
- package/dist/commands/install.js +16 -0
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/login.d.ts +8 -5
- package/dist/commands/login.js +143 -44
- package/dist/commands/login.js.map +1 -1
- package/dist/core/classify.d.ts +20 -4
- package/dist/core/classify.js +241 -43
- package/dist/core/classify.js.map +1 -1
- package/dist/core/orchestrate.js +115 -85
- package/dist/core/orchestrate.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/infra/config.d.ts +6 -0
- package/dist/infra/config.js.map +1 -1
- package/dist/infra/credentials.d.ts +28 -0
- package/dist/infra/credentials.js +52 -1
- package/dist/infra/credentials.js.map +1 -1
- package/dist/infra/update-check.d.ts +67 -0
- package/dist/infra/update-check.js +181 -0
- package/dist/infra/update-check.js.map +1 -0
- package/dist/interface/menu.d.ts +69 -0
- package/dist/interface/menu.js +334 -30
- package/dist/interface/menu.js.map +1 -1
- package/dist/interface/render.js +8 -1
- package/dist/interface/render.js.map +1 -1
- package/dist/interface/run.d.ts +12 -3
- package/dist/interface/run.js +3 -3
- package/dist/interface/run.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
* - 'code': a no-localhost flow that works anywhere.
|
|
14
14
|
* · claude → `claude setup-token`: prints a link; the user signs in at
|
|
15
15
|
* claude.ai, copies the authorization code, and pastes it back here.
|
|
16
|
-
*
|
|
17
|
-
* (sk-ant-oat…)
|
|
18
|
-
*
|
|
16
|
+
* We TEE the output so we can scan it for the minted token
|
|
17
|
+
* (sk-ant-oat…) automatically. If auto-capture finds the token, it is
|
|
18
|
+
* persisted immediately with no user action required. Otherwise we fall
|
|
19
|
+
* back to prompting the user to paste it (up to 3 retries, with helpful
|
|
20
|
+
* warnings for blank or wrong-type inputs).
|
|
19
21
|
* · codex → `codex login --device-auth`: prints a URL + one-time code;
|
|
20
22
|
* the user authorizes their ChatGPT account on any device.
|
|
21
23
|
*
|
|
@@ -23,14 +25,15 @@
|
|
|
23
25
|
* to 'code' (so the localhost trap is avoided), everything else to 'browser'.
|
|
24
26
|
*
|
|
25
27
|
* Security: myshell-tools never stores raw API keys or passwords. The Claude
|
|
26
|
-
* OAuth token (sk-ant-oat…) is captured
|
|
27
|
-
* it and is stored in
|
|
28
|
+
* OAuth token (sk-ant-oat…) is either auto-captured from the tee'd output or
|
|
29
|
+
* captured after the user explicitly pastes it, and is stored in
|
|
30
|
+
* ~/.myshell-tools/credentials.json (mode 0o600).
|
|
28
31
|
*/
|
|
29
32
|
import readline from 'node:readline';
|
|
30
33
|
import { execa } from 'execa';
|
|
31
34
|
import { detectProvider, getInstallCommand } from '../providers/detect.js';
|
|
32
|
-
import { bold, dim, green, red } from '../ui/theme.js';
|
|
33
|
-
import { extractClaudeToken, saveClaudeToken } from '../infra/credentials.js';
|
|
35
|
+
import { bold, dim, green, red, yellow } from '../ui/theme.js';
|
|
36
|
+
import { classifyPastedSecret, extractClaudeToken, saveClaudeToken, stripPastedSecretWrapper, } from '../infra/credentials.js';
|
|
34
37
|
/** Each provider's default (browser/localhost) sign-in command. */
|
|
35
38
|
const LOGIN_COMMAND = {
|
|
36
39
|
claude: { bin: 'claude', args: ['auth', 'login'] },
|
|
@@ -50,7 +53,8 @@ const LOGIN_CODE_COMMAND = {
|
|
|
50
53
|
guidance: 'A sign-in link will appear below.\n' +
|
|
51
54
|
' 1. Open it in any browser and sign in at claude.ai.\n' +
|
|
52
55
|
' 2. Copy the authorization code it shows you.\n' +
|
|
53
|
-
' 3. Paste the code back here at the prompt and press Enter
|
|
56
|
+
' 3. Paste the code back here at the prompt and press Enter.\n' +
|
|
57
|
+
' We will capture the token automatically when possible.',
|
|
54
58
|
},
|
|
55
59
|
codex: {
|
|
56
60
|
bin: 'codex',
|
|
@@ -142,28 +146,60 @@ export async function runLogin(out, providerArg, opts) {
|
|
|
142
146
|
}
|
|
143
147
|
// stdio:'inherit' hands the terminal to the provider CLI so its OAuth /
|
|
144
148
|
// device / paste flow runs in place. reject:false so we report rather than throw.
|
|
149
|
+
//
|
|
150
|
+
// For the claude code method we use a special tee-capture approach: stdout
|
|
151
|
+
// and stderr are piped so we can scan for the token, but each line is also
|
|
152
|
+
// written verbatim to the real terminal so the user sees the flow live.
|
|
153
|
+
// stdin is still inherited so the user can interact (paste the auth code).
|
|
145
154
|
let result;
|
|
146
|
-
|
|
155
|
+
let capturedOutput = '';
|
|
156
|
+
if (method === 'code' && id === 'claude') {
|
|
147
157
|
const { bin, args, guidance } = LOGIN_CODE_COMMAND[id];
|
|
148
158
|
out.write(bold(`\nSigning in to ${id} — code method (no localhost needed).\n`, out.color));
|
|
149
159
|
out.write(dim(guidance + '\n', out.color));
|
|
150
|
-
|
|
160
|
+
capturedOutput = await runWithTeeCapture(bin, [...args]);
|
|
161
|
+
// We use a synthetic exit-code-0 result — tee-capture never throws, and
|
|
162
|
+
// we treat the flow as complete regardless of the subprocess exit code
|
|
163
|
+
// (the token presence is the real signal).
|
|
164
|
+
result = { exitCode: 0 };
|
|
165
|
+
}
|
|
166
|
+
else if (method === 'code') {
|
|
167
|
+
const { bin, args, guidance } = LOGIN_CODE_COMMAND[id];
|
|
168
|
+
out.write(bold(`\nSigning in to ${id} — code method (no localhost needed).\n`, out.color));
|
|
169
|
+
out.write(dim(guidance + '\n', out.color));
|
|
170
|
+
result = await execa(bin, [...args], { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', reject: false });
|
|
151
171
|
}
|
|
152
172
|
else {
|
|
153
173
|
const { bin, args } = LOGIN_COMMAND[id];
|
|
154
174
|
out.write(bold(`\nSigning in to ${id} — a browser window may open…\n`, out.color));
|
|
155
|
-
result = await execa(bin, [...args], {
|
|
175
|
+
result = await execa(bin, [...args], { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit', reject: false });
|
|
156
176
|
}
|
|
157
177
|
if (result.exitCode === 0) {
|
|
158
|
-
out.write(green(`✓ ${id} sign-in complete.\n`, out.color));
|
|
159
178
|
// --- Claude code-method token capture -----------------------------------
|
|
160
179
|
// `claude setup-token` mints a long-lived token (sk-ant-oat01-…) and PRINTS
|
|
161
|
-
// it to the terminal
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
// every subsequent `claude -p …` spawn work without manual env-var setup.
|
|
180
|
+
// it to the terminal. We tee'd the output above and scan it first.
|
|
181
|
+
// If auto-capture finds the token, we're done. Otherwise we fall back to
|
|
182
|
+
// the paste prompt (up to 3 retries) so the user can paste it manually.
|
|
165
183
|
if (id === 'claude' && method === 'code') {
|
|
166
|
-
|
|
184
|
+
const autoToken = extractClaudeToken(capturedOutput);
|
|
185
|
+
if (autoToken !== null) {
|
|
186
|
+
try {
|
|
187
|
+
await saveClaudeToken(autoToken);
|
|
188
|
+
process.env['CLAUDE_CODE_OAUTH_TOKEN'] = autoToken;
|
|
189
|
+
out.write(green('✓ Claude token captured and saved — claude is now ready.\n', out.color));
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
out.write(dim('Could not save token to disk — re-run `myshell-tools login claude --code` later.\n', out.color));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Auto-capture found nothing — fall back to paste prompt.
|
|
197
|
+
out.write(green(`✓ ${id} sign-in complete.\n`, out.color));
|
|
198
|
+
await captureClaudeTokenWithPaste(out, opts?.readLine);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
out.write(green(`✓ ${id} sign-in complete.\n`, out.color));
|
|
167
203
|
}
|
|
168
204
|
}
|
|
169
205
|
else {
|
|
@@ -179,45 +215,108 @@ export async function runLogin(out, providerArg, opts) {
|
|
|
179
215
|
return 0;
|
|
180
216
|
}
|
|
181
217
|
// ---------------------------------------------------------------------------
|
|
182
|
-
//
|
|
218
|
+
// Tee-capture helper (internal)
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
/**
|
|
221
|
+
* Run a command with stdout and stderr piped (tee'd to the real terminal) and
|
|
222
|
+
* stdin inherited. Returns the accumulated stdout+stderr text, or an empty
|
|
223
|
+
* string on spawn failure. Never throws.
|
|
224
|
+
*
|
|
225
|
+
* Uses execa v9's `all: true` option to merge stdout+stderr into one stream,
|
|
226
|
+
* then iterates it via `subprocess.iterable({ from: 'all', preserveNewlines: true })`
|
|
227
|
+
* while mirroring each chunk to `process.stdout` so the user sees the output
|
|
228
|
+
* live. stdin is inherited so the user can interact (e.g. paste an auth code).
|
|
229
|
+
*/
|
|
230
|
+
async function runWithTeeCapture(bin, args) {
|
|
231
|
+
try {
|
|
232
|
+
const subprocess = execa(bin, [...args], {
|
|
233
|
+
stdin: 'inherit',
|
|
234
|
+
stdout: 'pipe',
|
|
235
|
+
stderr: 'pipe',
|
|
236
|
+
all: true,
|
|
237
|
+
reject: false,
|
|
238
|
+
timeout: 300_000,
|
|
239
|
+
});
|
|
240
|
+
const chunks = [];
|
|
241
|
+
for await (const line of subprocess.iterable({ from: 'all', preserveNewlines: true })) {
|
|
242
|
+
const text = typeof line === 'string' ? line : String(line);
|
|
243
|
+
process.stdout.write(text);
|
|
244
|
+
chunks.push(text);
|
|
245
|
+
}
|
|
246
|
+
await subprocess; // wait for exit (already resolved by the iteration above)
|
|
247
|
+
return chunks.join('');
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Spawn error or unexpected failure — return empty so the caller falls back
|
|
251
|
+
// to the paste prompt.
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Paste-fallback token capture helper (internal)
|
|
183
257
|
// ---------------------------------------------------------------------------
|
|
184
258
|
/**
|
|
185
259
|
* Prompt the user to paste the token shown by `claude setup-token`, extract
|
|
186
260
|
* it, persist it, and inject it into `process.env.CLAUDE_CODE_OAUTH_TOKEN`.
|
|
187
261
|
*
|
|
262
|
+
* Retries up to 3 times:
|
|
263
|
+
* - Blank input → skip silently with a note.
|
|
264
|
+
* - API key → print a specific warning (that's sk-ant-api, not sk-ant-oat).
|
|
265
|
+
* - Invalid → warn and re-prompt.
|
|
266
|
+
* - Valid token → save and set env.
|
|
267
|
+
*
|
|
188
268
|
* Uses the injected `readLine` when provided (menu shares its single readline
|
|
189
269
|
* interface). Otherwise creates a temporary readline interface, reads ONE line,
|
|
190
270
|
* and immediately closes it (so stdin is not held open).
|
|
191
271
|
*
|
|
192
|
-
* Never throws
|
|
272
|
+
* Never throws.
|
|
193
273
|
*/
|
|
194
|
-
async function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (token !== null) {
|
|
209
|
-
try {
|
|
210
|
-
await saveClaudeToken(token);
|
|
211
|
-
process.env['CLAUDE_CODE_OAUTH_TOKEN'] = token;
|
|
212
|
-
out.write(green('✓ Claude token saved — claude is now ready.\n', out.color));
|
|
274
|
+
async function captureClaudeTokenWithPaste(out, readLine) {
|
|
275
|
+
const MAX_RETRIES = 3;
|
|
276
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
277
|
+
out.write(`\nPaste the token shown above (starts with sk-ant-oat) and press Enter` +
|
|
278
|
+
` — or leave blank to skip (attempt ${attempt}/${MAX_RETRIES}):\n> `);
|
|
279
|
+
let raw;
|
|
280
|
+
if (readLine !== undefined) {
|
|
281
|
+
// Menu injected its own reader — use it directly, do NOT create a second
|
|
282
|
+
// readline interface (that would double-consume stdin).
|
|
283
|
+
raw = await readLine();
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// CLI direct path — create a temporary readline, read one line, close.
|
|
287
|
+
raw = await readOneLineFromStdin();
|
|
213
288
|
}
|
|
214
|
-
|
|
215
|
-
|
|
289
|
+
const normalised = stripPastedSecretWrapper(raw ?? '');
|
|
290
|
+
if (normalised === '') {
|
|
291
|
+
out.write(dim('Skipped — no token entered.\n', out.color));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const kind = classifyPastedSecret(normalised);
|
|
295
|
+
if (kind === 'api-key') {
|
|
296
|
+
out.write(yellow('That looks like an Anthropic API key (sk-ant-api…), not the setup-token\n' +
|
|
297
|
+
'OAuth token. Please paste the sk-ant-oat… value instead.\n', out.color));
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const token = extractClaudeToken(normalised);
|
|
301
|
+
if (token !== null) {
|
|
302
|
+
try {
|
|
303
|
+
await saveClaudeToken(token);
|
|
304
|
+
process.env['CLAUDE_CODE_OAUTH_TOKEN'] = token;
|
|
305
|
+
out.write(green('✓ Claude token saved — claude is now ready.\n', out.color));
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
out.write(dim('Could not save token to disk — you can re-run `myshell-tools login claude --code` later.\n', out.color));
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
// Not a recognised token format.
|
|
313
|
+
if (attempt < MAX_RETRIES) {
|
|
314
|
+
out.write(dim('Token not recognised — expected sk-ant-oat… format. Please try again.\n', out.color));
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
out.write(dim('Token not captured after 3 attempts. Re-run `myshell-tools login claude --code`\n' +
|
|
318
|
+
'and paste the sk-ant-oat… value (NOT an Anthropic API key starting with sk-ant-api).\n', out.color));
|
|
216
319
|
}
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
out.write(dim('Token not captured. Re-run `myshell-tools login claude --code` and paste the\n' +
|
|
220
|
-
'sk-ant-oat… value (NOT an Anthropic API key starting with sk-ant-api).\n', out.color));
|
|
221
320
|
}
|
|
222
321
|
}
|
|
223
322
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,QAAQ,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAG9B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AAKjC,mEAAmE;AACnE,MAAM,aAAa,GAAmF;IACpG,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;IAClD,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;IACxC,mFAAmF;IACnF,6EAA6E;IAC7E,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;CACvD,CAAC;AAEF;;;GAGG;AACH,MAAM,kBAAkB,GAGpB;IACF,MAAM,EAAE;QACN,GAAG,EAAE,QAAQ;QACb,IAAI,EAAE,CAAC,aAAa,CAAC;QACrB,QAAQ,EACN,qCAAqC;YACrC,yDAAyD;YACzD,kDAAkD;YAClD,gEAAgE;YAChE,0DAA0D;KAC7D;IACD,KAAK,EAAE;QACL,GAAG,EAAE,OAAO;QACZ,IAAI,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC;QAChC,QAAQ,EACN,gDAAgD;YAChD,qCAAqC;YACrC,iEAAiE;YACjE,4DAA4D;KAC/D;IACD,QAAQ,EAAE;QACR,GAAG,EAAE,UAAU;QACf,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;QACvB,QAAQ,EACN,8BAA8B;YAC9B,iFAAiF;YACjF,gDAAgD;YAChD,qEAAqE;KACxE;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,UAAU,CAAC;AACzE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,GAAsB,EAAE,QAAyB;IAC7E,IACE,GAAG,CAAC,SAAS,CAAC,KAAK,SAAS;QAC5B,GAAG,CAAC,mBAAmB,CAAC,KAAK,SAAS;QACtC,GAAG,CAAC,YAAY,CAAC,KAAK,SAAS;QAC/B,GAAG,CAAC,qBAAqB,CAAC,KAAK,SAAS,EACxC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAG,CAAC,gBAAgB,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,SAAS,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IACE,QAAQ,KAAK,OAAO;QACpB,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvD,CAAC,GAAG,CAAC,iBAAiB,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAC,EACvE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAiC,EACjC,GAAsB,EACtB,QAAyB;IAEzB,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,OAAO,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,GAAe,EACf,WAAoB,EACpB,IAAwE;IAExE,IAAI,OAAqB,CAAC;IAC1B,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,WAAW,uCAAuC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACnG,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE/E,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YACtB,GAAG,CAAC,KAAK,CACP,GAAG,CAAC,GAAG,EAAE,6CAA6C,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAC5F,CAAC;YACF,SAAS;QACX,CAAC;QAED,wEAAwE;QACxE,kFAAkF;QAClF,EAAE;QACF,2EAA2E;QAC3E,2EAA2E;QAC3E,wEAAwE;QACxE,2EAA2E;QAC3E,IAAI,MAAM,CAAC;QACX,IAAI,cAAc,GAAG,EAAE,CAAC;QAExB,IAAI,MAAM,KAAK,MAAM,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACvD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,EAAE,yCAAyC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3F,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3C,cAAc,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YACzD,wEAAwE;YACxE,uEAAuE;YACvE,2CAA2C;YAC3C,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAW,CAAC;QACpC,CAAC;aAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC;YACvD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,EAAE,yCAAyC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3F,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3C,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAClH,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;YACxC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,EAAE,iCAAiC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACnF,MAAM,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAClH,CAAC;QAED,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YAC1B,2EAA2E;YAC3E,4EAA4E;YAC5E,mEAAmE;YACnE,yEAAyE;YACzE,wEAAwE;YACxE,IAAI,EAAE,KAAK,QAAQ,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACzC,MAAM,SAAS,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;gBACrD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACvB,IAAI,CAAC;wBACH,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;wBACjC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,SAAS,CAAC;wBACnD,GAAG,CAAC,KAAK,CACP,KAAK,CACH,4DAA4D,EAC5D,GAAG,CAAC,KAAK,CACV,CACF,CAAC;oBACJ,CAAC;oBAAC,MAAM,CAAC;wBACP,GAAG,CAAC,KAAK,CACP,GAAG,CACD,oFAAoF,EACpF,GAAG,CAAC,KAAK,CACV,CACF,CAAC;oBACJ,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,0DAA0D;oBAC1D,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;oBAC3D,MAAM,2BAA2B,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;gBACzD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,sBAAsB,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CACP,GAAG,CAAC,KAAK,EAAE,mCAAmC,MAAM,CAAC,QAAQ,IAAI,SAAS,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAC7F,CAAC;YACF,yEAAyE;YACzE,mEAAmE;YACnE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,GAAG,CAAC,KAAK,CACP,GAAG,CACD,sEAAsE;oBACpE,yBAAyB,EAAE,WAAW,EACxC,GAAG,CAAC,KAAK,CACV,CACF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAED,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAuB;IACnE,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;YACvC,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,IAAI;YACT,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAa,EAAE,CAAC;QAE5B,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACtF,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,UAAU,CAAC,CAAC,0DAA0D;QAC5E,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,uBAAuB;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E;;;;;;;;;;;;;;;GAeG;AACH,KAAK,UAAU,2BAA2B,CACxC,GAAe,EACf,QAAuC;IAEvC,MAAM,WAAW,GAAG,CAAC,CAAC;IAEtB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACxD,GAAG,CAAC,KAAK,CACP,wEAAwE;YACtE,sCAAsC,OAAO,IAAI,WAAW,QAAQ,CACvE,CAAC;QAEF,IAAI,GAAkB,CAAC;QAEvB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,yEAAyE;YACzE,wDAAwD;YACxD,GAAG,GAAG,MAAM,QAAQ,EAAE,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,uEAAuE;YACvE,GAAG,GAAG,MAAM,oBAAoB,EAAE,CAAC;QACrC,CAAC;QAED,MAAM,UAAU,GAAG,wBAAwB,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QAEvD,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;YACtB,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAE9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,KAAK,CACP,MAAM,CACJ,2EAA2E;gBACzE,4DAA4D,EAC9D,GAAG,CAAC,KAAK,CACV,CACF,CAAC;YACF,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAE7C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,GAAG,KAAK,CAAC;gBAC/C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,+CAA+C,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YAC/E,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,KAAK,CACP,GAAG,CACD,4FAA4F,EAC5F,GAAG,CAAC,KAAK,CACV,CACF,CAAC;YACJ,CAAC;YACD,OAAO;QACT,CAAC;QAED,iCAAiC;QACjC,IAAI,OAAO,GAAG,WAAW,EAAE,CAAC;YAC1B,GAAG,CAAC,KAAK,CACP,GAAG,CACD,yEAAyE,EACzE,GAAG,CAAC,KAAK,CACV,CACF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,KAAK,CACP,GAAG,CACD,mFAAmF;gBACjF,wFAAwF,EAC1F,GAAG,CAAC,KAAK,CACV,CACF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB;IAC3B,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,EAAE;QAC5C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QAEH,IAAI,QAAQ,GAAG,KAAK,CAAC;QAErB,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE;YAC9B,QAAQ,GAAG,IAAI,CAAC;YAChB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,gFAAgF"}
|
package/dist/core/classify.d.ts
CHANGED
|
@@ -2,17 +2,33 @@
|
|
|
2
2
|
* src/core/classify.ts — pure task classification.
|
|
3
3
|
*
|
|
4
4
|
* Determines the orchestration tier and security risk level from a free-text
|
|
5
|
-
* task description using
|
|
5
|
+
* task description using a multi-signal scoring approach. No I/O, no time,
|
|
6
|
+
* no randomness — pure function.
|
|
6
7
|
*
|
|
7
8
|
* Honesty Contract: no fabricated confidence numbers are produced here.
|
|
8
|
-
* The rationale field names the matched
|
|
9
|
+
* The rationale field names the matched signals so callers can audit decisions.
|
|
10
|
+
*
|
|
11
|
+
* # Tier scoring model
|
|
12
|
+
*
|
|
13
|
+
* Each tier has a list of keyword patterns. The text is matched against all
|
|
14
|
+
* three lists and a score (count of matches) is accumulated per tier.
|
|
15
|
+
* Tie-break is deterministic: manager > ic > worker.
|
|
16
|
+
* Default when nothing matches: ic.
|
|
17
|
+
*
|
|
18
|
+
* # Risk model
|
|
19
|
+
*
|
|
20
|
+
* Priority cascade: critical > high > medium > low.
|
|
21
|
+
* Word boundaries prevent false positives (e.g. "keyboard" does not match
|
|
22
|
+
* the "key" critical signal).
|
|
9
23
|
*/
|
|
10
24
|
import type { Classification } from './types.js';
|
|
11
25
|
/**
|
|
12
26
|
* Classify a free-text `task` string into a {@link Classification}.
|
|
13
27
|
*
|
|
14
|
-
* Tier
|
|
15
|
-
* Risk priority
|
|
28
|
+
* Tier: multi-signal scoring; tie-break manager > ic > worker; default ic.
|
|
29
|
+
* Risk: priority cascade critical > high > medium > low; default low.
|
|
30
|
+
*
|
|
31
|
+
* Never throws — empty or whitespace input returns a safe default.
|
|
16
32
|
*
|
|
17
33
|
* @param task - The raw task description from the user.
|
|
18
34
|
*/
|
package/dist/core/classify.js
CHANGED
|
@@ -2,79 +2,277 @@
|
|
|
2
2
|
* src/core/classify.ts — pure task classification.
|
|
3
3
|
*
|
|
4
4
|
* Determines the orchestration tier and security risk level from a free-text
|
|
5
|
-
* task description using
|
|
5
|
+
* task description using a multi-signal scoring approach. No I/O, no time,
|
|
6
|
+
* no randomness — pure function.
|
|
6
7
|
*
|
|
7
8
|
* Honesty Contract: no fabricated confidence numbers are produced here.
|
|
8
|
-
* The rationale field names the matched
|
|
9
|
+
* The rationale field names the matched signals so callers can audit decisions.
|
|
10
|
+
*
|
|
11
|
+
* # Tier scoring model
|
|
12
|
+
*
|
|
13
|
+
* Each tier has a list of keyword patterns. The text is matched against all
|
|
14
|
+
* three lists and a score (count of matches) is accumulated per tier.
|
|
15
|
+
* Tie-break is deterministic: manager > ic > worker.
|
|
16
|
+
* Default when nothing matches: ic.
|
|
17
|
+
*
|
|
18
|
+
* # Risk model
|
|
19
|
+
*
|
|
20
|
+
* Priority cascade: critical > high > medium > low.
|
|
21
|
+
* Word boundaries prevent false positives (e.g. "keyboard" does not match
|
|
22
|
+
* the "key" critical signal).
|
|
9
23
|
*/
|
|
10
24
|
// ---------------------------------------------------------------------------
|
|
11
25
|
// Tier signal tables
|
|
12
26
|
// ---------------------------------------------------------------------------
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Manager-tier signals: review/planning/architecture/audit tasks that require
|
|
29
|
+
* high-level judgment across the codebase or system.
|
|
30
|
+
*/
|
|
31
|
+
const MANAGER_SIGNALS = [
|
|
32
|
+
/\breview\b/i,
|
|
33
|
+
/\bplan\b/i,
|
|
34
|
+
/\bdesign\b/i,
|
|
35
|
+
/\barchitect(?:ure)?\b/i,
|
|
36
|
+
/\baudit\b/i,
|
|
37
|
+
/\bsecurity\b/i,
|
|
38
|
+
/\bthreat\s+model\b/i,
|
|
39
|
+
/\bevaluate\b/i,
|
|
40
|
+
/\bassess\b/i,
|
|
41
|
+
/\bcompare\s+approaches?\b/i,
|
|
42
|
+
/\btrade[-\s]?offs?\b/i,
|
|
43
|
+
/\bwhich\s+approach\b/i,
|
|
44
|
+
/\bshould\s+we\b/i,
|
|
45
|
+
/\bstrategy\b/i,
|
|
46
|
+
/\bhigh[-\s]level\b/i,
|
|
47
|
+
/\bacross\s+the\s+(?:codebase|system)\b/i,
|
|
48
|
+
/\bend[-\s]to[-\s]end\b/i,
|
|
49
|
+
/\blarge\s+migration\b/i,
|
|
50
|
+
/\bcomplex(?:ity)?\b/i,
|
|
51
|
+
/\bcomplicated\b/i,
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Worker-tier signals: pure read-only lookups, searches, and explanations —
|
|
55
|
+
* no file mutation implied.
|
|
56
|
+
*/
|
|
57
|
+
const WORKER_SIGNALS = [
|
|
58
|
+
/\bfind\b/i,
|
|
59
|
+
/\bsearch\b/i,
|
|
60
|
+
/\bgrep\b/i,
|
|
61
|
+
/\blocate\b/i,
|
|
62
|
+
/\blist\b/i,
|
|
63
|
+
/\bread[-\s]?only\b/i,
|
|
64
|
+
/\blookup\b/i,
|
|
65
|
+
/\bscan\b/i,
|
|
66
|
+
/\bshow\b/i,
|
|
67
|
+
/\bdisplay\b/i,
|
|
68
|
+
/\bprint\b/i,
|
|
69
|
+
/\bcount\b/i,
|
|
70
|
+
/\bwhat\s+(?:is|are)\b/i,
|
|
71
|
+
/\bwhere\s+(?:is|are)\b/i,
|
|
72
|
+
/\bwhich\s+(?:is|are)\b/i,
|
|
73
|
+
/\bwho\s+(?:is|are)\b/i,
|
|
74
|
+
/\bhow\s+does\b/i,
|
|
75
|
+
/\bexplain\b/i,
|
|
76
|
+
/\bdescribe\b/i,
|
|
77
|
+
/\bsummariz(?:e|ise)\b/i,
|
|
78
|
+
/\bwhat\s+does\b/i,
|
|
79
|
+
];
|
|
80
|
+
/**
|
|
81
|
+
* IC-tier signals: implementation and hands-on editing tasks.
|
|
82
|
+
* (Used for tie-breaking and direct scoring; ic is the default tier.)
|
|
83
|
+
*/
|
|
84
|
+
const IC_SIGNALS = [
|
|
85
|
+
/\bimplement\b/i,
|
|
86
|
+
/\bwrite\b/i,
|
|
87
|
+
/\badd\b/i,
|
|
88
|
+
/\bcreate\b/i,
|
|
89
|
+
/\bbuild\b/i,
|
|
90
|
+
/\bfix\b/i,
|
|
91
|
+
/\bdebug\b/i,
|
|
92
|
+
/\brefactor\b/i,
|
|
93
|
+
/\bupdate\b/i,
|
|
94
|
+
/\bmodify\b/i,
|
|
95
|
+
/\bchange\b/i,
|
|
96
|
+
/\brename\b/i,
|
|
97
|
+
/\bremove\b/i,
|
|
98
|
+
/\bdelete\b/i,
|
|
99
|
+
/\bmove\b/i,
|
|
100
|
+
/\brewrite\b/i,
|
|
101
|
+
/\boptimize\b/i,
|
|
102
|
+
/\bformat\b/i,
|
|
103
|
+
/\bwire\s+up\b/i,
|
|
104
|
+
/\bhook\s+up\b/i,
|
|
105
|
+
/\badd\s+tests?\b/i,
|
|
106
|
+
];
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Risk signal tables — highest priority wins (critical > high > medium > low)
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
/**
|
|
111
|
+
* Critical: auth / secrets / credentials / encryption artefacts.
|
|
112
|
+
* Conservative: a security subject immediately elevates to critical.
|
|
113
|
+
*/
|
|
114
|
+
const CRITICAL_SIGNALS = [
|
|
115
|
+
/\bauth(?:entication|orization)?\b/i,
|
|
116
|
+
/\bcredential\b/i,
|
|
117
|
+
/\bsecret\b/i,
|
|
118
|
+
/\bpassword\b/i,
|
|
119
|
+
/\btoken\b/i,
|
|
120
|
+
/\bapi[-\s]?key\b/i,
|
|
121
|
+
/\bprivate[-\s]?key\b/i,
|
|
122
|
+
/\bencrypt(?:ion|ed)?\b/i,
|
|
123
|
+
/\bcertificate\b/i,
|
|
124
|
+
/\boauth\b/i,
|
|
125
|
+
/\bvault\b/i,
|
|
126
|
+
/\bsession\b/i,
|
|
127
|
+
/\bcookie\b/i,
|
|
128
|
+
/\bjwt\b/i,
|
|
129
|
+
/\.env\b/i,
|
|
130
|
+
];
|
|
131
|
+
/**
|
|
132
|
+
* High: payments, deploys, migrations, CI/CD, permissions, schema changes,
|
|
133
|
+
* production infrastructure.
|
|
134
|
+
*/
|
|
135
|
+
const HIGH_SIGNALS = [
|
|
136
|
+
/\blogin\b/i,
|
|
137
|
+
/\bpayment\b/i,
|
|
138
|
+
/\bbilling\b/i,
|
|
139
|
+
/\bdeploy(?:ment)?\b/i,
|
|
140
|
+
/\bmigration\b/i,
|
|
141
|
+
/\bci\/cd\b/i,
|
|
142
|
+
/\bpermission\b/i,
|
|
143
|
+
/\bschema\b/i,
|
|
144
|
+
/\bproduction\b/i,
|
|
145
|
+
/\bprod\b/i,
|
|
146
|
+
/\brelease\b/i,
|
|
147
|
+
/\brollback\b/i,
|
|
148
|
+
/\binfra(?:structure)?\b/i,
|
|
149
|
+
/\bterraform\b/i,
|
|
150
|
+
/\bkubernetes\b/i,
|
|
151
|
+
/\bk8s\b/i,
|
|
152
|
+
/\bdocker\b/i,
|
|
153
|
+
/\bdb\s+migration\b/i,
|
|
154
|
+
/\bdatabase\s+migration\b/i,
|
|
155
|
+
];
|
|
156
|
+
/**
|
|
157
|
+
* Medium: tests, configs, shared utilities, integrations, build/CI signals.
|
|
158
|
+
*/
|
|
159
|
+
const MEDIUM_SIGNALS = [
|
|
160
|
+
/\btests?\b/i,
|
|
161
|
+
/\bspecs?\b/i,
|
|
162
|
+
/\bconfig(?:uration)?\b/i,
|
|
163
|
+
/\bintegration\b/i,
|
|
164
|
+
/\bshared\b/i,
|
|
165
|
+
/\butil(?:ity|ities)?\b/i,
|
|
166
|
+
/\blib\b/i,
|
|
167
|
+
/\blint\b/i,
|
|
168
|
+
/\bci\b/i,
|
|
169
|
+
/\bbuild\b/i,
|
|
170
|
+
/\bdependenc(?:y|ies)\b/i,
|
|
171
|
+
];
|
|
17
172
|
// ---------------------------------------------------------------------------
|
|
18
|
-
//
|
|
173
|
+
// Scoring helpers
|
|
19
174
|
// ---------------------------------------------------------------------------
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Count how many patterns in `signals` match `text` (each pattern counts once).
|
|
177
|
+
* Returns matched signal labels (the matching portion of the first match) for
|
|
178
|
+
* rationale generation.
|
|
179
|
+
*/
|
|
180
|
+
function scoreSignals(text, signals) {
|
|
181
|
+
const matched = [];
|
|
182
|
+
for (const re of signals) {
|
|
183
|
+
const m = re.exec(text);
|
|
184
|
+
if (m !== null) {
|
|
185
|
+
matched.push(m[0].toLowerCase());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return matched;
|
|
189
|
+
}
|
|
26
190
|
// ---------------------------------------------------------------------------
|
|
27
191
|
// Public API
|
|
28
192
|
// ---------------------------------------------------------------------------
|
|
29
193
|
/**
|
|
30
194
|
* Classify a free-text `task` string into a {@link Classification}.
|
|
31
195
|
*
|
|
32
|
-
* Tier
|
|
33
|
-
* Risk priority
|
|
196
|
+
* Tier: multi-signal scoring; tie-break manager > ic > worker; default ic.
|
|
197
|
+
* Risk: priority cascade critical > high > medium > low; default low.
|
|
198
|
+
*
|
|
199
|
+
* Never throws — empty or whitespace input returns a safe default.
|
|
34
200
|
*
|
|
35
201
|
* @param task - The raw task description from the user.
|
|
36
202
|
*/
|
|
37
203
|
export function classify(task) {
|
|
38
|
-
//
|
|
204
|
+
// Treat empty / whitespace as ic + low with a clear rationale.
|
|
205
|
+
if (!task || task.trim().length === 0) {
|
|
206
|
+
return {
|
|
207
|
+
tier: 'ic',
|
|
208
|
+
risk: 'low',
|
|
209
|
+
rationale: 'tier: ic (empty input — defaulting to ic); risk: low (no risk signals — defaulting to low)',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// --- Tier scoring ---
|
|
213
|
+
const managerMatches = scoreSignals(task, MANAGER_SIGNALS);
|
|
214
|
+
const icMatches = scoreSignals(task, IC_SIGNALS);
|
|
215
|
+
const workerMatches = scoreSignals(task, WORKER_SIGNALS);
|
|
216
|
+
const managerScore = managerMatches.length;
|
|
217
|
+
const icScore = icMatches.length;
|
|
218
|
+
const workerScore = workerMatches.length;
|
|
39
219
|
let tier;
|
|
40
|
-
let
|
|
41
|
-
if (
|
|
220
|
+
let tierSignals;
|
|
221
|
+
if (managerScore > 0 && managerScore >= icScore && managerScore >= workerScore) {
|
|
222
|
+
// Manager wins (or ties with any other tier — manager has highest priority)
|
|
42
223
|
tier = 'manager';
|
|
43
|
-
|
|
44
|
-
tierSignal = m ? `manager keyword '${m[0].toLowerCase()}'` : 'manager keyword';
|
|
224
|
+
tierSignals = managerMatches;
|
|
45
225
|
}
|
|
46
|
-
else if (
|
|
226
|
+
else if (icScore > 0 && icScore >= workerScore) {
|
|
227
|
+
// IC wins over worker (ic has higher priority on tie)
|
|
228
|
+
tier = 'ic';
|
|
229
|
+
tierSignals = icMatches;
|
|
230
|
+
}
|
|
231
|
+
else if (workerScore > 0) {
|
|
47
232
|
tier = 'worker';
|
|
48
|
-
|
|
49
|
-
tierSignal = m ? `worker keyword '${m[0].toLowerCase()}'` : 'worker keyword';
|
|
233
|
+
tierSignals = workerMatches;
|
|
50
234
|
}
|
|
51
235
|
else {
|
|
236
|
+
// Nothing matched — default to ic
|
|
52
237
|
tier = 'ic';
|
|
53
|
-
|
|
238
|
+
tierSignals = [];
|
|
54
239
|
}
|
|
55
|
-
|
|
240
|
+
const tierRationale = tierSignals.length > 0
|
|
241
|
+
? `tier: ${tier} (matched: ${tierSignals.join(', ')})`
|
|
242
|
+
: `tier: ${tier} (no tier keyword matched — defaulting to ic)`;
|
|
243
|
+
// --- Risk cascade (critical > high > medium > low) ---
|
|
56
244
|
let risk;
|
|
57
|
-
let
|
|
58
|
-
|
|
245
|
+
let riskSignals;
|
|
246
|
+
const criticalMatches = scoreSignals(task, CRITICAL_SIGNALS);
|
|
247
|
+
if (criticalMatches.length > 0) {
|
|
59
248
|
risk = 'critical';
|
|
60
|
-
|
|
61
|
-
riskSignal = `critical keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
|
|
62
|
-
}
|
|
63
|
-
else if (HIGH_RE.test(task)) {
|
|
64
|
-
risk = 'high';
|
|
65
|
-
const m = task.match(HIGH_RE);
|
|
66
|
-
riskSignal = `high keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
|
|
67
|
-
}
|
|
68
|
-
else if (MEDIUM_RE.test(task)) {
|
|
69
|
-
risk = 'medium';
|
|
70
|
-
const m = task.match(MEDIUM_RE);
|
|
71
|
-
riskSignal = `medium keyword '${m ? m[0].toLowerCase() : 'unknown'}'`;
|
|
249
|
+
riskSignals = criticalMatches;
|
|
72
250
|
}
|
|
73
251
|
else {
|
|
74
|
-
|
|
75
|
-
|
|
252
|
+
const highMatches = scoreSignals(task, HIGH_SIGNALS);
|
|
253
|
+
if (highMatches.length > 0) {
|
|
254
|
+
risk = 'high';
|
|
255
|
+
riskSignals = highMatches;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const mediumMatches = scoreSignals(task, MEDIUM_SIGNALS);
|
|
259
|
+
if (mediumMatches.length > 0) {
|
|
260
|
+
risk = 'medium';
|
|
261
|
+
riskSignals = mediumMatches;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
risk = 'low';
|
|
265
|
+
riskSignals = [];
|
|
266
|
+
}
|
|
267
|
+
}
|
|
76
268
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
269
|
+
const riskRationale = riskSignals.length > 0
|
|
270
|
+
? `risk: ${risk} (matched: ${riskSignals.join(', ')})`
|
|
271
|
+
: `risk: ${risk} (no risk keyword matched — defaulting to low)`;
|
|
272
|
+
return {
|
|
273
|
+
tier,
|
|
274
|
+
risk,
|
|
275
|
+
rationale: `${tierRationale}; ${riskRationale}`,
|
|
276
|
+
};
|
|
79
277
|
}
|
|
80
278
|
//# sourceMappingURL=classify.js.map
|