@ngockhoale/ukit 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -176,6 +176,26 @@ const { pathToFileURL } = require('url');
176
176
  };
177
177
  }
178
178
 
179
+ function shouldKeepRouteEntryForIntent(entry, intentMode) {
180
+ if (entry.id === 'next-step' && ['scoped-advice', 'docs-specific'].includes(intentMode)) {
181
+ return false;
182
+ }
183
+
184
+ if (entry.id === 'update-status' && intentMode === 'docs-specific') {
185
+ return false;
186
+ }
187
+
188
+ if (entry.id === 'docs-quality' && ['open-ended-status', 'status-update'].includes(intentMode)) {
189
+ return false;
190
+ }
191
+
192
+ if (entry.id === 'code-review' && intentMode === 'implement-specific') {
193
+ return false;
194
+ }
195
+
196
+ return true;
197
+ }
198
+
179
199
  function deriveSkillPriorityBoost(skillId, routeSignals = {}) {
180
200
  const combinedPromptSignals = [
181
201
  routeSignals.promptRawText || '',
@@ -183,6 +203,10 @@ const { pathToFileURL } = require('url');
183
203
  routeSignals.commandRawText || '',
184
204
  routeSignals.commandNormalizedText || '',
185
205
  ].join('\n');
206
+ const rawPromptSignals = [
207
+ routeSignals.promptRawText || '',
208
+ routeSignals.commandRawText || '',
209
+ ].join('\n');
186
210
 
187
211
  if (
188
212
  skillId === 'testing-quality'
@@ -191,6 +215,14 @@ const { pathToFileURL } = require('url');
191
215
  return 1;
192
216
  }
193
217
 
218
+ if (
219
+ skillId === 'code-review'
220
+ && /\b(implement|build|create|add|ship|deliver|refactor|integrate|integration|scaffold|feature|update|change|modify|inject|apply)\b/.test(rawPromptSignals)
221
+ && !/\b(review|audit|diff|pr feedback|code review|kiem tra|soat)\b/.test(rawPromptSignals)
222
+ ) {
223
+ return -4;
224
+ }
225
+
194
226
  return 0;
195
227
  }
196
228
 
@@ -215,6 +247,131 @@ const { pathToFileURL } = require('url');
215
247
  };
216
248
  }
217
249
 
