@ngockhoale/ukit 3.4.53 → 3.4.55
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
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 3.4.55 - 2026-10-22
|
|
6
|
+
|
|
7
|
+
**Completion Gate: preference, conditional-future and prohibition-list text is not an order (field false-block).**
|
|
8
|
+
- Field case: an audit prompt saying "Ưu tiên configuration change" (preference) and "Nếu chỉ cần configuration change:" (future branch) was classified `CONFIGURATION`/external, so the Stop gate demanded `operation-verification` for a config edit nobody ordered (task `task-0c7425cb…`, `accepted-count=0`). A follow-up prompt listing what an authorization does NOT allow as bullets ("không cho phép: - restart …") plus "Negative controls: … chỉnh config/restart/deploy" was classified `MIXED`/service-runtime.
|
|
9
|
+
- Fix (`neutralizeNonOrders` in all three classifier copies: `src/core/executionContracts.js`, `route-resolver.mjs` mirror, `skill-router.sh` inline): a config preference, a prohibition-header bullet list (bounded to its own sentence and ended by a blank line) and "negative/positive controls" test-case bullets are neutralized; the audit/inspect/review lead filter now tolerates bullet prefixes. Gate, ledger and loop-breaker logic untouched; genuine config/restart/deploy orders and mixed implementation tasks keep their obligations (negative controls in tests).
|
|
10
|
+
- Regression: `tests/hooks/configPreferenceNotOrder.test.js` (src + installed + hook copy; real prompt fixtures `concurrency-audit-config-preference-prompt.txt`, `authorization-boundary-bullets-prompt.txt`). Independent review found one false negative (header regex swallowed the whole line before it); fixed and pinned.
|
|
11
|
+
- Housekeeping: `docs/STATUS.md` rotated under its 150-line budget (verbatim archive), concurrency audit report registered in `manifests/documentation.yaml`.
|
|
12
|
+
- Verified offline on macOS/Node 22; classifier replay, not a live OMP/Claude Code/Codex session; Windows/Linux not run.
|
|
13
|
+
|
|
14
|
+
## 3.4.54 - 2026-10-22
|
|
15
|
+
|
|
16
|
+
**Completion Gate: a stated prohibition is not an operation order (field false-block).**
|
|
17
|
+
- Fix: `classifyTaskSemantics` (src + shipped `route-resolver.mjs` mirror) treated "Production deploy/restart vẫn bị cấm", "production deploy vẫn bị chặn", "forbidden deploy/publish" and a bare list item "Production restart." as actionable runtime operations, so an audit/report prompt became MIXED + service-runtime and the Stop gate demanded `operation-verification` for a restart nobody ordered.
|
|
18
|
+
- New guard `operationNotOrdered` applies to the git/runtime operation checks only: a prohibition attached to the operation, or a bare production-operation noun item, orders nothing. Lift/past markers ("was prohibited, owner now allows", "even though it is not allowed", "khong bi chan") keep the clause an order. Real orders (restart + verify, deploy to production, fix + restart) are unchanged.
|
|
19
|
+
- Regression: `tests/hooks/prohibitionNotOrder.test.js` (78 cases across src + installed copies) with the real field prompt fixture. Ledger, gates and release logic untouched.
|
|
20
|
+
- Verified offline on macOS/Node 22; OMP/Claude Code coverage is classifier replay, not live host.
|
|
21
|
+
|
|
5
22
|
## 3.4.53 - 2026-10-22
|
|
6
23
|
|
|
7
24
|
**Stop-level closure of the UNPROVEN verdict (follow-up to 3.4.52).**
|
|
@@ -82,6 +82,17 @@ entries:
|
|
|
82
82
|
validation: [manual, test:tests/hooks/executionLedger.test.js, test:tests/hooks/verificationExitProof.test.js]
|
|
83
83
|
archive_policy: replace-with-snapshot
|
|
84
84
|
notes: evidence-based long-run field audit; current reproductions are distinct from incident attribution
|
|
85
|
+
- id: root-62h-field-regression-crosscheck
|
|
86
|
+
path: UKIT_62H_FIELD_REGRESSION_CROSSCHECK.md
|
|
87
|
+
class: canonical
|
|
88
|
+
audience: [owner, maintainer]
|
|
89
|
+
owner: runtime
|
|
90
|
+
source_of_truth: UKIT_62H_FIELD_REGRESSION_CROSSCHECK.md
|
|
91
|
+
merge_strategy: none
|
|
92
|
+
load_policy: on-demand
|
|
93
|
+
validation: [manual, test:tests/hooks/prohibitionNotOrder.test.js]
|
|
94
|
+
archive_policy: replace-with-snapshot
|
|
95
|
+
notes: BAGuide 62h field cross-check; observed/reproduced/hypothesis/unknown labelled, gate false-block root cause
|
|
85
96
|
- id: registry-self
|
|
86
97
|
path: manifests/documentation.yaml
|
|
87
98
|
class: canonical
|
|
@@ -429,6 +440,17 @@ entries:
|
|
|
429
440
|
validation: [manual]
|
|
430
441
|
archive_policy: owner-decides
|
|
431
442
|
notes: bounded investigation evidence; extension transition remains OPEN
|
|
443
|
+
- id: docs-analysis-agent-concurrency-optimization
|
|
444
|
+
path: docs/analysis/UKIT_AGENT_CONCURRENCY_OPTIMIZATION.md
|
|
445
|
+
class: canonical
|
|
446
|
+
audience: [owner, maintainer]
|
|
447
|
+
owner: runtime
|
|
448
|
+
source_of_truth: docs/analysis/UKIT_AGENT_CONCURRENCY_OPTIMIZATION.md
|
|
449
|
+
merge_strategy: none
|
|
450
|
+
load_policy: on-demand
|
|
451
|
+
validation: [manual]
|
|
452
|
+
archive_policy: replace-with-snapshot
|
|
453
|
+
notes: Phase A read-only concurrency audit; provisional metrics labelled, pilot not run
|
|
432
454
|
- id: docs-analysis-subagent-cost-audit
|
|
433
455
|
path: docs/analysis/SUBAGENT-COST-AUDIT.md
|
|
434
456
|
class: archive
|
package/package.json
CHANGED
|
@@ -211,10 +211,17 @@ function neutralizeNonOrders(text) {
|
|
|
211
211
|
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
212
212
|
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
213
213
|
return source
|
|
214
|
+
// A list item that DESCRIBES test cases ("Negative controls: yêu cầu thực sự chỉnh config/restart/deploy ...") is
|
|
215
|
+
// specification of the work, not an order; it runs to the next bullet or blank line.
|
|
216
|
+
.replace(/^[ \t]*(?:[-*+\u2022]|\d+[.)])[ \t]*(?:negative|positive)\s+controls?\b[^\n]*(?:\n(?![ \t]*(?:[-*+\u2022]|\d+[.)])|[ \t]*\n)[^\n]*)*/gm, ' ')
|
|
217
|
+
// A header that negates the list under it ("Authorization ... không cho phép:", "Prohibitions:", "not allowed:")
|
|
218
|
+
// makes every bullet an item of the prohibition, not an order.
|
|
219
|
+
.replace(/(?:^|(?<=[.;][ \t]))[^.;\n]*\b(?:khong\s+(?:cho\s+phep|duoc|lam)|(?:does|do|did|is|are)\s+not\s+(?:allow|permit|authori[sz]e)\w*|not\s+(?:allowed|permitted|authori[sz]ed)|prohibitions?|prohibited(?:\s+actions?)?|forbidden(?:\s+actions?)?|banned|must\s+not\s+do)\s*:[ \t]*(?:\n[ \t]*(?:[-*+\u2022]|\d+[.)])[^\n]*)+/gm, ' ')
|
|
214
220
|
.replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|shall\\s+not)\\s+[^.;\\n]*?(?=(?<!,)\\s+(?:and|va)\\s+(?:${NON_ORDER_VERBS})\\b|\\s+(?:but|nhung|then|however)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
215
221
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
216
222
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
217
|
-
.replace(/\b(?:
|
|
223
|
+
.replace(/\b(?:uu tien|prefer(?:red)?)\s+(?:a\s+|an\s+)?(?:configuration|config|cau hinh)\s+(?:change|changes|update|thay doi)\b/g, ' ')
|
|
224
|
+
.replace(/\b(?:neu\s+(?:chi\s+)?(?:phai|can|thieu|khong du|bang chung chua du)(?![^.;\n]*\b(?:restart|deploy|xoa|delete|remove|cancel)\b)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
|
|
218
225
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
219
226
|
.replace(/\bchinh\s+(?:xac|sach)\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b|\b(?:diem|cho|muc|phan)\s+(?:phai|can|nen)\s+(?:sua|fix)\b/g, ' ')
|
|
220
227
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -265,6 +272,22 @@ export function isVerificationOnlyRequest(promptText) {
|
|
|
265
272
|
&& !clauses.some((clause) => CHANGE_LEAD.test(clause));
|
|
266
273
|
}
|
|
267
274
|
|
|
275
|
+
// "Deploy and restart are banned", "Forbidden: deploy", "deploy: banned" name operations to prohibit them. The clause split on
|
|
276
|
+
// "and"/commas would separate the ban from the first operation, so a banned operation list is removed before splitting.
|
|
277
|
+
function neutralizeBannedLists(text) {
|
|
278
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
279
|
+
const LIST = `${OP}(?:\\s*(?:,|/|\\band\\b|\\bor\\b|\\bva\\b|\\bhoac\\b|\\balso\\b)\\s*${OP})*`;
|
|
280
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
281
|
+
const LIFT = /\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/;
|
|
282
|
+
const span = new RegExp(`\\b(?:(?:forbidden|prohibited|banned)\\s*:\\s*${LIST}|${LIST}\\s*:?\\s*(?:(?:is|are|remains?|van|con)\\s+)?(?:both\\s+)?${BAN})\\b`, 'g');
|
|
283
|
+
return text.replace(span, (m, offset, whole) => {
|
|
284
|
+
const start = Math.max(whole.lastIndexOf('.', offset), whole.lastIndexOf(';', offset), whole.lastIndexOf('\n', offset)) + 1;
|
|
285
|
+
const stops = [whole.indexOf('.', offset + m.length), whole.indexOf(';', offset + m.length), whole.indexOf('\n', offset + m.length)].filter((i) => i >= 0);
|
|
286
|
+
const end = stops.length ? Math.min(...stops) : whole.length;
|
|
287
|
+
return LIFT.test(whole.slice(start, end)) ? m : ' ';
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
268
291
|
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
269
292
|
// A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
|
|
270
293
|
const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
@@ -272,10 +295,10 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
272
295
|
const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
|
|
273
296
|
&& /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
|
|
274
297
|
? { kind: 'conditional-release', preflight } : null;
|
|
275
|
-
const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
|
|
298
|
+
const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
|
|
276
299
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
277
300
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
278
|
-
&&
|
|
301
|
+
&& !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
279
302
|
if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
|
|
280
303
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
281
304
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
@@ -293,11 +316,40 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
293
316
|
// actionable clause (negated/deferred/audit clauses are already filtered out). Git and
|
|
294
317
|
// runtime are separate domains with separate proof; a prompt naming both stays a coding
|
|
295
318
|
// task rather than being half-verified. A code-change verb anywhere keeps write evidence.
|
|
296
|
-
|
|
319
|
+
// A clause that STATES an operation is prohibited ("production deploy vẫn bị cấm", "forbidden deploy") or merely lists a
|
|
320
|
+
// production operation as a bare noun item ("* Production restart.") names it; it does not order it.
|
|
321
|
+
const operationNotOrdered = (clause) => {
|
|
322
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
323
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
324
|
+
// A lift or past marker ("was prohibited, owner now allows", "even though it is not allowed", "khong bi chan")
|
|
325
|
+
// means the prohibition is history or lifted: the clause still orders the operation.
|
|
326
|
+
if (/\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/.test(clause)) return false;
|
|
327
|
+
if (/^[\s*+•>\d.)-]*(?:production|prod|staging)\s+(?:restart|redeploy|deploy(?:ment)?)\s*$/.test(clause)) return true;
|
|
328
|
+
// Evaluated per operation occurrence: the clause orders nothing only when EVERY operation in it is prohibited.
|
|
329
|
+
// The ban must attach to its own operation: no parenthesis, dash, colon, conjunction, relative pronoun or second operation in between.
|
|
330
|
+
const gap = `(?:(?!\\b(?:which|that|who|where|when|if|and|but|then)\\b|${OP})[^.;,()\\-:]){0,40}?`;
|
|
331
|
+
const banAfter = new RegExp(`^${gap}\\b(?:(?:is|are|remains?|van|con)\\s+)?${BAN}\\b(?!\\s+(?:to|from)\\b)`);
|
|
332
|
+
// "deploy/restart is forbidden", "deploy and restart are banned": operations joined as a list share one ban.
|
|
333
|
+
const joined = new RegExp(`^\\s*(?:/|,|and|or|va|hoac)\\s*${OP}\\b`);
|
|
334
|
+
const bannedAfter = (rest) => {
|
|
335
|
+
if (banAfter.test(rest)) return true;
|
|
336
|
+
const j = joined.exec(rest);
|
|
337
|
+
return j ? bannedAfter(rest.slice(j[0].length)) : false;
|
|
338
|
+
};
|
|
339
|
+
const banBefore = /\b(?:forbidden|prohibited|banned)\s+(?:(?:production|prod)\s+)?$/;
|
|
340
|
+
let total = 0;
|
|
341
|
+
let banned = 0;
|
|
342
|
+
for (const match of clause.matchAll(new RegExp(`\\b${OP}\\b`, 'g'))) {
|
|
343
|
+
total += 1;
|
|
344
|
+
if (bannedAfter(clause.slice(match.index + match[0].length)) || banBefore.test(clause.slice(0, match.index))) banned += 1;
|
|
345
|
+
}
|
|
346
|
+
return total > 0 && banned === total;
|
|
347
|
+
};
|
|
348
|
+
const gitOperation = actionable.some((clause) => !operationNotOrdered(clause) && /\bgit\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b|\bpush\s+(?:to\s+)?origin\b/.test(clause));
|
|
297
349
|
// 'trien khai' is Vietnamese for both "deploy" and "implement": it orders a runtime operation only
|
|
298
350
|
// when it names a runtime target; bare it describes work ("Tier 3 truc tiep trien khai thay Tier 2").
|
|
299
351
|
// "restart"/"deploy" after a noun modifier ("process restart", "after redeploy") is a scenario, not an order.
|
|
300
|
-
const runtimeOperation = actionable.some((clause) => /(?<!\b(?:process|crash|hard|soft|graceful|after|upon|during|across|following)\s)\b(?:restart|redeploy|deploy|khoi dong lai|trien khai\s+(?:len\s+)?(?:service|server|production|prod|staging|dich vu|may chu|moi truong))\b/.test(clause)
|
|
352
|
+
const runtimeOperation = actionable.some((clause) => !operationNotOrdered(clause) && /(?<!\b(?:process|crash|hard|soft|graceful|after|upon|during|across|following)\s)\b(?:restart|redeploy|deploy|khoi dong lai|trien khai\s+(?:len\s+)?(?:service|server|production|prod|staging|dich vu|may chu|moi truong))\b/.test(clause)
|
|
301
353
|
&& !/\.[cm]?[jt]sx?\b/.test(clause) && !/\bgit\b/.test(clause));
|
|
302
354
|
const nativeDomain = cronOperation ? 'scheduled-job' : gitOperation !== runtimeOperation ? (gitOperation ? 'git-integrity' : 'service-runtime') : null;
|
|
303
355
|
const operation = Boolean(nativeDomain);
|
|
@@ -1893,10 +1893,17 @@ function neutralizeNonOrders(text) {
|
|
|
1893
1893
|
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
1894
1894
|
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
1895
1895
|
return source
|
|
1896
|
+
// A list item that DESCRIBES test cases ("Negative controls: yêu cầu thực sự chỉnh config/restart/deploy ...") is
|
|
1897
|
+
// specification of the work, not an order; it runs to the next bullet or blank line.
|
|
1898
|
+
.replace(/^[ \t]*(?:[-*+\u2022]|\d+[.)])[ \t]*(?:negative|positive)\s+controls?\b[^\n]*(?:\n(?![ \t]*(?:[-*+\u2022]|\d+[.)])|[ \t]*\n)[^\n]*)*/gm, ' ')
|
|
1899
|
+
// A header that negates the list under it ("Authorization ... không cho phép:", "Prohibitions:", "not allowed:")
|
|
1900
|
+
// makes every bullet an item of the prohibition, not an order.
|
|
1901
|
+
.replace(/(?:^|(?<=[.;][ \t]))[^.;\n]*\b(?:khong\s+(?:cho\s+phep|duoc|lam)|(?:does|do|did|is|are)\s+not\s+(?:allow|permit|authori[sz]e)\w*|not\s+(?:allowed|permitted|authori[sz]ed)|prohibitions?|prohibited(?:\s+actions?)?|forbidden(?:\s+actions?)?|banned|must\s+not\s+do)\s*:[ \t]*(?:\n[ \t]*(?:[-*+\u2022]|\d+[.)])[^\n]*)+/gm, ' ')
|
|
1896
1902
|
.replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|shall\\s+not)\\s+[^.;\\n]*?(?=(?<!,)\\s+(?:and|va)\\s+(?:${NON_ORDER_VERBS})\\b|\\s+(?:but|nhung|then|however)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1897
1903
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1898
1904
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
1899
|
-
.replace(/\b(?:
|
|
1905
|
+
.replace(/\b(?:uu tien|prefer(?:red)?)\s+(?:a\s+|an\s+)?(?:configuration|config|cau hinh)\s+(?:change|changes|update|thay doi)\b/g, ' ')
|
|
1906
|
+
.replace(/\b(?:neu\s+(?:chi\s+)?(?:phai|can|thieu|khong du|bang chung chua du)(?![^.;\n]*\b(?:restart|deploy|xoa|delete|remove|cancel)\b)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
|
|
1900
1907
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
1901
1908
|
.replace(/\bchinh\s+(?:xac|sach)\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b|\b(?:diem|cho|muc|phan)\s+(?:phai|can|nen)\s+(?:sua|fix)\b/g, ' ')
|
|
1902
1909
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -1936,7 +1943,7 @@ function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
|
1936
1943
|
const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
|
|
1937
1944
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
1938
1945
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
1939
|
-
&&
|
|
1946
|
+
&& !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
1940
1947
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
1941
1948
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
1942
1949
|
const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
|
|
@@ -1108,10 +1108,17 @@ function neutralizeNonOrders(text) {
|
|
|
1108
1108
|
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
1109
1109
|
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
1110
1110
|
return source
|
|
1111
|
+
// A list item that DESCRIBES test cases ("Negative controls: yêu cầu thực sự chỉnh config/restart/deploy ...") is
|
|
1112
|
+
// specification of the work, not an order; it runs to the next bullet or blank line.
|
|
1113
|
+
.replace(/^[ \t]*(?:[-*+\u2022]|\d+[.)])[ \t]*(?:negative|positive)\s+controls?\b[^\n]*(?:\n(?![ \t]*(?:[-*+\u2022]|\d+[.)])|[ \t]*\n)[^\n]*)*/gm, ' ')
|
|
1114
|
+
// A header that negates the list under it ("Authorization ... không cho phép:", "Prohibitions:", "not allowed:")
|
|
1115
|
+
// makes every bullet an item of the prohibition, not an order.
|
|
1116
|
+
.replace(/(?:^|(?<=[.;][ \t]))[^.;\n]*\b(?:khong\s+(?:cho\s+phep|duoc|lam)|(?:does|do|did|is|are)\s+not\s+(?:allow|permit|authori[sz]e)\w*|not\s+(?:allowed|permitted|authori[sz]ed)|prohibitions?|prohibited(?:\s+actions?)?|forbidden(?:\s+actions?)?|banned|must\s+not\s+do)\s*:[ \t]*(?:\n[ \t]*(?:[-*+\u2022]|\d+[.)])[^\n]*)+/gm, ' ')
|
|
1111
1117
|
.replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|shall\\s+not)\\s+[^.;\\n]*?(?=(?<!,)\\s+(?:and|va)\\s+(?:${NON_ORDER_VERBS})\\b|\\s+(?:but|nhung|then|however)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1112
1118
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1113
1119
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
1114
|
-
.replace(/\b(?:
|
|
1120
|
+
.replace(/\b(?:uu tien|prefer(?:red)?)\s+(?:a\s+|an\s+)?(?:configuration|config|cau hinh)\s+(?:change|changes|update|thay doi)\b/g, ' ')
|
|
1121
|
+
.replace(/\b(?:neu\s+(?:chi\s+)?(?:phai|can|thieu|khong du|bang chung chua du)(?![^.;\n]*\b(?:restart|deploy|xoa|delete|remove|cancel)\b)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
|
|
1115
1122
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
1116
1123
|
.replace(/\bchinh\s+(?:xac|sach)\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b|\b(?:diem|cho|muc|phan)\s+(?:phai|can|nen)\s+(?:sua|fix)\b/g, ' ')
|
|
1117
1124
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -1162,6 +1169,22 @@ export function isVerificationOnlyRequest(promptText) {
|
|
|
1162
1169
|
&& !clauses.some((clause) => CHANGE_LEAD.test(clause));
|
|
1163
1170
|
}
|
|
1164
1171
|
|
|
1172
|
+
// "Deploy and restart are banned", "Forbidden: deploy", "deploy: banned" name operations to prohibit them. The clause split on
|
|
1173
|
+
// "and"/commas would separate the ban from the first operation, so a banned operation list is removed before splitting.
|
|
1174
|
+
function neutralizeBannedLists(text) {
|
|
1175
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
1176
|
+
const LIST = `${OP}(?:\\s*(?:,|/|\\band\\b|\\bor\\b|\\bva\\b|\\bhoac\\b|\\balso\\b)\\s*${OP})*`;
|
|
1177
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
1178
|
+
const LIFT = /\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/;
|
|
1179
|
+
const span = new RegExp(`\\b(?:(?:forbidden|prohibited|banned)\\s*:\\s*${LIST}|${LIST}\\s*:?\\s*(?:(?:is|are|remains?|van|con)\\s+)?(?:both\\s+)?${BAN})\\b`, 'g');
|
|
1180
|
+
return text.replace(span, (m, offset, whole) => {
|
|
1181
|
+
const start = Math.max(whole.lastIndexOf('.', offset), whole.lastIndexOf(';', offset), whole.lastIndexOf('\n', offset)) + 1;
|
|
1182
|
+
const stops = [whole.indexOf('.', offset + m.length), whole.indexOf(';', offset + m.length), whole.indexOf('\n', offset + m.length)].filter((i) => i >= 0);
|
|
1183
|
+
const end = stops.length ? Math.min(...stops) : whole.length;
|
|
1184
|
+
return LIFT.test(whole.slice(start, end)) ? m : ' ';
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1165
1188
|
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
1166
1189
|
// A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
|
|
1167
1190
|
const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
@@ -1169,10 +1192,10 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
1169
1192
|
const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
|
|
1170
1193
|
&& /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
|
|
1171
1194
|
? { kind: 'conditional-release', preflight } : null;
|
|
1172
|
-
const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
|
|
1195
|
+
const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
|
|
1173
1196
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
1174
1197
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
1175
|
-
&&
|
|
1198
|
+
&& !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
1176
1199
|
if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
|
|
1177
1200
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
1178
1201
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
@@ -1190,11 +1213,40 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
1190
1213
|
// actionable clause (negated/deferred/audit clauses are already filtered out). Git and
|
|
1191
1214
|
// runtime are separate domains with separate proof; a prompt naming both stays a coding
|
|
1192
1215
|
// task rather than being half-verified. A code-change verb anywhere keeps write evidence.
|
|
1193
|
-
|
|
1216
|
+
// A clause that STATES an operation is prohibited ("production deploy vẫn bị cấm", "forbidden deploy") or merely lists a
|
|
1217
|
+
// production operation as a bare noun item ("* Production restart.") names it; it does not order it.
|
|
1218
|
+
const operationNotOrdered = (clause) => {
|
|
1219
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
1220
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
1221
|
+
// A lift or past marker ("was prohibited, owner now allows", "even though it is not allowed", "khong bi chan")
|
|
1222
|
+
// means the prohibition is history or lifted: the clause still orders the operation.
|
|
1223
|
+
if (/\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/.test(clause)) return false;
|
|
1224
|
+
if (/^[\s*+•>\d.)-]*(?:production|prod|staging)\s+(?:restart|redeploy|deploy(?:ment)?)\s*$/.test(clause)) return true;
|
|
1225
|
+
// Evaluated per operation occurrence: the clause orders nothing only when EVERY operation in it is prohibited.
|
|
1226
|
+
// The ban must attach to its own operation: no parenthesis, dash, colon, conjunction, relative pronoun or second operation in between.
|
|
1227
|
+
const gap = `(?:(?!\\b(?:which|that|who|where|when|if|and|but|then)\\b|${OP})[^.;,()\\-:]){0,40}?`;
|
|
1228
|
+
const banAfter = new RegExp(`^${gap}\\b(?:(?:is|are|remains?|van|con)\\s+)?${BAN}\\b(?!\\s+(?:to|from)\\b)`);
|
|
1229
|
+
// "deploy/restart is forbidden", "deploy and restart are banned": operations joined as a list share one ban.
|
|
1230
|
+
const joined = new RegExp(`^\\s*(?:/|,|and|or|va|hoac)\\s*${OP}\\b`);
|
|
1231
|
+
const bannedAfter = (rest) => {
|
|
1232
|
+
if (banAfter.test(rest)) return true;
|
|
1233
|
+
const j = joined.exec(rest);
|
|
1234
|
+
return j ? bannedAfter(rest.slice(j[0].length)) : false;
|
|
1235
|
+
};
|
|
1236
|
+
const banBefore = /\b(?:forbidden|prohibited|banned)\s+(?:(?:production|prod)\s+)?$/;
|
|
1237
|
+
let total = 0;
|
|
1238
|
+
let banned = 0;
|
|
1239
|
+
for (const match of clause.matchAll(new RegExp(`\\b${OP}\\b`, 'g'))) {
|
|
1240
|
+
total += 1;
|
|
1241
|
+
if (bannedAfter(clause.slice(match.index + match[0].length)) || banBefore.test(clause.slice(0, match.index))) banned += 1;
|
|
1242
|
+
}
|
|
1243
|
+
return total > 0 && banned === total;
|
|
1244
|
+
};
|
|
1245
|
+
const gitOperation = actionable.some((clause) => !operationNotOrdered(clause) && /\bgit\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b|\bpush\s+(?:to\s+)?origin\b/.test(clause));
|
|
1194
1246
|
// 'trien khai' is Vietnamese for both "deploy" and "implement": it orders a runtime operation only
|
|
1195
1247
|
// when it names a runtime target; bare it describes work ("Tier 3 truc tiep trien khai thay Tier 2").
|
|
1196
1248
|
// "restart"/"deploy" after a noun modifier ("process restart", "after redeploy") is a scenario, not an order.
|
|
1197
|
-
const runtimeOperation = actionable.some((clause) => /(?<!\b(?:process|crash|hard|soft|graceful|after|upon|during|across|following)\s)\b(?:restart|redeploy|deploy|khoi dong lai|trien khai\s+(?:len\s+)?(?:service|server|production|prod|staging|dich vu|may chu|moi truong))\b/.test(clause)
|
|
1249
|
+
const runtimeOperation = actionable.some((clause) => !operationNotOrdered(clause) && /(?<!\b(?:process|crash|hard|soft|graceful|after|upon|during|across|following)\s)\b(?:restart|redeploy|deploy|khoi dong lai|trien khai\s+(?:len\s+)?(?:service|server|production|prod|staging|dich vu|may chu|moi truong))\b/.test(clause)
|
|
1198
1250
|
&& !/\.[cm]?[jt]sx?\b/.test(clause) && !/\bgit\b/.test(clause));
|
|
1199
1251
|
const nativeDomain = cronOperation ? 'scheduled-job' : gitOperation !== runtimeOperation ? (gitOperation ? 'git-integrity' : 'service-runtime') : null;
|
|
1200
1252
|
const operation = Boolean(nativeDomain);
|