@ngockhoale/ukit 3.4.45 → 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 +9 -0
- package/manifests/documentation.yaml +12 -0
- package/package.json +3 -1
- package/src/core/executionContracts.js +21 -7
- package/template_project/.claude/hooks/skill-router.sh +23 -9
- package/template_project/.claude/ukit/index/route-resolver.mjs +21 -7
- package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +1 -1
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +56 -1
- package/template_project/.omp/hooks/pre/ukit-bridge.js +1 -1
- package/scripts/audit/decision-coverage.mjs +0 -322
- package/scripts/audit/subagent-cost/analyze.mjs +0 -107
- package/scripts/audit/subagent-cost/check-doc.mjs +0 -91
- package/scripts/audit/subagent-cost/duplicates.mjs +0 -195
- package/scripts/audit/subagent-cost/metrics.mjs +0 -193
- package/scripts/audit/subagent-cost/reader.mjs +0 -332
- package/scripts/audit/subagent-cost/render.mjs +0 -34
- package/scripts/audit/subagent-cost/usage-fields.mjs +0 -82
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,15 @@ 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
|
+
|
|
12
21
|
## 3.4.45 - 2026-10-17
|
|
13
22
|
|
|
14
23
|
**P1 correctness repair: implementation prompts, release identity, large-transcript intent, continuation ownership.**
|
|
@@ -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.
|
|
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*",
|
|
@@ -194,25 +194,39 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
194
194
|
// FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
|
|
195
195
|
// the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
|
|
196
196
|
// sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
|
|
197
|
-
// Vietnamese negators
|
|
197
|
+
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
198
198
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
199
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
200
|
function neutralizeNonOrders(text) {
|
|
201
201
|
return String(text)
|
|
202
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, ' ')
|
|
203
208
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
204
209
|
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
205
210
|
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
209
|
-
|
|
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());
|
|
210
221
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
211
222
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
212
223
|
&& !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
213
224
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
214
225
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
215
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);
|
|
216
230
|
const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
|
|
217
231
|
// A cron/job MENTION is not an operation: with a code-change verb in the request, an
|
|
218
232
|
// operation needs a concrete target id; otherwise it is a coding task about scheduling.
|
|
@@ -237,13 +251,13 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
237
251
|
// lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
|
|
238
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);
|
|
239
253
|
const statusReport = !mutationWord && !operation && !config
|
|
240
|
-
&& /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
|
|
241
|
-
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)
|
|
242
256
|
&& !sourceOrder && !codeChangeVerb && !operation && !config;
|
|
243
257
|
const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
|
|
244
|
-
: config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
258
|
+
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
245
259
|
const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
|
|
246
|
-
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' } : {}),
|
|
247
261
|
...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
|
|
248
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',
|
|
249
263
|
completionSemantics: /\b(?:ensure|dam bao)\b/.test(text) ? 'ensure-state' : 'execute-and-verify' } } : {}) };
|
|
@@ -254,7 +268,7 @@ export function applyTaskSemantics(contract, semantic) {
|
|
|
254
268
|
const operationOnly = semantic.taskKind === 'OPERATIONAL'
|
|
255
269
|
|| (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
|
|
256
270
|
return { ...(contract || {}), ...semantic,
|
|
257
|
-
completionEvidence: operationOnly ? ['operation-verification']
|
|
271
|
+
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
258
272
|
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
259
273
|
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
260
274
|
: semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
|
|
@@ -1818,25 +1818,39 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
1818
1818
|
// FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
|
|
1819
1819
|
// the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
|
|
1820
1820
|
// sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
|
|
1821
|
-
// Vietnamese negators
|
|
1821
|
+
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
1822
1822
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
1823
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
1824
|
function neutralizeNonOrders(text) {
|
|
1825
1825
|
return String(text)
|
|
1826
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, ' ')
|
|
1827
1832
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
1828
1833
|
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
1829
1834
|
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
|
|
1830
1835
|
}
|
|
1831
1836
|
|
|
1832
1837
|
function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
1833
|
-
|
|
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());
|
|
1834
1845
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
1835
1846
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
1836
1847
|
&& !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
1837
1848
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
1838
1849
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
1839
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);
|
|
1840
1854
|
const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
|
|
1841
1855
|
// A cron/job MENTION is not an operation: with a code-change verb in the request, an
|
|
1842
1856
|
// operation needs a concrete target id; otherwise it is a coding task about scheduling.
|
|
@@ -1861,13 +1875,13 @@ function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
|
1861
1875
|
// lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
|
|
1862
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);
|
|
1863
1877
|
const statusReport = !mutationWord && !operation && !config
|
|
1864
|
-
&& /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
|
|
1865
|
-
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)
|
|
1866
1880
|
&& !sourceOrder && !codeChangeVerb && !operation && !config;
|
|
1867
1881
|
const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
|
|
1868
|
-
: config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
1882
|
+
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
1869
1883
|
const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
|
|
1870
|
-
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' } : {}),
|
|
1871
1885
|
...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
|
|
1872
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',
|
|
1873
1887
|
completionSemantics: /\b(?:ensure|dam bao)\b/.test(text) ? 'ensure-state' : 'execute-and-verify' } } : {}) };
|
|
@@ -1878,7 +1892,7 @@ function applyTaskSemantics(contract, semantic) {
|
|
|
1878
1892
|
const operationOnly = semantic.taskKind === 'OPERATIONAL'
|
|
1879
1893
|
|| (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
|
|
1880
1894
|
return { ...(contract || {}), ...semantic,
|
|
1881
|
-
completionEvidence: operationOnly ? ['operation-verification']
|
|
1895
|
+
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
1882
1896
|
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
1883
1897
|
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
1884
1898
|
: semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
|
|
@@ -2238,7 +2252,7 @@ function applyTaskSemantics(contract, semantic) {
|
|
|
2238
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'), ' ')
|
|
2239
2253
|
.replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
|
|
2240
2254
|
.replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
|
|
2241
|
-
.replace(new RegExp(`\\
|
|
2255
|
+
.replace(new RegExp(`\\b(?:khong|chua)\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
|
|
2242
2256
|
.replace(new RegExp(`\\bdung\\s+${TAIL}`, 'g'), ' ')
|
|
2243
2257
|
// C89-008: a waiting clause narrates what the TURN waits on, not what
|
|
2244
2258
|
// the request orders — verbs inside it ("chờ sửa xong", "waiting for
|
|
@@ -2312,7 +2326,7 @@ function applyTaskSemantics(contract, semantic) {
|
|
|
2312
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'), ' ')
|
|
2313
2327
|
.replace(new RegExp(`\\bgiu\\s+nguyen\\b${TAIL}`, 'g'), ' ')
|
|
2314
2328
|
.replace(new RegExp(`\\bmuon\\s+giu\\b${TAIL}`, 'g'), ' ')
|
|
2315
|
-
.replace(new RegExp(`\\
|
|
2329
|
+
.replace(new RegExp(`\\b(?:khong|chua)\\s+(?!chi\\b)${TAIL}`, 'g'), ' ')
|
|
2316
2330
|
.replace(new RegExp(`\\bdung\\s+${TAIL}`, 'g'), ' ')
|
|
2317
2331
|
// C89-008: a waiting clause narrates what the TURN waits on, not what
|
|
2318
2332
|
// the request orders — verbs inside it belong to the wait.
|
|
@@ -1090,25 +1090,39 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
1090
1090
|
// FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
|
|
1091
1091
|
// the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
|
|
1092
1092
|
// sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
|
|
1093
|
-
// Vietnamese negators
|
|
1093
|
+
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
1094
1094
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
1095
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
1096
|
function neutralizeNonOrders(text) {
|
|
1097
1097
|
return String(text)
|
|
1098
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, ' ')
|
|
1099
1104
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
1100
1105
|
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
1101
1106
|
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
|
|
1102
1107
|
}
|
|
1103
1108
|
|
|
1104
1109
|
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
1105
|
-
|
|
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());
|
|
1106
1117
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
1107
1118
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
1108
1119
|
&& !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
1109
1120
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
1110
1121
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
1111
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);
|
|
1112
1126
|
const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
|
|
1113
1127
|
// A cron/job MENTION is not an operation: with a code-change verb in the request, an
|
|
1114
1128
|
// operation needs a concrete target id; otherwise it is a coding task about scheduling.
|
|
@@ -1133,13 +1147,13 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
1133
1147
|
// lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
|
|
1134
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);
|
|
1135
1149
|
const statusReport = !mutationWord && !operation && !config
|
|
1136
|
-
&& /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua)\b/.test(text);
|
|
1137
|
-
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)
|
|
1138
1152
|
&& !sourceOrder && !codeChangeVerb && !operation && !config;
|
|
1139
1153
|
const taskKind = operation ? (sourceOrder || codeChangeVerb ? 'MIXED' : 'OPERATIONAL')
|
|
1140
|
-
: config ? 'CONFIGURATION' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
1154
|
+
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
1141
1155
|
const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
|
|
1142
|
-
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' } : {}),
|
|
1143
1157
|
...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
|
|
1144
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',
|
|
1145
1159
|
completionSemantics: /\b(?:ensure|dam bao)\b/.test(text)
|
|
@@ -1153,7 +1167,7 @@ export function applyTaskSemantics(contract, semantic) {
|
|
|
1153
1167
|
const operationOnly = semantic.taskKind === 'OPERATIONAL'
|
|
1154
1168
|
|| (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
|
|
1155
1169
|
return { ...(contract || {}), ...semantic,
|
|
1156
|
-
completionEvidence: operationOnly ? ['operation-verification']
|
|
1170
|
+
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
1157
1171
|
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
1158
1172
|
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
1159
1173
|
: semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
|
|
@@ -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;
|
|
@@ -2225,6 +2225,8 @@ function applyReceiptCore(ledger, receipt, { vibecode = false } = {}) {
|
|
|
2225
2225
|
if (receipt.kind === 'operation') {
|
|
2226
2226
|
next.operationBank = foldOperationEvidence(ledger.operationBank, receipt, ledger);
|
|
2227
2227
|
}
|
|
2228
|
+
if (receipt.releasePreflight) next.releasePreflight = { ...receipt.releasePreflight, sequence: next.receiptSequence };
|
|
2229
|
+
if (receipt.kind === 'write' && receipt.success) next.releasePreflight = null;
|
|
2228
2230
|
if (receipt.kind === 'source') {
|
|
2229
2231
|
next.sourceSucceeded = next.sourceSucceeded || receipt.success;
|
|
2230
2232
|
if (receipt.success && receipt.file) {
|
|
@@ -2848,6 +2850,12 @@ export async function recordExecutionReceipt({
|
|
|
2848
2850
|
exitCode,
|
|
2849
2851
|
};
|
|
2850
2852
|
|
|
2853
|
+
const policy = routeState?.routeSummary?.executionContract?.releasePolicy;
|
|
2854
|
+
if (toolName === 'Bash' && !bashScope.foreign && policy?.kind === 'conditional-release'
|
|
2855
|
+
&& String(toolInput.command || '').trim() === policy.preflight && Number.isInteger(exitCode)) {
|
|
2856
|
+
receipt.releasePreflight = { command: policy.preflight, failed: exitCode !== 0, exitCode,
|
|
2857
|
+
taskInstanceId: routeState.taskInstanceId, workspaceFp };
|
|
2858
|
+
}
|
|
2851
2859
|
const toolResponse = payload.tool_response ?? payload.tool_output ?? payload.tool_result;
|
|
2852
2860
|
const nativeGoal = routeState?.routeSummary?.executionContract?.operationGoal;
|
|
2853
2861
|
const operationReceipt = cronOperationReceipt(toolName, toolInput, toolResponse, success, { sessionId: explicitSessionId(payload), harness })
|
|
@@ -2863,6 +2871,14 @@ export async function recordExecutionReceipt({
|
|
|
2863
2871
|
} else if (toolName === 'Edit' || toolName === 'Write') {
|
|
2864
2872
|
receipt.kind = 'write';
|
|
2865
2873
|
receipt.file = toolInput.file_path || toolInput.path || toolInput.paths?.[0] || null;
|
|
2874
|
+
// External scratch artifacts are not product mutations. Keep them out of write debt/proof entirely;
|
|
2875
|
+
// otherwise a /tmp report resurrects implementation obligations on a read-only investigation.
|
|
2876
|
+
if (typeof receipt.file === 'string') {
|
|
2877
|
+
const relative = path.relative(path.resolve(projectRoot), path.resolve(projectRoot, receipt.file));
|
|
2878
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
2879
|
+
return { rejected: true, eventId: null, reason: 'outside-product-workspace' };
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2866
2882
|
} else if (toolName === 'Bash' && !bashScope.foreign && (isVerificationCommand(toolInput.command) || evidence)) {
|
|
2867
2883
|
// TASK-009 (BL-011): a Bash command carrying an evidence attestation
|
|
2868
2884
|
// records as a verification receipt even when the command is not a known
|
|
@@ -3004,6 +3020,14 @@ function requiredEvidence(state = {}) {
|
|
|
3004
3020
|
|
|
3005
3021
|
function evidenceSatisfied(evidence, ledger = {}, state = {}, { cwd } = {}) {
|
|
3006
3022
|
const routeSummary = state?.routeSummary || {};
|
|
3023
|
+
if (evidence === 'report-artifact') {
|
|
3024
|
+
const artifact = routeSummary.executionContract?.reportArtifact;
|
|
3025
|
+
if (!cwd || typeof artifact !== 'string') return false;
|
|
3026
|
+
const target = path.resolve(cwd, artifact);
|
|
3027
|
+
const bank = evidenceBank(ledger, 'bankedWrites');
|
|
3028
|
+
if (bank[evidenceBankKey(artifact)] !== true && bank[evidenceBankKey(target)] !== true) return false;
|
|
3029
|
+
try { return fsSync.statSync(target).isFile() && fsSync.statSync(target).size > 0; } catch { return false; }
|
|
3030
|
+
}
|
|
3007
3031
|
if (evidence === 'operation-verification') {
|
|
3008
3032
|
return operationCompletionPolicy(scopedContract(state, ledger), null, withGitAuthority(ledger, cwd))?.complete === true;
|
|
3009
3033
|
}
|
|
@@ -3513,6 +3537,23 @@ function evaluateCompletionContract({ state = {}, ledger = {}, cwd, phase = null
|
|
|
3513
3537
|
const at = missingEvidence.indexOf('impact-evidence');
|
|
3514
3538
|
if (at !== -1) missingEvidence.splice(at, 1);
|
|
3515
3539
|
}
|
|
3540
|
+
const releasePolicy = routeSummary.executionContract?.releasePolicy;
|
|
3541
|
+
const preflight = effectiveLedger?.releasePreflight;
|
|
3542
|
+
if (['MIXED', 'OPERATIONAL'].includes(routeSummary.executionContract?.taskKind)
|
|
3543
|
+
&& releasePolicy?.kind === 'conditional-release' && preflight?.failed === true
|
|
3544
|
+
&& preflight.command === releasePolicy.preflight && preflight.taskInstanceId === state.taskInstanceId
|
|
3545
|
+
&& (!preflight.workspaceFp || preflight.workspaceFp === workspaceFingerprint(cwd))
|
|
3546
|
+
&& !missingEvidence.some((item) => item !== 'operation-verification' && item !== 'verification-evidence')) {
|
|
3547
|
+
return { continue: false, notify: true, complete: false, outcome: 'blocked',
|
|
3548
|
+
releaseOutcome: 'RELEASE_NO_GO', missingEvidence: [],
|
|
3549
|
+
reason: `RELEASE_NO_GO: owned preflight ${preflight.command} failed (exit ${preflight.exitCode}); publication is prohibited by this task's conditional release policy. No release was completed.` };
|
|
3550
|
+
}
|
|
3551
|
+
if (releasePolicy?.kind === 'conditional-release'
|
|
3552
|
+
&& !(preflight?.failed === false && preflight.command === releasePolicy.preflight
|
|
3553
|
+
&& preflight.taskInstanceId === state.taskInstanceId
|
|
3554
|
+
&& (!preflight.workspaceFp || preflight.workspaceFp === workspaceFingerprint(cwd)))) {
|
|
3555
|
+
missingEvidence.push('release-preflight');
|
|
3556
|
+
}
|
|
3516
3557
|
const releaseMissing = releasePublicationMissing(state, effectiveLedger, cwd);
|
|
3517
3558
|
if (releaseMissing) missingEvidence.push(releaseMissing);
|
|
3518
3559
|
// TASK-009 (BL-011): the bug-fix floor. On a `playbookId: bug-fix` route a
|
|
@@ -3731,7 +3772,7 @@ function evaluateCompletionContract({ state = {}, ledger = {}, cwd, phase = null
|
|
|
3731
3772
|
|
|
3732
3773
|
const finalAttempt = !vibecode && continuationCount === MAX_CONTINUATIONS - 1;
|
|
3733
3774
|
const instruction = missingEvidence.includes('release-publication')
|
|
3734
|
-
? 'The release is not proven published: run `npm publish` once if the registry does not yet serve this version, then confirm with `npm view <package> version` (must equal package.json) and that the release tag dereferences to HEAD (or to an ancestor of HEAD with no shipped change since). Never move the tag. Do not republish a version the registry already serves.'
|
|
3775
|
+
? 'The release is not proven published: run `npm publish` once if the registry does not yet serve this version, then confirm with `npm view <package> version` (must equal package.json) and that the release tag dereferences to HEAD (or to an ancestor of HEAD with no shipped change since). Never move the tag. Do not republish a version the registry already serves. Accepted evidence: a Bash result of exactly `npm view <package> version` (optionally `2>&1`, optionally after `cd <dir> &&`) that exited 0 and printed a bare semver. Rejected as unproven: the same command behind `| tail/head/cat`, `;`, `||` or inside a compound (a pipeline hides the exit status), dry runs, prose claims. Re-run the bare command once; do not republish.'
|
|
3735
3776
|
: operationalTask
|
|
3736
3777
|
? 'Inspect native mutation receipts and verify the requested operational state; do not fabricate source edits or source-test evidence.'
|
|
3737
3778
|
: recoveryInstruction(missingEvidence, effectiveLedger, routeSummary, { cwd });
|
|
@@ -3770,6 +3811,20 @@ function playbookStopAdvisory({ state = {}, ledger = {} } = {}, result = {}) {
|
|
|
3770
3811
|
|
|
3771
3812
|
export function evaluateCompletion(args = {}) {
|
|
3772
3813
|
const result = evaluateCompletionContract(args);
|
|
3814
|
+
// Explain the verdict from the SAME task-scoped snapshot used by the verifier. These are proof summaries,
|
|
3815
|
+
// not an alternative evidence source; a diagnostic must never satisfy or relax an obligation.
|
|
3816
|
+
const owned = effectiveRequestLedger(args.state, args.ledger) || {};
|
|
3817
|
+
const diagnostics = {
|
|
3818
|
+
taskInstanceId: args.state?.taskInstanceId || null,
|
|
3819
|
+
contract: args.state?.routeSummary?.executionContract?.taskKind || args.state?.routeSummary?.taskKind || args.state?.routeSummary?.executionMode || null,
|
|
3820
|
+
unmetObligations: result.missingEvidence || [],
|
|
3821
|
+
acceptedEvidence: result.complete === true || result.missingEvidence?.length
|
|
3822
|
+
? requiredEvidence(args.state).filter((item) => !(result.missingEvidence || []).includes(item)) : [],
|
|
3823
|
+
observedEvidence: [owned.sourceSucceeded && 'source-read', owned.writeSucceeded && 'write-receipt', owned.verificationSucceeded && 'verification-receipt'].filter(Boolean),
|
|
3824
|
+
rejectedEvidence: args.ledger && owned !== args.ledger ? ['ledger-task-ownership-mismatch'] : [],
|
|
3825
|
+
};
|
|
3826
|
+
result.diagnostics = diagnostics;
|
|
3827
|
+
if (result.reason) result.reason += ` [task=${diagnostics.taskInstanceId || 'unknown'} contract=${diagnostics.contract || 'unknown'} accepted-count=${diagnostics.acceptedEvidence.length} rejected=${diagnostics.rejectedEvidence.join(',') || 'none'}]`;
|
|
3773
3828
|
try {
|
|
3774
3829
|
const advisory = playbookStopAdvisory(args, result);
|
|
3775
3830
|
if (advisory) return { ...result, playbookAdvisory: advisory };
|
|
@@ -1176,7 +1176,7 @@ export async function runSessionStop(
|
|
|
1176
1176
|
// Command authority (RC-A): same shared phase signal the Claude Stop path uses, so a
|
|
1177
1177
|
// handoff-create/review session is never pushed toward Edit/Write on omp either.
|
|
1178
1178
|
const phase = await resolveStopPhase(payload.transcript_path);
|
|
1179
|
-
evaluation = evaluateCompletion({ state, ledger, phase });
|
|
1179
|
+
evaluation = evaluateCompletion({ state, ledger, phase, cwd: projectRoot });
|
|
1180
1180
|
} catch (error) {
|
|
1181
1181
|
// BUG-C22-05: fail-closed like the shell gate (continue the turn), but only up
|
|
1182
1182
|
// to the configured consecutive-crash cap — a persistently broken evaluator
|