@ngockhoale/ukit 3.4.62 → 3.4.64
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.64 - 2026-10-22
|
|
6
|
+
|
|
7
|
+
**Completion Gate: defect-contingent repair/release is not an unconditional publish order.**
|
|
8
|
+
- 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.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
- Pilot preflight narrowed to existing host interfaces and exact condition-change options; no paid arms, no relaxed limits adopted, no BAGuide/default changes.
|
|
12
|
+
|
|
13
|
+
## 3.4.63 - 2026-10-22
|
|
14
|
+
|
|
15
|
+
**Completion recovery: background pending is not a pipeline.**
|
|
16
|
+
- Reproduced task `task-848c14831946f7c359cdf11faf45edd2` / `bk1n4wpks`: bare release verifier exited 0 in host notification; Bash/grep read did not ingest completion. Launch and masked-shell receipts share `exitStatusUnproven`; recovery falsely asserted pipeline and requested reruns.
|
|
17
|
+
- Diagnostic-only fix: background launch -> wait SAME job, no duplicate; host completion -> Read task output through footer, not rerun; masked shell -> wrapper status unproven, actual pipe only a possible cause. Receipt acceptance/freshness unchanged; unsupported hosts remain fail-closed.
|
|
18
|
+
- Regressions: pending, host completion+Bash vs Read, later launch not masking valid completion, failed runner, masked wrapper, analysis-doc freshness vs living STATUS; existing forged/mismatched/duplicate/foreign/stale controls retained.
|
|
19
|
+
- Release-gate fixture repair: TERM-ignoring grandchild now signals readiness after handler installation before the measured runner deadline starts (injected real child); retains ≥700ms grace/escalation and no-survivor assertions. Production hook-process unchanged.
|
|
20
|
+
- Independent four-task fixture pilot and whole-workflow budget proposal recorded; no paid pilot. OMP genuine-service live smoke remains unverified (deterministic RPC bash lacks name/ready); no claim of every-service coverage.
|
|
21
|
+
|
|
5
22
|
## 3.4.62 - 2026-10-22
|
|
6
23
|
|
|
7
24
|
**Handoff commands: state what `handoff.maxParallelAgents` counts (wording only).**
|
|
@@ -462,6 +462,17 @@ entries:
|
|
|
462
462
|
validation: [manual]
|
|
463
463
|
archive_policy: replace-with-snapshot
|
|
464
464
|
notes: continuity checkpoint for the concurrency audit (R6 corrections, bash guard, prepared unpaid pilot)
|
|
465
|
+
- id: docs-analysis-concurrency-pilot
|
|
466
|
+
path: docs/analysis/concurrency-pilot/README.md
|
|
467
|
+
class: canonical
|
|
468
|
+
audience: [owner, maintainer]
|
|
469
|
+
owner: runtime
|
|
470
|
+
source_of_truth: docs/analysis/concurrency-pilot/README.md
|
|
471
|
+
merge_strategy: none
|
|
472
|
+
load_policy: on-demand
|
|
473
|
+
validation: [manual]
|
|
474
|
+
archive_policy: replace-with-snapshot
|
|
475
|
+
notes: independent frozen fixture pilot proposal; no paid run
|
|
465
476
|
- id: docs-analysis-subagent-cost-audit
|
|
466
477
|
path: docs/analysis/SUBAGENT-COST-AUDIT.md
|
|
467
478
|
class: archive
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -3268,7 +3270,9 @@ export async function recordExecutionReceipt({
|
|
|
3268
3270
|
if (backgroundLaunchAck && receipt.success) {
|
|
3269
3271
|
receipt.success = false;
|
|
3270
3272
|
receipt.exitStatusUnproven = true;
|
|
3271
|
-
|
|
3273
|
+
if (ledgerMessageEmitter) {
|
|
3274
|
+
try { ledgerMessageEmitter(`${JSON.stringify({ systemMessage: `UKit execution-ledger: background verification ${receipt.backgroundTaskId || '(unknown id)'} is pending. Wait for the same job; do not launch a duplicate. After the host task-notification, Read its tasks/<id>.output through the final exit footer to ingest completion; Bash/grep does not ingest it.` })}\n`); } catch { /* advisory */ }
|
|
3275
|
+
}
|
|
3272
3276
|
} else if (maskedStage && receipt.success) {
|
|
3273
3277
|
receipt.success = false;
|
|
3274
3278
|
receipt.exitStatusUnproven = true;
|
|
@@ -3605,7 +3609,15 @@ function recoveryInstruction(missingEvidence, ledger = {}, routeSummary = {}, {
|
|
|
3605
3609
|
// Unproven never sets `verificationFailed`, so this must not key off that flag.
|
|
3606
3610
|
const lastVerification = [...(ledger.receipts || [])].reverse().find((r) => r?.kind === 'verification');
|
|
3607
3611
|
if (!instruction && lastVerification?.exitStatusUnproven === true) {
|
|
3608
|
-
|
|
3612
|
+
if (lastVerification.backgroundTaskId && !lastVerification.backgroundCompletion) {
|
|
3613
|
+
const id = lastVerification.backgroundTaskId;
|
|
3614
|
+
const hostExit = hostCompletionExit(ledger.transcriptPath, id);
|
|
3615
|
+
instruction = hostExit === null
|
|
3616
|
+
? `Background verification ${id} has no ingested completion (pending or unsupported host evidence). Wait for the same job and its host task-notification; do not launch a duplicate. Then use Read on its tasks/${id}.output through the final exit footer. Bash/grep does not ingest completion; a footer without a matching host notification is not proof.`
|
|
3617
|
+
: `Background verification ${id} has host completion exit ${hostExit}, but no ingested completion receipt. Use Read on its tasks/${id}.output through the final exit footer (Bash/grep does not ingest completion). Do not rerun the completed job just to create evidence. If Read is rejected, check the matching launch, host notification, footer and output-file freshness; never fabricate a receipt.`;
|
|
3618
|
+
} else {
|
|
3619
|
+
instruction = `The last verification (\`${String(lastVerification.command || '').slice(0, 80)}\`) exit status is unproven: its shell wrapper may mask or skip the runner (a pipeline is one possible cause). Run the bare verification command and inspect its exit code; for an actual pipeline, propagate the runner status with \`set -o pipefail\`.`;
|
|
3620
|
+
}
|
|
3609
3621
|
}
|
|
3610
3622
|
if (!instruction && ledger.verificationFailed) {
|
|
3611
3623
|
instruction = 'Use the latest failed verification output, fix the failure, and rerun targeted verification.';
|
|
@@ -3771,12 +3783,23 @@ const PUBLISH_ORDER_HEADER = /prohibit|forbidden|out of scope|non-goal|scope con
|
|
|
3771
3783
|
// "Do not: ... npm publish") keeps its negation across comma/and/or list items and under a prohibition header; only a
|
|
3772
3784
|
// explicit command can end a negated non-release clause, but bare list verbs cannot. Explicit publication
|
|
3773
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
|
+
|
|
3774
3796
|
export function promptOrdersNpmPublish(state = {}) {
|
|
3775
3797
|
const text = String(state?.routingContext?.lastExplicitUserPromptText || '')
|
|
3776
3798
|
.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
3777
3799
|
// Quoted commands and historical/example blocks describe orders; they do not issue them.
|
|
3778
|
-
|
|
3800
|
+
let active = text.replace(/```[^]*?```|~~~[^]*?~~~/g, '')
|
|
3779
3801
|
.replace(/^[ \t]*>.*$/gm, '').replace(/[“"][^”"\n]*[”"]|‘[^’\n]*’/g, '');
|
|
3802
|
+
for (const branch of defectRepairBranches(active)) active = active.replace(branch, '');
|
|
3780
3803
|
let prohibited = false;
|
|
3781
3804
|
let ordered = false;
|
|
3782
3805
|
let blockNegated = false;
|
|
@@ -3811,6 +3834,16 @@ export function promptOrdersNpmPublish(state = {}) {
|
|
|
3811
3834
|
return ordered && !prohibited;
|
|
3812
3835
|
}
|
|
3813
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
|
+
|
|
3814
3847
|
// Conditional repair is not a publication order until owned project source actually changes.
|
|
3815
3848
|
// Reuse the affirmative publication parser on the taken branch, including its negation rules.
|
|
3816
3849
|
function conditionalRepairOrdersPublish(state, ledger, cwd) {
|
|
@@ -3818,21 +3851,22 @@ function conditionalRepairOrdersPublish(state, ledger, cwd) {
|
|
|
3818
3851
|
const text = String(state?.routingContext?.lastExplicitUserPromptText || '')
|
|
3819
3852
|
.normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
3820
3853
|
.replace(/```[^]*?```|~~~[^]*?~~~/g, '').replace(/^[ \t]*>.*$/gm, '');
|
|
3821
|
-
|
|
3822
|
-
const
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
return relative && !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)
|
|
3830
|
-
&& !/^(?:\.ukit|\.cache|node_modules|tests?|fixtures?|tmp)(?:[\\/]|$)/.test(relative);
|
|
3831
|
-
});
|
|
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));
|
|
3832
3862
|
if (!projectWrite) return false;
|
|
3833
3863
|
// Explicit task-wide publication prohibitions still dominate the conditional branch.
|
|
3834
3864
|
if (/(?:do not|never|khong|chua)\s+(?:npm\s+)?publish\b|\bno\s+(?:release|publication)\b/.test(text)) return false;
|
|
3835
|
-
return promptOrdersNpmPublish({ routingContext: {
|
|
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
|
+
} }));
|
|
3836
3870
|
}
|
|
3837
3871
|
|
|
3838
3872
|
function releasePublicationMissing(state, ledger, cwd) {
|