@ngockhoale/ukit 3.4.26 → 3.4.28

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/manifests/documentation.yaml +61 -0
  3. package/manifests/platform.full.yaml +24 -0
  4. package/package.json +7 -1
  5. package/scripts/audit/subagent-cost/metrics.mjs +14 -2
  6. package/scripts/audit/subagent-cost/reader.mjs +15 -3
  7. package/scripts/audit/subagent-cost/usage-fields.mjs +82 -0
  8. package/scripts/dev/worktree-preflight.mjs +147 -0
  9. package/src/cli/commands/update.js +59 -2
  10. package/src/core/agentRuntime/context.js +78 -0
  11. package/src/core/diffPlan.js +100 -102
  12. package/src/core/docContracts.js +3 -0
  13. package/src/core/observability/emit/sessionBoot.js +14 -0
  14. package/src/core/runtimeConfigMerge.js +125 -0
  15. package/src/core/updateSafety.js +174 -0
  16. package/src/index/contextPack.js +285 -0
  17. package/src/index/sensitivePath.js +21 -0
  18. package/src/index/taskRouting.js +3 -0
  19. package/template_project/.claude/commands/ukit/ci-check.md +53 -0
  20. package/template_project/.claude/hooks/auto-allow-bash.sh +20 -2
  21. package/template_project/.claude/hooks/handoff-model-guard.sh +9 -2
  22. package/template_project/.claude/hooks/handoff-resume.sh +25 -1
  23. package/template_project/.claude/hooks/record-execution.mjs +9 -0
  24. package/template_project/.claude/hooks/skill-router.sh +3 -0
  25. package/template_project/.claude/ukit/index/ci-check.mjs +665 -0
  26. package/template_project/.claude/ukit/index/post-edit-verify.mjs +4 -4
  27. package/template_project/.claude/ukit/index/pre-edit-backup.mjs +3 -3
  28. package/template_project/.claude/ukit/index/route-task.mjs +3 -0
  29. package/template_project/.claude/ukit/runtime/execution-ledger.mjs +154 -12
  30. package/template_project/.claude/ukit/runtime/handoff-intent.mjs +61 -0
  31. package/template_project/.claude/ukit/runtime/hook-chain-runner.mjs +70 -4
  32. package/template_project/.claude/ukit/runtime/loop-state.mjs +131 -0
  33. package/template_project/.claude/ukit/runtime/observability-emit.mjs +14 -0
  34. package/template_project/.claude/ukit/runtime/read-advisory.mjs +92 -0
  35. package/template_project/.claude/ukit/runtime/safe-patch-core.mjs +71 -0
  36. package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +53 -6
  37. package/template_project/.claude/ukit/runtime/usage-capture.mjs +338 -0
  38. package/template_project/.omp/hooks/pre/ukit-bridge.js +48 -4
@@ -751,6 +751,12 @@ function normalizeToolInput(toolName, input) {
751
751
  const paths = Array.isArray(normalized.paths) ? normalized.paths.filter(Boolean) : [];
752
752
  normalized.file_path = normalized.path || paths[0] || undefined;
753
753
  }
754
+ // omp's eval tool carries `{language, code}` (no `command`), but block-dangerous and the
755
+ // secret guard only read tool_input.command. Surface the code as the command so the same
756
+ // Bash chain scans it; otherwise an eval payload would reach the gate as an empty command.
757
+ if (toolName === 'Bash' && typeof normalized.command !== 'string' && typeof normalized.code === 'string') {
758
+ normalized.command = normalized.code;
759
+ }
754
760
  return normalized;
755
761
  }
756
762
 
@@ -789,8 +795,39 @@ function sendContext(pi, context, deliverAs, { display = false } = {}) {
789
795
  pi.sendMessage(hookContextMessage(content, { display }), { deliverAs });
790
796
  }
791
797
 
