@ngockhoale/ukit 3.4.54 → 3.4.56

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.56 - 2026-10-22
6
+
7
+ **Completion Gate: a stale self-classified contract is re-derived from the task's own prompt on continuation.**
8
+ - Field case: after the 3.4.55 classifier fix, an in-flight task kept its previously stored MIXED contract (continuations reused `routeSummary.executionContract` verbatim), so the gate still demanded `operation-verification` the user's prompt never ordered.
9
+ - Fix (`persistRouteState` in `skill-router.sh`): on a non-system continuation whose stored contract was classified by the router (`contractOrigin` != `carried-system`, not a status report), the contract is re-classified from `lastExplicitUserPromptText`; it is replaced only when the taskKind differs. Genuine contracts re-classify identically and are kept; system-carried contracts are never touched. No gate loosening, no receipts added.
10
+ - Regression: `tests/hooks/staleContractRederive.test.js` (stale MIXED corrected, genuine MIXED retained, carried-system untouched, new task classified on its own).
11
+ - Verified offline on macOS/Node 22; hook replay, not a live host.
12
+
13
+ ## 3.4.55 - 2026-10-22
14
+
15
+ **Completion Gate: preference, conditional-future and prohibition-list text is not an order (field false-block).**
16
+ - 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.
17
+ - 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).
18
+ - 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.
19
+ - Housekeeping: `docs/STATUS.md` rotated under its 150-line budget (verbatim archive), concurrency audit report registered in `manifests/documentation.yaml`.
20
+ - Verified offline on macOS/Node 22; classifier replay, not a live OMP/Claude Code/Codex session; Windows/Linux not run.
21
+
5
22
  ## 3.4.54 - 2026-10-22
6
23
 
7
24
  **Completion Gate: a stated prohibition is not an operation order (field false-block).**
@@ -440,6 +440,17 @@ entries:
440
440
  validation: [manual]
441
441
  archive_policy: owner-decides
442
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
443
454
  - id: docs-analysis-subagent-cost-audit
444
455
  path: docs/analysis/SUBAGENT-COST-AUDIT.md
445
456
  class: archive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.54",
3
+ "version": "3.4.56",
4
4
  "description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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(?:neu\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, ' ')
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, ' ')
@@ -291,7 +298,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
291
298
  const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
292
299
  const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
293
300
  const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
294
- && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
301
+ && !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
295
302
  if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
296
303
  const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
297
304
  && /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
@@ -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(?:neu\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, ' ')
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
- && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
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));
@@ -3913,7 +3920,22 @@ function applyTaskSemantics(contract, semantic) {
3913
3920
  }
3914
3921
  if (previous.requestKey) persisted.requestKey = previous.requestKey;
3915
3922
  }
3916
- const semantic = continuingTask && previous.routeSummary?.executionContract?.taskKind
3923
+ // A stored contract this router classified itself (never a carried-system one) is a cache of
3924
+ // classifyTaskSemantics(lastExplicitUserPromptText): when the classifier has since been fixed
3925
+ // the cache is stale and would keep enforcing evidence the user's own prompt never ordered.
3926
+ // Re-derive from the task's own prompt; genuine contracts re-classify identically and stay.
3927
+ let semanticFromPrompt = null;
3928
+ const storedContract = previous.routeSummary?.executionContract;
3929
+ if (continuingTask && !systemOriginTurn && storedContract?.taskKind && previous.contractOrigin !== 'carried-system'
3930
+ && !storedContract.statusReport && previous.routingContext?.lastExplicitUserPromptText) {
3931
+ try {
3932
+ const fresh = classifyTaskSemantics({ promptText: previous.routingContext.lastExplicitUserPromptText, targetFile: persisted.routingContext?.targetFile });
3933
+ if (fresh?.taskKind && fresh.taskKind !== storedContract.taskKind) semanticFromPrompt = fresh;
3934
+ } catch {}
3935
+ }
3936
+ const semantic = semanticFromPrompt
3937
+ ? semanticFromPrompt
3938
+ : continuingTask && previous.routeSummary?.executionContract?.taskKind
3917
3939
  ? previous.routeSummary.executionContract
3918
3940
  : classifyTaskSemantics({ promptText: extractPrompt(payload) || persisted.routingContext?.lastExplicitUserPromptText, targetFile: persisted.routingContext?.targetFile });
3919
3941
  const informationalTurn = !continuingTask && persisted.routingContext?.executionMode === 'informational'
@@ -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(?:neu\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, ' ')
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, ' ')
@@ -1188,7 +1195,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
1188
1195
  const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
1189
1196
  const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
1190
1197
  const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
1191
- && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
1198
+ && !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
1192
1199
  if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
1193
1200
  const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
1194
1201
  && /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));