250
+ function deriveIntentMode({ promptText = '', commandText = '', targetFile = null } = {}) {
251
+ const lower = buildRouteSignalText(promptText, commandText);
252
+ const raw = `${promptText || ''}\n${commandText || ''}`.toLowerCase();
253
+ const taskQueueNext = hasTaskQueueNextSignal(lower, raw);
254
+ const docsSpecific = hasDocsSpecificTaskSignal(lower, raw, targetFile, { taskQueueNext });
255
+ const statusUpdate = hasStatusUpdateSignal(lower, raw);
256
+ const openEndedStatus = hasOpenEndedStatusSignal(lower, raw) || taskQueueNext;
257
+ const concreteTask = hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext });
258
+
259
+ if (docsSpecific) {
260
+ return 'docs-specific';
261
+ }
262
+
263
+ if (statusUpdate) {
264
+ return 'status-update';
265
+ }
266
+
267
+ if (concreteTask && openEndedStatus) {
268
+ return 'scoped-advice';
269
+ }
270
+
271
+ if (openEndedStatus) {
272
+ return 'open-ended-status';
273
+ }
274
+
275
+ if (concreteTask) {
276
+ if (/\b(bug|debug|error|crash|broken|failing|stack trace|triage|fix|login|timeout)\b/.test(lower)) {
277
+ return 'debug-specific';
278
+ }
279
+ if (/\b(review|audit|diff|pr feedback|code review|kiem tra|soat)\b/.test(raw)) {
280
+ return 'review-specific';
281
+ }
282
+ if (/\b(implement|build|create|add|ship|deliver|refactor|integrate|integration|scaffold|feature|update|change|modify|inject|apply)\b/.test(raw)) {
283
+ return 'implement-specific';
284
+ }
285
+ }
286
+
287
+ return null;
288
+ }
289
+
290
+ function hasStatusUpdateSignal(lower, raw) {
291
+ return /\b(update|refresh|write|sync|record|capture|summarize|summarise).{0,64}\b(status\.md|project status|current state|next candidates|session state)\b/.test(lower)
292
+ || /\b(status\.md|project status).{0,64}\b(update|refresh|write|sync|record|capture|summarize|summarise)\b/.test(lower)
293
+ || /\b(wrap up|handoff|end this session|ending this session|session summary|before final)\b/.test(lower)
294
+ || /\b(cap nhat|ghi lai|tong ket|chot session|ban giao).{0,64}\b(status|trang thai|viec tiep theo)\b/.test(lower)
295
+ || /\b(cập nhật|ghi lại|tổng kết|chốt session|bàn giao).{0,64}\b(status|trạng thái|việc tiếp theo)\b/.test(raw);
296
+ }
297
+
298
+ function hasOpenEndedStatusSignal(lower, raw) {
299
+ return /\b(what next|what is next|what's next|next step|next steps|project status|current status|where are we|continue|continue from last session|roadmap|status\.md|task queue|tasks\.md|next queued task|pick next task|work from tasks)\b/.test(lower)
300
+ || /\b(lam gi tiep|buoc tiep theo|tiep theo lam gi|lam tiep|dang o dau|trang thai project|tinh trang project|task tiep theo|viec tiep theo trong tasks)\b/.test(lower)
301
+ || /\b(làm gì tiếp|bước tiếp theo|tiếp theo làm gì|làm tiếp|đang ở đâu|trạng thái project|tình trạng project|task tiếp theo|việc tiếp theo trong tasks)\b/.test(raw);
302
+ }
303
+
304
+ function hasTaskQueueNextSignal(lower, raw) {
305
+ return /\b(task queue|tasks\.md|next queued task|pick next task|work from tasks|next task from tasks|ready for ai)\b/.test(lower)
306
+ || /\b(task tiếp theo|việc tiếp theo trong tasks|làm task trong tasks|lấy task tiếp theo)\b/.test(raw);
307
+ }
308
+
309
+ function hasConcreteTaskSignal(lower, raw, targetFile, { taskQueueNext = false } = {}) {
310
+ if (targetFile && !isStatusFileTarget(targetFile) && !(taskQueueNext && isTasksFileTarget(targetFile))) {
311
+ return true;
312
+ }
313
+
314
+ return /\b(bug|debug|error|crash|broken|failing|stack trace|triage|fix|implement|build|create|add|ship|deliver|refactor|integrate|integration|scaffold|feature|update|change|modify|inject|apply|review|audit|diff|pr feedback|code review|auth|login|api|endpoint|test|spec)\b/.test(lower)
315
+ || /\b(sửa|fix|lỗi|bug|debug|implement|cài|thêm|review|kiểm tra|soát|đăng nhập)\b/.test(raw);
316
+ }
317
+
318
+ function hasDocsSpecificTaskSignal(lower, raw, targetFile, { taskQueueNext = false } = {}) {
319
+ if (!targetFile || !isDocsTarget(targetFile)) {
320
+ return /\b(clean tasks|cleanup tasks|prune tasks|dedupe tasks|clear completed tasks|dọn tasks|dọn task)\b/.test(lower)
321
+ || /\b(dọn tasks|dọn task|dọn danh sách task|xóa task đã xong)\b/.test(raw);
322
+ }
323
+
324
+ if (taskQueueNext && isTasksFileTarget(targetFile) && !/\b(clean|cleanup|prune|dedupe|edit|template|wording|format|structure)\b/.test(lower)) {
325
+ return false;
326
+ }
327
+
328
+ const targetName = path.posix.basename(String(targetFile || '').replaceAll('\\', '/')).toLowerCase();
329
+ const explicitStatusUpdate = hasExplicitStatusUpdateSignal(lower, raw);
330
+
331
+ if (!isStatusFileTarget(targetFile)) {
332
+ if (explicitStatusUpdate && !mentionsTargetDoc(lower, targetName)) {
333
+ return false;
334
+ }
335
+
336
+ return mentionsTargetDoc(lower, targetName)
337
+ || /\b(edit|write|improve|document|docs?|readme|changelog|worklog|memory|code map|template|wording|copy|grammar|format|structure|heading|section|handoff notes?)\b/.test(lower)
338
+ || /\b(câu chữ|chỉnh|sửa chữ|ngữ pháp|định dạng|cấu trúc|tài liệu|ghi chú bàn giao)\b/.test(raw);
339
+ }
340
+
341
+ return /\b(template|wording|edit|copy|grammar|format|structure|heading|section|docs?)\b/.test(lower)
342
+ || /\b(mẫu|câu chữ|chỉnh|sửa chữ|ngữ pháp|định dạng|cấu trúc|tài liệu)\b/.test(raw);
343
+ }
344
+
345
+ function hasExplicitStatusUpdateSignal(lower, raw) {
346
+ return /\b(update|refresh|write|sync|record|capture|summarize|summarise).{0,64}\b(status\.md|project status|current state|next candidates|session state)\b/.test(lower)
347
+ || /\b(status\.md|project status).{0,64}\b(update|refresh|write|sync|record|capture|summarize|summarise)\b/.test(lower)
348
+ || /\b(cap nhat|ghi lai|tong ket|chot session|ban giao).{0,64}\b(status|trang thai|viec tiep theo)\b/.test(lower)
349
+ || /\b(cập nhật|ghi lại|tổng kết|chốt session|bàn giao).{0,64}\b(status|trạng thái|việc tiếp theo)\b/.test(raw);
350
+ }
351
+
352
+ function mentionsTargetDoc(lower, targetName) {
353
+ if (!targetName) {
354
+ return false;
355
+ }
356
+
357
+ const escaped = targetName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
358
+ const withoutExt = escaped.replace(/\\\.md$/i, '');
359
+ return new RegExp(`\\b(?:${escaped}|${withoutExt})\\b`, 'i').test(lower);
360
+ }
361
+
362
+ function isStatusFileTarget(targetFile) {
363
+ return /(?:^|\/)docs\/STATUS\.md$|(?:^|\/)STATUS\.md$/i.test(String(targetFile || ''));
364
+ }
365
+
366
+ function isTasksFileTarget(targetFile) {
367
+ return /(?:^|\/)docs\/TASKS\.md$|(?:^|\/)TASKS\.md$/i.test(String(targetFile || ''));
368
+ }
369
+
370
+ function isDocsTarget(targetFile) {
371
+ const normalized = String(targetFile || '').replaceAll('\\', '/');
372
+ return /(?:^|\/)docs\/.+\.md$|(?:^|\/)(?:README|CHANGELOG|AGENTS|CLAUDE|STATUS)\.md$/i.test(normalized);
373
+ }
374
+
218
375
  function shouldUseIndexedContext({ activeSkills = [], targetFile = null } = {}) {
219
376
  if (activeSkills.length === 0) {
220
377
  return Boolean(targetFile);
@@ -312,6 +469,7 @@ const { pathToFileURL } = require('url');
312
469
  targetFile: routingContext.targetFile || '',
313
470
  contextIntent: routingContext.contextIntent || '',
314
471
  taskType: routingContext.taskType || '',
472
+ executionMode: routingContext.executionMode || '',
315
473
  previousContextFingerprint: previousContextFingerprint || null,
316
474
  });
317
475
  }
