@ngockhoale/ukit 3.4.44 → 3.4.46

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
@@ -9,6 +9,24 @@ All notable changes to UKit are documented here.
9
9
  - New `/ukit:ci-check` command + `ci-check.mjs` helper (core pack): prechecks, fail-closed budget guard, local preflight, at most one dispatch per invocation, write-ahead run-id state, `status` (never dispatches), `--ref`, `--dry-run`, per-OS report. Never pushes, merges or publishes.
10
10
  - Default-branch bootstrap stub documented in `docs/design/CI-CROSS-PLATFORM-MATRIX.md` (requires separate approval).
11
11
 
12
+ ## 3.4.46 - 2026-10-18
13
+
14
+ **Completion contract P0: forensic intent, attributable release evidence and conditional NO-GO.**
15
+ - Correct negated/quoted forensic requests and Vietnamese status questions; explicit report artifacts require owned writes and nonempty output. External scratch writes do not count as product changes.
16
+ - Conditional release contracts with a named `release:verify` preflight accept only current-task attributable native failure as terminal `RELEASE_NO_GO`, never publication success. Missing implementation writes still block; passing preflight retains publication obligations. Masked commands and stale receipts remain unproven.
17
+ - Accept standalone `npm view <package> version --prefer-online` evidence without republishing; improve task/obligation diagnostics. OMP artifact checks use the installed project cwd.
18
+ - Exclude only repository coverage/transcript audit scripts from npm; retain runtime and customer assets and the strict artifact budget.
19
+ - Verified locally on macOS/Node 22 with real isolated OMP child-hook chains; no provider calls or GitHub Actions dispatch. Historical release ledger replay remains partial; OMP mid-task freeze remains open. UNIC Decision stays unchanged/off.
20
+
21
+ ## 3.4.45 - 2026-10-17
22
+
23
+ **P1 correctness repair: implementation prompts, release identity, large-transcript intent, continuation ownership.**
24
+ - Implementation prompts ("fix/apply ...", Vietnamese "van sua" defer-strip) are no longer misread as READ_ONLY; negation scope is bounded and covered by a classification matrix built from a real prompt fixture.
25
+ - Completion Gate release identity is decided by tag + shipped-file state, not HEAD==tag alone: a docs-only HEAD ahead of the tag passes; uncommitted/untracked shipped files, or a missing tag in a tagging repo, still require `release-publication`.
26
+ - Transcripts above 32 MiB use one shared incremental intent scan (project-local cache under `.ukit/storage/cache/handoff-intent`, never written into `template_project`), wired into the Claude guard, OMP bridge and Stop coordinator. The first inconclusive call is a visible advisory, never a silent allow; append, truncation, rotation and corrupt/forged cache never create false authorization. Bounded-scan limits are unchanged.
27
+ - Continuation phrases ("tiếp tục", "continue", "go on", ...) inherit only the active task instance's contract; completed, blocked, read-only and cross-session tasks do not leak authority or evidence.
28
+ - Known limits: live OMP and Windows/Linux not exercised; verified on macOS/Node 22.
29
+
12
30
  ## 3.4.44 - 2026-10-14
13
31
 
14
32
  **Completion Gate scope and permission-choice preservation.**
@@ -955,6 +955,18 @@ entries:
955
955
  archive_policy: never
956
956
  notes: C69 G7 operator doc — timeline render, support bundle contents/exclusions, replay limits
957
957
 
958
+ - id: docs-completion-decision-assessment
959
+ path: docs/COMPLETION_DECISION_ASSESSMENT.md
960
+ class: canonical
961
+ audience: [maintainer]
962
+ owner: runtime
963
+ source_of_truth: docs/COMPLETION_DECISION_ASSESSMENT.md
964
+ merge_strategy: none
965
+ load_policy: on-demand
966
+ validation: [manual]
967
+ archive_policy: never
968
+ notes: P0 completion contract corrections and offline semantic decision architecture assessment
969
+
958
970
  - id: docs-unic-decision-integration-p1-fix
959
971
  path: docs/UNIC-DECISION-INTEGRATION-P1-FIX.md
960
972
  class: canonical
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngockhoale/ukit",
3
- "version": "3.4.44",
3
+ "version": "3.4.46",
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",
@@ -34,6 +34,8 @@
34
34
  "!scripts/audit/c100/implementer-attribution.mjs",