792
- export async function runToolCall(pi, event, { projectRoot, context: extensionContext = {} }) {
798
+ // eval threat model (F2): the Bash chain is a text matcher over a command string. It cannot
799
+ // prove that arbitrary Python/JS is safe (shutil.rmtree, fs.rmSync, pathlib loops, dynamic
800
+ // strings all pass it — verified in a sentinel sandbox). So eval is DENIED by the bridge unless
801
+ // the user opted in with orchestration.allowUnsafeEval=true in .ukit/storage/config.json,
802
+ // independent of .omp/config.yml tools.approval.eval. When opted in, the Bash chain still runs
803
+ // as best-effort defence in depth; it is NOT a safety guarantee for eval code.
804
+ async function readAllowUnsafeEval(projectRoot) {
805
+ try {
806
+ const parsed = JSON.parse(
807
+ await fs.promises.readFile(path.join(projectRoot, '.ukit', 'storage', 'config.json'), 'utf8'),
808
+ );
809
+ return parsed?.orchestration?.allowUnsafeEval === true;
810
+ } catch {
811
+ return false; // missing/corrupt config → fail closed
812
+ }
813
+ }
814
+
815
+ export async function runToolCall(pi, event, { projectRoot, context: extensionContext = {}, allowUnsafeEval } = {}) {
793
816
  const rawToolName = event.toolName ?? event.tool;
817
+ if (String(rawToolName ?? '').trim().toLowerCase() === 'eval') {
818
+ const allowed = allowUnsafeEval ?? await readAllowUnsafeEval(projectRoot);
819
+ if (!allowed) {
820
+ return {
821
+ block: true,
822
+ toolName: 'Bash',
823
+ invoked: [],
824
+ context: [],
825
+ reason: 'UKit denies the omp eval tool: arbitrary Python/JS cannot be verified by the shell guard. '
826
+ + 'Use the bash tool for commands (guarded), or opt in explicitly with orchestration.allowUnsafeEval=true '
827
+ + 'in .ukit/storage/config.json (unsafe; the guard is best-effort for eval).',
828
+ };
829
+ }
830
+ }
794
831
  const mappedToolName = mapToolName(rawToolName);
795
832
  // An unmapped tool that LOOKS like a file mutation must not sail past the guards — but
796
833
  // hard-blocking it froze the run whenever omp renamed or added a tool, because the model
@@ -811,9 +848,16 @@ export async function runToolCall(pi, event, { projectRoot, context: extensionCo
811
848
  toolUseId: event.toolCallId,
812
849
  ...metadata,
813
850
  });
814
- // Only Edit|Write stays fail-closed on a bridge/runner transport failure; Bash and
815
- // Read|Grep|Glob fail open so diagnosis/recovery tools are never bricked by a runner outage.
816
- const failClosedOnTransportError = matcherGroupFor(toolName) === 'Edit|Write';
851
+ // F4: Edit|Write and Bash (which also carries omp's `eval`) are security-critical and fail
852
+ // CLOSED on a bridge/runner transport failure — an unverifiable shell/eval call is not
853
+ // allowed to proceed. Read|Grep|Glob stay fail-open (documented limit: the secret-read guard
854
+ // is advisory on a runner outage) so diagnosis tools are not bricked. Recovery for a Bash
855
+ // outage is a user action, not a model one: the user exports UKIT_OMP_BASH_FAIL_OPEN=1 (or
856
+ // fixes Node/PATH) and restarts omp. This only affects PreToolUse; the Stop gate is separate,
857
+ // so a blocked Bash call cannot create a Stop-hook loop.
858
+ const group = matcherGroupFor(toolName);
859
+ const bashFailOpenOverride = process.env.UKIT_OMP_BASH_FAIL_OPEN === '1';
860
+ const failClosedOnTransportError = group === 'Edit|Write' || (group === 'Bash' && !bashFailOpenOverride);
817
861
  const result = await runScriptChain(pi, scriptsForToolCall(toolName), payload, {
818
862
  projectRoot,
819
863
  failClosedOnTransportError,