@@ -325,6 +483,17 @@ const { pathToFileURL } = require('url');
325
483
  return buildCompactMachineKey('routefp-v3', {
326
484
  skills: activeSkills.map((item) => item.id),
327
485
  taskType: routingContext?.taskType || null,
486
+ executionMode: routeSummary?.executionMode || null,
487
+ approachRiskLevel: routeSummary?.approachSelector?.riskLevel || null,
488
+ approachContextPolicy: routeSummary?.approachSelector?.contextPolicy || null,
489
+ approachVerificationPolicy: routeSummary?.approachSelector?.verificationPolicy || null,
490
+ approachCompletionRule: routeSummary?.approachSelector?.completionRule || null,
491
+ continuationRequired: routeSummary?.continuationState?.required || null,
492
+ continuationReasons: unique(routeSummary?.continuationState?.reasons ?? []),
493
+ continuationMilestone: routeSummary?.continuationState?.nextMilestone || null,
494
+ continuationRepeatCount: routeSummary?.continuationState?.repeatCount || null,
495
+ continuationStuckRisk: routeSummary?.continuationState?.stuckRisk || null,
496
+ continuationRescueMode: routeSummary?.continuationState?.rescueMode || null,
328
497
  previousContextLine: previousContext?.line || null,
329
498
  previousContextIds: unique(previousContext?.selectedIds ?? []),
330
499
  policyMode: routeSummary?.policyMode || null,
@@ -332,6 +501,8 @@ const { pathToFileURL } = require('url');
332
501
  nextActionType: routeSummary?.nextActionType || null,
333
502
  nextActionCommand: routeSummary?.nextActionCommand || null,
334
503
  helperHint: routeSummary?.helperHint || null,
504
+ completionRule: routeSummary?.executionContract?.completionRule || null,
505
+ completionMissingEvidence: unique(routeSummary?.completionState?.missingEvidence ?? []),
335
506
  primaryCommands: [...new Set((routeSummary?.primaryCommands || []).filter(Boolean))],
336
507
  fallbackCommands: [...new Set((routeSummary?.fallbackCommands || []).filter(Boolean))],
337
508
  preferredOrder: [...new Set((routeSummary?.preferredOrder || []).filter(Boolean))],
@@ -397,11 +568,16 @@ const { pathToFileURL } = require('url');
397
568
  return 'non-trivial';
398
569
  }
399
570
 
571
+ let inferred = 'simple';
400
572
  if (/\b(typo|label|text|rename|color|spacing|toggle|comment)\b/.test(lower)) {
401
- return 'trivial';
573
+ inferred = 'trivial';
574
+ }
575
+
576
+ if ((inferred === 'simple' || inferred === 'trivial') && isSharedImpactFile(targetFile)) {
577
+ return 'shared-simple';
402
578
  }
403
579
 
404
- return 'simple';
580
+ return inferred;
405
581
  }
406
582
 
407
583
  function shellEscape(value) {
@@ -1129,6 +1305,21 @@ const { pathToFileURL } = require('url');
1129
1305
  const compactHelperLane = nextAction?.type === 'pull-indexed-context'
1130
1306
  && typeof contextRecommendation?.command === 'string'
1131
1307
  && contextRecommendation.command.trim();
1308
+ const executionMode = routingContext.executionMode || null;
1309
+ const executionScores = routingContext.executionScores || null;
1310
+ const approachSelector = buildApproachSelectorResult({
1311
+ executionMode,
1312
+ executionScores,
1313
+ });
1314
+ const executionContract = buildExecutionContract(executionMode);
1315
+ const completionState = buildCompletionState({
1316
+ executionMode,
1317
+ verificationRecommendation,
1318
+ });
1319
+ const continuationState = buildContinuationState({
1320
+ nextActionType: nextAction?.type || null,
1321
+ completionState,
1322
+ });
1132
1323
  const helperHint = compactHelperHint(
1133
1324
  compactHelperLane
1134
1325
  ? contextRecommendation?.command
@@ -1152,6 +1343,12 @@ const { pathToFileURL } = require('url');
1152
1343
  fallbackCommands,
1153
1344
  preferredOrder,
1154
1345
  policyMode,
1346
+ executionMode,
1347
+ executionScores,
1348
+ approachSelector,
1349
+ executionContract,
1350
+ completionState,
1351
+ continuationState,
1155
1352
  delegateHint: delegationRecommendation?.hint || null,
1156
1353
  nextActionType: nextAction?.type || null,
1157
1354
  nextActionCommand,
@@ -1160,6 +1357,396 @@ const { pathToFileURL } = require('url');
1160
1357
  };
1161
1358
  }