35
35
  "!scripts/audit/c100/trees-lifetimes.mjs",
36
36
  "!scripts/audit/c100/usage-provenance.mjs",
37
+ "!scripts/audit/decision-coverage.mjs",
38
+ "!scripts/audit/subagent-cost/**",
37
39
  "template_project/",
38
40
  "template_user/",
39
41
  "!template_project/**/.smbdelete*",
@@ -190,14 +190,43 @@ function nativeOperationGoal(domain, actionable, text) {
190
190
  }
191
191
 
192
192
  // Task semantics are independent of source execution strategy and observed progress.
193
+ // P1-A (forensic false block): the clause splitter below cuts at commas, so a negation only reached the
194
+ // FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
195
+ // the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
196
+ // sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
197
+ // Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
198
+ // fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
199
+ 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';
200
+ function neutralizeNonOrders(text) {
201
+ return String(text)
202
+ .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'), ' ')
203
+ .replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
204
+ .replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
205
+ .replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
206
+ .replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
207
+ .replace(/\bchinh\s+xac\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b/g, ' ')
208
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
209
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
210
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
211
+ }
212
+
193
213
  export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
194
- const text = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/đ/g, 'd').toLowerCase();
214
+ // A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
215
+ const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
216
+ const preflight = releaseText.match(/\b(?:yarn\s+release:verify|npm\s+run\s+release:verify)\b/)?.[0];
217
+ const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
218
+ && /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
219
+ ? { kind: 'conditional-release', preflight } : null;
220
+ const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
195
221
  const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
196
222
  const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
197
223
  && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
198
- const sourceOrder = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
224
+ const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
199
225
  && /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
200
- const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
226
+ const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
227
+ const reportArtifact = String(promptText).match(/(?:^|\s)((?:docs\/|reports\/)[A-Za-z0-9_./-]+\.(?:md|txt))(?=\s|[.,;:]|$)/)?.[1];
228
+ const documentation = !sourceOrder && !/\b(?:implement|fix|refactor|modify|edit|update|sua|chinh)\b/.test(actionable.join(' ')) && reportArtifact && actionable.some((clause) => /\b(?:write|create|save|viet|tao|luu)\b/.test(clause))
229
+ && /\b(?:report|bao cao|analysis|phan tich|document)\b/.test(text);
201
230
  const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
202
231
  // A cron/job MENTION is not an operation: with a code-change verb in the request, an
203
232
  // operation needs a concrete target id; otherwise it is a coding task about scheduling.
@@ -222,13 +251,13 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
222
251
  // lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
223
252
  const mutationWord = /\b(?:implement|fix|refactor|modify|edit|write|update|add|create|build|make|install|deploy|remove|delete|rename|replace|change|sua|viet|chinh|them|tao|xoa|doi|cai|cap nhat)\b/.test(text);
224
253
  const statusReport = !mutationWord && !operation && !config
225
- && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
226
- const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(text) || statusReport)
254
+ && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text);
255
+ const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|debug|forensic|root cause|kiem tra|phan tich|dieu tra|doc log|tim hieu)\b/.test(text) || statusReport)
227
256
  && !sourceOrder && !codeChangeVerb && !operation && !config;
228
257
  const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
229
- : config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
258
+ : config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
230
259
  const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
