@ngockhoale/ukit 3.4.63 → 3.4.65

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,21 @@
2
2
 
3
3
  All notable changes to UKit are documented here.
4
4
 
5
+ ## 3.4.65 - 2026-10-22
6
+
7
+ **Configuration capture: Vietnamese comparison is not a mutation order.**
8
+ - Original pilot prompt reproduced CONFIGURATION/external with no operationGoal: “Đối chiếu cấu hình đang hoạt động” normalized to `doi chieu`, then `doi` was mistaken for “đổi” (change). Exclude only `doi chieu` from config mutation verbs in the three shared classifier copies; do not change repository detection, operation receipts or completion gates.
9
+ - Exact prompt fixture, comparison variants and genuine change commands (including comparison followed by change) retain regression coverage. Existing Stop-boundary reconciliation corrects the stale classified contract without resetting task identity/counters; original rotated live contract is not reconstructed or sealed.
10
+ - Authorized one-pair CLI-worker experiment: cap2 COMPLETE 4/4; cap4 INCOMPLETE 2/4. No four-worker adoption, extra arm, global/BAGuide/default change or native OMP throughput claim. Experiment harness/report are repository evidence, not installed runtime.
11
+
12
+ ## 3.4.64 - 2026-10-22
13
+
14
+ **Completion Gate: defect-contingent repair/release is not an unconditional publish order.**
15
+ - Exact approved pilot prompt reproduced false `release-publication`: line/comma splitting detached `release npm` from the preceding wrapped “Nếu gặp bug UKit…” condition. Standing policy/carryover is not needed to reproduce it.
16
+ - Preserve defect-repair sentence scope across line wraps; activate its release branch only after owned runtime writes. Direct publish commands still require registry verification, and foreign-task proofs remain rejected.
17
+ - Do not interpret hashed write-bank keys as paths; bank the runtime-write classification separately across same-task rekeys. Docs/test-only direct writes do not activate repair publication. Legacy pathless Bash deltas remain conservative (may demand release); no new path-attribution architecture added.
18
+ - Pilot preflight narrowed to existing host interfaces and exact condition-change options; no paid arms, no relaxed limits adopted, no BAGuide/default changes.
19
+
5
20
  ## 3.4.63 - 2026-10-22
6
21
 
7
22
  **Completion recovery: background pending is not a pipeline.**
@@ -472,7 +472,18 @@ entries:
472
472
  load_policy: on-demand
473
473
  validation: [manual]
474
474
  archive_policy: replace-with-snapshot
475
- notes: independent frozen fixture pilot proposal; no paid run
475
+ notes: one authorized pair executed; cap2 complete, cap4 quality incomplete; no default adoption
476
+ - id: docs-analysis-concurrency-pilot-support
477
+ path: docs/analysis/concurrency-pilot/
478
+ class: canonical
479
+ audience: [owner, maintainer]
480
+ owner: runtime
481
+ source_of_truth: docs/analysis/concurrency-pilot/RESULT.md
482
+ merge_strategy: none
483
+ load_policy: on-demand
484
+ validation: [manual]
485
+ archive_policy: replace-with-snapshot
486
+ notes: frozen specification, actual result, preparation and gate evidence for the single CLI-worker pair
476
487
  - id: docs-analysis-subagent-cost-audit
477
488
  path: docs/analysis/SUBAGENT-COST-AUDIT.md
