@ngockhoale/ukit 3.4.57 → 3.4.58

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,15 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.58 - 2026-10-22
6
+
7
+ **Completion Gate: plain publish orders create the registry obligation, new tasks do not inherit the previous task's evidence, and a Vietnamese status line about a config is not a config order.**
8
+ - Bug: "Please/Now npm publish the package", "Đưa package lên npm" and "Phát hành bản này lên npm" were not read as publish orders, so code + tests PASS completed the gate with nothing on the registry. `promptOrdersNpmPublish` now accepts polite/sequencing prefixes and the Vietnamese publish verbs; negations, `--dry-run` and prohibitions still create no debt.
9
+ - Bug: a new task inherited the previous task's classifier-owned contract fields and MIXED evidence (a stale `git-integrity push` goal and `operation-verification` under "npm publish the package"), so a correct publish run stayed blocked on an unordered push. `skill-router.sh` now resets them when a new task starts.
10
+ - Bug: "OMP version/config đã load lúc bắt đầu vẫn unknown" classified CONFIGURATION/external (`bat` in "bắt đầu", `tat` in "tất cả" read as toggle verbs), demanding operation proof for a status report. `bat`/`tat` no longer match those phrases; real "Bật/Tắt config" orders stay CONFIGURATION (3 classifier copies + mirrors).
11
+ - Regression: `tests/hooks/releaseCompletionEvidence.test.js` (resolver → contract → gate), `tests/hooks/configPreferenceNotOrder.test.js` + real-prompt fixture.
12
+ - Limit: the resolver still emits no release-specific contract field; the obligation is enforced gate-side from the prompt. Verified offline on macOS/Node 22; replay, not a live host.
13
+
5
14
  ## 3.4.57 - 2026-10-22
6
15
 
7
16
  **Completion Gate: background launch acknowledgements are not verification evidence; stale-contract reconciliation covers all obligation fields and the Stop boundary.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.57",
3
+ "version": "3.4.58",
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",
@@ -367,7 +367,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
367
367
  // the config noun AND no reporting/question frame opens the clause or precedes
368
368
  // that word: in "Report what change was made to the config" the mutation word is
369
369
  // a question's noun, never an order.
370
- const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat|tat|chinh)\b/;
370
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
371
371
  const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
372
372
  const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
373
373
  const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
@@ -1982,7 +1982,7 @@ function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
1982
1982
  // the config noun AND no reporting/question frame opens the clause or precedes
1983
1983
  // that word: in "Report what change was made to the config" the mutation word is
1984
1984
  // a question's noun, never an order.
1985
- const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat|tat|chinh)\b/;
1985
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
1986
1986
  const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
1987
1987
  const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
1988
1988
  const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
