@ngockhoale/ukit 3.4.47 → 3.4.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/manifests/documentation.yaml +22 -0
- package/manifests/platform.full.yaml +27 -0
- package/package.json +4 -1
- package/scripts/dev/worktree-preflight.mjs +20 -1
- package/src/core/diffPlan.js +38 -12
- package/src/core/executionContracts.js +79 -18
- package/src/core/metadata.js +4 -0
- package/src/core/runInstallPipeline.js +19 -2
- package/src/core/runtimeConfig.js +1 -1
- package/src/index/playbookRegistry.js +22 -3
- package/src/index/taskRouting.js +2 -2
- package/template_project/.claude/agents/bug-debugger.md +1 -0
- package/template_project/.claude/agents/code-reviewer.md +1 -0
- package/template_project/.claude/agents/feature-implementer.md +1 -0
- package/template_project/.claude/agents/handoff-planner.md +1 -0
- package/template_project/.claude/agents/ukit-small-task-maintainer.md +1 -0
- package/template_project/.claude/hooks/completion-gate.sh +1 -1
- package/template_project/.claude/hooks/delegation-gate.mjs +343 -0
- package/template_project/.claude/hooks/skill-router.sh +159 -8
- package/template_project/.claude/settings.json +8 -8
- package/template_project/.claude/ukit/index/playbook-registry.mjs +22 -3
- package/template_project/.claude/ukit/index/route-resolver.mjs +79 -17
- package/template_project/.claude/ukit/index/route-task.mjs +115 -37
- package/template_project/.claude/ukit/index/tier-lane.mjs +202 -0
- package/template_project/.claude/ukit/runtime/cron-operation-adapter.mjs +3 -1
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +331 -21
- package/template_project/.claude/ukit/runtime/hook-chain-runner.mjs +8 -0
- package/template_project/.claude/ukit/runtime/loop-state.mjs +3 -1
- package/template_project/.claude/ukit/runtime/observability-emit.mjs +38 -0
- package/template_project/.claude/ukit/runtime/playbook-execution.mjs +704 -0
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +79 -3
- package/template_project/.omp/agents/bug-debugger.md +1 -0
- package/template_project/.omp/agents/code-reviewer.md +1 -0
- package/template_project/.omp/agents/feature-implementer.md +1 -0
- package/template_project/.omp/agents/handoff-planner.md +1 -0
- package/template_project/.omp/agents/ukit-small-task-maintainer.md +1 -0
- package/template_project/.omp/hooks/pre/ukit-bridge.js +254 -7
- package/template_project/ukit/storage/config.json +1 -1
- package/scripts/bench/data-foundation.mjs +0 -929
- package/scripts/bench/decision-runtime-baseline.mjs +0 -427
- package/scripts/bench/decision-runtime-variant.mjs +0 -626
- package/scripts/bench/goldTasks.json +0 -38
- package/scripts/bench/memory-ablation.mjs +0 -495
- package/scripts/bench/memory-baseline.mjs +0 -596
- package/scripts/bench/memory-bench.mjs +0 -661
- package/scripts/bench/memory-canary.mjs +0 -331
- package/scripts/bench/memory-corpus.mjs +0 -354
- package/scripts/bench/memory-gate.mjs +0 -389
- package/scripts/bench/memory-metrics.mjs +0 -179
- package/scripts/bench/outline-savings.mjs +0 -107
- package/scripts/bench/parallel-agents.mjs +0 -224
- package/scripts/bench/recorder-overhead.mjs +0 -204
- package/scripts/bench/runGold.mjs +0 -238
- package/scripts/bench/sqlite-spike.mjs +0 -451
- package/scripts/bench/subagent-orchestrator-corpus.mjs +0 -275
- package/scripts/bench/subagent-orchestrator-eval.mjs +0 -565
- package/scripts/bench/v3-ceremony.mjs +0 -220
- package/scripts/perf/audit-perf.mjs +0 -1195
- package/scripts/perf/diff-perf-findings.mjs +0 -136
- package/scripts/perf/perf-findings.json +0 -474
- package/scripts/perf/perf-measure.md +0 -271
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
|
+
## 3.4.49 - 2026-10-19
|
|
6
|
+
|
|
7
|
+
**Conditional consumer verification intent closure.**
|
|
8
|
+
- Fix a live Completion Gate false write obligation: a verification request ("Mục tiêu: xác nhận …") whose source repair/release steps apply only if a defect is found, plus a disposable test-project setup, was classified CODE_IMPLEMENTATION and demanded write-evidence even after a real `npm test` exit 0. The classifier now recognizes a declared verification goal with a narrowly structured conditional repair branch and `xác nhận` as a verify lead; unconditional trailing orders, quoted goals and non-repair conditionals still block.
|
|
9
|
+
- Safety preserved: when the conditional repair branch is actually taken (an owned in-project substantive source write), the evaluator re-activates the release-publication demand; explicit publish prohibitions dominate. Classifier copies (src, resolver, hook) are pinned by a mirror parity test.
|
|
10
|
+
- Reproduced through the npm 3.4.48 consumer router/record/Stop lifecycle with real native exits; no receipt fabrication, task reassignment or ledger reset. Direct `node --test` recognition is unchanged. Verified on macOS/Node 22 only; OMP live attribution UNVERIFIED; no paid OMP smoke or CI dispatch.
|
|
11
|
+
|
|
12
|
+
## 3.4.48 - 2026-10-19
|
|
13
|
+
|
|
14
|
+
**Completion evidence closure and reproducible multi-host installation.**
|
|
15
|
+
- Recognize standalone `git diff --check` as native Git verification rather than a dry-run mutation. Failed/masked mutations, piped verification, foreign-task evidence and release remote/tag divergence still block.
|
|
16
|
+
- Preserve task ownership through host-consumed coordination notices and continuations; bound Stop diagnostics without accepting incomplete work. Explicit no-publication lists and quoted/report-only references do not authorize a release.
|
|
17
|
+
- Integrate verified OMP child-session/model-ownership safeguards, delegation runtime, installer defaults migration, runtime package dependencies and reproducible scaffolds.
|
|
18
|
+
- Verified locally on macOS/Node 22. Claude/Codex/OMP asset installation is covered; OMP Full Playbook live correctness and trusted worker/dispatch attribution remain UNVERIFIED. No paid OMP smoke or cross-platform live PASS is claimed.
|
|
19
|
+
|
|
5
20
|
## 3.4.47 - 2026-10-18
|
|
6
21
|
|
|
7
22
|
**Supervisor subagent usage attribution and rendered-instruction parity checks.**
|
|
@@ -517,6 +517,28 @@ entries:
|
|
|
517
517
|
validation: [none]
|
|
518
518
|
archive_policy: immutable
|
|
519
519
|
notes: 2026-10-10 orchestration regression test inventory
|
|
520
|
+
- id: docs-analysis-completion-task-ownership
|
|
521
|
+
path: docs/analysis/COMPLETION-TASK-OWNERSHIP.md
|
|
522
|
+
class: archive
|
|
523
|
+
audience: [maintainer]
|
|
524
|
+
owner: product
|
|
525
|
+
source_of_truth: docs/analysis/COMPLETION-TASK-OWNERSHIP.md
|
|
526
|
+
merge_strategy: none
|
|
527
|
+
load_policy: never
|
|
528
|
+
validation: [none]
|
|
529
|
+
archive_policy: immutable
|
|
530
|
+
notes: 2026-10-19 task boundary reproduction and bounded Stop verification
|
|
531
|
+
- id: docs-analysis-omp-runtime-closure
|
|
532
|
+
path: docs/analysis/OMP-RUNTIME-CLOSURE.md
|
|
533
|
+
class: archive
|
|
534
|
+
audience: [maintainer]
|
|
535
|
+
owner: product
|
|
536
|
+
source_of_truth: docs/analysis/OMP-RUNTIME-CLOSURE.md
|
|
537
|
+
merge_strategy: none
|
|
538
|
+
load_policy: never
|
|
539
|
+
validation: [none]
|
|
540
|
+
archive_policy: immutable
|
|
541
|
+
notes: 2026-10-19 captured OMP lifecycle closure and offline verification boundaries
|
|
520
542
|
- id: docs-analysis-orchestration-final-verification
|
|
521
543
|
path: docs/analysis/ORCHESTRATION-FINAL-VERIFICATION.md
|
|
522
544
|
class: archive
|
|
@@ -1222,6 +1222,7 @@ items:
|
|
|
1222
1222
|
- hook-auto-allow-bash
|
|
1223
1223
|
- hook-block-dangerous
|
|
1224
1224
|
- hook-handoff-model-guard
|
|
1225
|
+
- hook-delegation-gate-module
|
|
1225
1226
|
- hook-auto-prune-bash
|
|
1226
1227
|
- hook-project-important
|
|
1227
1228
|
# Env block is post-merged by applyGatewayResilienceEnv (TASK-011) to add managed
|
|
@@ -1422,6 +1423,20 @@ items:
|
|
|
1422
1423
|
packs:
|
|
1423
1424
|
- core
|
|
1424
1425
|
|
|
1426
|
+
# P0 delegation enforcement: in-proc PreToolUse chain step (Edit|Write, MultiEdit, Bash). Claude Code
|
|
1427
|
+
# only (agent_id main/worker identity); reads route state + ledger from ukit-runtime-scripts.
|
|
1428
|
+
- id: hook-delegation-gate-module
|
|
1429
|
+
type: hook
|
|
1430
|
+
sourceTemplate: .claude/hooks/delegation-gate.mjs
|
|
1431
|
+
targetPath: .claude/hooks/delegation-gate.mjs
|
|
1432
|
+
requires:
|
|
1433
|
+
- ukit-runtime-scripts
|
|
1434
|
+
mergeStrategy: overwrite_with_backup
|
|
1435
|
+
variables: []
|
|
1436
|
+
enabledByDefault: true
|
|
1437
|
+
packs:
|
|
1438
|
+
- core
|
|
1439
|
+
|
|
1425
1440
|
- id: hook-handoff-model-guard
|
|
1426
1441
|
type: hook
|
|
1427
1442
|
sourceTemplate: .claude/hooks/handoff-model-guard.sh
|
|
@@ -1442,6 +1457,7 @@ items:
|
|
|
1442
1457
|
- ukit-runtime-scripts
|
|
1443
1458
|
- ukit-index-route-resolver-script
|
|
1444
1459
|
- ukit-index-tier-selection-script
|
|
1460
|
+
- ukit-index-tier-lane-script
|
|
1445
1461
|
mergeStrategy: overwrite_with_backup
|
|
1446
1462
|
variables: []
|
|
1447
1463
|
enabledByDefault: true
|
|
@@ -1865,6 +1881,16 @@ items:
|
|
|
1865
1881
|
packs:
|
|
1866
1882
|
- core
|
|
1867
1883
|
|
|
1884
|
+
- id: ukit-index-tier-lane-script
|
|
1885
|
+
type: config
|
|
1886
|
+
sourceTemplate: .claude/ukit/index/tier-lane.mjs
|
|
1887
|
+
targetPath: .claude/ukit/index/tier-lane.mjs
|
|
1888
|
+
mergeStrategy: overwrite_with_backup
|
|
1889
|
+
variables: []
|
|
1890
|
+
enabledByDefault: true
|
|
1891
|
+
packs:
|
|
1892
|
+
- core
|
|
1893
|
+
|
|
1868
1894
|
- id: ukit-index-session-history-extractor-script
|
|
1869
1895
|
type: config
|
|
1870
1896
|
sourceTemplate: .claude/ukit/index/session-history-extractor.mjs
|
|
@@ -2012,6 +2038,7 @@ items:
|
|
|
2012
2038
|
- ukit-index-session-history-extractor-script
|
|
2013
2039
|
- ukit-index-fix-loop-escalation-script
|
|
2014
2040
|
- ukit-index-tier-selection-script
|
|
2041
|
+
- ukit-index-tier-lane-script
|
|
2015
2042
|
mergeStrategy: overwrite_with_backup
|
|
2016
2043
|
variables: []
|
|
2017
2044
|
enabledByDefault: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngockhoale/ukit",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.49",
|
|
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",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"!scripts/audit/c100/usage-provenance.mjs",
|
|
37
37
|
"!scripts/audit/decision-coverage.mjs",
|
|
38
38
|
"!scripts/audit/subagent-cost/**",
|
|
39
|
+
"!scripts/bench/**",
|
|
40
|
+
"scripts/bench/decision-runtime-metrics.mjs",
|
|
41
|
+
"!scripts/perf/**",
|
|
39
42
|
"template_project/",
|
|
40
43
|
"template_user/",
|
|
41
44
|
"!template_project/**/.smbdelete*",
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// - already bootstrapped → no-op
|
|
8
8
|
// - missing prerequisites → run `ukit install` once, re-check
|
|
9
9
|
// - not a linked worktree → refuse (the primary checkout's .claude/ is the maintainer runtime)
|
|
10
|
+
// Install can also create template-owned scaffolds missing from the checkout (docs/AI_HANDOFF/PLAN.md, SPEC.md);
|
|
11
|
+
// those are not gitignored, so they are reported as `visibleWrites` + a warning, never hidden or deleted.
|
|
10
12
|
// `ukit install` writes the install flavour of root CLAUDE.md/AGENTS.md, but those files are
|
|
11
13
|
// gitignored *render outputs* of template_project/instructions (docs:render). The preflight
|
|
12
14
|
// re-renders them when they drift — never a tracked render target, which is only reported.
|
|
@@ -57,6 +59,15 @@ function isTracked(root, rel) {
|
|
|
57
59
|
|
|
58
60
|
const RENDER_CLI = 'scripts/docs/render-instructions.mjs';
|
|
59
61
|
|
|
62
|
+
// Visible (non-ignored) paths in `git status`. `ukit install` creates template-owned scaffolds that
|
|
63
|
+
// are missing from the checkout (docs/AI_HANDOFF/PLAN.md, SPEC.md — mergeStrategy skip, handoff-clear
|
|
64
|
+
// restores them to template, never deletes them). They are not gitignored, so a HEAD that lost them
|
|
65
|
+
// turns the bootstrap into a visible write. Report it truthfully instead of hiding or deleting it.
|
|
66
|
+
function visibleStatus(root) {
|
|
67
|
+
const res = spawnSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: root, encoding: 'utf8', timeout: GIT_TIMEOUT_MS });
|
|
68
|
+
return res.status === 0 ? new Set(res.stdout.split('\n').filter(Boolean)) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
// → rel paths whose rendered bytes differ from disk (`DRIFT <target>` lines of --check).
|
|
61
72
|
export function renderDrift(root) {
|
|
62
73
|
const res = spawnSync(process.execPath, [path.join(root, RENDER_CLI), '--check'], { cwd: root, encoding: 'utf8', timeout: RENDER_TIMEOUT_MS });
|
|
@@ -71,7 +82,7 @@ export function missingPrerequisites(root) {
|
|
|
71
82
|
|
|
72
83
|
export function preflight({ root, check = false } = {}) {
|
|
73
84
|
const projectRoot = fs.realpathSync(root);
|
|
74
|
-
const result = { root: projectRoot, ok: false, bootstrapped: false, missing: [], rendered: [], warnings: [], home: null, error: null };
|
|
85
|
+
const result = { root: projectRoot, ok: false, bootstrapped: false, missing: [], rendered: [], visibleWrites: [], warnings: [], home: null, error: null };
|
|
75
86
|
|
|
76
87
|
if (!fs.existsSync(path.join(projectRoot, 'bin/ukit'))) {
|
|
77
88
|
return { ...result, exitCode: 2, error: `not a UKit checkout (no bin/ukit): ${projectRoot}` };
|
|
@@ -91,6 +102,7 @@ export function preflight({ root, check = false } = {}) {
|
|
|
91
102
|
// worktree's ignored .cache/ so a preflight never mutates the real home or leaks from it.
|
|
92
103
|
result.home = path.join(projectRoot, '.cache/worktree-preflight-home');
|
|
93
104
|
fs.mkdirSync(result.home, { recursive: true });
|
|
105
|
+
const visibleBefore = visibleStatus(projectRoot);
|
|
94
106
|
const install = spawnSync(process.execPath, [path.join(projectRoot, 'bin/ukit'), 'install'], {
|
|
95
107
|
cwd: projectRoot,
|
|
96
108
|
encoding: 'utf8',
|
|
@@ -98,6 +110,13 @@ export function preflight({ root, check = false } = {}) {
|
|
|
98
110
|
timeout: 120_000,
|
|
99
111
|
});
|
|
100
112
|
result.bootstrapped = true;
|
|
113
|
+
const visibleAfter = visibleStatus(projectRoot);
|
|
114
|
+
if (visibleBefore && visibleAfter) {
|
|
115
|
+
result.visibleWrites = [...visibleAfter].filter((l) => !visibleBefore.has(l)).map((l) => l.slice(3));
|
|
116
|
+
if (result.visibleWrites.length > 0) {
|
|
117
|
+
result.warnings.push(`ukit install created non-ignored file(s) missing from this checkout: ${result.visibleWrites.join(', ')} — template-owned scaffolds; restore/commit them (not deleted)`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
101
120
|
result.missing = missingPrerequisites(projectRoot);
|
|
102
121
|
if (install.status !== 0 || result.missing.length > 0) {
|
|
103
122
|
const tail = `${install.stdout || ''}${install.stderr || ''}`.trim().split('\n').slice(-5).join('\n');
|
package/src/core/diffPlan.js
CHANGED
|
@@ -62,6 +62,15 @@ async function checkLinkStatus(targetPath, linkTarget) {
|
|
|
62
62
|
// Template-filled: keys the user does not have take the template value.
|
|
63
63
|
// Gateway-resilience env keys are post-apply managed (applyGatewayResilienceEnv) and are
|
|
64
64
|
// ignored by the drift comparison.
|
|
65
|
+
//
|
|
66
|
+
// One-time defaults migration (orchestrator tier + effort): UKit used to ship `model: "sonnet"`
|
|
67
|
+
// and `effortLevel: "high"`; it now ships `opus` / `medium` (main orchestrator = Tier 3, effort
|
|
68
|
+
// Medium unless the task is high-risk). A key is migrated ONLY when the file is UKit-tracked, the
|
|
69
|
+
// user's value still equals the old shipped default, and the migration has not run before
|
|
70
|
+
// (install.json `settingsDefaultsMigration`) — so a later deliberate choice is never touched
|
|
71
|
+
// again, and settings.local.json / user settings (never edited by UKit) always win at runtime.
|
|
72
|
+
export const SETTINGS_DEFAULTS_MIGRATION_ID = 'orchestrator-opus-medium-v1';
|
|
73
|
+
const LEGACY_SHIPPED_SETTINGS = { model: 'sonnet', effortLevel: 'high' };
|
|
65
74
|
const UKIT_OWNED_TOP_LEVEL_KEYS = new Set(['hooks']);
|
|
66
75
|
const PERMISSION_LIST_KEYS = new Set(['allow', 'deny', 'ask', 'additionalDirectories']);
|
|
67
76
|
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
@@ -94,7 +103,22 @@ function unionList(templateList, userList) {
|
|
|
94
103
|
return out;
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
|
|
106
|
+
// Keys whose old shipped default is still in place and should take the new template value.
|
|
107
|
+
function legacyDefaultKeys(existing, rendered, migrate) {
|
|
108
|
+
if (!migrate) return [];
|
|
109
|
+
return Object.keys(LEGACY_SHIPPED_SETTINGS).filter((key) => (
|
|
110
|
+
existing[key] === LEGACY_SHIPPED_SETTINGS[key] && key in rendered && rendered[key] !== existing[key]
|
|
111
|
+
));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function settingsDefaultsMigrationKeys(existingContent, renderedContent) {
|
|
115
|
+
const existing = parseSettingsJson(existingContent);
|
|
116
|
+
const rendered = parseSettingsJson(renderedContent);
|
|
117
|
+
return existing && rendered ? legacyDefaultKeys(existing, rendered, true) : [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mergeSettingsObjects(existing, rendered, { migrateDefaults = false } = {}) {
|
|
121
|
+
const migrated = new Set(legacyDefaultKeys(existing, rendered, migrateDefaults));
|
|
98
122
|
const merged = {};
|
|
99
123
|
for (const key of Object.keys(rendered)) {
|
|
100
124
|
if (BLOCKED_KEYS.has(key)) continue;
|
|
@@ -124,7 +148,7 @@ function mergeSettingsObjects(existing, rendered) {
|
|
|
124
148
|
// user value has the wrong shape (e.g. permissions: "x") — template shape wins
|
|
125
149
|
merged[key] = templateValue;
|
|
126
150
|
} else {
|
|
127
|
-
merged[key] = userValue; // user-owned scalar/section (model, effortLevel, …)
|
|
151
|
+
merged[key] = migrated.has(key) ? templateValue : userValue; // user-owned scalar/section (model, effortLevel, …)
|
|
128
152
|
}
|
|
129
153
|
}
|
|
130
154
|
return merged;
|
|
@@ -133,11 +157,11 @@ function mergeSettingsObjects(existing, rendered) {
|
|
|
133
157
|
// Merge rendered settings.json with the user's existing file per the ownership model above.
|
|
134
158
|
// Returns the merged JSON as a string, or the rendered content unchanged when either side is
|
|
135
159
|
// unparseable (the caller still takes a timestamped backup before writing).
|
|
136
|
-
export function mergeSettingsJson(existingContent, renderedContent) {
|
|
160
|
+
export function mergeSettingsJson(existingContent, renderedContent, options = {}) {
|
|
137
161
|
const existing = parseSettingsJson(existingContent);
|
|
138
162
|
const rendered = parseSettingsJson(renderedContent);
|
|
139
163
|
if (!existing || !rendered) return renderedContent;
|
|
140
|
-
return `${JSON.stringify(mergeSettingsObjects(existing, rendered), null, 2)}\n`;
|
|
164
|
+
return `${JSON.stringify(mergeSettingsObjects(existing, rendered, options), null, 2)}\n`;
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
function withoutGatewayEnv(settings) {
|
|
@@ -147,11 +171,11 @@ function withoutGatewayEnv(settings) {
|
|
|
147
171
|
return { ...settings, env };
|
|
148
172
|
}
|
|
149
173
|
|
|
150
|
-
function settingsJsonIgnoresGatewayEnvDiff(existingContent, renderedContent) {
|
|
174
|
+
function settingsJsonIgnoresGatewayEnvDiff(existingContent, renderedContent, options = {}) {
|
|
151
175
|
const existing = parseSettingsJson(existingContent);
|
|
152
176
|
const rendered = parseSettingsJson(renderedContent);
|
|
153
177
|
if (!existing || !rendered) return false;
|
|
154
|
-
const merged = mergeSettingsObjects(existing, rendered);
|
|
178
|
+
const merged = mergeSettingsObjects(existing, rendered, options);
|
|
155
179
|
return JSON.stringify(canonicalize(withoutGatewayEnv(merged)))
|
|
156
180
|
=== JSON.stringify(canonicalize(withoutGatewayEnv(existing)));
|
|
157
181
|
}
|
|
@@ -208,7 +232,7 @@ function ukitInstalledText(mergedText, renderedText) {
|
|
|
208
232
|
}
|
|
209
233
|
}
|
|
210
234
|
|
|
211
|
-
function resolveFileAction(entry, existingContent, ompOwnership = null) {
|
|
235
|
+
function resolveFileAction(entry, existingContent, ompOwnership = null, settingsMigration = null) {
|
|
212
236
|
const exists = existingContent !== null;
|
|
213
237
|
const binaryEntry = Buffer.isBuffer(entry.renderedContent);
|
|
214
238
|
|
|
@@ -273,9 +297,11 @@ function resolveFileAction(entry, existingContent, ompOwnership = null) {
|
|
|
273
297
|
// User-owned permission keys (permissions.defaultMode) are excluded from the drift
|
|
274
298
|
// check AND merged back into the content that gets written, so install/update can
|
|
275
299
|
// never flip the user's chosen permission mode.
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
300
|
+
const mergeOptions = { migrateDefaults: settingsMigration?.pending === true };
|
|
301
|
+
const merged = mergeSettingsJson(existingContent, entry.renderedContent, mergeOptions);
|
|
302
|
+
action = settingsJsonIgnoresGatewayEnvDiff(existingContent, entry.renderedContent, mergeOptions) ? 'unchanged' : 'update';
|
|
303
|
+
const migratedKeys = mergeOptions.migrateDefaults ? settingsDefaultsMigrationKeys(existingContent, entry.renderedContent) : [];
|
|
304
|
+
return { ...entry, renderedContent: merged, exists, action, existingContent, ...(migratedKeys.length ? { settingsDefaultsMigrated: migratedKeys } : {}) };
|
|
279
305
|
} else if (entry.id === 'runtime-config' && typeof existingContent === 'string' && typeof entry.renderedContent === 'string') {
|
|
280
306
|
// U2: .ukit/storage/config.json is user-owned state seeded from the template — merge
|
|
281
307
|
// by ownership instead of overwriting (see runtimeConfigMerge.js).
|
|
@@ -332,7 +358,7 @@ async function resolveSkipEntryAction(entry, ompOwnership = null) {
|
|
|
332
358
|
return resolveFileAction(entry, read.content, ompOwnership);
|
|
333
359
|
}
|
|
334
360
|
|
|
335
|
-
export async function diffInstallPlan(plan, { ompOwnership = null } = {}) {
|
|
361
|
+
export async function diffInstallPlan(plan, { ompOwnership = null, settingsMigration = null } = {}) {
|
|
336
362
|
// Check all entries in parallel
|
|
337
363
|
const results = await Promise.all(
|
|
338
364
|
plan.entries.map(async (entry) => {
|
|
@@ -352,7 +378,7 @@ export async function diffInstallPlan(plan, { ompOwnership = null } = {}) {
|
|
|
352
378
|
if (read.status === 'unreadable') {
|
|
353
379
|
return conflictResult(entry, read.error);
|
|
354
380
|
}
|
|
355
|
-
return resolveFileAction(entry, read.status === 'ok' ? read.content : null, ompOwnership);
|
|
381
|
+
return resolveFileAction(entry, read.status === 'ok' ? read.content : null, ompOwnership, settingsMigration);
|
|
356
382
|
}),
|
|
357
383
|
);
|
|
358
384
|
|
|
@@ -95,37 +95,39 @@ export const MODEL_TIER_BY_CONTRACT = {
|
|
|
95
95
|
// no registry, no leases, no outbound capability calls.
|
|
96
96
|
export const ROUTE_EFFORTS = new Set(['low', 'medium', 'high']);
|
|
97
97
|
|
|
98
|
-
// Tier
|
|
99
|
-
|
|
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
100
|
const MODEL_TIER_EFFORT = {
|
|
101
101
|
lite: 'low',
|
|
102
102
|
code: 'medium',
|
|
103
|
-
smart: '
|
|
103
|
+
smart: 'medium',
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* Resolves {tier, effort, advisoryOnly} for a route. A 'high-risk' floor
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
107
|
+
* Resolves {tier, effort, advisoryOnly} for a route. A 'high-risk' floor lifts a Tier 1 chore to the
|
|
108
|
+
* Tier 2 executor (lite→code) and goes no further: smart is the orchestrator / acceptance tier and is
|
|
109
|
+
* reached only through its own contract (review-release) or failure evidence, never through risk, so
|
|
110
|
+
* risk cannot turn the orchestrator into the implementation worker. Risk raises verification rigor
|
|
111
|
+
* (the route's riskFloor / rigor), not the tier ceiling or the effort. Effort is 'high' only with
|
|
112
|
+
* `complexityEvidence`. Unknown mode → {tier:null, effort:null, advisoryOnly:true}. Missing/unknown
|
|
113
|
+
* riskFloor → 'none'. Missing hostCapabilities → advisoryOnly:true (the route text is all the host
|
|
114
|
+
* gets when it cannot bind model and effort).
|
|
112
115
|
*/
|
|
113
116
|
export function resolveModelTier({
|
|
114
117
|
executionMode = null,
|
|
115
118
|
riskFloor = null,
|
|
116
119
|
hostCapabilities = null,
|
|
120
|
+
complexityEvidence = false,
|
|
117
121
|
} = {}) {
|
|
118
122
|
const baseTier = MODEL_TIER_BY_CONTRACT[executionMode] ?? null;
|
|
119
123
|
if (!baseTier) {
|
|
120
124
|
return { tier: null, effort: null, advisoryOnly: true };
|
|
121
125
|
}
|
|
122
126
|
const highRisk = riskFloor?.floor === 'high-risk';
|
|
123
|
-
const tier = highRisk
|
|
124
|
-
? MODEL_TIER_ORDER[Math.min(MODEL_TIER_ORDER.indexOf(baseTier) + 1, MODEL_TIER_ORDER.length - 1)]
|
|
125
|
-
: baseTier;
|
|
127
|
+
const tier = highRisk && baseTier === 'lite' ? 'code' : baseTier;
|
|
126
128
|
return {
|
|
127
129
|
tier,
|
|
128
|
-
effort:
|
|
130
|
+
effort: complexityEvidence === true ? 'high' : MODEL_TIER_EFFORT[tier],
|
|
129
131
|
advisoryOnly: !(hostCapabilities?.canBindModel === true && hostCapabilities?.canBindEffort === true),
|
|
130
132
|
};
|
|
131
133
|
}
|
|
@@ -197,19 +199,72 @@ function nativeOperationGoal(domain, actionable, text) {
|
|
|
197
199
|
// Vietnamese negators scope to the sentence end as well. Noun uses of verbs ("write/verification advisory", "each proposed
|
|
198
200
|
// fix", "state change") name things, not orders. Neutralised spans become blanks, never new orders.
|
|
199
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;
|
|
200
210
|
function neutralizeNonOrders(text) {
|
|
201
|
-
|
|
211
|
+
let source = String(text).replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
212
|
+
if (VERIFICATION_GOAL.test(source)) source = source.replace(GOAL_LABEL, ' ').replace(CONDITIONAL_BRANCH, '\n').replace(DISPOSABLE_SETUP, ' ');
|
|
213
|
+
return source
|
|
202
214
|
.replace(new RegExp(`\\b(?:do\\s+not|don't|dont|must\\s+not|never|shall\\s+not)\\s+[^.;\\n]*?(?=(?<!,)\\s+(?:and|va)\\s+(?:${NON_ORDER_VERBS})\\b|\\s+(?:but|nhung|then|however)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
203
215
|
.replace(new RegExp(`\\b(?:khong|chua)\\s+[^.;\\n]*?(?=\\s+(?:nhung|tuy nhien)\\b|[.;\\n]|$)`, 'g'), ' ')
|
|
204
216
|
.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, ' ')
|
|
217
|
+
.replace(/\b(?:neu\s+(?:phai|can|thieu|khong du|bang chung chua du)(?![^.;\n]*\b(?:restart|deploy|xoa|delete|remove|cancel)\b)|if\s+(?:needed|necessary|required|evidence is insufficient))\b[^.;\n]*/g, ' ')
|
|
206
218
|
.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, ' ')
|
|
219
|
+
.replace(/\bchinh\s+(?:xac|sach)\b|\b(?:phuong an|cach|de xuat|ban)\s+(?:sua|fix)\b|\b(?:diem|cho|muc|phan)\s+(?:phai|can|nen)\s+(?:sua|fix)\b/g, ' ')
|
|
208
220
|
.replace(/\bwrite\s*\/\s*verification\b|\bverification\s*\/\s*write\b|\bwrite\s+(?:or|and)\s+verification\b/g, ' ')
|
|
209
221
|
.replace(/\b(?:state|status)\s+changes?\b/g, ' ')
|
|
210
222
|
.replace(/\b(?:proposed|suggested|recommended|minimal|each|the|an?|any|that|this|no|which|exact|smallest|possible)\s+fix(?:es)?\b/g, ' ');
|
|
211
223
|
}
|
|
212
224
|
|
|
225
|
+
// Explicit scope declaration (intent ownership): when the request itself declares it is read-only
|
|
226
|
+
// ("READ-ONLY audit", "chi doc", "make no source changes") and carries no explicit order to change
|
|
227
|
+
// anything, verbs and operation names inside it are descriptions (a "git stash" that already ran, "a
|
|
228
|
+
// worklog edit", "confirmed fixes"), not orders - so they must not demand write or operation proof.
|
|
229
|
+
// An explicit order (imperative-initial or modal request) keeps the request MIXED/IMPLEMENTATION:
|
|
230
|
+
// the declaration never overrides a real instruction, and fenced/quoted/blockquoted text is never one.
|
|
231
|
+
// A bare verb with no object is the tail of a negated list ("no commit, release, publish"), not an order.
|
|
232
|
+
const READ_ONLY_DECLARATION = /\bread[\s-]*only\s+(?:audit|investigation|review|analysis|inspection|task|request|session|pass|check|diagnos\w+|verification|forensics?)\b|(?:^|\n)[\s*#>_-]*read[\s-]*only\s*[.:!—-]|\b(?:audit|investigation|review|analysis)[\s-]*only\b|[(,]\s*read[\s-]*only\s*[).]|\bchi\s+(?:doc|audit|kiem tra|phan tich|dieu tra)\b|\b(?:make|with)\s+no\s+(?:source\s+|code\s+|file\s+)?(?:changes|edits|modifications)\b/;
|
|
233
|
+
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)';
|
|
234
|
+
const IMPERATIVE_ORDER = new RegExp(`^(?:[-*+\\u2022>\\d.)\\s]*)(?:(?:please|hay|vui long|now|also|first|then)\\s+)*(?:${ORDER_VERBS})\\b\\s+\\S`);
|
|
235
|
+
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`);
|
|
236
|
+
function hasExplicitOrder(clauses) {
|
|
237
|
+
return clauses.some((clause) => IMPERATIVE_ORDER.test(clause) || MODAL_ORDER.test(clause));
|
|
238
|
+
}
|
|
239
|
+
function declaredReadOnly(promptText, releasePolicy) {
|
|
240
|
+
if (releasePolicy) return false;
|
|
241
|
+
const plain = String(promptText).normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
242
|
+
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
243
|
+
if (!READ_ONLY_DECLARATION.test(plain)) return false;
|
|
244
|
+
const text = neutralizeNonOrders(plain);
|
|
245
|
+
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/)
|
|
246
|
+
.map((clause) => clause.split(/\bwithout\b/)[0])
|
|
247
|
+
.filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause));
|
|
248
|
+
return /\b(?:audit|inspect|review|analyze|analyse|explain|read|investigate|investigation|forensic|root cause|kiem tra|phan tich|dieu tra|doc log|tim hieu)\b/.test(text)
|
|
249
|
+
&& !hasExplicitOrder(clauses);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Verification-only request. Root cause of a false `write-evidence` block on a "verify / re-run /
|
|
253
|
+
// confirm" prompt: the vocabulary scans counted nouns and compounds ("coordinator fix", "re-run",
|
|
254
|
+
// "template/install", "source change") as mutation orders. Order is structural: a request whose
|
|
255
|
+
// command-position verbs are verification verbs, with no explicit order (hasExplicitOrder) and no
|
|
256
|
+
// change-leading clause, orders no change - it owes verification evidence, not a write.
|
|
257
|
+
const VERIFY_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:verify|re-?run|confirm|xac nhan|validate|double-?check)\b/;
|
|
258
|
+
const CHANGE_LEAD = /^(?:[-*+•>\d.)\s]*)(?:(?:please|hay|vui long|now|also|first|then)\s+)*(?:make|rebuild|redo|rewrite|restore|revert|correct|clean|patch|resolve|ensure|run(?!(?=[^.]*\b(?:tests?|suite|gate|checks?|lint|typecheck|pilot|smoke|e2e|verif\w*|vitest|jest|audit)\b)))\b/;
|
|
259
|
+
export function isVerificationOnlyRequest(promptText) {
|
|
260
|
+
const plain = String(promptText ?? '').normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase()
|
|
261
|
+
.replace(/```[\s\S]*?```/g, ' ').replace(/^[ \t]*>.*$/gm, ' ');
|
|
262
|
+
const clauses = neutralizeNonOrders(plain).split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/)
|
|
263
|
+
.map((clause) => clause.split(/\bwithout\b/)[0]);
|
|
264
|
+
return clauses.some((clause) => VERIFY_LEAD.test(clause)) && !hasExplicitOrder(clauses)
|
|
265
|
+
&& !clauses.some((clause) => CHANGE_LEAD.test(clause));
|
|
266
|
+
}
|
|
267
|
+
|
|
213
268
|
export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {}) {
|
|
214
269
|
// A named preflight and both explicit branches are required; a failed arbitrary test is not a release policy.
|
|
215
270
|
const releaseText = String(promptText).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[đĐ]/g, 'd').toLowerCase();
|
|
@@ -221,6 +276,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
221
276
|
const clauses = text.split(/\s*(?:[;\n,]|\.(?:\s+|$)|\band\b|\bthen\b|\bva\b)\s*/);
|
|
222
277
|
const actionable = clauses.map((clause) => clause.split(/\bwithout\b/)[0]).filter((clause) => !/\b(?:do not|don't|never|khong|dung)\b/.test(clause)
|
|
223
278
|
&& !/^\s*(?:audit|inspect|review|analyze|analyse|explain|read|kiem tra|phan tich)\b/.test(clause));
|
|
279
|
+
if (declaredReadOnly(promptText, releasePolicy)) return { taskKind: 'READ_ONLY' };
|
|
224
280
|
const sourceOrder = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause)
|
|
225
281
|
&& /\b(?:source|code|logic|scheduler|implementation|ma nguon)\b|\.[cm]?[jt]sx?\b/.test(clause));
|
|
226
282
|
const codeChangeVerb = actionable.some((clause) => /\b(?:implement|fix(?:es)?|apply|refactor|modify|edit|write|update|sua|viet|chinh)\b/.test(clause));
|
|
@@ -238,10 +294,15 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
238
294
|
// runtime are separate domains with separate proof; a prompt naming both stays a coding
|
|
239
295
|
// task rather than being half-verified. A code-change verb anywhere keeps write evidence.
|
|
240
296
|
const gitOperation = actionable.some((clause) => /\bgit\s+(?:push|clean|commit|reset|rebase|merge|tag|stash|checkout|restore|cherry-pick)\b|\bpush\s+(?:to\s+)?origin\b/.test(clause));
|
|
241
|
-
|
|
297
|
+
// 'trien khai' is Vietnamese for both "deploy" and "implement": it orders a runtime operation only
|
|
298
|
+
// when it names a runtime target; bare it describes work ("Tier 3 truc tiep trien khai thay Tier 2").
|
|
299
|
+
// "restart"/"deploy" after a noun modifier ("process restart", "after redeploy") is a scenario, not an order.
|
|
300
|
+
const runtimeOperation = actionable.some((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)
|
|
242
301
|
&& !/\.[cm]?[jt]sx?\b/.test(clause) && !/\bgit\b/.test(clause));
|
|
243
302
|
const nativeDomain = cronOperation ? 'scheduled-job' : gitOperation !== runtimeOperation ? (gitOperation ? 'git-integrity' : 'service-runtime') : null;
|
|
244
303
|
const operation = Boolean(nativeDomain);
|
|
304
|
+
// A verification-only request orders no change; a concrete operation order (cancel/restart/push) stays operational.
|
|
305
|
+
if (!releasePolicy && !operation && isVerificationOnlyRequest(promptText)) return { taskKind: 'READ_ONLY', verificationOnly: true };
|
|
245
306
|
// A config MENTION is not a config change: the same clause needs a mutation verb, and a
|
|
246
307
|
// request that also changes source/code is a coding task (config is part of its tests/fixtures).
|
|
247
308
|
const config = !sourceOrder && actionable.some((clause) => /\b(?:configuration|config|toggle|cau hinh)\b/.test(clause)
|
|
@@ -252,7 +313,7 @@ export function classifyTaskSemantics({ promptText = '', targetFile = '' } = {})
|
|
|
252
313
|
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);
|
|
253
314
|
const statusReport = !mutationWord && !operation && !config
|
|
254
315
|
&& /\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)
|
|
316
|
+
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)
|
|
256
317
|
&& !sourceOrder && !codeChangeVerb && !operation && !config;
|
|
257
318
|
const taskKind = operation ? (sourceOrder || (nativeDomain !== 'scheduled-job' && codeChangeVerb) ? 'MIXED' : 'OPERATIONAL')
|
|
258
319
|
: config ? 'CONFIGURATION' : documentation ? 'DOCUMENTATION_OUTPUT' : readOnly ? 'READ_ONLY' : 'CODE_IMPLEMENTATION';
|
|
@@ -271,5 +332,5 @@ export function applyTaskSemantics(contract, semantic) {
|
|
|
271
332
|
completionEvidence: semantic.taskKind === 'DOCUMENTATION_OUTPUT' ? ['report-artifact'] : operationOnly ? ['operation-verification']
|
|
272
333
|
: semantic.taskKind === 'MIXED' ? [...new Set([...evidence, 'write-evidence', 'operation-verification'])]
|
|
273
334
|
: semantic.taskKind === 'READ_ONLY' && semantic.statusReport ? []
|
|
274
|
-
: semantic.taskKind === 'READ_ONLY' ? (evidence.length === 1 && evidence[0] === 'verification-evidence' ? evidence : ['impact-evidence']) : evidence };
|
|
335
|
+
: semantic.taskKind === 'READ_ONLY' ? (semantic.verificationOnly || (evidence.length === 1 && evidence[0] === 'verification-evidence') ? ['verification-evidence'] : ['impact-evidence']) : evidence };
|
|
275
336
|
}
|
package/src/core/metadata.js
CHANGED
|
@@ -69,6 +69,7 @@ export async function writeInstallMetadata({
|
|
|
69
69
|
managedRelativePaths = null,
|
|
70
70
|
retainedManagedRelativePaths = [],
|
|
71
71
|
ompConfigOwnership = null,
|
|
72
|
+
settingsDefaultsMigration = null,
|
|
72
73
|
lockStaleMs,
|
|
73
74
|
lockMaxWaitMs,
|
|
74
75
|
}) {
|
|
@@ -138,6 +139,9 @@ export async function writeInstallMetadata({
|
|
|
138
139
|
// before) — lets uninstall revert only UKit's changes. Kept across runs.
|
|
139
140
|
const ownership = ompConfigOwnership ?? (previousData?.tool === 'ukit' ? previousData.ompConfigOwnership : null);
|
|
140
141
|
if (ownership) metadata.ompConfigOwnership = ownership;
|
|
142
|
+
// One-time .claude/settings.json defaults migration marker (see diffPlan.js). Kept across runs.
|
|
143
|
+
const migrationMarker = settingsDefaultsMigration ?? (previousData?.tool === 'ukit' ? previousData.settingsDefaultsMigration : null);
|
|
144
|
+
if (migrationMarker) metadata.settingsDefaultsMigration = migrationMarker;
|
|
141
145
|
|
|
142
146
|
await ensureDir(path.dirname(installMetaPath));
|
|
143
147
|
await writeJson(installMetaPath, metadata);
|
|
@@ -7,7 +7,7 @@ import { detectProjectContext } from '../context/detectProjectContext.js';
|
|
|
7
7
|
import { detectProviders } from '../context/detectProviders.js';
|
|
8
8
|
import { buildTemplateVariables } from '../render/buildVariables.js';
|
|
9
9
|
import { buildInstallPlan } from './buildPlan.js';
|
|
10
|
-
import { diffInstallPlan } from './diffPlan.js';
|
|
10
|
+
import { diffInstallPlan, SETTINGS_DEFAULTS_MIGRATION_ID } from './diffPlan.js';
|
|
11
11
|
import { applyDiffResults, pruneOldBackups } from './applyPlan.js';
|
|
12
12
|
import { summarizeDiff, toDiffRows } from './report.js';
|
|
13
13
|
import { writeInstallMetadata, writeCliLocator, readInstallMetadata } from './metadata.js';
|
|
@@ -439,7 +439,10 @@ export async function runInstallPipeline({
|
|
|
439
439
|
.some((e) => (typeof e === 'string' ? e : e?.p) === '.omp/config.yml');
|
|
440
440
|
const ompOwnership = installMetaData?.ompConfigOwnership
|
|
441
441
|
?? (trackedOmpConfig ? { originalText: null, installedText: 'compaction:\n thresholdTokens: 350000\n' } : null);
|
|
442
|
-
const
|
|
442
|
+
const trackedSettings = (Array.isArray(installMetaData?.files) ? installMetaData.files : [])
|
|
443
|
+
.some((e) => (typeof e === 'string' ? e : e?.p) === '.claude/settings.json');
|
|
444
|
+
const settingsMigration = { pending: trackedSettings && !installMetaData?.settingsDefaultsMigration };
|
|
445
|
+
const diffResults = await diffInstallPlan(plan, { ompOwnership, settingsMigration });
|
|
443
446
|
const plannedSummary = summarizeDiff(diffResults);
|
|
444
447
|
|
|
445
448
|
if (!dryRun) {
|
|
@@ -464,6 +467,19 @@ export async function runInstallPipeline({
|
|
|
464
467
|
}
|
|
465
468
|
}
|
|
466
469
|
|
|
470
|
+
// One-time settings.json defaults migration: report it explicitly (never silent) and mark it
|
|
471
|
+
// done once the settings entry was processed, so later user edits are never migrated again.
|
|
472
|
+
let settingsDefaultsMigration = null;
|
|
473
|
+
for (const entry of diffResults) {
|
|
474
|
+
if (!/(^|[\\/])\.claude[\\/]settings\.json$/.test(String(entry.targetPath))) continue;
|
|
475
|
+
if (!['create', 'update', 'unchanged'].includes(entry.action)) continue;
|
|
476
|
+
if (entry.action !== 'unchanged' && !writes.some((w) => w.targetPath === entry.targetPath)) continue;
|
|
477
|
+
settingsDefaultsMigration = SETTINGS_DEFAULTS_MIGRATION_ID;
|
|
478
|
+
if (entry.settingsDefaultsMigrated?.length) {
|
|
479
|
+
console.log(`[UKit] .claude/settings.json: migrated old default ${entry.settingsDefaultsMigrated.join(' + ')} to model "opus" (main orchestrator) / effortLevel "medium"; previous file backed up. To pin your own, set it in .claude/settings.local.json (UKit never edits it).`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
467
483
|
// SPEC §7 step 9: surface the .omp/config.yml unattended migration result.
|
|
468
484
|
for (const entry of diffResults) {
|
|
469
485
|
const report = entry?.migrationReport;
|
|
@@ -546,6 +562,7 @@ export async function runInstallPipeline({
|
|
|
546
562
|
managedRelativePaths: [...trackedManagedRelativePaths],
|
|
547
563
|
retainedManagedRelativePaths,
|
|
548
564
|
ompConfigOwnership,
|
|
565
|
+
settingsDefaultsMigration,
|
|
549
566
|
});
|
|
550
567
|
|
|
551
568
|
// 3.3.0: hooks spawn the CLI (episodes, self-improve) from a GUI-launched
|
|
@@ -442,7 +442,7 @@ export function buildDefaultRuntimeConfig(overrides = {}) {
|
|
|
442
442
|
},
|
|
443
443
|
orchestration: {
|
|
444
444
|
enabled: true,
|
|
445
|
-
orchestratorModel: 'claude-
|
|
445
|
+
orchestratorModel: 'claude-opus-5',
|
|
446
446
|
permissionMode: 'unattended',
|
|
447
447
|
// FR-003: eval-subprocess gate for rendered omp approval (tools.approval.eval).
|
|
448
448
|
// Fail-closed: only an explicit user `true` renders `eval: allow`; absent,
|