@ngockhoale/ukit 3.4.69 → 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 +10 -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/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
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Every routing decision that is keyed on an execution contract derives from the tables in
|
|
4
|
-
// this file:
|
|
5
|
-
// - src/index/taskRouting.js (in-process routing: contracts + risk profiles + mode order)
|
|
6
|
-
// - src/core/runtimeConfig.js (config defaults: contracts minus runtime-only keys)
|
|
7
|
-
// - template_project/ukit/storage/config.json (literal mirror of the config defaults)
|
|
8
|
-
// - template_project/.claude/ukit/index/route-task.mjs (shipped standalone CLI: contracts + tiers)
|
|
9
|
-
//
|
|
10
|
-
// route-task.mjs runs inside target projects and cannot import from src/, so it keeps a
|
|
11
|
-
// literal copy — tests/consistency/executionContractSync.test.js locks that copy to these
|
|
12
|
-
// tables so the four can never drift again (the local-build completionEvidence drift that
|
|
13
|
-
// motivated this module is the failure mode it exists to prevent).
|
|
1
|
+
export { classifyTaskSemantics, applyTaskSemantics, isVerificationOnlyRequest } from '../../template_project/.claude/ukit/index/publication-intent.mjs';
|
|
14
2
|
|
|
15
3
|
export const EXECUTION_MODE_ORDER = [
|
|
16
4
|
'tiny-fix',
|
|
@@ -90,13 +78,8 @@ export const MODEL_TIER_BY_CONTRACT = {
|
|
|
90
78
|
'review-release': 'smart',
|
|
91
79
|
};
|
|
92
80
|
|
|
93
|
-
// Decision table v2 (V3_RESHAPE §5): MODEL_TIER_BY_CONTRACT stays the base row;
|
|
94
|
-
// resolveModelTier adds the (riskFloor, hostCapabilities) columns. Deterministic —
|
|
95
|
-
// no registry, no leases, no outbound capability calls.
|
|
96
81
|
export const ROUTE_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
97
82
|
|
|
98
|
-
// Tier, effort and risk are independent decisions. Effort is Medium for every tier (Low for chores);
|
|
99
|
-
// High needs complexity evidence (`complexityEvidence` below), never risk or tier alone.
|
|
100
83
|
const MODEL_TIER_EFFORT = {
|
|
101
84
|
lite: 'low',
|
|
102
85
|
code: 'medium',
|
|
@@ -168,294 +151,3 @@ export function buildConfigContracts() {
|
|
|
168
151
|
Object.entries(EXECUTION_CONTRACTS).map(([mode, contract]) => [mode, stripContractForConfig(contract)])
|
|
169
152
|
);
|
|
170
153
|
}
|
|
171
|
-
|
|
172
|
-
// What exactly was ordered, so a receipt for a DIFFERENT verb/service/probe cannot discharge it.
|
|
173
|
-
// Unnamed specifics stay empty (any proven receipt of the domain counts); named ones are required.
|
|
174
|
-
const NOT_A_SERVICE = new Set(['it', 'this', 'that', 'them', 'the', 'all', 'everything', 'service', 'services', 'again', 'now', 'to', 'and', 'then']);
|
|
175
|
-
function nativeOperationGoal(domain, actionable, text) {
|
|
176
|
-
const goal = { domain, target: null, desiredState: 'verified', completionSemantics: 'execute-and-verify', requiredTargets: [] };
|
|
177
|
-
const clauses = actionable.join(' . ');
|
|
178
|
-
if (domain === 'git-integrity') {
|
|
179
|
-
const verbs = [...clauses.matchAll(/\bgit\s+(push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b/g)].map((m) => m[1]);
|
|
180
|
-
if (/\bpush\s+(?:to\s+)?origin\b/.test(clauses)) verbs.push('push');
|
|
181
|
-
goal.requiredTargets = [...new Set(verbs)];
|
|
182
|
-
goal.check = /\bfsck\b/.test(text) ? 'fsck' : /\bdiff\s+--check\b/.test(text) ? 'diff-check' : null;
|
|
183
|
-
return goal;
|
|
184
|
-
}
|
|
185
|
-
const services = [...clauses.matchAll(/\b(?:restart|redeploy|deploy|khoi dong lai|trien khai)\s+(?:the\s+|lai\s+)?([a-z0-9][\w.-]*)/g)]
|
|
186
|
-
.map((m) => m[1]).filter((name) => !NOT_A_SERVICE.has(name));
|
|
187
|
-
goal.requiredTargets = [...new Set(services)];
|
|
188
|
-
const probePath = text.match(/(?:^|\s)(\/[a-z0-9_.-]+(?:\/[a-z0-9_.-]+)*)/)?.[1] || null;
|
|
189
|
-
const probeStatus = text.match(/\b(?:returns?|returning|responds?(?:\s+with)?|http|status(?:\s+code)?)\s+([1-5]\d\d)\b/)?.[1] || null;
|
|
190
|
-
if (probePath || probeStatus) goal.probe = { path: probePath, status: probeStatus };
|
|
191
|
-
return goal;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Task semantics are independent of source execution strategy and observed progress.
|
|
195
|
-
// P1-A (forensic false block): the clause splitter below cuts at commas, so a negation only reached the
|
|
196
|
-
// FIRST item of an enumeration ("Do not implement fixes, modify source code, restart services, ...") and
|
|
197
|
-
// the later items became actionable orders (MIXED + service-runtime). English negators now scope to the
|
|
198
|
-
// sentence end (', and'/', or' continue the list; a bare 'and <verb>' or but/then starts a new clause);
|
|
199
|
-
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
200
|
-
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
201
|
-
const NON_ORDER_VERBS = 'implement|apply|update|modify|add|create|ship|deliver|fix|refactor|remove|delete|rename|change|write|build|make|install|run|deploy|execute|edit|restart|sua|viet|chinh|them|tao|xoa';
|
|
202
|
-
// Declared verification purpose ("Mục tiêu: xác nhận ...", "Goal: verify ..."). Under that declaration a
|
|
203
|
-
// conditional repair/release branch ("Nếu phát hiện lỗi ... sửa ...", "Neu co source fix ... publish") is a
|
|
204
|
-
// branch the check MAY take, not an unconditional order, and disposable test-project setup ("Tạo project thử
|
|
205
|
-
// nghiệm độc lập") is scaffolding of the check. Unconditional orders in the same request keep blocking.
|
|
206
|
-
const VERIFICATION_GOAL = /(?:^|\n)[ \t*#_-]*(?:muc tieu|goal|purpose|objective)\s*:[^\n]*\b(?:xac nhan|verify|confirm|kiem tra|validate|double-?check|re-?run)\b/;
|
|
207
|
-
const CONDITIONAL_BRANCH = /(?:^|\n|\.\s+)[ \t]*(?:\d+[.)]\s*)?(?:neu\s+(?:phat hien\s+(?:false-block|loi|bug|defect)|co\s+(?:source fix|sua doi))|if\s+(?:(?:a\s+)?(?:bug|defect|failure)\s+is\s+found|(?:you\s+)?find\s+(?:a\s+)?(?:bug|defect)))\b[^\n]*?(?=\.\s+|$|\n)/g;
|
|
208
|
-
const GOAL_LABEL = /\b(?:muc tieu|goal|purpose|objective)\s*:/g;
|
|
209
|
-
const DISPOSABLE_SETUP = /\b(?:tao|create)\s+(?:mot\s+)?(?:project|du an)\s+(?:thu nghiem|test|disposable|doc lap)\b(?:\s+(?:doc lap|independent))?/g;
|
|
210
|
-
// Round-2b (FS-01 B5): a bare "no <verb>, <verb>, ..." enumeration names operations that are NOT
|
|
211
|
-
// ordered ("no commit, release, publish"). The clause splitter cuts at commas, so without this the
|
|
212
|
-
// tails ("release", "publish") would look like separate imperative clauses to the request-wide order
|
|
213
|
-
// guard. Only a "no"-headed verb list is consumed; a real order never starts with "no". Built lazily
|
|
214
|
-
// because ORDER_VERBS is declared below this function.
|
|
215
|
-
const noVerbList = () => new RegExp(`\\bno\\s+(?:${ORDER_VERBS})\\b(?:\\s*,\\s*(?:${ORDER_VERBS})\\b)*`, 'g');
|
|
216
|
-
function neutralizeNonOrders(text) {
|
|
217
|
-
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
218
|
-
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
219
|
-
return source.replace(noVerbList(), ' ')
|
|
220
|
-
// A list item that DESCRIBES test cases ("Negative controls: yêu cầu thực sự chỉnh config/restart/deploy ...") is
|
|
221
|
-
// specification of the work, not an order; it runs to the next bullet or blank line.
|
|
222
|
-
.replace(/^[ \t]*(?:[-*+\u2022]|\d+[.)])[ \t]*(?:negative|positive)\s+controls?\b[^\n]*(?:\n(?![ \t]*(?:[-*+\u2022]|\d+[.)])|[ \t]*\n)[^\n]*)*/gm, ' ')
|
|
223
|
-
// A header that negates the list under it ("Authorization ... không cho phép:", "Prohibitions:", "not allowed:")
|
|
224
|
-
// makes every bullet an item of the prohibition, not an order.
|
|
225
|
-
.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, ' ')
|
|
226
|
-
.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'), ' ')
|
|
227
|
-
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
228
|
-
.replace(/"[^"\n]*\s[^"\n]*"|`[^`\n]*\s[^`\n]*`|\u201c[^\u201d\n]*\u201d/g, ' ')
|
|
229
|
-
.replace(/\b(?:uu tien|prefer(?:red)?)\s+(?:a\s+|an\s+)?(?:configuration|config|cau hinh)\s+(?:change|changes|update|thay doi)\b/g, ' ')
|
|
230
|
-
.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, ' ')
|
|
231
|
-
.replace(/[^.;\n]*\b(?:nao|gi|sao|the nao|bao nhieu)\b[^.;\n]*\?/g, ' ')
|
|
232
|
-
.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, ' ')
|
|
233
|
-
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
234
|
-
.replace(/\bwrite\s+permission\b/g, 'permission')
|
|
235
|
-
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
236
|
-
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ')
|
|
237
|
-
// An output/deliverable spec ("Output: fix direction", "each: title, fix direction", "Output:
|
|
238
|
-
// fixes / fix list / acceptance to close") NAMES the artifacts the answer must contain; inside
|
|
239
|
-
// such a clause "fix" is a deliverable noun, not a mutation order, so a read-only audit with a
|
|
240
|
-
// fix-shaped deliverable list must not owe write evidence (FS-07, src twin of the FS-01 fix in
|
|
241
|
-
// route-resolver.mjs). A determiner object inside the span ("Output: fix the bug in parser.ts")
|
|
242
|
-
// stays a real order - fail closed.
|
|
243
|
-
// Round-2 (FS-01 B1-B4, src twin): a span runs through a file-extension dot ("fix items.ts"), a bare
|
|
244
|
-
// singular "fix" is an order (never a noun), and a span keeps its order unless EVERY fix token in it is a
|
|
245
|
-
// deliverable noun and no order verb / "then" remains in the clause ("fix list, commit and push").
|
|
246
|
-
.replace(/(?:^|[\n.;])[ \t]*(?:[-*+•]|\d+[.)])?[ \t]*(?:outputs?|reports?|deliverables?|each|lists?|includes?|fields?|format|sections?|columns?)(?:\s+\w+){0,3}?\s*:(?:[^\n.;]|\.(?=\w))*/g,
|
|
247
|
-
(span) => {
|
|
248
|
-
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;
|
|
249
|
-
const residual = span.replace(fixNouns, ' ');
|
|
250
|
-
if (residual === span) return span;
|
|
251
|
-
if (new RegExp(`\\b(?:then|${NON_ORDER_VERBS}|${ORDER_VERBS})\\b`).test(residual)) return span;
|
|
252
|
-
return residual;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Explicit scope declaration (intent ownership): when the request itself declares it is read-only
|
|
257
|
-
// ("READ-ONLY audit", "chi doc", "make no source changes") and carries no explicit order to change
|
|
258
|
-
// anything, verbs and operation names inside it are descriptions (a "git stash" that already ran, "a
|
|
259
|
-
// worklog edit", "confirmed fixes"), not orders - so they must not demand write or operation proof.
|
|
260
|
-
// An explicit order (imperative-initial or modal request) keeps the request MIXED/IMPLEMENTATION:
|
|
261
|
-
// the declaration never overrides a real instruction, and fenced/quoted/blockquoted text is never one.
|
|
262
|
-
// A bare verb with no object is the tail of a negated list ("no commit, release, publish"), not an order.
|
|
263
|
-
const READ_ONLY_DECLARATION = /\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/;
|
|
264
|
-
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|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)';
|
|
265
|
-
const IMPERATIVE_ORDER = new RegExp(`^(?:[-*+\\u2022>\\d.)\\s]*)(?:(?:please|hay|vui long|now|also|first|then)\\s+)*(?:${ORDER_VERBS})\\b\\s+\\S`);
|
|
266
|
-
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`);
|
|
267
|
-
function hasExplicitOrder(clauses) {
|
|
268
|
-
return clauses.some((clause) => IMPERATIVE_ORDER.test(clause) || MODAL_ORDER.test(clause));
|
|
269
|
-
}
|
|
270
|
-
// Round-2b (FS-01 B5): a clause AFTER the output span still orders a change ("Output: fix lists\ncommit
|
|
271
|
-
// the changes", "Output: fix lists; commit"). Order detection must be request-wide, not span-local: any
|
|
272
|
-
// non-negated clause whose command position is an ORDER verb - with or without an object, so a bare
|
|
273
|
-
// trailing "commit" counts - is an explicit order. The vocabulary is the order class the read-only
|
|
274
|
-
// declaration must never absorb (git/publish/release/apply/run-implementation); noun-ish scaffolding
|
|
275
|
-
// ("tao timeline ...", "create a list") is NOT an order and must not defeat a read-only audit.
|
|
276
|
-
// Negated lists ("no commit, release, publish", "Khong npm publish, git push, release") are blanked
|
|
277
|
-
// first by neutralizeNonOrders, so their bare tails never read as orders.
|
|
278
|
-
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/;
|
|
279
|
-
function ordersChangeAcrossClauses(clauses) {
|
|
280
|
-
return clauses.some((clause) => CLAUSE_ORDER_LEAD.test(clause));
|
|
281
|
-
}
|
|
282
|
-
function declaredReadOnly(promptText, releasePolicy) {
|
|
283
|
-
if (releasePolicy) return false;
|
|
284
|
-
const plain = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
285
|
-
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
286
|
-
if (!READ_ONLY_DECLARATION.test(plain)) return false;
|
|
287
|
-
const text = neutralizeNonOrders(plain);
|
|
288
|
-
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/)
|
|
289
|
-
.map((clause) => clause.split(/\bwithout\b/)[0])
|
|
290
|
-
.filter((clause) => !/\b(?:do not|don't|never|khong)\b|^[\s*+•>\d.)-]*(?:(?:vui long|xin|hay|please)\s+)*dung\b/.test(clause));
|
|
291
|
-
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)
|
|
292
|
-
&& !hasExplicitOrder(clauses)
|
|
293
|
-
&& !ordersChangeAcrossClauses(clauses);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Verification-only request. Root cause of a false `write-evidence` block on a "verify / re-run /
|
|
297
|
-
// confirm" prompt: the vocabulary scans counted nouns and compounds ("coordinator fix", "re-run",
|
|
298
|
-
// "template/install", "source change") as mutation orders. Order is structural: a request whose
|
|
299
|
-
// command-position verbs are verification verbs, with no explicit order (hasExplicitOrder) and no
|
|
300
|
-
// change-leading clause, orders no change - it owes verification evidence, not a write.
|
|
301
|
-
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/;
|
|
302
|
-
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/;
|
|
303
|
-
export function isVerificationOnlyRequest(promptText) {
|
|
304
|
-
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
305
|
-
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
306
|
-
const clauses = neutralizeNonOrders(plain).split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b|\broi\b)\s*/)
|
|
307
|
-
.map((clause) => clause.split(/\bwithout\b/)[0]);
|
|
308
|
-
return clauses.some((clause) => VERIFY_LEAD.test(clause)) && !hasExplicitOrder(clauses)
|
|
309
|
-
&& !clauses.some((clause) => CHANGE_LEAD.test(clause));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// "Deploy and restart are banned", "Forbidden: deploy", "deploy: banned" name operations to prohibit them. The clause split on
|
|
313
|
-
// "and"/commas would separate the ban from the first operation, so a banned operation list is removed before splitting.
|
|
314
|
-
function neutralizeBannedLists(text) {
|
|
315
|
-
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
316
|
-
const LIST = `${OP}(?:\\s*(?:,|/|\\band\\b|\\bor\\b|\\bva\\b|\\bhoac\\b|\\balso\\b)\\s*${OP})*`;
|
|
317
|
-
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
318
|
-
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/;
|
|
319
|
-
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');
|
|
320
|
-
return text.replace(span, (m, offset, whole) => {
|
|
321
|
-
const start = Math.max(whole.lastIndexOf('.', offset), whole.lastIndexOf(';', offset), whole.lastIndexOf('\n', offset)) + 1;
|
|
322
|
-
const stops = [whole.indexOf('.', offset + m.length), whole.indexOf(';', offset + m.length), whole.indexOf('\n', offset + m.length)].filter((i) => i >= 0);
|
|
323
|
-
const end = stops.length ? Math.min(...stops) : whole.length;
|
|
324
|
-
return LIFT.test(whole.slice(start, end)) ? m : ' ';
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
329
|
-
// A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
|
|
330
|
-
const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
331
|
-
const preflight = releaseText.match(/\b(?:yarn\s+release:verify|npm\s+run\s+release:verify)\b/)?.[0];
|
|
332
|
-
const releasePolicy = preflight && /\b(?:if|neu|only when|only if)\b[^\n.;]*(?:pass|go)\b[^\n.;]*(?:release|publish)\b/.test(releaseText)
|
|
333
|
-
&& /\b(?:fail|no-go)\b[^\n.;]*(?:do not publish|don't publish|no publish|khong publish|khong npm publish)\b/.test(releaseText)
|
|
334
|
-
? { kind: 'conditional-release', preflight } : null;
|
|
335
|
-
const text = neutralizeBannedLists(neutralizeNonOrders(String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()));
|
|
336
|
-
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
337
|
-
// Folded đúng (correctly) is also "dung"; only command-position đừng negates an order.
|
|
338
|
-
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)
|
|
339
|
-
&& !/^[\s*+•>\d.)-]*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
340
|
-
if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
|
|
341
|
-
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
342
|
-
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
343
|
-
const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
|
|
344
|
-
const reportArtifact = String(promptText).match(/(?:^|\s)((?:docs\/|reports\/)[A-Za-z0-9_./-]+\.(?:md|txt))(?=\s|[.,;:]|$)/)?.[1];
|
|
345
|
-
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))
|
|
346
|
-
&& /\b(?:report|bao cao|analysis|phan tich|document)\b/.test(text);
|
|
347
|
-
const hasOperationId = /\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/.test(text);
|
|
348
|
-
// A cron/job MENTION is not an operation: with a code-change verb in the request, an
|
|
349
|
-
// operation needs a concrete target id; otherwise it is a coding task about scheduling.
|
|
350
|
-
const cronOperation = actionable.some((clause) => /\b(?:delete|remove|cancel|xoa|huy|ensure|dam bao)\b/.test(clause)
|
|
351
|
-
&& /\bcron\b|scheduled\s+(?:cron\s+)?(?:task|job)|tac vu\s+(?:cron|dinh ky)/.test(clause))
|
|
352
|
-
&& (!codeChangeVerb || hasOperationId);
|
|
353
|
-
// Non-source operations (git history/push, service restart/deploy): ordered only by an
|
|
354
|
-
// actionable clause (negated/deferred/audit clauses are already filtered out). Git and
|
|
355
|
-
// runtime are separate domains with separate proof; a prompt naming both stays a coding
|
|
356
|
-
// task rather than being half-verified. A code-change verb anywhere keeps write evidence.
|
|
357
|
-
// A clause that STATES an operation is prohibited ("production deploy vẫn bị cấm", "forbidden deploy") or merely lists a
|
|
358
|
-
// production operation as a bare noun item ("* Production restart.") names it; it does not order it.
|
|
359
|
-
const operationNotOrdered = (clause) => {
|
|
360
|
-
const OP = '(?:restart|redeploy|deploy(?:ment)?|trien khai|khoi dong lai|git\\s+push|push\\s+(?:to\\s+)?origin)';
|
|
361
|
-
const BAN = '(?:bi\\s+(?:cam|chan)|forbidden|prohibited|banned|not\\s+allowed|not\\s+permitted|disallowed)';
|
|
362
|
-
// A lift or past marker ("was prohibited, owner now allows", "even though it is not allowed", "khong bi chan")
|
|
363
|
-
// means the prohibition is history or lifted: the clause still orders the operation.
|
|
364
|
-
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;
|
|
365
|
-
if (/^[\s*+•>\d.)-]*(?:production|prod|staging)\s+(?:restart|redeploy|deploy(?:ment)?)\s*$/.test(clause)) return true;
|
|
366
|
-
// Evaluated per operation occurrence: the clause orders nothing only when EVERY operation in it is prohibited.
|
|
367
|
-
// The ban must attach to its own operation: no parenthesis, dash, colon, conjunction, relative pronoun or second operation in between.
|
|
368
|
-
const gap = `(?:(?!\\b(?:which|that|who|where|when|if|and|but|then)\\b|${OP})[^.;,()\\-:]){0,40}?`;
|
|
369
|
-
const banAfter = new RegExp(`^${gap}\\b(?:(?:is|are|remains?|van|con)\\s+)?${BAN}\\b(?!\\s+(?:to|from)\\b)`);
|
|
370
|
-
// "deploy/restart is forbidden", "deploy and restart are banned": operations joined as a list share one ban.
|
|
371
|
-
const joined = new RegExp(`^\\s*(?:/|,|and|or|va|hoac)\\s*${OP}\\b`);
|
|
372
|
-
const bannedAfter = (rest) => {
|
|
373
|
-
if (banAfter.test(rest)) return true;
|
|
374
|
-
const j = joined.exec(rest);
|
|
375
|
-
return j ? bannedAfter(rest.slice(j[0].length)) : false;
|
|
376
|
-
};
|
|
377
|
-
const banBefore = /\b(?:forbidden|prohibited|banned)\s+(?:(?:production|prod)\s+)?$/;
|
|
378
|
-
let total = 0;
|
|
379
|
-
let banned = 0;
|
|
380
|
-
for (const match of clause.matchAll(new RegExp(`\\b${OP}\\b`, 'g'))) {
|
|
381
|
-
total += 1;
|
|
382
|
-
if (bannedAfter(clause.slice(match.index + match[0].length)) || banBefore.test(clause.slice(0, match.index))) banned += 1;
|
|
383
|
-
}
|
|
384
|
-
return total > 0 && banned === total;
|
|
385
|
-
};
|
|
386
|
-
const gitOperation = actionable.some((clause) => !operationNotOrdered(clause) && /\bgit\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b|\bpush\s+(?:to\s+)?origin\b/.test(clause));
|
|
387
|
-
// 'trien khai' is Vietnamese for both "deploy" and "implement": it orders a runtime operation only
|
|
388
|
-
// when it names a runtime target; bare it describes work ("Tier 3 truc tiep trien khai thay Tier 2").
|
|
389
|
-
// "restart"/"deploy" after a noun modifier ("process restart", "after redeploy") is a scenario, not an order.
|
|
390
|
-
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)
|
|
391
|
-
&& !/\.[cm]?[jt]sx?\b/.test(clause) && !/\bgit\b/.test(clause));
|
|
392
|
-
const nativeDomain = cronOperation ? 'scheduled-job' : gitOperation !== runtimeOperation ? (gitOperation ? 'git-integrity' : 'service-runtime') : null;
|
|
393
|
-
const operation = Boolean(nativeDomain);
|
|
394
|
-
// A verification-only request orders no change; a concrete operation order (cancel/restart/push) stays operational.
|
|
395
|
-
if (!releasePolicy && !operation && isVerificationOnlyRequest(promptText)) return { taskKind: 'READ_ONLY', verificationOnly: true };
|
|
396
|
-
// A config MENTION is not a config change: the same clause needs a mutation verb, and a
|
|
397
|
-
// request that also changes source/code is a coding task (config is part of its tests/fixtures).
|
|
398
|
-
// A DIAGNOSTIC report of past changes ("which parameters/configuration has changed, if any" /
|
|
399
|
-
// "tham so/cau hinh nao da thay doi, neu co") is a question about history, never an authorization
|
|
400
|
-
// to mutate: only a forward order ("change X to Y") stays a config mutation.
|
|
401
|
-
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/;
|
|
402
|
-
// A relative past-change clause inside a FORWARD imperative ("Set the config that
|
|
403
|
-
// has changed to dark mode") names WHICH config — it does not make the sentence a
|
|
404
|
-
// report. The override holds only when the clause's FIRST mutation word precedes
|
|
405
|
-
// the config noun AND no reporting/question frame opens the clause or precedes
|
|
406
|
-
// that word: in "Report what change was made to the config" the mutation word is
|
|
407
|
-
// a question's noun, never an order.
|
|
408
|
-
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/;
|
|
409
|
-
const CONFIG_NOUN = /\b(?:configuration|config|toggle|cau hinh)\b/;
|
|
410
|
-
const CONFIG_REPORT_FRAME = /^\s*(?:report|show|tell|list|describe|explain|summari[sz]e|bao cao|cho biet|liet ke|mo ta)\b/;
|
|
411
|
-
const CONFIG_QUESTION_FRAME_PREFIX = /\b(?:what|which|how|report|bao cao|nao|gi)\b/;
|
|
412
|
-
const config = !sourceOrder && actionable.some((clause) => {
|
|
413
|
-
if (!CONFIG_NOUN.test(clause) || !CONFIG_MUTATION_VERB.test(clause)) return false;
|
|
414
|
-
if (CONFIG_REPORT_FRAME.test(clause)) return false;
|
|
415
|
-
const verb = CONFIG_MUTATION_VERB.exec(clause);
|
|
416
|
-
if (CONFIG_QUESTION_FRAME_PREFIX.test(clause.slice(0, verb.index))) return false;
|
|
417
|
-
const noun = CONFIG_NOUN.exec(clause);
|
|
418
|
-
return !CONFIG_CHANGE_REPORT_CLAUSE.test(clause) || Boolean(noun && noun.index > verb.index);
|
|
419
|
-
});
|
|
420
|
-
const repository = Boolean(targetFile) || /[\w.-]+\.(?:json|ya?ml|toml|ini)\b/.test(text);
|
|
421
|
-
// A status/result lookup ("check the suite output and report") orders no change: it needs a
|
|
422
|
-
// lookup verb AND no mutation vocabulary anywhere in the request (fail-closed otherwise).
|
|
423
|
-
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);
|
|
424
|
-
// A bounded, recognizable reporting QUESTION about what changed ("Tham số/cấu hình
|
|
425
|
-
// nào đã thay đổi, nếu có." / "which configuration has changed, if any") is a
|
|
426
|
-
// status report even without a lookup verb — but ONLY when no actionable clause
|
|
427
|
-
// actually orders a mutation: a mixed request ("Add a dark-mode button; report
|
|
428
|
-
// which configuration changed") keeps its implementation contract and evidence.
|
|
429
|
-
// The two lookbehinds keep a question's noun ("what change was made") and the
|
|
430
|
-
// Vietnamese past-tense verb "thay đổi" from counting as mutation orders.
|
|
431
|
-
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/;
|
|
432
|
-
const configReportQuestion = !actionable.some((clause) => ACTIONABLE_MUTATION_ORDER.test(clause))
|
|
433
|
-
&& actionable.some((clause) => /\b(?:configuration|config|toggle|cau hinh|tham so)\b/.test(clause)
|
|
434
|
-
&& CONFIG_CHANGE_REPORT_CLAUSE.test(clause));
|
|
435
|
-
const statusReport = (configReportQuestion || (!mutationWord
|
|
436
|
-
&& /\b(?:check|status|report|show|summari[sz]e|result|results|kiem tra|bao cao|trang thai|ket qua|da len chua)\b/.test(text)))
|
|
437
|
-
&& !operation && !config;
|
|
438
|
-
// Round-2b (FS-01 B5): order detection is request-wide. A later clause that commands an order verb
|
|
439
|
-
// ("Output: fix lists\ncommit the changes", "...; publish") keeps the mutation even when the output
|
|
440
|
-
// span itself was neutralised to deliverable nouns; a true noun-only audit output stays read-only.
|
|
441
|
-
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)
|
|
442
|
-
&& !sourceOrder && !codeChangeVerb && !operation && !config && !ordersChangeAcrossClauses(actionable);
|
|
443
|
-
const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
|
|
444
|
-
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
445
|
-
const id = text.match(/\b[a-f0-9]{8}(?:[a-f0-9-]{0,28})\b/)?.[0] || null;
|
|
446
|
-
return { taskKind, ...(releasePolicy ? { releasePolicy } : {}), ...(documentation ? { reportArtifact } : {}), ...(taskKind === 'READ_ONLY' && statusReport ? { statusReport: true } : {}), ...(config ? { configurationScope: repository ? 'repository' : 'external' } : {}),
|
|
447
|
-
...(operation && nativeDomain !== 'scheduled-job' ? { operationGoal: nativeOperationGoal(nativeDomain, actionable, text) } : {}),
|
|
448
|
-
...(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',
|
|
449
|
-
completionSemantics: /\b(?:ensure|dam bao)\b/.test(text) ? 'ensure-state' : 'execute-and-verify' } } : {}) };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
export function applyTaskSemantics(contract, semantic) {
|
|
453
|
-
const evidence = contract?.completionEvidence || [];
|
|
454
|
-
const operationOnly = semantic.taskKind === 'OPERATIONAL'
|
|
455
|
-
|| (semantic.taskKind === 'CONFIGURATION' && semantic.configurationScope === 'external');
|
|
456
|
-
return { ...(contract || {}), ...semantic,
|
|
457
|
-
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
458
|
-
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
459
|
-
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
460
|
-
: semantic.taskKind === 'READ_ONLY' ? (semantic.verificationOnly || (evidence.length === 1 && evidence[0] === 'verification-evidence') ? ['verification-evidence'] : ['impact-evidence']) : evidence };
|
|
461
|
-
}
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
$ARGUMENTS
|
|
13
13
|
|
|
14
14
|
> **Can be run multiple times.** If the plan or tasks aren't satisfactory after the first run, re-run `/ukit:handoff-create` to refine until the plan is good — as long as no task has been implemented yet.
|
|
15
|
-
> **NO git commit. NO git push.** This phase only writes docs files. No code changes.
|
|
15
|
+
> **NO git commit. NO git push by default.** This phase only writes docs files. No code changes. The
|
|
16
|
+
> one exception is the owner-authorized docs publication below: an explicit owner message in this
|
|
17
|
+
> session may authorize a **docs-only** `git commit` / `git push`, which the guard still verifies —
|
|
18
|
+
> source, mixed or out-of-scope file sets are denied, and publication never activates the backlog.
|
|
16
19
|
|
|
17
20
|
## Planning-only contract — this command never implements
|
|
18
21
|
|
|
@@ -31,6 +34,35 @@ handoff-fullstack cursor. Running this command **makes the session's handoff int
|
|
|
31
34
|
planning**, so the Stop gate and the SessionStart compact/resume hook both keep quiet
|
|
32
35
|
until the human explicitly resumes.
|
|
33
36
|
|
|
37
|
+
### Owner-authorized docs publication — the only git exception
|
|
38
|
+
|
|
39
|
+
`git commit` / `git push` in a create session require BOTH of:
|
|
40
|
+
|
|
41
|
+
1. **An affirmative owner ORDER, per operation** — an explicit message from the human owner
|
|
42
|
+
in THIS session's transcript naming BOTH the exact operation and the docs scope (e.g.
|
|
43
|
+
"commit the docs" authorizes commit only; "push the docs to origin" authorizes push
|
|
44
|
+
only). The latest decisive owner text per op wins: a later "don't push yet" revokes.
|
|
45
|
+
Questions ("why did push fail?"), history ("the push failed yesterday"), wholly quoted
|
|
46
|
+
text, foreign-session/sidechain turns, assistant prose, tool output, and this command's
|
|
47
|
+
own "NO git commit" text never authorize (and never revoke).
|
|
48
|
+
2. **A strictly verified docs-only operation** — one pure `git commit`/`git push` (no
|
|
49
|
+
`;`/`&&` chains, no `-C`/`-c`/`--git-dir`/`--work-tree` redirects, no
|
|
50
|
+
`--force`/`--mirror`/`--all`/`--tags`/`--delete`/`-a`/`--amend`/`--no-verify`, no
|
|
51
|
+
`src:dst` refspecs) where every staged file (commit) or every outgoing commit's files
|
|
52
|
+
(push — commits are inspected with rename detection off, not the net diff) is a docs
|
|
53
|
+
path holding a regular file (symlinks and submodules are rejected). Denied even with
|
|
54
|
+
authorization: source or mixed sets, tests-only sets, an empty index, pushes whose
|
|
55
|
+
destination cannot be pinned to the verified upstream, and widening/redirecting config
|
|
56
|
+
(`push.default` beyond simple/current/upstream, `push.followTags`, `remote.*.mirror`,
|
|
57
|
+
`remote.*.push`, `remote.pushDefault`, `branch.*.pushRemote`). A first push of a NEW
|
|
58
|
+
remote branch is possible only as `git push -u <remote> <branch>` with an owner order
|
|
59
|
+
that explicitly says create/new; every commit between the remote default head and the
|
|
60
|
+
branch must be docs-only. Any failed verification step denies — nothing is allowed on
|
|
61
|
+
an unreadable repository state.
|
|
62
|
+
|
|
63
|
+
Publication moves bytes, not the backlog: it never runs the ready gate, never flips task
|
|
64
|
+
statuses, and never folds a queued blueprint into a cycle.
|
|
65
|
+
|
|
34
66
|
### A live fullstack run is never overwritten — the new plan is QUEUED
|
|
35
67
|
|
|
36
68
|
Before writing anything, read `docs/AI_HANDOFF/RUN.md`. If it exists and its `Phase:`
|
|
@@ -205,6 +237,14 @@ Do not repeat or respawn a refused dispatch, change its writer, or manufacture a
|
|
|
205
237
|
fallback plan to satisfy Stop. Preserve the exact refusal and incomplete planning
|
|
206
238
|
state. A hook refusal is not a transient network error.
|
|
207
239
|
|
|
240
|
+
A **deterministic dispatch/init failure** (a field the host validator rejects, a refused
|
|
241
|
+
agent, an isolation refusal) is never retried with an unchanged payload: either apply the
|
|
242
|
+
refusal's own correction once — a genuinely different payload — and retry, or record the
|
|
243
|
+
refused state and move on; retry is legitimate only after the condition actually changed.
|
|
244
|
+
If the independent reviewer cannot be dispatched at all, do not review from the main
|
|
245
|
+
session and do not fabricate a verdict: keep the affected tasks `needs_breakdown` with the
|
|
246
|
+
missing gate named, unless the owner/policy explicitly authorizes a fallback.
|
|
247
|
+
|
|
208
248
|
## Step 1 — Read context (lite model)
|
|
209
249
|
|
|
210
250
|
Before this first context-agent call, omit the `isolation` field entirely.
|