@@ -3971,11 +3971,24 @@ function applyTaskSemantics(contract, semantic) {
3971
3971
  delete persisted.routeSummary.continuationState;
3972
3972
  } else if (semantic.taskKind !== 'CODE_IMPLEMENTATION' || persisted.routeSummary) {
3973
3973
  persisted.routeSummary ||= { executionMode: 'local-build' };
3974
- if (semanticFromPrompt && persisted.routeSummary.executionContract) {
3975
- // Re-derivation replaces the classifier-owned obligation fields wholesale: applyTaskSemantics
3974
+ if ((semanticFromPrompt || !continuingTask) && persisted.routeSummary.executionContract) {
3975
+ // A NEW task never inherits the previous task's classifier-owned obligations either (e.g. a stale
3976
+ // git-integrity push goal under "npm publish the package"). Re-derivation replaces them wholesale: applyTaskSemantics
3976
3977
  // spreads the old contract first, so a stale operationGoal / releasePolicy would survive.
3977
3978
  const kept = { ...persisted.routeSummary.executionContract };
3978
- for (const key of ['operationGoal', 'configurationScope', 'releasePolicy', 'reportArtifact', 'verificationOnly', 'statusReport']) delete kept[key];
3979
+ // statusReport / verificationOnly are only stripped on re-derivation: for a NEW task they drive the
3980
+ // carried-status rebuild below, so they must survive to be recognised there.
3981
+ const staleKeys = semanticFromPrompt ? ['operationGoal', 'configurationScope', 'releasePolicy', 'reportArtifact', 'verificationOnly', 'statusReport']
3982
+ : ['operationGoal', 'configurationScope', 'releasePolicy', 'reportArtifact'];
3983
+ for (const key of staleKeys) delete kept[key];
3984
+ // MIXED/OPERATIONAL evidence tags belong to the previous task's classification: a new task starts from
3985
+ // the mode's base evidence so it is not asked for operation proof its own prompt never ordered.
3986
+ if ((!continuingTask || semanticFromPrompt) && semantic.taskKind !== 'MIXED' && semantic.taskKind !== 'OPERATIONAL'
3987
+ && Array.isArray(kept.completionEvidence) && kept.completionEvidence.includes('operation-verification')) {
3988
+ const baseMode = persisted.routeSummary.executionMode === 'informational' ? 'local-build' : persisted.routeSummary.executionMode;
3989
+ const base = buildExecutionContract(baseMode);
3990
+ if (Array.isArray(base?.completionEvidence)) kept.completionEvidence = base.completionEvidence;
3991
+ }
3979
3992
  persisted.routeSummary.executionContract = kept;
3980
3993
  }
3981
3994
  // A carried status-report contract must not leak its empty evidence / flag onto a
@@ -1264,7 +1264,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
1264
1264
  // the config noun AND no reporting/question frame opens the clause or precedes
1265
1265
  // that word: in "Report what change was made to the config" the mutation word is
1266
1266
  // a question's noun, never an order.
1267
- const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat|tat|chinh)\b/;
1267
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi|bat(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
1268
1268
  const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
1269
1269
  const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
1270
1270
  const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
@@ -3712,8 +3712,12 @@ export function promptOrdersNpmPublish(state = {}) {
3712
3712
  const releaseOrder = /^(?:please\s+|just\s+|now\s+|also\s+)*(?:release|ship|publish)\s+(?!readiness\b|verification\b|evidence\b|policy\b|intent\b|notes\b|terminology\b)\S/.test(item);
3713
3713
  const releaseReference = /\b(?:release|publish|publication)\b/.test(item);
3714
3714
  if (negated && releaseReference && !/--dry-run/.test(item)) prohibited = true;
3715
- if (!negated && (releaseOrder || (publication && (
3716
- /^npm\s+publish\b/.test(item) || PUBLISH_ORDER_IMPERATIVE.test(item)
3715
+ // Politeness/sequencing prefixes ("please npm publish ...") and Vietnamese verbs ("dua ... len npm",
3716
+ // "phat hanh ... len npm") are still affirmative orders when nothing negates them.
3717
+ const politeItem = item.replace(/^(?:(?:please|just|now|also|then|vui long|hay)\s+)+/, '');
3718
+ const viPublish = /^(?:dua|phat hanh|xuat ban|deploy|day)\b[^.]{0,60}\b(?:len|to|vao)\s+npm\b/.test(politeItem);
3719
+ if (!negated && (releaseOrder || viPublish || (publication && (
3720
+ /^npm\s+publish\b/.test(politeItem) || PUBLISH_ORDER_IMPERATIVE.test(item)
3717
3721
  || /\b(?:chay|run)\s+`?npm\s+publish\b/.test(item)
3718
3722
  || /continue the release workflow:/.test(item))))) ordered = true;
3719
3723
  }
@@ -4503,6 +4507,14 @@ export async function reconcileStaleContract(projectRoot, state) {
4503
4507
  if (!fresh?.taskKind || contractSignature(fresh) === contractSignature(stored)) return state;
4504
4508
  const base = { ...stored };
4505
4509
  for (const key of CLASSIFIER_OWNED_FIELDS) delete base[key];
4510
+ // Evidence tags added by the stale classification (operation-verification, report-artifact, ...) do not
4511
+ // describe the re-derived kind: restart from the execution mode's base evidence unless the fresh kind
4512
+ // sets its own (MIXED/OPERATIONAL/DOCUMENTATION/READ_ONLY are rewritten by applyTaskSemantics).
4513
+ if (fresh.taskKind === 'CODE_IMPLEMENTATION' || (fresh.taskKind === 'CONFIGURATION' && fresh.configurationScope !== 'external')) {
4514
+ const mode = state.routeSummary?.executionMode === 'informational' ? 'local-build' : state.routeSummary?.executionMode;
4515
+ const baseEvidence = resolver.buildExecutionContract(mode)?.completionEvidence;
4516
+ if (Array.isArray(baseEvidence)) base.completionEvidence = baseEvidence;
4517
+ }
4506
4518
  return { ...state, routeSummary: { ...state.routeSummary, executionContract: resolver.applyTaskSemantics(base, fresh) } };
4507
4519
  } catch { return state; }
4508
4520
  }