231
- return { taskKind, ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
260
+ return { taskKind, ...(releasePolicy ? { releasePolicy } : {}), ...(documentation ? { reportArtifact } : {}), ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
232
261
  ...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
233
262
  ...(operation && nativeDomain === 'scheduled-job' ? { operationGoal: { domain: 'scheduled-job', target: id, desiredState: /\b(?:delete|remove|cancel|xoa|huy|absent|removed|deleted|no longer|khong con)\b/.test(actionable.join(' ')) ? 'absent' : 'present',
234
263
  completionSemantics: /\b(?:ensure|dam bao)\b/.test(text) ? 'ensure-state' : 'execute-and-verify' } } : {}) };
@@ -239,7 +268,7 @@ export function applyTaskSemantics(contract, semantic) {
239
268
  const operationOnly = semantic.taskKind === 'OPERATIONAL'
240
269
  || (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
241
270
  return { ...(contract || {}), ...semantic,
242
- completionEvidence: operationOnly ? ['operation-verification']
271
+ completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
243
272
  : semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
244
273
  : semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
245
274
  : semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
@@ -1103,8 +1103,17 @@ function hasAdvisoryOnlyAssertion({ promptText = '', commandText = '' } = {}) {
1103
1103
  const residue = trimmed
1104
1104
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
1105
1105
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
1106
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
1106
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
1107
1107
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
1108
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
1109
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
1110
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
1111
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
1112
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
1113
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
1114
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
1115
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
1116
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
1108
1117
  .replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
1109
1118
  .replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
1110
1119
  .replace(new RegExp(`\\bkhong\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
@@ -1125,7 +1134,7 @@ function hasAdvisoryOnlyAssertion({ promptText = '', commandText = '' } = {}) {
1125
1134
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
1126
1135
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
1127
1136
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|not|never|no)\\s+(?!only\\b)${TAIL}`, 'g'), ' ')
1128
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
1137
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
1129
1138
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
1130
1139
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
1131
1140
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -1216,11 +1225,20 @@ function hasMutationOrder({ promptText = '', commandText = '' } = {}) {
1216
1225
  const residue = trimmed
1217
1226
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
1218
1227
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
1219
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
1228
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
1220
1229
  // Descriptive state, not an order: "đã/vừa [được] + verb" ("hooks vừa được cài đặt") reports
1221
1230
  // something already done. Only the verb is stripped, so a later imperative still survives.
1222
1231
  .replace(/(?<![a-z0-9_])(?:da|vua)\s+(?:duoc\s+)?(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\s+the|cap\s+nhat|viet|chay|cai|chinh|trien\s+khai|cau\s+hinh)(?![a-z0-9_])/g, ' ')
1223
1232
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
1233
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
1234
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
1235
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
1236
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
1237
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
1238
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
1239
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
1240
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
1241
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
1224
1242
  .replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
1225
1243
  .replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
1226
1244
  .replace(new RegExp(`\\bkhong\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
@@ -1252,7 +1270,7 @@ function hasMutationOrder({ promptText = '', commandText = '' } = {}) {
1252
1270
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
1253
1271
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
1254
1272
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|not|never|no)\\s+(?!only\\b)${TAIL}`, 'g'), ' ')
1255
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
1273
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
1256
1274
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
1257
1275
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
1258
1276
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -668,7 +668,7 @@ async function scanTranscript() {
668
668
  if (readable) {
669
669
  try {
670
670
  const mod = await import(require('url').pathToFileURL(path.resolve(process.env.HOOK_DIR || '.', '../ukit/runtime/handoff-intent.mjs')).href);
671
- scan = await (mod.transcriptHandoffIntentInherited || mod.transcriptHandoffIntent)(transcriptPath);
671
+ scan = await (mod.transcriptHandoffIntentInherited || mod.transcriptHandoffIntent)(transcriptPath, { cacheDir: path.join(projectRoot, '.ukit/storage/cache/handoff-intent') });
672
672
  } catch {
673
673
  scan = { intent: null, known: false };
674
674
  }
@@ -1814,14 +1814,43 @@ function nativeOperationGoal(domain, actionable, text) {
1814
1814
  }
1815
1815
 
1816
1816
  // Task semantics are independent of source execution strategy and observed progress.
1817
+ // P1-A (forensic false block): the clause splitter below cuts at commas, so a negation only reached the
1818
+ // FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
1819
+ // the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
1820
+ // sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
1821
+ // Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
1822
+ // fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
1823
+ 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';
1824
+ function neutralizeNonOrders(text) {
1825
+ return String(text)
1826
+ .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'), ' ')
1827
+ .replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
1828
+ .replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
1829
+ .replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
1830
+ .replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
1831
+ .replace(/\bchinh\s+xac\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b/g, ' ')
1832
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
1833
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
1834
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
1835
+ }
1836
+
1817
1837
  function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
1818
- const text = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/đ/g, 'd').toLowerCase();
1838
+ // A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
1839
+ const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
1840
+ const preflight = releaseText.match(/\b(?:yarn\s+release:verify|npm\s+run\s+release:verify)\b/)?.[0];
1841
+ const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
1842
+ && /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
1843
+ ? { kind: 'conditional-release', preflight } : null;
1844
+ const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
1819
1845
  const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
1820
1846
  const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
1821
1847
  && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
1822
- const sourceOrder = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
1848
+ const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
1823
1849
  && /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
1824
- const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
1850
+ const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
1851
+ const reportArtifact = String(promptText).match(/(?:^|\s)((?:docs\/|reports\/)[A-Za-z0-9_./-]+\.(?:md|txt))(?=\s|[.,;:]|$)/)?.[1];
1852
+ const documentation = !sourceOrder && !/\b(?:implement|fix|refactor|modify|edit|update|sua|chinh)\b/.test(actionable.join(' ')) && reportArtifact && actionable.some((clause) => /\b(?:write|create|save|viet|tao|luu)\b/.test(clause))
1853
+ && /\b(?:report|bao cao|analysis|phan tich|document)\b/.test(text);
1825
1854
  const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
1826
1855
  // A cron/job MENTION is not an operation: with a code-change verb in the request, an
1827
1856
  // operation needs a concrete target id; otherwise it is a coding task about scheduling.
@@ -1846,13 +1875,13 @@ function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
1846
1875
  // lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
1847
1876
  const mutationWord = /\b(?:implement|fix|refactor|modify|edit|write|update|add|create|build|make|install|deploy|remove|delete|rename|replace|change|sua|viet|chinh|them|tao|xoa|doi|cai|cap nhat)\b/.test(text);
1848
1877
  const statusReport = !mutationWord && !operation && !config
1849
- && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
1850
- const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(text) || statusReport)
1878
+ && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text);
1879
+ const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|debug|forensic|root cause|kiem tra|phan tich|dieu tra|doc log|tim hieu)\b/.test(text) || statusReport)
1851
1880
  && !sourceOrder && !codeChangeVerb && !operation && !config;
1852
1881
  const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
1853
- : config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
1882
+ : config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
1854
1883
  const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
1855
- return { taskKind, ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
1884
+ return { taskKind, ...(releasePolicy ? { releasePolicy } : {}), ...(documentation ? { reportArtifact } : {}), ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
1856
1885
  ...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
1857
1886
  ...(operation && nativeDomain === 'scheduled-job' ? { operationGoal: { domain: 'scheduled-job', target: id, desiredState: /\b(?:delete|remove|cancel|xoa|huy|absent|removed|deleted|no longer|khong con)\b/.test(actionable.join(' ')) ? 'absent' : 'present',
1858
1887
  completionSemantics: /\b(?:ensure|dam bao)\b/.test(text) ? 'ensure-state' : 'execute-and-verify' } } : {}) };
@@ -1863,7 +1892,7 @@ function applyTaskSemantics(contract, semantic) {
1863
1892
  const operationOnly = semantic.taskKind === 'OPERATIONAL'
1864
1893
  || (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
1865
1894
  return { ...(contract || {}), ...semantic,
1866
- completionEvidence: operationOnly ? ['operation-verification']
1895
+ completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
1867
1896
  : semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
1868
1897
  : semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
1869
1898
  : semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
@@ -2207,14 +2236,23 @@ function applyTaskSemantics(contract, semantic) {
2207
2236
  const residue = trimmed
2208
2237
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
2209
2238
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
2210
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
2239
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
2211
2240
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
2241
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
2242
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
2243
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
2244
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
2245
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
2246
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
2247
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
2248
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
2249
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
2212
2250
  // A negated LIST ("do not implement, fix, or refactor"; "khong implement, sua, refactor hay chinh") negates every
2213
2251
  // verb in the list: the TAIL strip below stops at the first comma, which would leave the later verbs as orders.
2214
2252
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])(?:\\s*(?:,|,?\\s+(?:or|and|nor|hay|hoac|va))\\s*(?:(?:${MUT_VERBS})(?![A-Za-z0-9_])|(?:${MUT_VERBS})\\s))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
2215
2253
  .replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
2216
2254
  .replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
2217
- .replace(new RegExp(`\\bkhong\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
2255
+ .replace(new RegExp(`\\b(?:khong|chua)\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
2218
2256
  .replace(new RegExp(`\\bdung\\s+${TAIL}`, 'g'), ' ')
2219
2257
  // C89-008: a waiting clause narrates what the TURN waits on, not what
2220
2258
  // the request orders — verbs inside it ("chờ sửa xong", "waiting for
@@ -2231,7 +2269,7 @@ function applyTaskSemantics(contract, semantic) {
2231
2269
  .replace(new RegExp(`\\b(?:de|cho)\\s+${TAIL}`, 'g'), ' ')
2232
2270
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
2233
2271
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
2234
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
2272
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
2235
2273
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
2236
2274
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
2237
2275
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -2269,17 +2307,26 @@ function applyTaskSemantics(contract, semantic) {
2269
2307
  const residue = trimmed
2270
2308
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
2271
2309
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
2272
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
2310
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
2273
2311
  // Descriptive state, not an order: "đã/vừa [được] + verb" ("hooks vừa được cài đặt") reports
2274
2312
  // something already done. Only the verb is stripped, so a later imperative still survives.
2275
2313
  .replace(/(?<![a-z0-9_])(?:da|vua)\s+(?:duoc\s+)?(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\s+the|cap\s+nhat|viet|chay|cai|chinh|trien\s+khai|cau\s+hinh)(?![a-z0-9_])/g, ' ')
2276
2314
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
2315
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
2316
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
2317
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
2318
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
2319
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
2320
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
2321
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
2322
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
2323
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
2277
2324
  // A negated LIST ("do not implement, fix, or refactor"; "khong implement, sua, refactor hay chinh") negates every
2278
2325
  // verb in the list: the TAIL strip below stops at the first comma, which would leave the later verbs as orders.
2279
2326
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])(?:\\s*(?:,|,?\\s+(?:or|and|nor|hay|hoac|va))\\s*(?:(?:${MUT_VERBS})(?![A-Za-z0-9_])|(?:${MUT_VERBS})\\s))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
2280
2327
  .replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
2281
2328
  .replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
2282
- .replace(new RegExp(`\\bkhong\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
2329
+ .replace(new RegExp(`\\b(?:khong|chua)\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
2283
2330
  .replace(new RegExp(`\\bdung\\s+${TAIL}`, 'g'), ' ')
2284
2331
  // C89-008: a waiting clause narrates what the TURN waits on, not what
2285
2332
  // the request orders — verbs inside it belong to the wait.
@@ -2307,7 +2354,7 @@ function applyTaskSemantics(contract, semantic) {
2307
2354
  .replace(new RegExp(`\\b(?:de|cho)\\s+${TAIL}`, 'g'), ' ')
2308
2355
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
2309
2356
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
2310
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
2357
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
2311
2358
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
2312
2359
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
2313
2360
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -3783,7 +3830,7 @@ function applyTaskSemantics(contract, semantic) {
3783
3830
  let previous = sessionId
3784
3831
  ? [storedPrevious, sidecarPrevious].find((state) => String(state?.sessionId || '').trim() === sessionId) || {}
3785
3832
  : storedPrevious;
3786
- const continuationPrompt = /^(?:continue|resume|keep going|proceed|tiep tuc)(?:\s+(?:the task|this task|previous task))?[.!]?$/i.test(
3833
+ const continuationPrompt = /^(?:continue|resume|keep going|go on|proceed|tiep tuc|lam tiep)(?:\s+(?:with\s+)?(?:the\s+|this\s+)?(?:previous\s+|last\s+|current\s+|same\s+)?(?:implementation\s+)?(?:task|work))?(?:\s+(?:thoi|di|đi|nhe|nao|nua|a|ha|please|pls|now|then))*\s*[.!]?$/i.test(
3787
3834
  extractPrompt(payload).normalize('NFD').replace(/[̀-ͯ]/g, '').trim());
3788
3835
  // Restore a contract parked by an informational turn: only for the SAME session, and only
3789
3836
  // on an explicit continuation or a mutating tool event (never a fresh prompt, which routes on its own,
@@ -1086,14 +1086,43 @@ function nativeOperationGoal(domain, actionable, text) {
1086
1086
  }
1087
1087
 
1088
1088
  // Task semantics are independent of source execution strategy and observed progress.
1089
+ // P1-A (forensic false block): the clause splitter below cuts at commas, so a negation only reached the
1090
+ // FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
1091
+ // the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
1092
+ // sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
1093
+ // Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
1094
+ // fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
1095
+ 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';
1096
+ function neutralizeNonOrders(text) {
1097
+ return String(text)
1098
+ .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'), ' ')
1099
+ .replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
1100
+ .replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
1101
+ .replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
1102
+ .replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
1103
+ .replace(/\bchinh\s+xac\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b/g, ' ')
1104
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
1105
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
1106
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
1107
+ }
1108
+
1089
1109
  export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
1090
- const text = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/đ/g, 'd').toLowerCase();
1110
+ // A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
1111
+ const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
1112
+ const preflight = releaseText.match(/\b(?:yarn\s+release:verify|npm\s+run\s+release:verify)\b/)?.[0];
1113
+ const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
1114
+ && /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
1115
+ ? { kind: 'conditional-release', preflight } : null;
1116
+ const text = neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase());
1091
1117
  const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
1092
1118
  const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
1093
1119
  && !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
1094
- const sourceOrder = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
1120
+ const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
1095
1121
  && /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
1096
- const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
1122
+ const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
1123
+ const reportArtifact = String(promptText).match(/(?:^|\s)((?:docs\/|reports\/)[A-Za-z0-9_./-]+\.(?:md|txt))(?=\s|[.,;:]|$)/)?.[1];
1124
+ const documentation = !sourceOrder && !/\b(?:implement|fix|refactor|modify|edit|update|sua|chinh)\b/.test(actionable.join(' ')) && reportArtifact && actionable.some((clause) => /\b(?:write|create|save|viet|tao|luu)\b/.test(clause))
1125
+ && /\b(?:report|bao cao|analysis|phan tich|document)\b/.test(text);
1097
1126
  const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
1098
1127
  // A cron/job MENTION is not an operation: with a code-change verb in the request, an
1099
1128
  // operation needs a concrete target id; otherwise it is a coding task about scheduling.
@@ -1118,13 +1147,13 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
1118
1147
  // lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
1119
1148
  const mutationWord = /\b(?:implement|fix|refactor|modify|edit|write|update|add|create|build|make|install|deploy|remove|delete|rename|replace|change|sua|viet|chinh|them|tao|xoa|doi|cai|cap nhat)\b/.test(text);
1120
1149
  const statusReport = !mutationWord && !operation && !config
1121
- && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
1122
- const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(text) || statusReport)
1150
+ && /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text);
1151
+ const readOnly = (/\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|debug|forensic|root cause|kiem tra|phan tich|dieu tra|doc log|tim hieu)\b/.test(text) || statusReport)
1123
1152
  && !sourceOrder && !codeChangeVerb && !operation && !config;
1124
1153
  const taskKind = operation ? (sourceOrder || codeChangeVerb ? 'MIXED' : 'OPERATIONAL')
1125
- : config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
1154
+ : config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
1126
1155
  const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
1127
- return { taskKind, ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
1156
+ return { taskKind, ...(releasePolicy ? { releasePolicy } : {}), ...(documentation ? { reportArtifact } : {}), ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
1128
1157
  ...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
1129
1158
  ...(operation && nativeDomain === 'scheduled-job' ? { operationGoal: { domain: 'scheduled-job', target: id, desiredState: /\b(?:delete|remove|cancel|xoa|huy|absent|removed|deleted|no longer|khong con)\b/.test(actionable.join(' ')) ? 'absent' : 'present',
1130
1159
  completionSemantics: /\b(?:ensure|dam bao)\b/.test(text)
@@ -1138,7 +1167,7 @@ export function applyTaskSemantics(contract, semantic) {
1138
1167
  const operationOnly = semantic.taskKind === 'OPERATIONAL'
1139
1168
  || (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
1140
1169
  return { ...(contract || {}), ...semantic,
1141
- completionEvidence: operationOnly ? ['operation-verification']
1170
+ completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
1142
1171
  : semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
1143
1172
  : semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
1144
1173
  : semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
@@ -1167,7 +1167,7 @@ async function main() {
1167
1167
  }
1168
1168
 
1169
1169
  const continuingTask = Boolean(previousState.taskInstanceId && lastPrompt
1170
- && /^(?:continue|resume|keep going|proceed|tiep tuc)(?:\s+(?:the task|this task|previous task))?[.!]?$/i.test(
1170
+ && /^(?:continue|resume|keep going|go on|proceed|tiep tuc|lam tiep)(?:\s+(?:with\s+)?(?:the\s+|this\s+)?(?:previous\s+|last\s+|current\s+|same\s+)?(?:implementation\s+)?(?:task|work))?(?:\s+(?:thoi|di|đi|nhe|nao|nua|a|ha|please|pls|now|then))*\s*[.!]?$/i.test(
1171
1171
  promptText.normalize('NFD').replace(/[̀-ͯ]/g, '').trim()));
1172
1172
  const taskInstanceId = readFlagValue(args, '--task-instance-id')
1173
1173
  || (((['skill-router', 'skill-router-provisional'].includes(previousState.source)
@@ -4431,8 +4431,17 @@ function hasAdvisoryOnlyAssertion({ promptText = '', commandText = '' } = {}) {
4431
4431
  const residue = trimmed
4432
4432
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
4433
4433
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
4434
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
4434
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
4435
4435
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
4436
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
4437
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
4438
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
4439
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
4440
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
4441
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
4442
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
4443
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
4444
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
4436
4445
  // A negated LIST ("do not implement, fix, or refactor"; "khong implement, sua, refactor hay chinh") negates every
4437
4446
  // verb in the list: the TAIL strip below stops at the first comma, which would leave the later verbs as orders.
4438
4447
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])(?:\\s*(?:,|,?\\s+(?:or|and|nor|hay|hoac|va))\\s*(?:(?:${MUT_VERBS})(?![A-Za-z0-9_])|(?:${MUT_VERBS})\\s))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
@@ -4455,7 +4464,7 @@ function hasAdvisoryOnlyAssertion({ promptText = '', commandText = '' } = {}) {
4455
4464
  .replace(new RegExp(`\\b(?:de|cho)\\s+${TAIL}`, 'g'), ' ')
4456
4465
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
4457
4466
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
4458
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
4467
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
4459
4468
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
4460
4469
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
4461
4470
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -4547,11 +4556,20 @@ function hasMutationOrder({ promptText = '', commandText = '' } = {}) {
4547
4556
  const residue = trimmed
4548
4557
  .replace(/"[^"\n]*"|«[^»\n]*»/g, ' ')
4549
4558
  // "tạo plan" / "create a plan": the plan is the deliverable, not a mutation order.
4550
- .replace(/\b(?:tao|lap|create|make|write|draft)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap)\b/g, ' ')
4559
+ .replace(/\b(?:tao|lap|viet|create|make|write|draft|produce)\s+(?:cho\s+toi\s+)?(?:a\s+|an\s+|the\s+|1\s+|mot\s+)?(?:one\s+)?(?:structured\s+|forensic\s+|short\s+|brief\s+)?(?:plan|ke\s+hoach|phuong\s+an|lo\s+trinh|roadmap|bao\s+cao|report|danh\s+gia|assessment)\b/g, ' ')
4551
4560
  // Descriptive state, not an order: "đã/vừa [được] + verb" ("hooks vừa được cài đặt") reports
4552
4561
  // something already done. Only the verb is stripped, so a later imperative still survives.
4553
4562
  .replace(/(?<![a-z0-9_])(?:da|vua)\s+(?:duoc\s+)?(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\s+the|cap\s+nhat|viet|chay|cai|chinh|trien\s+khai|cau\s+hinh)(?![a-z0-9_])/g, ' ')
4554
4563
  .replace(/\bwrite-evidence\b|\bverification-evidence\b|\bimpact-evidence\b|\broot-cause\b/g, ' ')
4564
+ // P1-A (forensic false block): nouns and investigation questions are not orders. 'write/verification
4565
+ // advisory', 'state change', 'each proposed fix' name things; 'whether X may create Y' asks a question to
4566
+ // answer. A negated ENUMERATION with objects ('do not implement fixes, modify source code, ... or create
4567
+ // commits') negates every item to its sentence end; the older verb-only chain broke at 'fixes'.
4568
+ .replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
4569
+ .replace(/\b(?:state|status)\s+changes?\b/g, ' ')
4570
+ .replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
4571
+ .replace(new RegExp(`\\bwhether\\b[^.,;\\n]*?(?=\\s+(?:va|and)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])|[.,;\\n]|$)`, 'g'), ' ')
4572
+ .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh)(?![A-Za-z0-9_])(?:[^.,;\\n\u2014]*?(?:\\s*,\\s*(?:(?:or|and|nor|hay|hoac|va)\\s+)?|\\s+(?:or|nor|hay|hoac)\\s+)(?:implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|sua|them|tao|xoa|doi|thay\\s+the|cap\\s+nhat|viet|chay|cai|chinh|trien\\s+khai|cau\\s+hinh|restart|manipulate|resume|push|merge|publish|touch|alter|reset|rollback|revert|commit|kill|release|tag)(?![A-Za-z0-9_]))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
4555
4573
  // A negated LIST ("do not implement, fix, or refactor"; "khong implement, sua, refactor hay chinh") negates every
4556
4574
  // verb in the list: the TAIL strip below stops at the first comma, which would leave the later verbs as orders.
4557
4575
  .replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|not|khong(?:\\s+duoc)?|tuyet\\s+doi\\s+khong)\\s+(?:${MUT_VERBS})(?![A-Za-z0-9_])(?:\\s*(?:,|,?\\s+(?:or|and|nor|hay|hoac|va))\\s*(?:(?:${MUT_VERBS})(?![A-Za-z0-9_])|(?:${MUT_VERBS})\\s))*[^.,;\\n\u2014]*?(?=\\s+(?:va|and|then|roi|nhung|but)\\s|[.,;\u2014\\n]|\\s*$)`, 'g'), ' ')
@@ -4585,7 +4603,7 @@ function hasMutationOrder({ promptText = '', commandText = '' } = {}) {
4585
4603
  .replace(new RegExp(`\\b(?:de|cho)\\s+${TAIL}`, 'g'), ' ')
4586
4604
  .replace(new RegExp(`\\b(?:yeu\\s+cau|doi\\s+hoi|ep\\s+phai|bat\\s+buoc|bat\\s+phai)\\b${TAIL}`, 'g'), ' ')
4587
4605
  .replace(new RegExp(`\\b(?:demand(?:ed|s)?|require(?:d|s)?|forcing|forced|forcibly|must|has\\s+to|have\\s+to)\\b${TAIL}`, 'g'), ' ')
4588
- .replace(new RegExp(`\\b(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
4606
+ .replace(new RegExp(`\\b(?<!\\bvan\\s)(?:can|nen|phai|duoc)\\s+(?:duoc\\s+)?(?:sua|cau\\s+hinh|thay\\s+doi|cap\\s+nhat|edit|fix|config)\\b${TAIL}`, 'g'), ' ')
4589
4607
  .replace(/\bco\s+(?:can|nen)\s+sua\b[^.,;\n?]*/g, ' ')
4590
4608
  .replace(/(?:^|[.,;\n]\s*)(?:should|shall)\s+(?:i|we|you)\b[^.,;\n?]*/g, ' ')
4591
4609
  .replace(/\bchi\s+(?:tu\s+van|hoi|giai\s+thich|xem|la\s+cau\s+hoi|can|la)\b[^.,;\n]*/g, ' ')
@@ -140,7 +140,7 @@ export function bashOperationReceipt(rawCommand, response, transportSuccess, goa
140
140
  // the registry actually printed a semver. Agent prose is never evidence; the completion gate compares the
141
141
  // recorded version with package.json and live Git state before it treats a release as published.
142
142
  const NPM_PUBLISH = /^npm\s+publish(?:\s|$)/;
143
- const NPM_VIEW = /^npm\s+view\s+(\S+?)(?:@\S+)?\s+version(?:\s+2>&1)?\s*$/;
143
+ const NPM_VIEW = /^npm\s+view\s+(\S+?)(?:@\S+)?\s+version(?:\s+--prefer-online)?(?:\s+2>&1)?\s*$/;
144
144
  const SEMVER_LINE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
145
145
  export function releaseReceipt(rawCommand, response, transportSuccess) {
146
146
  if (typeof rawCommand !== 'string') return null;