@ngockhoale/ukit 2.3.23 → 2.3.24
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 +34 -0
- package/package.json +1 -1
- package/src/core/buildPlan.js +12 -9
- package/src/index/taskRouting.js +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 2.3.24 - 2026-09-13
|
|
6
|
+
|
|
7
|
+
Post-2.3.23 routing and self-install audit. Two real defects fixed, plus the repo-local
|
|
8
|
+
Release Policy mirror restored and guarded.
|
|
9
|
+
|
|
10
|
+
- **`isInformationalPrompt` ported into the src canonical copy.** `src/index/taskRouting.js`
|
|
11
|
+
was missing the informational-prompt guard that the shipped copies in
|
|
12
|
+
`templates/.claude/ukit/index/route-task.mjs` and `templates/.claude/hooks/skill-router.sh`
|
|
13
|
+
received in 2.2.14. The 2.3.22 `consultationOnlySignal` only catches question-shaped
|
|
14
|
+
prompts (trailing `?`, a leading interrogative, or Vietnamese `…là gì / được không`), so a
|
|
15
|
+
prompt with an **embedded** interrogative and no leading marker — `"please tell me what the
|
|
16
|
+
auth module does"`, `"explain what files the installer writes"` — routed to `shared-edit`
|
|
17
|
+
with a write+verification contract in src while the shipped templates routed it
|
|
18
|
+
`informational`. That is exactly the fabricated-mutation-debt class 2.3.22 set out to fix,
|
|
19
|
+
still live in the copy that drives `ukit index route`. The helper is now byte-equivalent
|
|
20
|
+
across all three copies; implement/debug-worded questions still keep their lanes. Locked by
|
|
21
|
+
a new `tests/index/taskRouting.test.js` regression (verified RED without the call site).
|
|
22
|
+
- **Self-install detection no longer misfires on a local install.** The 2.3.23 guard treated
|
|
23
|
+
any `templates/` directory inside the install root as a self-install. A downstream project
|
|
24
|
+
that installs UKit locally (`node_modules/@ngockhoale/ukit/templates`) therefore matched,
|
|
25
|
+
and every `docs/` entry was downgraded to `skip` — so `ukit update` silently stopped
|
|
26
|
+
refreshing that project's docs. `isSelfInstall` now requires the package root itself to be
|
|
27
|
+
the project root (`<projectRoot>/templates` exactly) or the target `package.json` to name
|
|
28
|
+
`@ngockhoale/ukit`. Locked by a new `buildPlan` regression for the `node_modules` shape
|
|
29
|
+
(verified RED under the old containment test).
|
|
30
|
+
- **Release Policy mirror restored.** A `ukit install` run inside this repo (self-install)
|
|
31
|
+
had overwritten root `CLAUDE.md`/`AGENTS.md` from `templates/`, wiping the repo-local
|
|
32
|
+
Release Policy section that `docs/PROJECT.md` mandates at the top of both files. Restored
|
|
33
|
+
from the byte-identical pre-wipe copy; a new `tests/consistency/configDocsSync.test.js`
|
|
34
|
+
guard fails loudly if a future self-install wipes either copy again.
|
|
35
|
+
- **Verification.** `yarn test` 87 files / 1,553 tests green (also under `--sequence.shuffle`);
|
|
36
|
+
`yarn release:verify` exit 0; artifact unpacked ceiling raised once to 5,960,000 with
|
|
37
|
+
rationale in `tests/integration/packageArtifact.test.js`.
|
|
38
|
+
|
|
5
39
|
## 2.3.23 - 2026-09-13
|
|
6
40
|
|
|
7
41
|
Self-install no longer clobbers UKit's own canonical docs. Running `ukit install` inside the UKit
|
package/package.json
CHANGED
package/src/core/buildPlan.js
CHANGED
|
@@ -45,22 +45,25 @@ function safeResolve(basePath, relativePath) {
|
|
|
45
45
|
return resolvedPath;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function isPathInside(parentPath, childPath) {
|
|
49
|
-
const relative = path.relative(parentPath, childPath);
|
|
50
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
48
|
function isDocsTarget(targetPath, projectRoot) {
|
|
54
49
|
const relative = path.relative(projectRoot, targetPath);
|
|
55
50
|
return relative === 'docs' || relative.startsWith(`docs${path.sep}`);
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
// Detect `ukit install` running against the UKit source repository itself. Two independent
|
|
59
|
-
// signals: the templates resolve
|
|
60
|
-
// global CLI does not), or the target's package.json
|
|
61
|
-
// misses the common case where the global CLI runs
|
|
54
|
+
// signals: the templates resolve directly under the install target (`<projectRoot>/templates`
|
|
55
|
+
// — the repo layout, which the global CLI does not have), or the target's package.json
|
|
56
|
+
// declares UKIT_PACKAGE_NAME (the path check misses the common case where the global CLI runs
|
|
57
|
+
// inside the repo).
|
|
58
|
+
//
|
|
59
|
+
// The path signal deliberately requires `<projectRoot>/templates` exactly, not merely
|
|
60
|
+
// "templates somewhere inside projectRoot". A downstream project that installs UKit locally
|
|
61
|
+
// (`node_modules/@ngockhoale/ukit/templates`) also has templates inside its root; treating
|
|
62
|
+
// that as a self-install downgraded every docs entry to `skip`, so `ukit update` never
|
|
63
|
+
// refreshed that project's docs. Only the package root itself being the project root is a
|
|
64
|
+
// self-install.
|
|
62
65
|
async function isSelfInstall({ templatesRoot, projectRoot }) {
|
|
63
|
-
if (
|
|
66
|
+
if (path.dirname(path.resolve(templatesRoot)) === path.resolve(projectRoot)) {
|
|
64
67
|
return true;
|
|
65
68
|
}
|
|
66
69
|
try {
|
package/src/index/taskRouting.js
CHANGED
|
@@ -406,6 +406,56 @@ function deriveExecutionScores({
|
|
|
406
406
|
};
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
// A prompt that only asks a question (no command, no action verb, no error report) is
|
|
410
|
+
// asking for an explanation, not a repository change. Routing it to an investigation or
|
|
411
|
+
// edit lane fabricates write/verification debt the completion gate then enforces against an
|
|
412
|
+
// answer turn. Kept byte-equivalent to the shipped copies in
|
|
413
|
+
// templates/.claude/ukit/index/route-task.mjs and templates/.claude/hooks/skill-router.sh;
|
|
414
|
+
// this is the canonical copy those are mirror-tested against. It deliberately ignores
|
|
415
|
+
// targetFile: an edit order phrased as a question is caught by implementWords below, while a
|
|
416
|
+
// pure targeted question ("how does X work?") is still just a question.
|
|
417
|
+
function isInformationalPrompt({
|
|
418
|
+
promptText = '',
|
|
419
|
+
commandText = '',
|
|
420
|
+
scores = null,
|
|
421
|
+
} = {}) {
|
|
422
|
+
if (String(commandText || '').trim()) return false;
|
|
423
|
+
const raw = String(promptText || '').toLowerCase().trim();
|
|
424
|
+
if (!raw) return false;
|
|
425
|
+
const questionSignal = raw.includes('?')
|
|
426
|
+
|| /\b(what|how|when|which|where|who|is|are|does|do|did|can|could|would|should|will)\b/.test(raw)
|
|
427
|
+
|| /(là\s+(?:[^\s]+\s+){0,2}gì|thế nào|như thế nào|bao nhiêu|khi nào|bao giờ|ở đâu|nghĩa là|được không|không\b)/.test(raw);
|
|
428
|
+
// '?' or a leading/Vietnamese interrogative — required to overrule error wording.
|
|
429
|
+
const strongQuestion = raw.includes('?')
|
|
430
|
+
|| /^(what|how|when|which|where|who|is|are|does|do|did|can|could|would|should|will)\b/.test(raw)
|
|
431
|
+
|| /(là\s+(?:[^\s]+\s+){0,2}gì|thế nào|như thế nào|bao nhiêu|khi nào|bao giờ|ở đâu)/.test(raw);
|
|
432
|
+
if (scores) {
|
|
433
|
+
if (
|
|
434
|
+
scores.implementSignal
|
|
435
|
+
|| scores.reviewSignal
|
|
436
|
+
|| scores.debugSignal
|
|
437
|
+
|| scores.impactSignal
|
|
438
|
+
|| scores.smallFixSignal
|
|
439
|
+
|| scores.directTransformSignal
|
|
440
|
+
) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
if (scores.failureSignal && !strongQuestion) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const implementWords = /(?<![A-Za-z0-9_])(implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|sửa|thêm|tạo|xóa|đổi|thay thế|cập nhật|viết|build|chạy|cài)(?![A-Za-z0-9_])/.test(raw);
|
|
448
|
+
if (implementWords) return false;
|
|
449
|
+
const investigationWords = /\b(why|debug|triage|root cause|investigate|tại sao)\b/.test(raw);
|
|
450
|
+
if (investigationWords) return false;
|
|
451
|
+
// \b never matches around 'lỗi' (diacritics are not \w), so test it as a plain
|
|
452
|
+
// substring: an error report is informational only when phrased as a question.
|
|
453
|
+
if (raw.includes('lỗi') && !strongQuestion) return false;
|
|
454
|
+
const reviewWords = /\b(review|audit|verify)\b/.test(raw);
|
|
455
|
+
if (reviewWords) return false;
|
|
456
|
+
return questionSignal;
|
|
457
|
+
}
|
|
458
|
+
|
|
409
459
|
function deriveExecutionMode({
|
|
410
460
|
promptText = '',
|
|
411
461
|
commandText = '',
|
|
@@ -494,6 +544,10 @@ function deriveExecutionMode({
|
|
|
494
544
|
&& !scores.sharedRisk
|
|
495
545
|
&& !targetFile;
|
|
496
546
|
|
|
547
|
+
if (isInformationalPrompt({ promptText, commandText, scores })) {
|
|
548
|
+
return 'informational';
|
|
549
|
+
}
|
|
550
|
+
|
|
497
551
|
if (
|
|
498
552
|
releaseVerificationContinuation
|
|
499
553
|
|| ((intentMode === 'review-specific' || explicitReviewLead) && !scores.implementSignal)
|