1162
1359
 
1360
+ function deriveExecutionScores({
1361
+ promptText = '',
1362
+ commandText = '',
1363
+ targetFile = null,
1364
+ intentMode = null,
1365
+ taskType = null,
1366
+ } = {}) {
1367
+ const raw = `${promptText || ''}\n${commandText || ''}`.toLowerCase();
1368
+ const sharedRisk = isSharedImpactFile(targetFile);
1369
+ const directTransformSignal = /\b(change|replace|set|rename|convert|swap)\b/.test(raw) || /\bchange\s+.+\s+to\s+.+\b/.test(raw);
1370
+ const smallFixSignal = /\b(adjust|tweak|patch|fix|modify|update|apply)\b/.test(raw);
1371
+ const buildSignal = /\b(build|create|add|implement|feature)\b/.test(raw);
1372
+ const debugSignal = intentMode === 'debug-specific' || /\b(root cause|debug|triage|flaky|investigate|why)\b/.test(raw);
1373
+ const reviewSignal = intentMode === 'review-specific' || /\b(review|audit|verify|release readiness)\b/.test(raw);
1374
+
1375
+ let editCertainty = 0;
1376
+ if (directTransformSignal) {
1377
+ editCertainty = 3;
1378
+ } else if (smallFixSignal) {
1379
+ editCertainty = 2;
1380
+ } else if (buildSignal) {
1381
+ editCertainty = 1;
1382
+ }
1383
+
1384
+ let investigationNeed = 0;
1385
+ if (debugSignal) {
1386
+ investigationNeed = 3;
1387
+ } else if (/\b(failing|broken|error|crash|timeout)\b/.test(raw)) {
1388
+ investigationNeed = 2;
1389
+ }
1390
+
1391
+ let blastRadius = 0;
1392
+ if (sharedRisk) {
1393
+ blastRadius = 3;
1394
+ } else if (taskType === 'non-trivial' || taskType === 'shared-simple') {
1395
+ blastRadius = 2;
1396
+ } else if (!targetFile) {
1397
+ blastRadius = 1;
1398
+ }
1399
+
1400
+ let verificationBurden = 1;
1401
+ if (reviewSignal || sharedRisk) {
1402
+ verificationBurden = 3;
1403
+ } else if (debugSignal || taskType === 'non-trivial') {
1404
+ verificationBurden = 2;
1405
+ }
1406
+
1407
+ let ambiguity = 0;
1408
+ if (!targetFile) {
1409
+ ambiguity = 2;
1410
+ } else if (buildSignal && !directTransformSignal && !smallFixSignal) {
1411
+ ambiguity = 1;
1412
+ }
1413
+
1414
+ return {
1415
+ editCertainty,
1416
+ investigationNeed,
1417
+ blastRadius,
1418
+ verificationBurden,
1419
+ ambiguity,
1420
+ };
1421
+ }
1422
+
1423
+ function deriveExecutionMode({
1424
+ promptText = '',
1425
+ commandText = '',
1426
+ targetFile = null,
1427
+ intentMode = null,
1428
+ executionScores = {},
1429
+ } = {}) {
1430
+ const raw = `${promptText || ''}\n${commandText || ''}`.toLowerCase();
1431
+ const scores = executionScores;
1432
+ const explicitReviewLead = /\b(review this|audit this|review\b.*\brelease readiness|verify\b.*\brelease readiness)\b/.test(raw);
1433
+
1434
+ if (intentMode === 'review-specific' || explicitReviewLead) {
1435
+ return 'review-release';
1436
+ }
1437
+
1438
+ if (scores.blastRadius >= 3 && (scores.ambiguity >= 2 || scores.investigationNeed >= 2)) {
1439
+ return 'map-impact';
1440
+ }
1441
+
1442
+ if (scores.blastRadius >= 3 || isSharedImpactFile(targetFile)) {
1443
+ return 'shared-edit';
1444
+ }
1445
+
1446
+ if (scores.investigationNeed >= 3) {
1447
+ return 'find-cause';
1448
+ }
1449
+
1450
+ if (
1451
+ scores.editCertainty >= 3
1452
+ && scores.ambiguity === 0
1453
+ && scores.blastRadius === 0
1454
+ && scores.verificationBurden <= 1
1455
+ ) {
1456
+ return 'tiny-fix';
1457
+ }
1458
+
1459
+ if (
1460
+ /\b(build|create|add|implement|feature|summary card)\b/.test(raw)
1461
+ && scores.investigationNeed === 0
1462
+ && scores.blastRadius < 3
1463
+ ) {
1464
+ return 'local-build';
1465
+ }
1466
+
1467
+ if (scores.editCertainty >= 2 && scores.investigationNeed === 0 && scores.blastRadius === 0) {
1468
+ return 'local-fix';
1469
+ }
1470
+
1471
+ return 'local-build';
1472
+ }
1473
+
1474
+ function buildExecutionContract(executionMode = null) {
1475
+ if (!executionMode) {
1476
+ return null;
1477
+ }
1478
+
1479
+ const contracts = {
1480
+ 'tiny-fix': {
1481
+ maxReadPasses: 0,
1482
+ maxContextPulls: 0,
1483
+ verificationPolicy: 'minimal-or-targeted',
1484
+ completionRule: 'never-claim-done-without-write',
1485
+ delegationPolicy: 'disallow',
1486
+ completionEvidence: ['write-evidence'],
1487
+ },
1488
+ 'local-fix': {
1489
+ maxReadPasses: 1,
1490
+ maxContextPulls: 1,
1491
+ verificationPolicy: 'targeted-if-covered',
1492
+ completionRule: 'require-write',
1493
+ delegationPolicy: 'disallow',
1494
+ completionEvidence: ['write-evidence'],
1495
+ },
1496
+ 'local-build': {
1497
+ maxReadPasses: 2,
1498
+ maxContextPulls: 1,
1499
+ verificationPolicy: 'targeted-if-covered',
1500
+ completionRule: 'require-write',
1501
+ delegationPolicy: 'disallow-by-default',
1502
+ completionEvidence: ['write-evidence'],
1503
+ },
1504
+ 'find-cause': {
1505
+ maxReadPassesBeforeReassess: 3,
1506
+ verificationPolicy: 'root-cause-then-targeted',
1507
+ completionRule: 'never-claim-fixed-without-write-and-verification',
1508
+ delegationPolicy: 'allow-specialized-debug-lane',
1509
+ completionEvidence: ['write-evidence', 'verification-evidence'],
1510
+ },
1511
+ 'shared-edit': {
1512
+ maxReadPasses: 2,
1513
+ maxContextPulls: 2,
1514
+ verificationPolicy: 'targeted-then-widen-on-risk',
1515
+ completionRule: 'require-write-and-verification',
1516
+ delegationPolicy: 'allow-qualified-sidecar',
1517
+ completionEvidence: ['write-evidence', 'verification-evidence'],
1518
+ mirrorConsistencyRequired: true,
1519
+ },
1520
+ 'map-impact': {
1521
+ maxReadPasses: 3,
1522
+ maxContextPulls: 3,
1523
+ verificationPolicy: 'impact-first-then-targeted-then-widen-on-risk',
1524
+ completionRule: 'require-impact-evidence-before-edit-claim',
1525
+ delegationPolicy: 'allow-impact-sidecar',
1526
+ completionEvidence: ['impact-evidence', 'write-evidence', 'verification-evidence'],
1527
+ mirrorConsistencyRequired: true,
1528
+ },
1529
+ 'review-release': {
1530
+ verificationPolicy: 'evidence-first',
1531
+ completionRule: 'report-findings-not-implementation',
1532
+ delegationPolicy: 'allow-review-sidecar',
1533
+ completionEvidence: ['verification-evidence'],
1534
+ },
1535
+ };
1536
+
1537
+ return contracts[executionMode] ? { ...contracts[executionMode] } : null;
1538
+ }
1539
+
1540
+ function buildApproachSelectorResult({
1541
+ executionMode = null,
1542
+ executionScores = null,
1543
+ } = {}) {
1544
+ if (!executionMode) {
1545
+ return null;
1546
+ }
1547
+
1548
+ const executionContract = buildExecutionContract(executionMode);
1549
+ const policyByMode = {
1550
+ 'tiny-fix': {
1551
+ riskLevel: 'minimal',
1552
+ contextPolicy: 'confirm-target-only',
1553
+ },
1554
+ 'local-fix': {
1555
+ riskLevel: 'local',
1556
+ contextPolicy: 'bounded-local',
1557
+ },
1558
+ 'local-build': {
1559
+ riskLevel: 'local',
1560
+ contextPolicy: 'bounded-with-related-files',
1561
+ },
1562
+ 'find-cause': {
1563
+ riskLevel: 'investigation',
1564
+ contextPolicy: 'trace-then-bounded-context',
1565
+ },
1566
+ 'shared-edit': {
1567
+ riskLevel: 'shared',
1568
+ contextPolicy: 'bounded-with-related-tests',
1569
+ },
1570
+ 'map-impact': {
1571
+ riskLevel: 'wide',
1572
+ contextPolicy: 'impact-map-first',
1573
+ },
1574
+ 'review-release': {
1575
+ riskLevel: 'release',
1576
+ contextPolicy: 'evidence-review-only',
1577
+ },
1578
+ };
1579
+ const selectedPolicy = policyByMode[executionMode] ?? {
1580
+ riskLevel: 'local',
1581
+ contextPolicy: 'bounded-local',
1582
+ };
1583
+
1584
+ return {
1585
+ executionMode,
1586
+ executionScores: executionScores ? { ...executionScores } : null,
1587
+ riskLevel: selectedPolicy.riskLevel,
1588
+ contextPolicy: selectedPolicy.contextPolicy,
1589
+ verificationPolicy: executionContract?.verificationPolicy ?? null,
1590
+ completionRule: executionContract?.completionRule ?? null,
1591
+ };
1592
+ }
1593
+
1594
+ function buildCompletionState({ executionMode = null, verificationRecommendation = null } = {}) {
1595
+ if (!executionMode) {
1596
+ return null;
1597
+ }
1598
+
1599
+ const contract = buildExecutionContract(executionMode);
1600
+ const missingEvidence = [...(contract?.completionEvidence ?? [])];
1601
+ const requiresVerification = missingEvidence.includes('verification-evidence');
1602
+ if (
1603
+ requiresVerification
1604
+ && verificationRecommendation
1605
+ && !(verificationRecommendation.commands?.length || verificationRecommendation.fallbackCommands?.length)
1606
+ ) {
1607
+ missingEvidence.push('verification-plan');
1608
+ }
1609
+
1610
+ let reason = 'completion evidence is still required';
1611
+ if (['tiny-fix', 'local-fix', 'local-build', 'shared-edit'].includes(executionMode)) {
1612
+ reason = 'implement request has not produced an edit yet';
1613
+ } else if (executionMode === 'review-release') {
1614
+ reason = 'review/release evidence is still required before final claim';
1615
+ } else if (executionMode === 'map-impact') {
1616
+ reason = 'impact evidence is still required before safe completion claim';
1617
+ }
1618
+
1619
+ return {
1620
+ claimAllowed: false,
1621
+ missingEvidence,
1622
+ reason,
1623
+ };
1624
+ }
1625
+
1626
+ function buildContinuationState({
1627
+ nextActionType = null,
1628
+ completionState = null,
1629
+ } = {}) {
1630
+ const reasons = [];
1631
+ const missingEvidence = unique(completionState?.missingEvidence ?? []);
1632
+
1633
+ if (nextActionType === 'pull-indexed-context') {
1634
+ reasons.push('pending-bounded-context');
1635
+ }
1636
+
1637
+ if (nextActionType === 'read-skill-instructions') {
1638
+ reasons.push('pending-skill-read');
1639
+ }
1640
+
1641
+ if (missingEvidence.includes('write-evidence')) {
1642
+ reasons.push('missing-write-evidence');
1643
+ }
1644
+
1645
+ if (missingEvidence.includes('verification-evidence') || missingEvidence.includes('verification-plan')) {
1646
+ reasons.push('missing-verification-evidence');
1647
+ }
1648
+
1649
+ if (missingEvidence.includes('impact-evidence')) {
1650
+ reasons.push('missing-impact-evidence');
1651
+ }
1652
+
1653
+ const nextMilestone = reasons.includes('pending-bounded-context')
1654
+ ? 'complete-bounded-context-then-continue'
1655
+ : (
1656
+ reasons.includes('missing-write-evidence')
1657
+ ? 'produce-write-evidence'
1658
+ : (
1659
+ reasons.includes('missing-verification-evidence')
1660
+ ? 'produce-verification-evidence'
1661
+ : (
1662
+ reasons.includes('missing-impact-evidence')
1663
+ ? 'produce-impact-evidence'
1664
+ : null
1665
+ )
1666
+ )
1667
+ );
1668
+
1669
+ return {
1670
+ required: reasons.length > 0,
1671
+ reasons,
1672
+ doneLanguageBlocked: reasons.length > 0,
1673
+ stopAllowedOnlyFor: reasons.length > 0 ? ['real-blocker'] : [],
1674
+ nextMilestone,
1675
+ repeatCount: reasons.length > 0 ? 1 : 0,
1676
+ stuckRisk: null,
1677
+ rescueMode: null,
1678
+ };
1679
+ }
1680
+
1681
+ function advanceContinuationState(continuationState = null, previousContinuationState = null) {
1682
+ if (!continuationState || typeof continuationState !== 'object') {
1683
+ return continuationState;
1684
+ }
1685
+
1686
+ if (!continuationState.required) {
1687
+ return {
1688
+ ...continuationState,
1689
+ repeatCount: 0,
1690
+ stuckRisk: null,
1691
+ rescueMode: null,
1692
+ };
1693
+ }
1694
+
1695
+ const currentReasons = unique(continuationState.reasons ?? []);
1696
+ const previousReasons = unique(previousContinuationState?.reasons ?? []);
1697
+ const sameMilestone = Boolean(
1698
+ previousContinuationState?.required
1699
+ && continuationState.nextMilestone
1700
+ && previousContinuationState.nextMilestone === continuationState.nextMilestone
1701
+ && JSON.stringify(previousReasons) === JSON.stringify(currentReasons)
1702
+ );
1703
+ const repeatCount = sameMilestone
1704
+ ? Math.max(1, Number(previousContinuationState?.repeatCount ?? 1) + 1)
1705
+ : 1;
1706
+ const rescueMode = repeatCount >= 2
1707
+ ? (
1708
+ currentReasons.includes('pending-bounded-context')
1709
+ ? 'finish-current-milestone-before-more-reading'
1710
+ : (
1711
+ currentReasons.includes('missing-write-evidence')
1712
+ ? 'finish-write-before-more-analysis'
1713
+ : 'finish-required-milestone-before-widening'
1714
+ )
1715
+ )
1716
+ : null;
1717
+ const stuckRisk = repeatCount >= 3
1718
+ ? 'high'
1719
+ : (repeatCount === 2 ? 'elevated' : null);
1720
+
1721
+ return {
1722
+ ...continuationState,
1723
+ repeatCount,
1724
+ stuckRisk,
1725
+ rescueMode,
1726
+ };
1727
+ }
1728
+
1729
+ const SHARED_IMPACT_PATTERNS = [
1730
+ /^\.claude\/hooks\//,
1731
+ /^\.claude\/ukit\//,
1732
+ /^\.codex\//,
1733
+ /^src\/index\//,
1734
+ /^src\/core\/(runInstallPipeline|applyPlan|buildPlan|diffPlan|metadata|migrateLegacy|uninstall|runtimeConfig|runtimePaths)\.js$/,
1735
+ /^src\/core\/(output|token|compact)\//,
1736
+ /^templates\/\.claude\/hooks\//,
1737
+ /^templates\/\.claude\/ukit\//,
1738
+ /^manifests\/platform\.full\.yaml$/,
1739
+ /^templates\//,
1740
+ ];
1741
+
1742
+ function isSharedImpactFile(filePath) {
1743
+ const normalized = String(filePath ?? '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
1744
+ if (!normalized) {
1745
+ return false;
1746
+ }
1747
+ return SHARED_IMPACT_PATTERNS.some((pattern) => pattern.test(normalized));
1748
+ }
1749
+
1163
1750
  function summarizeCompactList(values, limit = 2) {
1164
1751
  const normalized = [...new Set((values || []).filter(Boolean))];
1165
1752
  return {
@@ -1268,6 +1855,8 @@ const { pathToFileURL } = require('url');
1268
1855
  return {
1269
1856
  lastExplicitUserPromptText: routingContext.lastExplicitUserPromptText || '',
1270
1857
  taskType: routingContext.taskType || null,
1858
+ intentMode: routingContext.intentMode || null,
1859
+ executionMode: routingContext.executionMode || null,
1271
1860
  };
1272
1861
  }
1273
1862
 
@@ -1299,6 +1888,12 @@ const { pathToFileURL } = require('url');
1299
1888
  fallbackCommands: [...new Set((routeSummary.fallbackCommands || []).filter(Boolean))].slice(0, 3),
1300
1889
  preferredOrder: [...new Set((routeSummary.preferredOrder || []).filter(Boolean))].slice(0, 5),
1301
1890
  policyMode: routeSummary.policyMode || null,
1891
+ executionMode: routeSummary.executionMode || null,
1892
+ executionScores: routeSummary.executionScores || null,
1893
+ approachSelector: routeSummary.approachSelector || null,
1894
+ executionContract: routeSummary.executionContract || null,
1895
+ completionState: routeSummary.completionState || null,
1896
+ continuationState: routeSummary.continuationState || null,
1302
1897
  delegateHint: routeSummary.delegateHint || null,
1303
1898
  nextActionType: routeSummary.nextActionType || null,
1304
1899
  nextActionCommand: routeSummary.nextActionCommand || null,
@@ -1373,7 +1968,7 @@ const { pathToFileURL } = require('url');
1373
1968
  }
1374
1969
 
1375
1970
  const nextActionType = String(routeSummary.nextActionType ?? '').trim();
1376
- if (!nextActionType || nextActionType === 'read-skill-instructions') {
1971
+ if (!nextActionType || nextActionType === 'read-skill-instructions' || nextActionType === 'pull-indexed-context') {
1377
1972
  return '';
1378
1973
  }
1379
1974
 
@@ -1426,12 +2021,18 @@ const { pathToFileURL } = require('url');
1426
2021
  commandNormalizedText: buildRouteSignalText(commandText),
1427
2022
  fileText: String(filePath || '').toLowerCase(),
1428
2023
  };
2024
+ const intentMode = deriveIntentMode({
2025
+ promptText,
2026
+ commandText,
2027
+ targetFile: filePath,
2028
+ });
1429
2029
 
1430
2030
  const catalog = await loadRouteCatalog(projectRoot);
1431
2031
 
1432
2032
  const active = catalog
1433
2033
  .map((entry) => scoreSkillRouteEntry(entry, routeSignals))
1434
- .filter((entry) => entry.score > 0 && existsSkill(projectRoot, entry.path));
2034
+ .filter((entry) => entry.score > 0 && existsSkill(projectRoot, entry.path))
2035
+ .filter((entry) => shouldKeepRouteEntryForIntent(entry, intentMode));
1435
2036
 
1436
2037
  const now = Date.now();
1437
2038
  const debounceMs = 10 * 60 * 1000;
@@ -1498,6 +2099,21 @@ const { pathToFileURL } = require('url');
1498
2099
  selectedIds,
1499
2100
  buildRouteSignalText,
1500
2101
  });
