@ngockhoale/ukit 3.4.48 → 3.4.50
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 +15 -0
- package/manifests/documentation.yaml +11 -0
- package/package.json +1 -1
- package/src/core/executionContracts.js +13 -3
- package/template_project/.claude/hooks/skill-router.sh +13 -3
- package/template_project/.claude/ukit/index/route-resolver.mjs +13 -3
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +97 -46
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.50 - 2026-10-21
|
|
6
|
+
|
|
7
|
+
**Verification exit-proof integrity (long-run OMP field audit).**
|
|
8
|
+
- Fix execution-ledger false PASS receipts: a bare `pipefail`/`PIPESTATUS` word (e.g. `echo pipefail`) no longer exempts a masked pipe; only real `set -o pipefail` before the pipe or an immediate `exit ${PIPESTATUS[n]}` with the correct index proves the runner status.
|
|
9
|
+
- A runner followed by `|| …`, backgrounded with `&`, inside a `for`/`while`/`case` flow, behind `bash -c` hiding a masked pipe, or followed by `exit 0`/`true`/`:`/leading `!` is recorded as `exitStatusUnproven` (not passing). Claude `tool_response.interrupted:true` now counts as failure.
|
|
10
|
+
- Bare runners, `cd repo; runner`, `&&` chains, real pipefail and terminal subshell/group runners stay accepted. Sequential consumers (including `; echo "exit=$?"`) and `if runner; then echo ok; fi` no longer receive PASS credit: recognizing a runner attempt does not prove its exit. Loop keywords as runner arguments (`vitest -t case`) are not loops.
|
|
11
|
+
- Audit report: `UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md`. Verified locally on macOS/Node 22 only; OMP live host-ordering attribution remains UNVERIFIED. No CI dispatch.
|
|
12
|
+
|
|
13
|
+
## 3.4.49 - 2026-10-19
|
|
14
|
+
|
|
15
|
+
**Conditional consumer verification intent closure.**
|
|
16
|
+
- Fix a live Completion Gate false write obligation: a verification request ("Mục tiêu: xác nhận …") whose source repair/release steps apply only if a defect is found, plus a disposable test-project setup, was classified CODE_IMPLEMENTATION and demanded write-evidence even after a real `npm test` exit 0. The classifier now recognizes a declared verification goal with a narrowly structured conditional repair branch and `xác nhận` as a verify lead; unconditional trailing orders, quoted goals and non-repair conditionals still block.
|
|
17
|
+
- Safety preserved: when the conditional repair branch is actually taken (an owned in-project substantive source write), the evaluator re-activates the release-publication demand; explicit publish prohibitions dominate. Classifier copies (src, resolver, hook) are pinned by a mirror parity test.
|
|
18
|
+
- Reproduced through the npm 3.4.48 consumer router/record/Stop lifecycle with real native exits; no receipt fabrication, task reassignment or ledger reset. Direct `node --test` recognition is unchanged. Verified on macOS/Node 22 only; OMP live attribution UNVERIFIED; no paid OMP smoke or CI dispatch.
|
|
19
|
+
|
|
5
20
|
## 3.4.48 - 2026-10-19
|
|
6
21
|
|
|
7
22
|
**Completion evidence closure and reproducible multi-host installation.**
|
|
@@ -71,6 +71,17 @@ entries:
|
|
|
71
71
|
validation: [manual]
|
|
72
72
|
archive_policy: never
|
|
73
73
|
notes: release record
|
|
74
|
+
- id: root-long-run-orchestration-audit
|
|
75
|
+
path: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
|
|
76
|
+
class: canonical
|
|
77
|
+
audience: [owner, maintainer]
|
|
78
|
+
owner: runtime
|
|
79
|
+
source_of_truth: UKIT_LONG_RUN_ORCHESTRATION_AUDIT.md
|
|
80
|
+
merge_strategy: none
|
|
81
|
+
load_policy: on-demand
|
|
82
|
+
validation: [manual, test:tests/hooks/executionLedger.test.js, test:tests/hooks/verificationExitProof.test.js]
|
|
83
|
+
archive_policy: replace-with-snapshot
|
|
84
|
+
notes: evidence-based long-run field audit; current reproductions are distinct from incident attribution
|
|
74
85
|
- id: registry-self
|
|
75
86
|
path: manifests/documentation.yaml
|
|
76
87
|
class: canonical
|
package/package.json
CHANGED
|
@@ -199,12 +199,22 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
199
199
|
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
200
200
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
201
201
|
const NON_ORDER_VERBS = 'implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|restart|sua|viet|chinh|them|tao|xoa';
|
|
202
|
+
// Declared verification purpose ("Mục tiêu: xác nhận ...", "Goal: verify ..."). Under that declaration a
|
|
203
|
+
// conditional repair/release branch ("Nếu phát hiện lỗi ... sửa ...", "Neu co source fix ... publish") is a
|
|
204
|
+
// branch the check MAY take, not an unconditional order, and disposable test-project setup ("Tạo project thử
|
|
205
|
+
// nghiệm độc lập") is scaffolding of the check. Unconditional orders in the same request keep blocking.
|
|
206
|
+
const VERIFICATION_GOAL = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|kiem tra|validate|double-?check|re-?run)\b/;
|
|
207
|
+
const CONDITIONAL_BRANCH = /(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+(?:phat hien\s+(?:false-block|loi|bug|defect)|co\s+(?:source fix|sua doi))|if\s+(?:(?:a\s+)?(?:bug|defect|failure)\s+is\s+found|(?:you\s+)?find\s+(?:a\s+)?(?:bug|defect)))\b[^\n]*?(?=\.\s+|$|\n)/g;
|
|
208
|
+
const GOAL_LABEL = /\b(?:muc tieu|goal|purpose|objective)\s*:/g;
|
|
209
|
+
const DISPOSABLE_SETUP = /\b(?:tao|create)\s+(?:mot\s+)?(?:project|du an)\s+(?:thu nghiem|test|disposable|doc lap)\b(?:\s+(?:doc lap|independent))?/g;
|
|
202
210
|
function neutralizeNonOrders(text) {
|
|
203
|
-
|
|
211
|
+
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
212
|
+
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
213
|
+
return source
|
|
204
214
|
.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'), ' ')
|
|
205
215
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
206
216
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
207
|
-
.replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/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, ' ')
|
|
208
218
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
209
219
|
.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, ' ')
|
|
210
220
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -244,7 +254,7 @@ function declaredReadOnly(promptText, releasePolicy) {
|
|
|
244
254
|
// "template/install", "source change") as mutation orders. Order is structural: a request whose
|
|
245
255
|
// command-position verbs are verification verbs, with no explicit order (hasExplicitOrder) and no
|
|
246
256
|
// change-leading clause, orders no change - it owes verification evidence, not a write.
|
|
247
|
-
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|validate|double-?check)\b/;
|
|
257
|
+
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|xac nhan|validate|double-?check)\b/;
|
|
248
258
|
const CHANGE_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:make|rebuild|redo|rewrite|restore|revert|correct|clean|patch|resolve|ensure|run(?!(?=[^.]*\b(?:tests?|suite|gate|checks?|lint|typecheck|pilot|smoke|e2e|verif\w*|vitest|jest|audit)\b)))\b/;
|
|
249
259
|
export function isVerificationOnlyRequest(promptText) {
|
|
250
260
|
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
@@ -1881,12 +1881,22 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
1881
1881
|
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
1882
1882
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
1883
1883
|
const NON_ORDER_VERBS = 'implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|restart|sua|viet|chinh|them|tao|xoa';
|
|
1884
|
+
// Declared verification purpose ("Mục tiêu: xác nhận ...", "Goal: verify ..."). Under that declaration a
|
|
1885
|
+
// conditional repair/release branch ("Nếu phát hiện lỗi ... sửa ...", "Neu co source fix ... publish") is a
|
|
1886
|
+
// branch the check MAY take, not an unconditional order, and disposable test-project setup ("Tạo project thử
|
|
1887
|
+
// nghiệm độc lập") is scaffolding of the check. Unconditional orders in the same request keep blocking.
|
|
1888
|
+
const VERIFICATION_GOAL = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|kiem tra|validate|double-?check|re-?run)\b/;
|
|
1889
|
+
const CONDITIONAL_BRANCH = /(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+(?:phat hien\s+(?:false-block|loi|bug|defect)|co\s+(?:source fix|sua doi))|if\s+(?:(?:a\s+)?(?:bug|defect|failure)\s+is\s+found|(?:you\s+)?find\s+(?:a\s+)?(?:bug|defect)))\b[^\n]*?(?=\.\s+|$|\n)/g;
|
|
1890
|
+
const GOAL_LABEL = /\b(?:muc tieu|goal|purpose|objective)\s*:/g;
|
|
1891
|
+
const DISPOSABLE_SETUP = /\b(?:tao|create)\s+(?:mot\s+)?(?:project|du an)\s+(?:thu nghiem|test|disposable|doc lap)\b(?:\s+(?:doc lap|independent))?/g;
|
|
1884
1892
|
function neutralizeNonOrders(text) {
|
|
1885
|
-
|
|
1893
|
+
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
1894
|
+
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
1895
|
+
return source
|
|
1886
1896
|
.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'), ' ')
|
|
1887
1897
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1888
1898
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
1889
|
-
.replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/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, ' ')
|
|
1890
1900
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
1891
1901
|
.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, ' ')
|
|
1892
1902
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -1905,7 +1915,7 @@ function hasExplicitOrder(clauses) {
|
|
|
1905
1915
|
// "template/install", "source change") as mutation orders. Order is structural: a request whose
|
|
1906
1916
|
// command-position verbs are verification verbs, with no explicit order (hasExplicitOrder) and no
|
|
1907
1917
|
// change-leading clause, orders no change - it owes verification evidence, not a write.
|
|
1908
|
-
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|validate|double-?check)\b/;
|
|
1918
|
+
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|xac nhan|validate|double-?check)\b/;
|
|
1909
1919
|
const CHANGE_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:make|rebuild|redo|rewrite|restore|revert|correct|clean|patch|resolve|ensure|run(?!(?=[^.]*\b(?:tests?|suite|gate|checks?|lint|typecheck|pilot|smoke|e2e|verif\w*|vitest|jest|audit)\b)))\b/;
|
|
1910
1920
|
function isVerificationOnlyRequest(promptText) {
|
|
1911
1921
|
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
@@ -1096,12 +1096,22 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
1096
1096
|
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
1097
1097
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
1098
1098
|
const NON_ORDER_VERBS = 'implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|restart|sua|viet|chinh|them|tao|xoa';
|
|
1099
|
+
// Declared verification purpose ("Mục tiêu: xác nhận ...", "Goal: verify ..."). Under that declaration a
|
|
1100
|
+
// conditional repair/release branch ("Nếu phát hiện lỗi ... sửa ...", "Neu co source fix ... publish") is a
|
|
1101
|
+
// branch the check MAY take, not an unconditional order, and disposable test-project setup ("Tạo project thử
|
|
1102
|
+
// nghiệm độc lập") is scaffolding of the check. Unconditional orders in the same request keep blocking.
|
|
1103
|
+
const VERIFICATION_GOAL = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|kiem tra|validate|double-?check|re-?run)\b/;
|
|
1104
|
+
const CONDITIONAL_BRANCH = /(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+(?:phat hien\s+(?:false-block|loi|bug|defect)|co\s+(?:source fix|sua doi))|if\s+(?:(?:a\s+)?(?:bug|defect|failure)\s+is\s+found|(?:you\s+)?find\s+(?:a\s+)?(?:bug|defect)))\b[^\n]*?(?=\.\s+|$|\n)/g;
|
|
1105
|
+
const GOAL_LABEL = /\b(?:muc tieu|goal|purpose|objective)\s*:/g;
|
|
1106
|
+
const DISPOSABLE_SETUP = /\b(?:tao|create)\s+(?:mot\s+)?(?:project|du an)\s+(?:thu nghiem|test|disposable|doc lap)\b(?:\s+(?:doc lap|independent))?/g;
|
|
1099
1107
|
function neutralizeNonOrders(text) {
|
|
1100
|
-
|
|
1108
|
+
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
1109
|
+
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
1110
|
+
return source
|
|
1101
1111
|
.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'), ' ')
|
|
1102
1112
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
1103
1113
|
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
1104
|
-
.replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/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, ' ')
|
|
1105
1115
|
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
1106
1116
|
.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, ' ')
|
|
1107
1117
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
@@ -1141,7 +1151,7 @@ function declaredReadOnly(promptText, releasePolicy) {
|
|
|
1141
1151
|
// "template/install", "source change") as mutation orders. Order is structural: a request whose
|
|
1142
1152
|
// command-position verbs are verification verbs, with no explicit order (hasExplicitOrder) and no
|
|
1143
1153
|
// change-leading clause, orders no change - it owes verification evidence, not a write.
|
|
1144
|
-
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|validate|double-?check)\b/;
|
|
1154
|
+
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|xac nhan|validate|double-?check)\b/;
|
|
1145
1155
|
const CHANGE_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:make|rebuild|redo|rewrite|restore|revert|correct|clean|patch|resolve|ensure|run(?!(?=[^.]*\b(?:tests?|suite|gate|checks?|lint|typecheck|pilot|smoke|e2e|verif\w*|vitest|jest|audit)\b)))\b/;
|
|
1146
1156
|
export function isVerificationOnlyRequest(promptText) {
|
|
1147
1157
|
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
@@ -460,6 +460,10 @@ function explicitError(payload = {}) {
|
|
|
460
460
|
return [
|
|
461
461
|
payload.isError,
|
|
462
462
|
payload.is_error,
|
|
463
|
+
payload.interrupted,
|
|
464
|
+
payload.tool_output?.interrupted,
|
|
465
|
+
payload.tool_result?.interrupted,
|
|
466
|
+
payload.tool_response?.interrupted,
|
|
463
467
|
payload.tool_output?.isError,
|
|
464
468
|
payload.tool_output?.is_error,
|
|
465
469
|
payload.tool_result?.isError,
|
|
@@ -530,8 +534,8 @@ function extractExitCode(payload = {}) {
|
|
|
530
534
|
return null;
|
|
531
535
|
}
|
|
532
536
|
|
|
533
|
-
//
|
|
534
|
-
//
|
|
537
|
+
// Recognize runner presence separately from exit proof. A trailing echo of `$?`
|
|
538
|
+
// can report a status in text but cannot make the wrapper's exit authoritative.
|
|
535
539
|
// Blank out quoted literals so `echo '(jest passed)'` / `git commit -m "run yarn test"` do not
|
|
536
540
|
// look like a runner invocation. A `bash|sh|zsh -c '<script>'` wrapper really executes its
|
|
537
541
|
// quoted script, so that script is tested on its own (bounded recursion).
|
|
@@ -584,71 +588,94 @@ function isVerificationCommand(command, depth = 0) {
|
|
|
584
588
|
].some((pattern) => pattern.test(text));
|
|
585
589
|
}
|
|
586
590
|
|
|
587
|
-
//
|
|
588
|
-
//
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const text = String(command || '');
|
|
592
|
-
const pipelines = [];
|
|
593
|
-
let stages = [];
|
|
591
|
+
// Bounded shell exit-proof recognition, not a shell interpreter. Keep separators so a
|
|
592
|
+
// later echo/OR/background job cannot turn a child runner failure into verification PASS.
|
|
593
|
+
function verificationShellUnits(command) {
|
|
594
|
+
const units = [];
|
|
594
595
|
let buffer = '';
|
|
595
596
|
let quote = null;
|
|
596
597
|
let escaped = false;
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
};
|
|
601
|
-
const closePipeline = () => {
|
|
602
|
-
closeStage();
|
|
603
|
-
if (stages.length) pipelines.push(stages);
|
|
604
|
-
stages = [];
|
|
605
|
-
};
|
|
606
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
607
|
-
const char = text[i];
|
|
598
|
+
const close = (separator) => { units.push({ text: buffer.trim(), separator }); buffer = ''; };
|
|
599
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
600
|
+
const char = command[i];
|
|
608
601
|
if (escaped) { escaped = false; buffer += char; continue; }
|
|
609
|
-
if (char === '\\') { escaped = true; buffer += char; continue; }
|
|
602
|
+
if (char === '\\' && quote !== "'") { escaped = true; buffer += char; continue; }
|
|
610
603
|
if (quote) { if (char === quote) quote = null; buffer += char; continue; }
|
|
611
604
|
if (char === "'" || char === '"') { quote = char; buffer += char; continue; }
|
|
612
|
-
if (char === '
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (text[i + 1] === '&') i += 1;
|
|
605
|
+
if (char === '#' && (!buffer || /\s$/.test(buffer))) {
|
|
606
|
+
while (i < command.length && command[i] !== '\n') i += 1;
|
|
607
|
+
if (i < command.length) close(';');
|
|
616
608
|
continue;
|
|
617
609
|
}
|
|
618
|
-
if (char === '&') {
|
|
619
|
-
|
|
620
|
-
|
|
610
|
+
if (char === '&' && (/[<>]$/.test(buffer) || command[i + 1] === '>')) { buffer += char; continue; }
|
|
611
|
+
if (char === '|' || char === '&') {
|
|
612
|
+
const next = command[i + 1];
|
|
613
|
+
const separator = next === char ? char + next : char === '|' && next === '&' ? '|&' : char;
|
|
614
|
+
close(separator);
|
|
615
|
+
if (separator.length === 2) i += 1;
|
|
621
616
|
continue;
|
|
622
617
|
}
|
|
623
|
-
if (char === ';' || char === '\n') {
|
|
618
|
+
if (char === ';' || char === '\n' || char === '\r') { close(';'); continue; }
|
|
624
619
|
buffer += char;
|
|
625
620
|
}
|
|
626
|
-
|
|
627
|
-
|
|
621
|
+
if (quote || escaped) return null;
|
|
622
|
+
if (buffer.trim()) close('');
|
|
623
|
+
return units.filter((unit) => unit.text || unit.separator !== ';');
|
|
628
624
|
}
|
|
629
625
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
626
|
+
function maskedVerificationStage(command, depth = 0) {
|
|
627
|
+
const text = String(command || '').trim();
|
|
628
|
+
const units = verificationShellUnits(text);
|
|
629
|
+
if (!units?.length || depth > 2) return text;
|
|
630
|
+
let pipefail = false;
|
|
631
|
+
let sawRunner = false;
|
|
632
|
+
for (let i = 0; i < units.length; i += 1) {
|
|
633
|
+
const unit = units[i];
|
|
634
|
+
const plain = maskQuotedLiterals(unit.text);
|
|
635
|
+
// Unsupported flow may skip a runner or discard an earlier iteration's failure.
|
|
636
|
+
// Parens, braces and `if` are not interpreted here. A sequential consumer must not
|
|
637
|
+
// turn the status of an earlier runner into PASS, even when it prints that status.
|
|
638
|
+
if (sawRunner && units[i - 1]?.separator === ';' && !/^[)}]+$/.test(plain)) return unit.text;
|
|
639
|
+
// Command position only: `vitest -t case` is an argument, not a loop.
|
|
640
|
+
if (/^(?:for|while|until|case|function|done)\b/.test(plain)) return unit.text;
|
|
641
|
+
// A later `exit 0`/`true`/`:` replaces the runner's status; a leading `!` inverts it.
|
|
642
|
+
if (sawRunner && /^(?:exit\s+0|true|:)$/.test(plain)) return unit.text;
|
|
643
|
+
if (/^!\s/.test(plain) && isVerificationCommand(unit.text)) return unit.text;
|
|
644
|
+
if (unit.separator === '||' || unit.separator === '&') return unit.text;
|
|
645
|
+
const enable = /^set\s+-(?:[a-z]*o)\s+pipefail$/.test(plain);
|
|
646
|
+
const disable = /^set\s+\+(?:[a-z]*o)\s+pipefail$/.test(plain);
|
|
647
|
+
if (enable || disable) pipefail = enable;
|
|
648
|
+
let runner = isVerificationCommand(unit.text);
|
|
649
|
+
// Only an entire simple shell wrapper can inherit the inner script's proof.
|
|
650
|
+
const wrapper = unit.text.match(/^(?:bash|sh|zsh|dash|ksh)\s+(?:-o\s+\w+\s+|-[a-z]+\s+)*(-[a-z]*c)\s+(?:'([^']*)'|"((?:[^"\\]|\\.)*)")$/);
|
|
651
|
+
if (runner && wrapper) {
|
|
652
|
+
if (maskedVerificationStage(wrapper[2] ?? wrapper[3] ?? '', depth + 1)) return unit.text;
|
|
653
|
+
} else if (runner && /(?:^|\s)(?:bash|sh|zsh|dash|ksh)\s/.test(plain)) return unit.text;
|
|
654
|
+
if (runner) sawRunner = true;
|
|
655
|
+
if (unit.separator === '|' || unit.separator === '|&') {
|
|
656
|
+
if (runner && !pipefail) {
|
|
657
|
+
// A single runner's immediate Bash PIPESTATUS read can propagate its status.
|
|
658
|
+
let end = i;
|
|
659
|
+
while (units[end]?.separator === '|' || units[end]?.separator === '|&') end += 1;
|
|
660
|
+
const proof = units[end + 1];
|
|
661
|
+
const expected = `exit ${'${'}PIPESTATUS[${i - (units.slice(0, i).findLastIndex((u) => u.separator !== '|' && u.separator !== '|&') + 1)}]}`;
|
|
662
|
+
if (units[end]?.separator !== ';' || proof?.text !== expected || proof.separator
|
|
663
|
+
|| units.slice(i + 1, end + 1).some((u) => isVerificationCommand(u.text))) return unit.text;
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
continue;
|
|
640
667
|
}
|
|
641
668
|
}
|
|
642
|
-
return null;
|
|
669
|
+
return sawRunner ? null : text;
|
|
643
670
|
}
|
|
644
671
|
|
|
645
672
|
// Announce through the in-proc hook emitter only (never fd 1 from library callers).
|
|
646
673
|
function noteUnprovenExitStatus(stage) {
|
|
647
674
|
if (!ledgerMessageEmitter) return;
|
|
648
675
|
const message =
|
|
649
|
-
`UKit execution-ledger:
|
|
650
|
-
+ '
|
|
651
|
-
+ 'Re-run
|
|
676
|
+
`UKit execution-ledger: runner exit status is unproven for \`${stage}\` — `
|
|
677
|
+
+ 'the shell wrapper may mask or skip the runner; it is NOT counted as passing verification. '
|
|
678
|
+
+ 'Re-run the bare verification command in the same workspace, without loops, backgrounding or status-swallowing consumers.';
|
|
652
679
|
try {
|
|
653
680
|
ledgerMessageEmitter(`${JSON.stringify({ systemMessage: message })}\n`);
|
|
654
681
|
} catch { /* an emitter failure never loses the ledger event */ }
|
|
@@ -3532,8 +3559,32 @@ export function promptOrdersNpmPublish(state = {}) {
|
|
|
3532
3559
|
return ordered && !prohibited;
|
|
3533
3560
|
}
|
|
3534
3561
|
|
|
3562
|
+
// Conditional repair is not a publication order until owned project source actually changes.
|
|
3563
|
+
// Reuse the affirmative publication parser on the taken branch, including its negation rules.
|
|
3564
|
+
function conditionalRepairOrdersPublish(state, ledger, cwd) {
|
|
3565
|
+
if (ledger?.substantiveWrite !== true || !cwd) return false;
|
|
3566
|
+
const text = String(state?.routingContext?.lastExplicitUserPromptText || '')
|
|
3567
|
+
.normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
3568
|
+
.replace(/```[^]*?```|~~~[^]*?~~~/g, '').replace(/^[ \t]*>.*$/gm, '');
|
|
3569
|
+
if (!/(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|validate)\b/.test(text)) return false;
|
|
3570
|
+
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];
|
|
3571
|
+
if (!branch) return false;
|
|
3572
|
+
const writes = [...(ledger.receipts || []), ...Object.keys(ledger.bankedWrites || {}).map((file) => ({ kind: 'write', success: true, file }))];
|
|
3573
|
+
const projectWrite = writes.some((receipt) => {
|
|
3574
|
+
if (receipt.kind !== 'write' || receipt.success !== true || !receipt.file || isBookkeepingWrite(receipt.file)) return false;
|
|
3575
|
+
if (receipt.detail === 'bash-workspace-delta' && /^bash-workspace:/.test(receipt.file)) return true;
|
|
3576
|
+
const relative = path.relative(path.resolve(cwd), path.resolve(cwd, receipt.file));
|
|
3577
|
+
return relative && !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)
|
|
3578
|
+
&& !/^(?:\.ukit|\.cache|node_modules|tests?|fixtures?|tmp)(?:[\\/]|$)/.test(relative);
|
|
3579
|
+
});
|
|
3580
|
+
if (!projectWrite) return false;
|
|
3581
|
+
// Explicit task-wide publication prohibitions still dominate the conditional branch.
|
|
3582
|
+
if (/(?:do not|never|khong|chua)\s+(?:npm\s+)?publish\b|\bno\s+(?:release|publication)\b/.test(text)) return false;
|
|
3583
|
+
return promptOrdersNpmPublish({ routingContext: { lastExplicitUserPromptText: branch.replace(/^.*?:/, 'Run ') } });
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3535
3586
|
function releasePublicationMissing(state, ledger, cwd) {
|
|
3536
|
-
if (!promptOrdersNpmPublish(state)) return null;
|
|
3587
|
+
if (!promptOrdersNpmPublish(state) && !conditionalRepairOrdersPublish(state, ledger, cwd)) return null;
|
|
3537
3588
|
let pkg = null;
|
|
3538
3589
|
try { pkg = JSON.parse(fsSync.readFileSync(path.join(cwd || process.cwd(), 'package.json'), 'utf8')); } catch { return null; }
|
|
3539
3590
|
if (!pkg?.version) return null;
|