@ngockhoale/ukit 3.4.68 → 3.4.70
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 +19 -0
- package/manifests/documentation.yaml +88 -0
- package/manifests/platform.full.yaml +13 -1
- package/package.json +5 -1
- package/scripts/install/module-dependency-closure.mjs +53 -0
- package/scripts/install/sync-installed-mirror.mjs +17 -4
- package/scripts/release/candidate-state.mjs +31 -0
- package/scripts/release/verify-release.mjs +60 -6
- package/src/core/buildPlan.js +2 -0
- package/src/core/executionContracts.js +1 -309
- package/template_project/.claude/commands/ukit/handoff-create.md +41 -1
- package/template_project/.claude/hooks/handoff-model-guard.sh +459 -1
- package/template_project/.claude/hooks/skill-router.sh +585 -357
- package/template_project/.claude/ukit/index/publication-intent.mjs +528 -0
- package/template_project/.claude/ukit/index/route-resolver.mjs +1 -290
- package/template_project/.claude/ukit/index/route-task.mjs +13 -1
- package/template_project/.claude/ukit/runtime/async-lock.mjs +3 -3
- package/template_project/.claude/ukit/runtime/block-breaker.mjs +232 -1
- package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +46 -8
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +161 -56
- package/template_project/.claude/ukit/runtime/hook-chain-runner.mjs +51 -1
- package/template_project/.claude/ukit/runtime/operation-evidence.mjs +34 -10
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +357 -44
- package/template_project/.omp/hooks/pre/ukit-bridge.js +50 -26
- package/scripts/dev/capture-omp-stall.mjs +0 -246
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
// Canonical task semantics; intent never grants authorization or proves completion.
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
export const SEMANTIC_ENGINE_VERSION = 'round4-semantic-1:1dcaa0253e7bc7f4356b02fe5de33b1edda6f0e00d9f2022d9c8cdbe907003a2';
|
|
4
|
+
|
|
5
|
+
export function semanticContractIdentity({ promptText = '', targetFile = '', taskInstanceId = null } = {}) {
|
|
6
|
+
return { taskInstanceId, targetFile, engineVersion: SEMANTIC_ENGINE_VERSION,
|
|
7
|
+
promptFingerprint: crypto.createHash('sha256').update(String(promptText)).digest('hex') };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const semanticContractShape = (contract) => JSON.stringify([
|
|
11
|
+
contract?.taskKind, contract?.publication === true, contract?.operationGoal ?? null,
|
|
12
|
+
contract?.configurationScope ?? null, contract?.releasePolicy ?? null, contract?.conditionalObligations ?? null,
|
|
13
|
+
contract?.reportArtifact ?? null, contract?.verificationOnly === true, contract?.statusReport === true,
|
|
14
|
+
[...(contract?.completionEvidence || [])].sort(),
|
|
15
|
+
]);
|
|
16
|
+
export function compareSemanticContracts({ stored, fresh, previousIdentity, identity }) {
|
|
17
|
+
if (semanticContractShape(stored) === semanticContractShape(fresh)) return { status: 'equal', identity };
|
|
18
|
+
const sameInput = previousIdentity && previousIdentity.taskInstanceId === identity.taskInstanceId
|
|
19
|
+
&& previousIdentity.promptFingerprint === identity.promptFingerprint && previousIdentity.targetFile === identity.targetFile;
|
|
20
|
+
const status = sameInput && previousIdentity.engineVersion === identity.engineVersion ? 'invariant-violation'
|
|
21
|
+
: sameInput ? 'engine-migration' : previousIdentity ? 'input-change' : 'legacy-migration';
|
|
22
|
+
return { status, identity, previousIdentity, stored, rederived: fresh };
|
|
23
|
+
}
|
|
24
|
+
const foldIntent = (text) => String(text).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
25
|
+
|
|
26
|
+
export function analyzeTaskIntent({ promptText = '', targetFile = '', taskInstanceId = null, sessionId = null } = {}) {
|
|
27
|
+
const original = String(promptText);
|
|
28
|
+
const spans = [];
|
|
29
|
+
let fence = null;
|
|
30
|
+
let description = false;
|
|
31
|
+
let offset = 0;
|
|
32
|
+
for (const rawLine of original.split(/(?<=\n)/)) {
|
|
33
|
+
const label = foldIntent(rawLine).replace(/^[\s#*\d.)-]+/, '').trim();
|
|
34
|
+
const bullet = /^\s*(?:[-*+•]|\d+[.)])\s+/.test(rawLine);
|
|
35
|
+
const marker = rawLine.match(/^\s*(`{3,}|~{3,})/);
|
|
36
|
+
if (marker) fence = fence ? null : marker[1][0];
|
|
37
|
+
const execution = /^(?:now |then |please )*(?:execute|perform|follow|run)\b.*\b(?:steps?|workflow|instructions)\b|^(?:instructions|commands|yeu cau)\s*:/.test(label);
|
|
38
|
+
if (execution) description = false;
|
|
39
|
+
else if (/\b(?:examples?|cases|inputs?|reproductions?|history|historical|observed|expected|distinguish|compare|checklist|findings)\b[^\n]*:\s*$/.test(label)) description = true;
|
|
40
|
+
else if (!bullet && label && !/:\s*$/.test(label)) description = false;
|
|
41
|
+
const parts = [...rawLine.replace(/\r/g, ' ').matchAll(/.+?(?:[;\n]|\.(?=\s|$)|(?=\s*(?:,\s*)?(?:but|then|however|nhưng|sau đó)\s+(?:(?:now|please|run)\s+)*(?:implement|fix|modify|edit|apply|restart|deploy|publish|git|sửa)\b)|$)/gi)];
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
const raw = rawLine.slice(part.index, part.index + part[0].length);
|
|
44
|
+
const text = foldIntent(raw);
|
|
45
|
+
const quoted = Boolean(marker || fence || /^\s*>/.test(raw));
|
|
46
|
+
const context = quoted ? 'quotation' : description && bullet ? 'description' : 'instruction';
|
|
47
|
+
const polarity = /\b(?:do not|don't|never|must not|no|khong|chua)\b|đừng/i.test(raw) || /\b(?:prohibited|forbidden|banned)\b/.test(text) ? 'prohibited' : 'affirmative';
|
|
48
|
+
const condition = text.match(/\b(?:if|neu|only if|only when)\b[^,;.\n]*/)?.[0] || null;
|
|
49
|
+
spans.push({ start: offset + part.index, end: offset + part.index + raw.length, raw,
|
|
50
|
+
context, polarity, condition, activation: condition ? 'pending' : context !== 'instruction' || polarity === 'prohibited' ? 'inactive' : 'eligible' });
|
|
51
|
+
}
|
|
52
|
+
offset += rawLine.length;
|
|
53
|
+
}
|
|
54
|
+
for (let index = 1; index < spans.length; index += 1) {
|
|
55
|
+
const span = spans[index];
|
|
56
|
+
if (!span.condition && /^\s*otherwise\b/.test(foldIntent(span.raw)) && spans[index - 1].condition) {
|
|
57
|
+
span.condition = `otherwise:${spans[index - 1].condition}`;
|
|
58
|
+
span.activation = 'pending';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
let semanticInput = original;
|
|
62
|
+
// Preserve the existing native preflight and defect-repair lifecycles. Other
|
|
63
|
+
// leading predicates have no receipt resolver yet: retain them as pending
|
|
64
|
+
// obligations rather than upgrading their consequence into an immediate order.
|
|
65
|
+
const nativeReleasePolicy = classifySemanticText({ promptText: original, targetFile }).releasePolicy;
|
|
66
|
+
const deferredSpans = new Set(spans.filter(span => span.context === 'instruction'
|
|
67
|
+
&& span.polarity === 'affirmative' && span.condition
|
|
68
|
+
&& /^(?:[-*+•\d.)\s]*)(?:if|neu|only if|only when)\b/.test(foldIntent(span.raw))
|
|
69
|
+
&& !nativeReleasePolicy && !VERIFICATION_GOAL.test(foldIntent(original))
|
|
70
|
+
&& !defectRepairBranches(foldIntent(span.raw)).length
|
|
71
|
+
&& ordersPublicationText(foldIntent(span.raw).replace(/^[^,]*,\s*/, ''))));
|
|
72
|
+
// An explicit alternative links both consequences. Keep the unconditional
|
|
73
|
+
// review prefix, but do not require a repair before its review has found one.
|
|
74
|
+
const alternatives = spans.filter(span => span.condition?.startsWith('otherwise:'));
|
|
75
|
+
for (const alternative of alternatives) {
|
|
76
|
+
const preceding = spans[spans.indexOf(alternative) - 1];
|
|
77
|
+
if (preceding.context === 'instruction' && preceding.polarity === 'affirmative'
|
|
78
|
+
&& alternative.context === 'instruction' && alternative.polarity === 'affirmative') {
|
|
79
|
+
deferredSpans.add(preceding);
|
|
80
|
+
deferredSpans.add(alternative);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const span of [...spans].reverse()) {
|
|
84
|
+
const approvalPending = span.condition && /\b(?:approv\w*|permission|authori[sz]\w*)\b/.test(span.condition);
|
|
85
|
+
if (span.context === 'quotation' || span.context === 'description' || approvalPending || deferredSpans.has(span)) {
|
|
86
|
+
// Retain the prefix before a comma-introduced postfix repair condition.
|
|
87
|
+
// Leading conditions and otherwise consequences are masked in full.
|
|
88
|
+
const prefix = deferredSpans.has(span) && !/^(?:\s*)(?:if|neu|only if|only when|otherwise)\b/.test(foldIntent(span.raw))
|
|
89
|
+
? span.raw.match(/^[^,]*,/)?.[0] || '' : '';
|
|
90
|
+
const masked = prefix + span.raw.slice(prefix.length).replace(/[^\r\n]/g, ' ');
|
|
91
|
+
semanticInput = semanticInput.slice(0, span.start) + masked + semanticInput.slice(span.end);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const semantic = classifySemanticText({ promptText: semanticInput.replace(/\r\n?/g, '\n'), targetFile });
|
|
95
|
+
const actions = spans.flatMap((span, spanIndex) => {
|
|
96
|
+
const text = foldIntent(span.raw);
|
|
97
|
+
// Categories are independent obligations, not mutually exclusive labels. Keep
|
|
98
|
+
// pending actions in the inventory even though no predicate or permission is proven.
|
|
99
|
+
const categories = [
|
|
100
|
+
['publication', /\b(?:publish|npm publish|phat hanh)\b/],
|
|
101
|
+
['git', /\bgit\s+(?:push|commit|tag|reset|clean|merge|rebase)\b|\b(?:commit|tag|push)(?:\/(?:commit|tag|publish|push))+\b/],
|
|
102
|
+
['runtime', /\b(?:restart|deploy|redeploy)\b/],
|
|
103
|
+
['source', /\b(?:implement|fix|modify|edit|sua|refactor|apply)\b/],
|
|
104
|
+
['verification', /\b(?:verify|test|rerun|confirm|validate)\b/],
|
|
105
|
+
].filter(([, pattern]) => pattern.test(text)).map(([category]) => category);
|
|
106
|
+
const sourceTarget = span.raw.match(/\b((?:[\w.-]+\/)+[\w.-]+\.[cm]?[jt]sx?)\b/)?.[1];
|
|
107
|
+
return categories.map(category => {
|
|
108
|
+
const permittedBySemantic = category === 'publication' ? semantic.publication === true
|
|
109
|
+
: category === 'git' || category === 'runtime' ? Boolean(semantic.operationGoal)
|
|
110
|
+
: category === 'source' ? ['CODE_IMPLEMENTATION', 'MIXED', 'CONFIGURATION'].includes(semantic.taskKind) : true;
|
|
111
|
+
return { spanIndex, category, target: category === 'source' ? sourceTarget || targetFile || null : semantic.operationGoal || null,
|
|
112
|
+
condition: span.condition, authorization: 'not-evaluated',
|
|
113
|
+
active: span.activation === 'eligible' && permittedBySemantic,
|
|
114
|
+
explanation: span.activation === 'eligible' ? 'canonical classification of current owner prompt' : `${span.context}/${span.polarity}/${span.activation}` };
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
const conditionalObligations = actions.filter(action => deferredSpans.has(spans[action.spanIndex])
|
|
118
|
+
&& ['publication', 'git', 'runtime', 'source'].includes(action.category))
|
|
119
|
+
.map(action => ({ ...action, activation: 'pending' }));
|
|
120
|
+
if (conditionalObligations.length) {
|
|
121
|
+
semantic.conditionalObligations = conditionalObligations;
|
|
122
|
+
if (semantic.taskKind === 'READ_ONLY') {
|
|
123
|
+
delete semantic.statusReport;
|
|
124
|
+
semantic.verificationOnly = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { engineVersion: SEMANTIC_ENGINE_VERSION, identity: { taskInstanceId, sessionId }, original, spans, actions, semantic };
|
|
128
|
+
}
|
|
129
|
+
const PUBLISH_ORDER_NEGATOR = /\b(?:do not|don't|dont|never|without|not yet|no|not|avoid|skip|exclud\w*|forbid\w*|prohibit\w*|khong|chua)\b|--dry-run/;
|
|
130
|
+
const PUBLISH_ORDER_IMPERATIVE = /^(?:please\s+|just\s+|now\s+|also\s+)*(?:run|do|execute|perform|go|ship|publish|release|push|tag|commit|verify|make|cut|chay)\b/;
|
|
131
|
+
const PUBLISH_ORDER_HEADER = /prohibit|forbidden|out of scope|non-goal|scope control/;
|
|
132
|
+
function defectRepairBranches(text) {
|
|
133
|
+
return [...text.matchAll(/(?:^|\n|\.\s+)[ \t]*(?:[-*]|\d+[.)])?\s*((?:neu\s+(?:gap|phat hien|tim thay|co)|if\b)[^.!?]*(?:\.(?!\w)|$))/g)]
|
|
134
|
+
.map((match) => match[1])
|
|
135
|
+
.filter((branch) => !/\n\s*\n/.test(branch)
|
|
136
|
+
&& /^(?:neu\s+(?:gap|phat hien|tim thay|co)\s+(?:bug|loi)|if\s+[^,\n]*(?:bug|defect|error)\b)/.test(branch)
|
|
137
|
+
&& /\b(?:sua|fix|repair)\b/.test(branch));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function publicationScope(promptText = '') {
|
|
141
|
+
const plain = String(promptText).normalize('NFC')
|
|
142
|
+
.replace(/\bdùng\b/giu, 'su dung').replace(/đừng/giu, 'khong')
|
|
143
|
+
.replace(/(^|[\n;.]\s*)([ \t]*(?:[-*+•]|\d+[.)])?[ \t]*)dung(?=\s+(?:publish|release|npm|git|run|fix|sua)\b)/gi, '$1$2khong')
|
|
144
|
+
.normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
145
|
+
.replace(/```[^]*?```|~~~[^]*?~~~/g, '').replace(/^[ \t]*>.*$/gm, '')
|
|
146
|
+
.replace(/[“"][^”"\n]*[”"]|‘[^’\n]*’/g, '')
|
|
147
|
+
.replace(/\b(?:not a request to|khong phai yeu cau)\b[^.;\n]*(?=[.;\n]|$)/g, 'review')
|
|
148
|
+
.replace(/^([ \t]*(?:[-*+•]|\d+[.)])?[ \t]*)(?:(?:hay|vui long|xin)\s+)+/gm, '$1')
|
|
149
|
+
.replace(/\bout of scope\s*:[^.;\n]*/g, 'review')
|
|
150
|
+
.replace(/(?:^|(?<=[.;\n]))\s*(?:to\s+[^.;\n]{0,80},\s*you can\s+|(?:the\s+)?reviewer\s+suggested\s*:)\s*[^.;\n]*(?=[.;\n]|$)/g, '');
|
|
151
|
+
let section = null;
|
|
152
|
+
let review = false;
|
|
153
|
+
let described = false;
|
|
154
|
+
let reproduction = false;
|
|
155
|
+
let exampleIndent = null;
|
|
156
|
+
const lines = [];
|
|
157
|
+
for (const line of plain.split('\n')) {
|
|
158
|
+
if (!line.trim()) { lines.push(''); continue; }
|
|
159
|
+
const label = line.replace(/^[\s#*\d.)-]+/, '').trim();
|
|
160
|
+
const bullet = /^\s*(?:[-*+•]|\d+[.)]|[a-z][.)])\s+/.test(line);
|
|
161
|
+
const execution = /^(?:(?:please|now|then)\s+)*(?:execute|perform|follow|run|thuc hien|chay)\b.*\b(?:steps?|workflow|process|instructions|cac buoc)\b/.test(label)
|
|
162
|
+
|| /^(?:instructions|commands|yeu cau)\s*:/.test(label);
|
|
163
|
+
if (/^\s*\d+[.)]\s+release\b.*\b(?:bat buoc|mandatory)\b/.test(line)) { section = 'execute'; lines.push('instructions:'); continue; }
|
|
164
|
+
if (execution) section = 'execute';
|
|
165
|
+
else if (section === 'execute' && /:\s*$/.test(label)) section = 'execute';
|
|
166
|
+
else if (/^(?:do not|prohibitions?|forbidden)\s*:\s*$/.test(label)) section = 'prohibit';
|
|
167
|
+
else if (section === 'prohibit' && bullet) { lines.push(`do not ${label}`); continue; }
|
|
168
|
+
else if (/^(?:distinguish|compare)(?:\s+between)?\s*:\s*$/.test(label)) { section = 'describe'; lines.push('review distinctions:'); continue; }
|
|
169
|
+
else if (/\b(?:review|audit|inspect|checklist)\b.*:\s*$/.test(label)
|
|
170
|
+
|| (review && (/\b(?:steps?|usage|process|workflow)\s*:\s*$/.test(label)
|
|
171
|
+
|| /^\s*[a-z][.)]\s+[^\n]*:\s*$/.test(line)))) section = 'describe';
|
|
172
|
+
if (/^(?:the\s+)?(?:docs?|documentation|manual|readme)\s+(?:says?|states?|shows?|describes?)\s*:/.test(label)) {
|
|
173
|
+
lines.push('review described documentation');
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (section === 'describe' && bullet && !execution) continue;
|
|
177
|
+
if (!bullet && !/:\s*$/.test(label)) section = null;
|
|
178
|
+
if (/\b(?:review|audit|read[_ -]*only)\b/.test(label)) review = true;
|
|
179
|
+
if (/\b(?:false negatives?|findings|regression matrix|test matrix)\s*:\s*$/.test(label)
|
|
180
|
+
|| /^(?:exact\s+)?(?:(?:positive|negative)\s+)?reproductions\b[^:]*:\s*$/.test(label)) { section = 'describe'; lines.push('review described context'); continue; }
|
|
181
|
+
if (label === 'reproduction:') { reproduction = true; continue; }
|
|
182
|
+
if (reproduction) { reproduction = false; continue; }
|
|
183
|
+
const indent = line.search(/\S/);
|
|
184
|
+
if (exampleIndent !== null && indent > exampleIndent && bullet) continue;
|
|
185
|
+
exampleIndent = null;
|
|
186
|
+
if (/^(?:them (?:positive|negative) fixtures|nhung van nhan dung imperative that)\s*:/.test(label)) {
|
|
187
|
+
exampleIndent = indent;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (/^\s*\d+[.)]\s+release\b.*—\s*(?:dieu kien|prerequisites)\b/.test(line)) continue;
|
|
191
|
+
if (/\b(?:prompt|request|task)\b[^.;\n]*\b(?:prohibit\w*|cam)\b/.test(label)) { lines.push(`do not ${label}`); continue; }
|
|
192
|
+
const example = /^(?:(?:cac|the)\s+)?(?:(?:positive|negative|test)\s+(?:cases?|inputs?|examples?|controls?)|examples?|inputs?|observed|actual|expected|bug report|confirm|historical (?:evidence|input)|vi du)\s*:|^required replay matrix\s*$/.test(label);
|
|
193
|
+
if (example) { described = /:\s*$|^required replay matrix\s*$/.test(label); continue; }
|
|
194
|
+
if (described && bullet) continue;
|
|
195
|
+
if (!bullet) described = false;
|
|
196
|
+
if (/^(?:input|observed|actual|expected|bug description|history|checklist)\s*:/.test(label)) continue;
|
|
197
|
+
// Inspecting an operation names its subject, not an execution order. Keep later clauses active.
|
|
198
|
+
if (/^(?:check|inspect|review|audit|compare)\s+git\b/.test(label)) {
|
|
199
|
+
lines.push(line.replace(/^[^.;\n]*?(?=\s+(?:then|but|instead)\s+|\s+and\s+(?:run|execute|implement|modify|edit|fix|publish)\b|[.;\n]|$)/, 'review subject'));
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
lines.push(line);
|
|
203
|
+
}
|
|
204
|
+
const text = lines.join('\n');
|
|
205
|
+
let prohibited = false;
|
|
206
|
+
let prohibitionBlock = false;
|
|
207
|
+
for (const line of text.split('\n')) {
|
|
208
|
+
if (!line.trim()) continue;
|
|
209
|
+
const bullet = /^\s*(?:[-*•]|\d+[.)])\s+/.test(line);
|
|
210
|
+
if (!bullet) prohibitionBlock = PUBLISH_ORDER_NEGATOR.test(line) && /:\s*$/.test(line);
|
|
211
|
+
for (const clause of line.split(/;|\.\s+|\bthen\b|\bbut\b|\binstead\b/)) {
|
|
212
|
+
let negated = bullet && prohibitionBlock;
|
|
213
|
+
for (const raw of clause.split(/,|\band\b|\bor\b|\bnor\b/)) {
|
|
214
|
+
const item = raw.replace(/^[\s*•-]+/, '').trim();
|
|
215
|
+
if (PUBLISH_ORDER_NEGATOR.test(item)) negated = true;
|
|
216
|
+
else if (/^(?:please\s+|now\s+|also\s+)*(?:run|execute|perform|chay)\s+\S/.test(item)) negated = false;
|
|
217
|
+
if (negated && /\b(?:release|publish|publication)\b/.test(item)) prohibited = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { text, prohibited };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function ordersPublication(promptText = '') {
|
|
225
|
+
return analyzeTaskIntent({ promptText }).semantic.publication === true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function ordersPublicationText(promptText = '') {
|
|
229
|
+
let active = publicationScope(promptText).text.replace(/```[^]*?```|~~~[^]*?~~~/g, '')
|
|
230
|
+
.replace(/^[ \t]*>.*$/gm, '').replace(/[“"][^”"\n]*[”"]|‘[^’\n]*’/g, '');
|
|
231
|
+
for (const branch of defectRepairBranches(active)) active = active.replace(branch, '');
|
|
232
|
+
let ordered = false;
|
|
233
|
+
let blockNegated = false;
|
|
234
|
+
for (const line of active.split('\n')) {
|
|
235
|
+
const bullet = /^\s*(?:[-*\u2022]|\d+[.)])\s+/.test(line);
|
|
236
|
+
if (!line.trim()) { blockNegated = false; continue; }
|
|
237
|
+
if (!bullet) blockNegated = line.trim().length <= 80 && (PUBLISH_ORDER_NEGATOR.test(line) || PUBLISH_ORDER_HEADER.test(line)) && /:\s*$|^[^.]{0,40}$/.test(line.trim()) && !/\bnpm\s+publish\b/.test(line);
|
|
238
|
+
for (const sentence of line.split(/\s*;\s*|\.\s+|\.$/)) {
|
|
239
|
+
for (const hard of sentence.split(/\bthen\b|\bbut\b|\binstead\b/)) {
|
|
240
|
+
let negated = bullet && blockNegated;
|
|
241
|
+
for (const raw of hard.split(/,|\band\b|\bor\b|\bnor\b/)) {
|
|
242
|
+
const item = raw.replace(/^[\s\-*\u2022]+|^\d+[.)]\s*/g, '').trim();
|
|
243
|
+
if (PUBLISH_ORDER_NEGATOR.test(item)) negated = true;
|
|
244
|
+
else if (/^(?:please\s+|now\s+|also\s+)*(?:run|execute|perform|chay)\s+\S/.test(item)) negated = false;
|
|
245
|
+
const commandItem = item.replace(/^(?:please|just|now|also|then)\s+/, '');
|
|
246
|
+
const publication = /\bnpm\s+publish\b/.test(commandItem) && /^(?:npm\s+publish|run|do|execute|perform|go|ship|publish|release|push|tag|commit|verify|make|cut|chay)\b/.test(commandItem);
|
|
247
|
+
const releaseOrder = /^(?:please\s+|just\s+|now\s+|also\s+)*(?:release|ship|publish)\s+(?!instructions?\b|obligations?\b|policies\b|boundary\b|safety\b|progress\b|candidate\s+review\b|readiness\b|verifier\b|verification\b|evidence\b|policy\b|intent\b|notes\b|terminology\b)\S/.test(item);
|
|
248
|
+
const politeItem = item.replace(/^(?:(?:please|just|now|also|then|vui long|hay)\s+)+/, '');
|
|
249
|
+
const viPublish = /^(?:(?:su dung|phat hanh bang)\s+`?npm\s+publish\b|(?:dua|phat hanh|xuat ban|deploy|day)\b(?:[^.;\n]|\.(?=\d)){0,60}\b(?:len|to|vao)\s+npm\b)/.test(politeItem);
|
|
250
|
+
if (!negated && (releaseOrder || viPublish || (publication && (
|
|
251
|
+
(/^npm\s+publish\b/.test(politeItem) && (/^(?:[\s*•-]|\d+[.)])*(?:(?:please|just|now|also)\s+)*(?:npm\s+publish|run|execute|perform|commit|push|tag)\b/.test(hard.trim()) || /\bcontinue the release workflow:/.test(hard))) || PUBLISH_ORDER_IMPERATIVE.test(item)
|
|
252
|
+
|| /\b(?:chay|run)\s+`?npm\s+publish\b/.test(item)
|
|
253
|
+
|| /continue the release workflow:/.test(item))))) ordered = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return ordered;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const NOT_A_SERVICE = new Set(['it', 'this', 'that', 'them', 'the', 'all', 'everything', 'service', 'services', 'again', 'now', 'to', 'and', 'then']);
|
|
262
|
+
function nativeOperationGoal(domain, actionable, text) {
|
|
263
|
+
const goal = { domain, target: null, desiredState: 'verified', completionSemantics: 'execute-and-verify', requiredTargets: [] };
|
|
264
|
+
const clauses = actionable.join(' . ');
|
|
265
|
+
if (domain === 'git-integrity') {
|
|
266
|
+
const verbs = [...clauses.matchAll(/\bgit\s+(push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b/g)].map((m) => m[1]);
|
|
267
|
+
if (/\bpush\s+(?:to\s+)?origin\b/.test(clauses)) verbs.push('push');
|
|
268
|
+
for (const clause of actionable) {
|
|
269
|
+
const bare = clause.match(GIT_ORDER_LEAD);
|
|
270
|
+
if (bare) verbs.push(bare[1]);
|
|
271
|
+
}
|
|
272
|
+
goal.requiredTargets = [...new Set(verbs)];
|
|
273
|
+
goal.check = /\bfsck\b/.test(text) ? 'fsck' : /\bdiff\s+--check\b/.test(text) ? 'diff-check' : null;
|
|
274
|
+
if (actionable.some((clause) => PRIVATE_ORDER.test(String(clause).replace(/"[^"\n]*"|`[^`\n]*`/g, ' ')))) goal.visibility = 'PRIVATE';
|
|
275
|
+
return goal;
|
|
276
|
+
}
|
|
277
|
+
const services = [...clauses.matchAll(/\b(?:restart|redeploy|deploy|khoi dong lai|trien khai)\s+(?:the\s+|lai\s+)?([a-z0-9][\w.-]*)/g)]
|
|
278
|
+
.map((m) => m[1]).filter((name) => !NOT_A_SERVICE.has(name));
|
|
279
|
+
goal.requiredTargets = [...new Set(services)];
|
|
280
|
+
const probePath = text.match(/(?:^|\s)(\/[a-z0-9_.-]+(?:\/[a-z0-9_.-]+)*)/)?.[1] || null;
|
|
281
|
+
const probeStatus = text.match(/\b(?:returns?|returning|responds?(?:\s+with)?|http|status(?:\s+code)?)\s+([1-5]\d\d)\b/)?.[1] || null;
|
|
282
|
+
if (probePath || probeStatus) goal.probe = { path: probePath, status: probeStatus };
|
|
283
|
+
return goal;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
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';
|
|
287
|
+
const VERIFICATION_GOAL = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|kiem tra|validate|double-?check|re-?run)\b/;
|
|
288
|
+
const CONDITIONAL_BRANCH = /(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+(?:phat hien\s+(?:false-block|loi|bug|defect)|co\s+(?:source fix|sua doi))|if\s+(?:(?:a\s+)?(?:bug|defect|failure)\s+is\s+found|(?:you\s+)?find\s+(?:a\s+)?(?:bug|defect)))\b[^\n]*?(?=\.\s+|$|\n)/g;
|
|
289
|
+
const GOAL_LABEL = /\b(?:muc tieu|goal|purpose|objective)\s*:/g;
|
|
290
|
+
const DISPOSABLE_SETUP = /\b(?:tao|create)\s+(?:mot\s+)?(?:project|du an)\s+(?:thu nghiem|test|disposable|doc lap)\b(?:\s+(?:doc lap|independent))?/g;
|
|
291
|
+
const noVerbList = () => new RegExp(`\\bno\\s+(?:${ORDER_VERBS})\\b(?:\\s*,\\s*(?:${ORDER_VERBS})\\b)*`, 'g');
|
|
292
|
+
function neutralizeNonOrders(text) {
|
|
293
|
+
let source = publicationScope(text).text.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
294
|
+
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
295
|
+
// A labeled goal/acceptance span enumerates desired end states, so it is an order only when one of its own
|
|
296
|
+
// sentence fragments is imperative ("goal: ship it, then update the docs" orders; "goal: clean install and
|
|
297
|
+
// normal ukit update pass" does not — "update" there is a noun inside the acceptance criterion).
|
|
298
|
+
source = source.replace(/(^|\n|[.;]\s[ \t]*)([ \t*#_-]*(?:(?:final\s+)?(?:product\s+)?(?:muc tieu|goal|purpose|objective|acceptance criteria|tieu chi)\s*:))([^\n]*)/g,
|
|
299
|
+
(m, pre, label, content) => {
|
|
300
|
+
const frags = content.split(/\s*(?:[;,.]|\band\b|\bor\b|\bthen\b|\bva\b)\s*/).filter(Boolean);
|
|
301
|
+
return frags.some((f) => IMPERATIVE_ORDER.test(f) || MODAL_ORDER.test(f)) ? m : pre + label;
|
|
302
|
+
});
|
|
303
|
+
source = source
|
|
304
|
+
.replace(/\bno\s+git\s+(?:push|tag)\b(?:\s*\/\s*tag\b)?/g, ' ')
|
|
305
|
+
.replace(/\brelease\s+verifier\b/g, 'verification topic')
|
|
306
|
+
.replace(/\b(?:but|however)\s+(?=(?:run|execute|implement|modify|edit)\b)/g, '; ')
|
|
307
|
+
.replace(/\b(?:add|create|write)\s+(?:bounded\s+)?(?:independent\s+)?(?:adversarial\s+)?(?:replay\s+)?(?:variants|cases|tests|fixtures|evidence)\s+in\s+scratch\s+only\b/g, 'review replay');
|
|
308
|
+
const publicationList = '(?:ci|commit|push|tag|release|(?:npm\\s+)?publish)';
|
|
309
|
+
source = source.replace(new RegExp(`\\b(?:no|forbidden|prohibited|banned)\\s*:?\\s+${publicationList}\\b(?:\\s*(?:,|and|or)\\s*${publicationList}\\b)*`, 'g'), ' ');
|
|
310
|
+
return source.replace(noVerbList(), ' ')
|
|
311
|
+
.replace(/\brelease-(?:publication|verification)\b|\brelease\s+(?:instructions?|authorization|obligations?|policies|readiness)\b/g, 'publication topic')
|
|
312
|
+
.replace(/\brelease\s+(?:safety|verification\s+plan)\b/g, 'publication topic')
|
|
313
|
+
.replace(/^[ \t]*[-*+•][ \t]*owner\s+yeu\s+cau\b[^\n]*/gm, ' ')
|
|
314
|
+
.replace(/^([ \t]*[-*+•][ \t]*)release\s+progress\s+reporter(?=[ \t]*(?:,|$))/gm, '$1progress reporter')
|
|
315
|
+
.replace(/^[ \t]*(?:[-*+\u2022]|\d+[.)])[ \t]*(?:negative|positive)\s+controls?\b[^\n]*(?:\n(?![ \t]*(?:[-*+\u2022]|\d+[.)])|[ \t]*\n)[^\n]*)*/gm, ' ')
|
|
316
|
+
.replace(/(?:^|(?<=[.;][ \t]))[^.;\n]*\b(?:khong\s+(?:cho\s+phep|duoc|lam)|(?:does|do|did|is|are)\s+not\s+(?:allow|permit|authori[sz]e)\w*|not\s+(?:allowed|permitted|authori[sz]ed)|prohibitions?|prohibited(?:\s+actions?)?|forbidden(?:\s+actions?)?|banned|must\s+not\s+do)\s*:[ \t]*(?:\n[ \t]*(?:[-*+\u2022]|\d+[.)])[^\n]*)+/gm, ' ')
|
|
317
|
+
.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'), ' ')
|
|
318
|
+
.replace(new RegExp(`\\b(?:(?:tuyet\\s+doi\\s+)?khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
319
|
+
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
320
|
+
.replace(/\b(?:uu tien|prefer(?:red)?)\s+(?:a\s+|an\s+)?(?:configuration|config|cau hinh)\s+(?:change|changes|update|thay doi)\b/g, ' ')
|
|
321
|
+
.replace(/\b(?:neu\s+(?:chi\s+)?(?:phai|can|thieu|khong du|bang chung chua du)(?![^.;\n]*\b(?:restart|deploy|xoa|delete|remove|cancel)\b)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
|
|
322
|
+
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
323
|
+
.replace(/\bchinh\s+(?:xac|sach)\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b|\b(?:diem|cho|muc|phan)\s+(?:phai|can|nen)\s+(?:sua|fix)\b/g, ' ')
|
|
324
|
+
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
325
|
+
.replace(/\bwrite\s+permission\b/g, 'permission')
|
|
326
|
+
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
327
|
+
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
|
|
328
|
+
.replace(/(?:^|[\n.;])[ \t]*(?:[-*+•]|\d+[.)])?[ \t]*(?:outputs?|reports?|deliverables?|each|lists?|includes?|fields?|format|sections?|columns?)(?:\s+\w+){0,3}?\s*:(?:[^\n.;]|\.(?=\w))*/g,
|
|
329
|
+
(span, offset, whole) => {
|
|
330
|
+
const fixNouns = /\bfix(?:es)?(?:\s+applied\b|\s+(?:directions?|lists?|plans?|suggestions?|proposals?|items?|entries?|notes?|findings?|acceptance|recommendations?)\b)(?=\s*(?:,|$))|\bfixes\b(?=\s*(?:,|$))/g;
|
|
331
|
+
const residual = span.replace(fixNouns, ' ');
|
|
332
|
+
if (residual === span) return span;
|
|
333
|
+
if (new RegExp(`\\b(?:then|${NON_ORDER_VERBS}|${ORDER_VERBS})\\b`).test(residual)) return span;
|
|
334
|
+
const tail = whole.slice(offset + span.length);
|
|
335
|
+
if (tail.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/).some((clause) => CLAUSE_ORDER_LEAD.test(clause))) return span;
|
|
336
|
+
return residual;
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const READ_ONLY_DECLARATION = /\breviewer\s*[,—:-]?\s+read[\s_-]*only\b|\bread[\s_-]*only(?:\s+\w+){0,3}?\s+(?:audit|investigation|review|analysis|inspection|inventory|task|request|session|pass|check|diagnos\w+|verification|forensics?|reports?)\b|(?:^|\n)[\s*#>_-]*read[\s_-]*only\s*[.:!—-]|\b(?:audit|investigation|review|analysis|inventory)[\s-]*only\b|[(,]\s*read[\s_-]*only\s*[).]|\bchi\s+(?:doc|audit|kiem tra|kiem ke|phan tich|dieu tra)\b|\bkhong\s+sua\b|\b(?:make|with)\s+no\s+(?:source\s+|code\s+|file\s+)?(?:changes|edits|modifications)\b|\b(?:do\s+not|don't|dont|never)\s+(?:\w+\s+){0,2}edits?\b/;
|
|
341
|
+
const ORDER_VERBS = 'implement|fix|apply|refactor|modify|edit|write|update|add|create|build|install|deploy|redeploy|restart|push|commit|rename|remove|delete|replace|change|sua|viet|chinh|them|bo sung|tao|xoa|enable|disable|set|toggle|configure|migrate|upgrade|publish|release|trien khai|khoi dong lai|(?:run|execute)\\s+(?:git\\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)|(?:npm|yarn)\\s+publish)|git\\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)';
|
|
342
|
+
const IMPERATIVE_ORDER = new RegExp(`^(?:[-*+\\u2022>\\d.)\\s]*)(?:(?:please|hay|vui long|now|also|first|then)\\s+)*(?:${ORDER_VERBS})\\b\\s+\\S`);
|
|
343
|
+
const MODAL_ORDER = new RegExp(`\\b(?:please|hay|vui long|need to|needs to|must|should|want(?:\\s+you)? to|toi muon|can phai|yeu cau)\\s+(?:[\\w-]+\\s+){0,2}(?:${ORDER_VERBS})\\b`);
|
|
344
|
+
function hasExplicitOrder(clauses) {
|
|
345
|
+
return clauses.some((clause) => IMPERATIVE_ORDER.test(clause) || MODAL_ORDER.test(clause));
|
|
346
|
+
}
|
|
347
|
+
const CLAUSE_ORDER_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:commit|push(?!\s+back\b)|publish|release(?!\s+(?:notes?|process|works?|plan|candidate|checklist)\b)|apply|(?:run|execute)\s+(?:the\s+)?(?:implementation|build|migration|deploy(?:ment)?|publish|release|script|command))\b/;
|
|
348
|
+
const ADDITIVE_ORDER_LEAD = /^(?:[-*+\u2022>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:add|create|build|implement|them|bo sung|tao)\s+(?=[^.;\n]{0,80}?(?:\b(?:tests?|checks?|e2e|specs?|fixtures?|files?|modules?|functions?|features?|endpoints?|handlers?|resolvers?|parsers?|helpers?|scripts?|hooks?|options?|flags?|source|code)\b|\.[cm]?[jt]sx?\b))/;
|
|
349
|
+
function ordersChangeAcrossClauses(clauses) {
|
|
350
|
+
return clauses.some((clause) => CLAUSE_ORDER_LEAD.test(clause));
|
|
351
|
+
}
|
|
352
|
+
const GIT_ORDER_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|chi|now|also|first|then)\s+)*(commit|push(?!\s+back\b))\b/;
|
|
353
|
+
const PRIVATE_ORDER = /\b(?:make|set|keep|switch|change|lam|giu|chuyen|dat)\b[^.;\n]{0,40}\b(?:private|rieng tu|che do private)\b|\bvisibility\s+(?:to\s+|sang\s+)?private\b|\b(?:create|tao|push|publish|day)\b[^.;\n]{0,40}\b(?:private\s+(?:github\s+|git\s+)?(?:repo|repos|repository|remote)|(?:repo|repos|repository|remote)\s+(?:github\s+)?private)\b|\b(?:to|vao|len|tren)\s+(?:the\s+)?private\s+(?:repo|repository|remote)\b/;
|
|
354
|
+
function declaredReadOnly(promptText, releasePolicy) {
|
|
355
|
+
if (releasePolicy) return false;
|
|
356
|
+
const plain = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
357
|
+
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
358
|
+
if (!READ_ONLY_DECLARATION.test(plain)) return false;
|
|
359
|
+
const text = neutralizeNonOrders(plain);
|
|
360
|
+
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/)
|
|
361
|
+
.map((clause) => clause.split(/\bwithout\b/)[0])
|
|
362
|
+
.filter((clause) => !/\b(?:do not|don't|never|khong)\b|^[\s*+•>\d.)-]*(?:(?:vui long|xin|hay|please)\s+)*dung\b/.test(clause));
|
|
363
|
+
return /\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|investigation|inventory|forensic|root cause|kiem tra|kiem ke|phan tich|dieu tra|doc log|tim hieu)\b/.test(text.replace(/\bread[\s_-]*only\b/g, 'task mode'))
|
|
364
|
+
&& !hasExplicitOrder(clauses)
|
|
365
|
+
&& !ordersChangeAcrossClauses(clauses);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:(?:keep|continue|tiep tuc)\s+)?(?:verify|re-?run|confirm|xac nhan|validate|double-?check|wait(?:ing)?|monitor|watch|cho(?=\s+(?:dung\s+)?(?:job|verifier|build|test|suite|gate|ci|run|task|ket qua|xong|output)\b))\b/;
|
|
369
|
+
const CHANGE_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:make|rebuild|redo|rewrite|restore|revert|correct|clean|patch|resolve|ensure|run(?!(?=[^.]*\b(?:tests?|suite|gate|checks?|lint|typecheck|pilot|smoke|e2e|verif\w*|vitest|jest|audit)\b)))\b/;
|
|
370
|
+
export function isVerificationOnlyRequest(promptText) {
|
|
371
|
+
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
372
|
+
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
373
|
+
const clauses = neutralizeNonOrders(plain).split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b|\broi\b)\s*/)
|
|
374
|
+
.map((clause) => clause.split(/\bwithout\b/)[0]);
|
|
375
|
+
return clauses.some((clause) => VERIFY_LEAD.test(clause)) && !hasExplicitOrder(clauses)
|
|
376
|
+
&& !clauses.some((clause) => CHANGE_LEAD.test(clause));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function neutralizeBannedLists(text) {
|
|
380
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin|commit|push)';
|
|
381
|
+
const LIST = `${OP}(?:\\s*(?:,|/|\\band\\b|\\bor\\b|\\bva\\b|\\bhoac\\b|\\balso\\b)\\s*${OP})*`;
|
|
382
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
383
|
+
const LIFT = /\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/;
|
|
384
|
+
const span = new RegExp(`\\b(?:(?:forbidden|prohibited|banned)\\s*:\\s*${LIST}|${LIST}\\s*:?\\s*(?:(?:is|are|remains?|van|con)\\s+)?(?:both\\s+)?${BAN})\\b`, 'g');
|
|
385
|
+
return text.replace(span, (m, offset, whole) => {
|
|
386
|
+
const start = Math.max(whole.lastIndexOf('.', offset), whole.lastIndexOf(';', offset), whole.lastIndexOf('\n', offset)) + 1;
|
|
387
|
+
const stops = [whole.indexOf('.', offset + m.length), whole.indexOf(';', offset + m.length), whole.indexOf('\n', offset + m.length)].filter((i) => i >= 0);
|
|
388
|
+
const end = stops.length ? Math.min(...stops) : whole.length;
|
|
389
|
+
return LIFT.test(whole.slice(start, end)) ? m : ' ';
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function classifyTaskSemantics(input = {}) {
|
|
394
|
+
return analyzeTaskIntent(input).semantic;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function actionableClauses(promptText) {
|
|
398
|
+
const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
|
|
399
|
+
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
400
|
+
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong)\b|^[\s*+•>\d.)-]*(?:(?:vui long|xin|hay|please)\s+)*dung\b/.test(clause)
|
|
401
|
+
&& !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause)
|
|
402
|
+
// A fragment led by a subordinator continues the previous clause's predicate ("…, including source
|
|
403
|
+
// fixes F-3" is prose about the past, never a new imperative).
|
|
404
|
+
&& !/^[\s*+•>\d.)-]*(?:including|such as|e\.g\.|i\.e\.|via|per)\b/.test(clause));
|
|
405
|
+
return { text, clauses, actionable };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Positive additive-order signal (add/create/bo sung ... <code or test artifact>) for callers that must not treat the
|
|
409
|
+
// classifier's CODE_IMPLEMENTATION default as an order (a bare "ok" is that default, not a change order).
|
|
410
|
+
export function ordersAdditiveChange(promptText = '') {
|
|
411
|
+
return actionableClauses(publicationScope(promptText).text).actionable.some((clause) => ADDITIVE_ORDER_LEAD.test(clause));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function classifySemanticText({ promptText = '', targetFile = '' } = {}) {
|
|
415
|
+
promptText = publicationScope(promptText).text;
|
|
416
|
+
const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
417
|
+
const preflight = releaseText.match(/\b(?:yarn\s+release:verify|npm\s+run\s+release:verify)\b/)?.[0];
|
|
418
|
+
const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
|
|
419
|
+
&& /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
|
|
420
|
+
? { kind: 'conditional-release', preflight } : null;
|
|
421
|
+
const { text, clauses, actionable } = actionableClauses(promptText);
|
|
422
|
+
if (ordersPublicationText(promptText)) {
|
|
423
|
+
const implementation = hasExplicitOrder(clauses.filter((clause) => !/\b(?:release|publish)\b/.test(clause)));
|
|
424
|
+
const remaining = clauses.filter((clause) => !ordersPublicationText(clause) && !/\b(?:release|publish)\b/.test(clause)).join('; ');
|
|
425
|
+
const base = remaining.trim() ? classifySemanticText({ promptText: remaining, targetFile }) : {};
|
|
426
|
+
delete base.verificationOnly;
|
|
427
|
+
delete base.statusReport;
|
|
428
|
+
return { ...base, taskKind: implementation ? 'MIXED' : 'OPERATIONAL', publication: true, ...(releasePolicy ? { releasePolicy } : {}) };
|
|
429
|
+
}
|
|
430
|
+
if (!/[a-z0-9]/.test(text)) return { taskKind: 'READ_ONLY' };
|
|
431
|
+
if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY', ...(isVerificationOnlyRequest(promptText) ? { verificationOnly: true } : {}) };
|
|
432
|
+
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
433
|
+
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
434
|
+
const codeChangeVerb = actionable.some((clause) => !/^(?:run\s+|execute\s+)?git\s+/.test(clause) && /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
|
|
435
|
+
const reportArtifact = String(promptText).match(/(?:^|\s)((?:docs\/|reports\/)[A-Za-z0-9_./-]+\.(?:md|txt))(?=\s|[.,;:]|$)/)?.[1];
|
|
436
|
+
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))
|
|
437
|
+
&& /\b(?:report|bao cao|analysis|phan tich|document)\b/.test(text);
|
|
438
|
+
const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
|
|
439
|
+
const cronOperation = actionable.some((clause) => /\b(?:delete|remove|cancel|xoa|huy|ensure|dam bao)\b/.test(clause)
|
|
440
|
+
&& /\bcron\b|scheduled\s+(?:cron\s+)?(?:task|job)|tac vu\s+(?:cron|dinh ky)/.test(clause))
|
|
441
|
+
&& (!codeChangeVerb || hasOperationId);
|
|
442
|
+
const operationNotOrdered = (clause) => {
|
|
443
|
+
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin|commit|push)';
|
|
444
|
+
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
445
|
+
if (/\b(?:was|were|previously|before|formerly|no\s+longer|even\s+though|although|though|now\s+allows?|now\s+permits?|owner\s+allows?|khong\s+bi|da\s+duoc\s+phep|except)\b/.test(clause)) return false;
|
|
446
|
+
if (/^[\s*+•>\d.)-]*(?:production|prod|staging)\s+(?:restart|redeploy|deploy(?:ment)?)\s*$/.test(clause)) return true;
|
|
447
|
+
const gap = `(?:(?!\\b(?:which|that|who|where|when|if|and|but|then)\\b|${OP})[^.;,()\\-:]){0,40}?`;
|
|
448
|
+
const banAfter = new RegExp(`^${gap}\\b(?:(?:is|are|remains?|van|con)\\s+)?${BAN}\\b(?!\\s+(?:to|from)\\b)`);
|
|
449
|
+
const joined = new RegExp(`^\\s*(?:/|,|and|or|va|hoac)\\s*${OP}\\b`);
|
|
450
|
+
const bannedAfter = (rest) => {
|
|
451
|
+
if (banAfter.test(rest)) return true;
|
|
452
|
+
const j = joined.exec(rest);
|
|
453
|
+
return j ? bannedAfter(rest.slice(j[0].length)) : false;
|
|
454
|
+
};
|
|
455
|
+
const banBefore = /\b(?:forbidden|prohibited|banned)\s+(?:(?:production|prod)\s+)?$/;
|
|
456
|
+
let total = 0;
|
|
457
|
+
let banned = 0;
|
|
458
|
+
for (const match of clause.matchAll(new RegExp(`\\b${OP}\\b`, 'g'))) {
|
|
459
|
+
total += 1;
|
|
460
|
+
if (bannedAfter(clause.slice(match.index + match[0].length)) || banBefore.test(clause.slice(0, match.index))) banned += 1;
|
|
461
|
+
}
|
|
462
|
+
return total > 0 && banned === total;
|
|
463
|
+
};
|
|
464
|
+
const gitOperation = actionable.some((clause) => !operationNotOrdered(clause)
|
|
465
|
+
&& (GIT_ORDER_LEAD.test(clause) || /\bgit\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b|\bpush\s+(?:to\s+)?origin\b/.test(clause)));
|
|
466
|
+
const runtimeOperation = actionable.some((clause) => !operationNotOrdered(clause) && /(?<!\b(?:process|crash|hard|soft|graceful|after|upon|during|across|following)\s)\b(?:restart|redeploy|deploy|khoi dong lai|trien khai\s+(?:len\s+)?(?:service|server|production|prod|staging|dich vu|may chu|moi truong))\b/.test(clause)
|
|
467
|
+
&& !/\.[cm]?[jt]sx?\b/.test(clause) && !/\bgit\b/.test(clause));
|
|
468
|
+
const nativeDomain = cronOperation ? 'scheduled-job' : gitOperation !== runtimeOperation ? (gitOperation ? 'git-integrity' : 'service-runtime') : null;
|
|
469
|
+
const operation = Boolean(nativeDomain);
|
|
470
|
+
if (!releasePolicy && !operation && isVerificationOnlyRequest(promptText)) return { taskKind: 'READ_ONLY', verificationOnly: true };
|
|
471
|
+
const CONFIG_CHANGE_REPORT_CLAUSE = /\b(?:da|has|have|had|already)\s+(?:doi|thay doi|changed?|updated?)\b|\b(?:nao|gi|which|what|report|bao cao)\b[^.;]*?\b(?:doi|thay doi|changed?|updated?)\b/;
|
|
472
|
+
const CONFIG_MUTATION_VERB = /\b(?:set|change|toggle|enable|disable|switch|turn|configure|modify|update|edit|doi(?!\s+chieu\b)|bat(?!\s+dau\b)|tat(?!\s+ca\b)|chinh)\b/;
|
|
473
|
+
const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
|
|
474
|
+
const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
|
|
475
|
+
const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
|
|
476
|
+
const config = !sourceOrder && actionable.some((clause) => {
|
|
477
|
+
if (!CONFIG_NOUN.test(clause) || !CONFIG_MUTATION_VERB.test(clause)) return false;
|
|
478
|
+
if (CONFIG_REPORT_FRAME.test(clause)) return false;
|
|
479
|
+
const verb = CONFIG_MUTATION_VERB.exec(clause);
|
|
480
|
+
if (CONFIG_QUESTION_FRAME_PREFIX.test(clause.slice(0, verb.index))) return false;
|
|
481
|
+
const noun = CONFIG_NOUN.exec(clause);
|
|
482
|
+
return !CONFIG_CHANGE_REPORT_CLAUSE.test(clause) || Boolean(noun && noun.index > verb.index);
|
|
483
|
+
});
|
|
484
|
+
const repository = Boolean(targetFile) || /[\w.-]+\.(?:json|ya?ml|toml|ini)\b/.test(text);
|
|
485
|
+
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);
|
|
486
|
+
const ACTIONABLE_MUTATION_ORDER = /(?<!\b(?:what|which|how|nao|gi)\s)(?<!thay )\b(?:implement|fix(?:es)?|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/;
|
|
487
|
+
const configReportQuestion = !actionable.some((clause) => ACTIONABLE_MUTATION_ORDER.test(clause))
|
|
488
|
+
&& actionable.some((clause) => /\b(?:configuration|config|toggle|cau hinh|tham so)\b/.test(clause)
|
|
489
|
+
&& CONFIG_CHANGE_REPORT_CLAUSE.test(clause));
|
|
490
|
+
const statusReport = (configReportQuestion || (!mutationWord
|
|
491
|
+
&& /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text)))
|
|
492
|
+
&& !operation && !config;
|
|
493
|
+
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.replace(/\bread[\s-]*only\b/g, ' ')) || statusReport)
|
|
494
|
+
// An additive imperative (add/create/build/them/bo sung/tao) leading its own clause is an implementation order even
|
|
495
|
+
// when a sibling clause only reviews or checks, provided its object is a code/test/check artifact (a report such as
|
|
496
|
+
// "create a timeline" stays read-only); descriptive mentions never lead a clause.
|
|
497
|
+
&& !sourceOrder && !codeChangeVerb && !operation && !config && !ordersChangeAcrossClauses(actionable)
|
|
498
|
+
&& !actionable.some((clause) => ADDITIVE_ORDER_LEAD.test(clause));
|
|
499
|
+
const taskKind = operation ? (sourceOrder || codeChangeVerb ? 'MIXED' : 'OPERATIONAL')
|
|
500
|
+
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
501
|
+
const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
|
|
502
|
+
return { taskKind, ...(releasePolicy ? { releasePolicy } : {}), ...(documentation ? { reportArtifact } : {}), ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
|
|
503
|
+
...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
|
|
504
|
+
...(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',
|
|
505
|
+
completionSemantics: /\b(?:ensure|dam bao)\b/.test(text)
|
|
506
|
+
|| actionable.some((clause) => /\b(?:delete|remove|cancel|xoa|huy)\b/.test(clause)
|
|
507
|
+
&& /\b(?:if\s+(?:it\s+)?(?:still\s+)?exists|if\s+present|neu\s+(?:van\s+|con\s+)?ton tai)\b/.test(clause))
|
|
508
|
+
? 'ensure-state' : 'execute-and-verify' } } : {}) };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function applyTaskSemantics(contract, semantic) {
|
|
512
|
+
const evidence = contract?.completionEvidence || [];
|
|
513
|
+
if (semantic.taskKind === 'READ_ONLY' && semantic.conditionalObligations?.length) {
|
|
514
|
+
return { ...(contract || {}), ...semantic, statusReport: false, completionEvidence: ['verification-evidence'] };
|
|
515
|
+
}
|
|
516
|
+
if (semantic.publication) return { ...(contract || {}), ...semantic, completionEvidence: semantic.taskKind === 'MIXED'
|
|
517
|
+
? [...new Set([...evidence.filter((item) => item !== 'impact-evidence'), 'write-evidence', ...(semantic.operationGoal ? ['operation-verification'] : ['verification-evidence']), 'release-publication'])] : ['release-publication'] };
|
|
518
|
+
const operationOnly = semantic.taskKind === 'OPERATIONAL'
|
|
519
|
+
|| (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
|
|
520
|
+
return { ...(contract || {}), ...semantic,
|
|
521
|
+
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
522
|
+
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
523
|
+
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
524
|
+
: semantic.taskKind === 'READ_ONLY' ? (semantic.verificationOnly ? ['verification-evidence'] : ['impact-evidence'])
|
|
525
|
+
// A routed contract owns the debt; an unrouted (informational/null) turn carries none, because
|
|
526
|
+
// CODE_IMPLEMENTATION is the classifier's default kind, not proof that a change was ordered.
|
|
527
|
+
: semantic.taskKind === 'CODE_IMPLEMENTATION' && contract ? [...new Set([...evidence, 'write-evidence', 'verification-evidence'])] : evidence };
|
|
528
|
+
}
|