2102
+ const executionScores = deriveExecutionScores({
2103
+ promptText,
2104
+ commandText,
2105
+ targetFile: filePath,
2106
+ intentMode,
2107
+ taskType,
2108
+ });
2109
+ const executionMode = deriveExecutionMode({
2110
+ promptText,
2111
+ commandText,
2112
+ targetFile: filePath,
2113
+ intentMode,
2114
+ taskType,
2115
+ executionScores,
2116
+ });
1501
2117
  const lastExplicitUserPromptText = promptText.trim()
1502
2118
  || previous?.routingContext?.lastExplicitUserPromptText
1503
2119
  || '';
@@ -1508,6 +2124,9 @@ const { pathToFileURL } = require('url');
1508
2124
  targetFile: filePath,
1509
2125
  contextIntent,
1510
2126
  taskType,
2127
+ intentMode,
2128
+ executionScores,
2129
+ executionMode,
1511
2130
  };
1512
2131
  const useIndexedContext = shouldUseIndexedContext({
1513
2132
  activeSkills: selected,
@@ -1565,12 +2184,23 @@ const { pathToFileURL } = require('url');
1565
2184
  && cachedRouteState?.routeSummary
1566
2185
  && Array.isArray(cachedRouteState?.activeSkills)
1567
2186
  ) {
2187
+ const rescuedRouteSummary = cachedRouteState.routeSummary
2188
+ ? {
2189
+ ...cachedRouteState.routeSummary,
2190
+ continuationState: advanceContinuationState(
2191
+ cachedRouteState.routeSummary.continuationState || null,
2192
+ cachedRouteState.routeSummary.continuationState || null,
2193
+ ),
2194
+ }
2195
+ : null;
1568
2196
  const reusedState = {
1569
2197
  ...cachedRouteState,
1570
2198
  source: 'skill-router',
1571
2199
  ts: now,
1572
2200
  requestKey,
2201
+ routeSummary: rescuedRouteSummary,
1573
2202
  };
2203
+ reusedState.fingerprint = buildRouteStateFingerprint(reusedState);
1574
2204
 
1575
2205
  if (previous?.fingerprint === reusedState.fingerprint && now - previousTs < debounceMs) {
1576
2206
  process.exit(0);
@@ -1588,7 +2218,11 @@ const { pathToFileURL } = require('url');
1588
2218
  if (reusedState.routeSummary?.delegateHint) {
1589
2219
  process.stdout.write(`[ukit-skill-router] Delegate: ${reusedState.routeSummary.delegateHint}\n`);
1590
2220
  }
1591
- if (reusedState.routeSummary?.helperHint && !reusedState.routeSummary?.nextActionCommand) {
2221
+ if (
2222
+ reusedState.routeSummary?.helperHint
2223
+ && !reusedState.routeSummary?.nextActionCommand
2224
+ && reusedState.routeSummary?.nextActionType !== 'pull-indexed-context'
2225
+ ) {
1592
2226
  process.stdout.write(`[ukit-skill-router] Helper: ${reusedState.routeSummary.helperHint}\n`);
1593
2227
  }
1594
2228
  process.exit(0);
@@ -1618,13 +2252,17 @@ const { pathToFileURL } = require('url');
1618
2252
  contextRecommendation,
1619
2253
  verificationRecommendation,
1620
2254
  });
1621
- const routeSummary = buildRouteSummary({
1622
- activeSkills: selected,
1623
- routingContext,
1624
- contextRecommendation,
1625
- verificationRecommendation,
1626
- nextAction,
1627
- });
2255
+ const routeSummary = buildRouteSummary({
2256
+ activeSkills: selected,
2257
+ routingContext,
2258
+ contextRecommendation,
2259
+ verificationRecommendation,
2260
+ nextAction,
2261
+ });
2262
+ routeSummary.continuationState = advanceContinuationState(
2263
+ routeSummary.continuationState,
2264
+ previous?.routeSummary?.continuationState || null,
2265
+ );
1628
2266
 
1629
2267
  const fingerprint = buildRouteStateFingerprint({
1630
2268
  activeSkills: selected,
@@ -1669,9 +2307,13 @@ const { pathToFileURL } = require('url');
1669
2307
  if (routeSummary.delegateHint) {
1670
2308
  process.stdout.write(`[ukit-skill-router] Delegate: ${routeSummary.delegateHint}\n`);
1671
2309
  }
1672
- if (routeSummary.helperHint && !routeSummary.nextActionCommand) {
1673
- process.stdout.write(`[ukit-skill-router] Helper: ${routeSummary.helperHint}\n`);
1674
- }
2310
+ if (
2311
+ routeSummary.helperHint
2312
+ && !routeSummary.nextActionCommand
2313
+ && routeSummary.nextActionType !== 'pull-indexed-context'
2314
+ ) {
2315
+ process.stdout.write(`[ukit-skill-router] Helper: ${routeSummary.helperHint}\n`);
2316
+ }
1675
2317
  })().catch(() => {
1676
2318
  process.exit(0);
1677
2319
  });