478
489
  class: archive
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.63",
3
+ "version": "3.4.65",
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(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
370
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi(?!\s+chieu\b)|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(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
1985
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi(?!\s+chieu\b)|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/;
@@ -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(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
1267
+ const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi(?!\s+chieu\b)|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/;
@@ -1513,6 +1513,7 @@ function carriedEvidenceLedger(fresh, current) {
1513
1513
  writeAttempted: fresh.writeAttempted || current.writeAttempted === true,
1514
1514
  writeSucceeded: fresh.writeSucceeded || current.writeSucceeded === true,
1515
1515
  // undefined (a ledger written before this field existed) keeps the legacy any-write behavior.
1516
+ conditionalRuntimeWrite: fresh.conditionalRuntimeWrite === true || current.conditionalRuntimeWrite === true,
1516
1517
  substantiveWrite: fresh.substantiveWrite === true || current.substantiveWrite === true
1517
1518
  ? true
1518
1519
  : (current.substantiveWrite === undefined && current.writeSucceeded === true ? undefined : false),
@@ -1982,13 +1983,13 @@ async function quarantineJournalLines(target, lines) {
1982
1983
 
1983
1984
  // Uniform applier: returns { ledger, value } or null when the record is unappliable
1984
1985
  // (the caller quarantines it instead of applying it).
1985
- function applyJournalRecord(ledger, record) {
1986
+ function applyJournalRecord(ledger, record, projectRoot) {
1986
1987
  const sameTask = ledger.taskInstanceId || record.taskInstanceId
1987
1988
  ? Boolean(ledger.taskInstanceId && ledger.taskInstanceId === record.taskInstanceId)
1988
1989
  : Boolean(ledger.requestKey && ledger.requestKey === record.requestKey && (ledger.promptKey || null) === (record.promptKey || null));
1989
1990
  if (!sameTask) return null;
1990
1991
  if (record.type === 'receipt') {
1991
- return applyReceiptToLedger(ledger, record.event?.receipt, { vibecode: record.event?.vibecode === true });
1992
+ return applyReceiptToLedger(ledger, record.event?.receipt, { vibecode: record.event?.vibecode === true, projectRoot });
1992
1993
  }
1993
1994
  if (record.type === 'continuation') {
1994
1995
  return { ledger: applyContinuationToLedger(ledger, record.event), value: null };
@@ -2008,7 +2009,7 @@ function applyJournalRecord(ledger, record) {
2008
2009
  // Read the journal and apply every unseen record onto `ledger` IN MEMORY. The journal is
2009
2010
  // consumed only after the caller's ledger write lands (commit()), so a crash anywhere
2010
2011
  // before that leaves the journal intact and the records re-apply idempotently by eventId.
2011
- async function drainJournal(target, { ledger, payload, signal, deadlineMs }) {
2012
+ async function drainJournal(target, { ledger, payload, signal, deadlineMs, projectRoot }) {
2012
2013
  const journalPath = journalPathFor(target);
2013
2014
  let text = null;
2014
2015
  try {
@@ -2031,7 +2032,7 @@ async function drainJournal(target, { ledger, payload, signal, deadlineMs }) {
2031
2032
  continue;
2032
2033
  }
2033
2034
  if (seen.has(record.eventId)) continue; // idempotent replay
2034
- const result = applyJournalRecord(next, record);
2035
+ const result = applyJournalRecord(next, record, projectRoot);
2035
2036
  if (!result) {
2036
2037
  malformed.push(line);
2037
2038
  continue;
@@ -2529,7 +2530,7 @@ function applyReceiptToLedger(ledger, receipt, options = {}) {
2529
2530
  return applyReceiptCore(base, receipt, options);
2530
2531
  }
2531
2532
 
2532
- function applyReceiptCore(ledger, receipt, { vibecode = false } = {}) {
2533
+ function applyReceiptCore(ledger, receipt, { vibecode = false, projectRoot } = {}) {
2533
2534
  if (!receipt || typeof receipt !== 'object' || !RECEIPT_KINDS.has(receipt.kind)) return null;
2534
2535
  const next = { ...ledger, receiptSequence: Number(ledger.receiptSequence || 0) + 1 };
2535
2536
  if (receipt.kind === 'delegation-violation') {
@@ -2565,6 +2566,7 @@ function applyReceiptCore(ledger, receipt, { vibecode = false } = {}) {
2565
2566
  } else if (receipt.kind === 'write') {
2566
2567
  next.writeAttempted = true;
2567
2568
  next.writeSucceeded = next.writeSucceeded || receipt.success;
2569
+ if (receipt.success && isConditionalRuntimeWrite(receipt, receipt.workspaceRoot || projectRoot)) next.conditionalRuntimeWrite = true;
2568
2570
  if (receipt.success && receipt.file && !isBookkeepingWrite(receipt.file)) next.substantiveWrite = true;
2569
2571
  else if (next.substantiveWrite === undefined) next.substantiveWrite = false;
2570
2572
  // Evidence tier: a fingerprint-derived Bash delta is inferred (supporting) evidence; only a
@@ -2946,7 +2948,7 @@ export async function recordLedgerEvent(event, {
2946
2948
  let ledger = liveBaseLedger(event, current, payload, state, harness, fallbackLedger);
2947
2949
  // Reconcile pending journaled events first — inside this caller's acquired lock and
2948
2950
  // before its own evidence, so a timed-out writer's receipt is never stranded.
2949
- const drained = await drainJournal(target, { ledger, payload, signal, deadlineMs });
2951
+ const drained = await drainJournal(target, { ledger, payload, signal, deadlineMs, projectRoot });
2950
2952
  ledger = drained.ledger;
2951
2953
  let value;
2952
2954
  if (event.type === 'receipt') {
@@ -2975,7 +2977,7 @@ export async function recordLedgerEvent(event, {
2975
2977
  const stampedReceipt = TASK_OWNED_RECEIPT_KINDS.has(event.receipt.kind)
2976
2978
  ? { ...event.receipt, taskInstanceId: state?.taskInstanceId || null, sessionKey: sessionIdentity(payload) }
2977
2979
  : event.receipt;
2978
- const applied = applyReceiptToLedger(ledger, stampedReceipt, { vibecode: event.vibecode === true });
2980
+ const applied = applyReceiptToLedger(ledger, stampedReceipt, { vibecode: event.vibecode === true, projectRoot });
2979
2981
  if (!applied) return { committed: false, eventId, reason: 'invalid-receipt' };
2980
2982
  ledger = applied.ledger;
2981
2983
  // TASK-005 (FR-005): completion receipts (write/verification terminal
@@ -3781,12 +3783,23 @@ const PUBLISH_ORDER_HEADER = /prohibit|forbidden|out of scope|non-goal|scope con
3781
3783
  // "Do not: ... npm publish") keeps its negation across comma/and/or list items and under a prohibition header; only a
3782
3784
  // explicit command can end a negated non-release clause, but bare list verbs cannot. Explicit publication
3783
3785
  // prohibitions win over positive references elsewhere. Only the task's authoritative prompt is consulted.
3786
+ // Keep a defect-contingent repair sentence together across soft line wraps. Its
3787
+ // publication instruction becomes active only after an owned runtime write.
3788
+ function defectRepairBranches(text) {
3789
+ return [...text.matchAll(/(?:^|\n|\.\s+)[ \t]*(?:[-*]|\d+[.)])?\s*((?:neu\s+(?:gap|phat hien|tim thay|co)|if\b)[^.!?]*(?:\.(?!\w)|$))/g)]
3790
+ .map((match) => match[1])
3791
+ .filter((branch) => !/\n\s*\n/.test(branch)
3792
+ && /^(?:neu\s+(?:gap|phat hien|tim thay|co)\s+(?:bug|loi)|if\s+[^,\n]*(?:bug|defect|error)\b)/.test(branch)
3793
+ && /\b(?:sua|fix|repair)\b/.test(branch));
3794
+ }
3795
+
3784
3796
  export function promptOrdersNpmPublish(state = {}) {
3785
3797
  const text = String(state?.routingContext?.lastExplicitUserPromptText || '')
3786
3798
  .normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
3787
3799
  // Quoted commands and historical/example blocks describe orders; they do not issue them.
3788
- const active = text.replace(/```[^]*?```|~~~[^]*?~~~/g, '')
3800
+ let active = text.replace(/```[^]*?```|~~~[^]*?~~~/g, '')
3789
3801
  .replace(/^[ \t]*>.*$/gm, '').replace(/[“"][^”"\n]*[”"]|‘[^’\n]*’/g, '');
3802
+ for (const branch of defectRepairBranches(active)) active = active.replace(branch, '');
3790
3803
  let prohibited = false;
3791
3804
  let ordered = false;
3792
3805
  let blockNegated = false;
@@ -3821,6 +3834,16 @@ export function promptOrdersNpmPublish(state = {}) {
3821
3834
  return ordered && !prohibited;
3822
3835
  }
3823
3836
 
3837
+ function isConditionalRuntimeWrite(receipt, cwd) {
3838
+ if (receipt.kind !== 'write' || receipt.success !== true || !receipt.file || isBookkeepingWrite(receipt.file)) return false;
3839
+ // Older inferred Bash deltas have no paths: keep their conservative release demand.
3840
+ if (receipt.detail === 'bash-workspace-delta' && /^bash-workspace:/.test(receipt.file)) return true;
3841
+ const relative = cwd ? path.relative(path.resolve(cwd), path.resolve(cwd, receipt.file)) : String(receipt.file);
3842
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`)) return false;
3843
+ return !/^(?:\.ukit|\.cache|node_modules|docs|tests?|fixtures?)(?:[\\/]|$)/.test(relative)
3844
+ && !/^tmp(?:[\\/]|$)/.test(relative);
3845
+ }
3846
+
3824
3847
  // Conditional repair is not a publication order until owned project source actually changes.
3825
3848
  // Reuse the affirmative publication parser on the taken branch, including its negation rules.
3826
3849
  function conditionalRepairOrdersPublish(state, ledger, cwd) {
@@ -3828,21 +3851,22 @@ function conditionalRepairOrdersPublish(state, ledger, cwd) {
3828
3851
  const text = String(state?.routingContext?.lastExplicitUserPromptText || '')
3829
3852
  .normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
3830
3853
  .replace(/```[^]*?```|~~~[^]*?~~~/g, '').replace(/^[ \t]*>.*$/gm, '');
3831
- if (!/(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|validate)\b/.test(text)) return false;
3832
- const branch = text.match(/(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+co\s+source fix|if\s+(?:there is a\s+)?source fix)\s*:[^\n]*/)?.[0];
3833
- if (!branch) return false;
3834
- const writes = [...(ledger.receipts || []), ...Object.keys(ledger.bankedWrites || {}).map((file) => ({ kind: 'write', success: true, file }))];
3835
- const projectWrite = writes.some((receipt) => {
3836
- if (receipt.kind !== 'write' || receipt.success !== true || !receipt.file || isBookkeepingWrite(receipt.file)) return false;
3837
- if (receipt.detail === 'bash-workspace-delta' && /^bash-workspace:/.test(receipt.file)) return true;
3838
- const relative = path.relative(path.resolve(cwd), path.resolve(cwd, receipt.file));
3839
- return relative && !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)
3840
- && !/^(?:\.ukit|\.cache|node_modules|tests?|fixtures?|tmp)(?:[\\/]|$)/.test(relative);
3841
- });
3854
+ const defectBranches = defectRepairBranches(text);
3855
+ const verificationPurpose = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|validate)\b/.test(text);
3856
+ const legacyBranch = verificationPurpose ? text.match(/(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+co\s+source fix|if\s+(?:there is a\s+)?source fix)\s*:[^\n]*/)?.[0] : null;
3857
+ if (!defectBranches.length && !legacyBranch) return false;
3858
+ // bankedWrites keys are hashes, never filenames. Keep the runtime-write bit
3859
+ // separately so receipt-window eviction cannot activate a report-only branch.
3860
+ const projectWrite = ledger.conditionalRuntimeWrite === true
3861
+ || (ledger.receipts || []).some((receipt) => isConditionalRuntimeWrite(receipt, cwd));
3842
3862
  if (!projectWrite) return false;
3843
3863
  // Explicit task-wide publication prohibitions still dominate the conditional branch.
3844
3864
  if (/(?:do not|never|khong|chua)\s+(?:npm\s+)?publish\b|\bno\s+(?:release|publication)\b/.test(text)) return false;
3845
- return promptOrdersNpmPublish({ routingContext: { lastExplicitUserPromptText: branch.replace(/^.*?:/, 'Run ') } });
3865
+ return defectBranches.some((branch) => promptOrdersNpmPublish({ routingContext: {
3866
+ lastExplicitUserPromptText: branch.replace(/^[^,\n]*[,\n]\s*/, '').replace(/\s*\n\s*/g, ' '),
3867
+ } })) || Boolean(legacyBranch && promptOrdersNpmPublish({ routingContext: {
3868
+ lastExplicitUserPromptText: legacyBranch.replace(/^.*?:/, 'Run '),
3869
+ } }));
3846
3870
  }
3847
3871
 
3848
3872
  function releasePublicationMissing(state, ledger, cwd) {