@ngockhoale/ukit 3.3.2 → 3.4.0

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 (89) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/manifests/engineConformance.yaml +17 -1
  3. package/manifests/hostCapabilities.yaml +68 -1
  4. package/manifests/platform.full.yaml +138 -0
  5. package/manifests/platform.user.yaml +255 -3
  6. package/package.json +1 -1
  7. package/scripts/bench/subagent-orchestrator-corpus.mjs +275 -0
  8. package/scripts/bench/subagent-orchestrator-eval.mjs +565 -0
  9. package/scripts/probe/codex-capability-probe.mjs +169 -0
  10. package/src/cli/commands/doctor.js +168 -0
  11. package/src/cli/commands/indexTools.js +7 -0
  12. package/src/cli/commands/metrics.js +66 -2
  13. package/src/cli/commands/playbook.js +4 -4
  14. package/src/cli/commands/vm.js +49 -8
  15. package/src/core/agentRuntime/adapters.js +328 -27
  16. package/src/core/agentRuntime/artifacts.js +89 -0
  17. package/src/core/agentRuntime/context.js +345 -1
  18. package/src/core/agentRuntime/contract.js +296 -0
  19. package/src/core/agentRuntime/eventStore.js +176 -0
  20. package/src/core/agentRuntime/shadowRun.js +481 -5
  21. package/src/core/agentRuntime/telemetry.js +121 -0
  22. package/src/core/observability/emit/lifecycle.js +68 -1
  23. package/src/core/observability/emit/sessionBoot.js +393 -0
  24. package/src/core/observability/privacy/allowlist.js +10 -1
  25. package/src/core/observability/schema/registry.js +10 -0
  26. package/src/core/runtimeConfig.js +133 -0
  27. package/src/core/userPlaybooks.js +18 -3
  28. package/src/decision/registry.js +19 -0
  29. package/src/diagnostics/feedbackEvents.js +7 -4
  30. package/src/diagnostics/routeOutcomes.js +51 -6
  31. package/src/diagnostics/skillAccuracy.js +43 -3
  32. package/src/index/crossCheckMatrix.js +412 -0
  33. package/src/index/fixLoopEscalation.js +453 -0
  34. package/src/index/playbookRegistry.js +691 -0
  35. package/src/index/reviewPolicy.js +368 -0
  36. package/src/index/routeResolver.js +915 -0
  37. package/src/index/sessionHistoryExtractor.js +359 -0
  38. package/src/index/taskRouting.js +764 -581
  39. package/src/index/tierSelection.js +308 -0
  40. package/src/index/verificationMap.js +404 -0
  41. package/template_project/.claude/hooks/observability-emit.mjs +14 -0
  42. package/template_project/.claude/hooks/record-execution.mjs +19 -1
  43. package/template_project/.claude/hooks/skill-router.sh +691 -25
  44. package/template_project/.claude/hooks/verification-guard.sh +230 -1
  45. package/template_project/.claude/settings.json +2 -2
  46. package/template_project/.claude/ukit/index/cross-check-matrix.mjs +415 -0
  47. package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +456 -0
  48. package/template_project/.claude/ukit/index/playbook-registry.mjs +690 -0
  49. package/template_project/.claude/ukit/index/review-panel-aggregate.mjs +20 -2
  50. package/template_project/.claude/ukit/index/review-policy.mjs +376 -0
  51. package/template_project/.claude/ukit/index/route-resolver.mjs +1059 -0
  52. package/template_project/.claude/ukit/index/route-task.mjs +1253 -846
  53. package/template_project/.claude/ukit/index/session-history-extractor.mjs +362 -0
  54. package/template_project/.claude/ukit/index/tier-selection.mjs +309 -0
  55. package/template_project/.claude/ukit/index/verification-map.mjs +403 -0
  56. package/template_project/.claude/ukit/index/worktree-sweep.mjs +195 -0
  57. package/template_project/.claude/ukit/runtime/execution-ledger.mjs +789 -11
  58. package/template_project/.claude/ukit/runtime/observability-emit.mjs +1102 -0
  59. package/template_project/.claude/ukit/runtime/reinject-context.mjs +9 -1
  60. package/template_project/.claude/ukit/runtime/resumable-run.mjs +149 -5
  61. package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +323 -6
  62. package/template_project/.codex/README.md +8 -0
  63. package/template_project/.omp/hooks/pre/ukit-bridge.js +8 -1
  64. package/template_project/ukit/README.md +1 -1
  65. package/template_project/ukit/storage/config.json +20 -0
  66. package/template_user/playbooks/architecture-decision.md +28 -0
  67. package/template_user/playbooks/autonomous-run.md +43 -0
  68. package/template_user/playbooks/autopilot-full.md +59 -0
  69. package/template_user/playbooks/autopilot-stack.md +54 -0
  70. package/template_user/playbooks/babysit.md +39 -0
  71. package/template_user/playbooks/bug-fix.md +3 -1
  72. package/template_user/playbooks/{issue-implementation.md → feature-implementation.md} +4 -2
  73. package/template_user/playbooks/hillclimb.md +44 -0
  74. package/template_user/playbooks/investigation.md +21 -0
  75. package/template_user/playbooks/migration.md +21 -0
  76. package/template_user/playbooks/open-pr.md +48 -0
  77. package/template_user/playbooks/orchestrate.md +45 -0
  78. package/template_user/playbooks/performance.md +33 -0
  79. package/template_user/playbooks/prototype.md +28 -0
  80. package/template_user/playbooks/refactor.md +19 -0
  81. package/template_user/playbooks/release.md +28 -0
  82. package/template_user/playbooks/runtime-forensics.md +23 -0
  83. package/template_user/playbooks/session-pickup.md +31 -0
  84. package/template_user/playbooks/shipping.md +53 -0
  85. package/template_user/playbooks/skill-evaluation.md +48 -0
  86. package/template_user/playbooks/small-feature.md +20 -0
  87. package/template_user/playbooks/verification-map.json +153 -0
  88. package/template_user/playbooks/verification.md +22 -0
  89. package/template_user/playbooks/worktree-cleanup.md +37 -0
@@ -27,10 +27,13 @@
27
27
  */
28
28
 
29
29
  import { promises as fs } from 'node:fs';
30
+ import crypto from 'node:crypto';
30
31
  import path from 'node:path';
31
32
 
32
33
  import { withFileLock } from '../fileOps.js';
33
34
 
35
+ import { writeRunArtifact } from './artifacts.js';
36
+
34
37
  import {
35
38
  CONTRACT_VERSION,
36
39
  OPERATION_STATES,
@@ -569,3 +572,176 @@ export async function migrateJournal(fromDir, outDir, opts = {}) {
569
572
  }
570
573
  return { migrated: outputs.reduce((n, o) => n + (o.body ? o.body.trimEnd().split('\n').length : 0), 0) };
571
574
  }
575
+
576
+ // --- C89-003: bounded run-artifact resolution (SPEC §5) --------------------
577
+ // `resolveRunArtifact` is the ONLY path that turns an envelope/journal ref
578
+ // back into bytes. Authorization and honesty rules:
579
+ // - caller must name the run it is entitled to (`caller.runId`); a ref
580
+ // tagged for another runId resolves `cross_run` and is never read;
581
+ // - `sensitivity:'sensitive'` refs resolve only for `caller.role:'owner'`
582
+ // (support/reviewer lanes get `redacted` — the ref may be named but the
583
+ // content never leaks);
584
+ // - the lexical path AND the physical realpath must stay inside runRoot —
585
+ // `..`, absolute escapes, and symlinks out of the run dir all resolve
586
+ // `path_escape` before a byte is read;
587
+ // - `sha256` is mandatory on the ref: without it the bytes are
588
+ // unverifiable, so the ref is `malformed_ref` rather than trusted;
589
+ // - success returns `{bytes, sha256}` (frozen signature); every failure is
590
+ // `{status:'unavailable', reason}` — typed, never a throw for ordinary
591
+ // missing/corrupt evidence so the caller can mark the result `partial`.
592
+
593
+ const SHA256_HEX_RE = /^[0-9a-f]{64}$/;
594
+
595
+ const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;
596
+ const isPlainRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
597
+
598
+ const unavailable = (reason) => ({ status: 'unavailable', reason });
599
+
600
+ /**
601
+ * Resolve a bounded run-artifact ref for an authorized caller.
602
+ *
603
+ * @param {object} ref `{path, sha256, bytes?, runId?, truncated?, sensitivity?}`
604
+ * @param {object} scope `{runRoot, caller:{runId, role?}}`
605
+ * @returns {Promise<{bytes:Uint8Array, sha256:string, truncated?:boolean}
606
+ * |{status:'unavailable', reason:string}>}
607
+ */
608
+ export async function resolveRunArtifact(ref, { runRoot, caller } = {}) {
609
+ if (!isPlainRecord(caller) || !isNonEmptyString(caller.runId)) {
610
+ return unavailable('unauthorized');
611
+ }
612
+ if (!isPlainRecord(ref) || !isNonEmptyString(ref.path)) {
613
+ return unavailable('malformed_ref');
614
+ }
615
+ if (isNonEmptyString(ref.runId) && ref.runId !== caller.runId) {
616
+ return unavailable('cross_run');
617
+ }
618
+ if (ref.sensitivity === 'sensitive' && caller.role !== 'owner') {
619
+ return unavailable('redacted');
620
+ }
621
+ if (!SHA256_HEX_RE.test(ref.sha256)) {
622
+ return unavailable('malformed_ref');
623
+ }
624
+ if (typeof runRoot !== 'string' || runRoot === '') {
625
+ return unavailable('unavailable');
626
+ }
627
+ const root = path.resolve(runRoot);
628
+ const resolved = path.isAbsolute(ref.path)
629
+ ? path.resolve(ref.path)
630
+ : path.resolve(root, ref.path);
631
+ if (resolved !== root && !resolved.startsWith(root + path.sep)) {
632
+ return unavailable('path_escape');
633
+ }
634
+ let real;
635
+ try {
636
+ // realpath BOTH sides: tmp dirs and CI roots can themselves be reached
637
+ // through symlinks (/var → /private/var on macOS), so compare physical
638
+ // paths — this is also what catches a symlink inside the run dir
639
+ // pointing outside it.
640
+ const [realResolved, realRoot] = await Promise.all([
641
+ fs.realpath(resolved),
642
+ fs.realpath(root),
643
+ ]);
644
+ real = realResolved;
645
+ if (real !== realRoot && !real.startsWith(realRoot + path.sep)) {
646
+ return unavailable('path_escape');
647
+ }
648
+ } catch (err) {
649
+ if (err && err.code === 'ENOENT') return unavailable('missing');
650
+ return unavailable('unreadable');
651
+ }
652
+ let bytes;
653
+ try {
654
+ bytes = await fs.readFile(real);
655
+ } catch (err) {
656
+ if (err && err.code === 'ENOENT') return unavailable('missing');
657
+ return unavailable('unreadable');
658
+ }
659
+ if (Number.isInteger(ref.bytes) && ref.bytes !== bytes.length) {
660
+ return unavailable('bytes_mismatch');
661
+ }
662
+ const sha256 = crypto.createHash('sha256').update(bytes).digest('hex');
663
+ if (sha256 !== ref.sha256) {
664
+ return unavailable('checksum_mismatch');
665
+ }
666
+ const out = { bytes, sha256 };
667
+ if (ref.truncated === true) out.truncated = true;
668
+ return out;
669
+ }
670
+
671
+ // --- C89-006: context-manifest persistence (SPEC §7) ------------------------
672
+ // `persistContextManifestRef` lands a built ContextManifest inside the
673
+ // existing journal + artifact machinery: the full manifest goes to the run
674
+ // directory as a checksummed artifact (original retrievable through
675
+ // `resolveRunArtifact` for authorized callers) and a compact `context.manifest`
676
+ // event records only the ref + selection counts in safePayload — item
677
+ // bodies never ride the journal, so secrets stay behind the ref boundary.
678
+
679
+ const CONTEXT_MANIFEST_EVENT = 'context.manifest';
680
+ const CONTEXT_MANIFEST_PRODUCER = 'ukit-context-manifest';
681
+ const MANIFEST_NAME_RE = /[^A-Za-z0-9._-]/g;
682
+
683
+ /**
684
+ * Persist a ContextManifest as a run artifact and journal a compact
685
+ * `context.manifest` event against `operationId`.
686
+ *
687
+ * Commit order: artifact first, then the event — a crash between them leaves
688
+ * an orphaned artifact (harmless), never an event pointing at a missing
689
+ * original.
690
+ *
691
+ * @param {string} dir store root (same layout as appendEvent)
692
+ * @param {object} args `{manifest, operationId, runId?, eventId?, now?}`
693
+ * @returns {Promise<{seq:number, manifestRef:object}>}
694
+ * @throws {EventStoreError} 'malformed_manifest' before any I/O; journal
695
+ * errors propagate typed from appendEvent.
696
+ */
697
+ export async function persistContextManifestRef(dir, {
698
+ manifest,
699
+ operationId,
700
+ runId,
701
+ eventId,
702
+ now,
703
+ } = {}) {
704
+ const isPlain = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
705
+ if (!isPlain(manifest) || manifest.version !== 1 || !Array.isArray(manifest.items)
706
+ || typeof operationId !== 'string' || operationId === '') {
707
+ throw new EventStoreError('malformed_manifest', 'manifest must be a v1 ContextManifest');
708
+ }
709
+ const effectiveRunId = isNonEmptyString(runId)
710
+ ? runId
711
+ : (isNonEmptyString(manifest.runId) ? manifest.runId : operationId);
712
+
713
+ const bytes = Buffer.from(JSON.stringify(manifest), 'utf8');
714
+ const name = `context-manifest-${operationId.replace(MANIFEST_NAME_RE, '_')}.json`;
715
+ const artRef = await writeRunArtifact(dir, name, bytes);
716
+ // Tag the ref for this run so cross-run resolution is denied at the
717
+ // resolveRunArtifact boundary (never read, never leaked).
718
+ const manifestRef = { ...artRef, runId: effectiveRunId, version: 1 };
719
+
720
+ const cursor = await loadCursor(dir, operationId);
721
+ const event = {
722
+ eventId: isNonEmptyString(eventId)
723
+ ? eventId
724
+ : `ctx-manifest-${operationId}-${crypto.randomUUID()}`,
725
+ operationId,
726
+ seq: cursor.lastSeq + 1,
727
+ eventType: CONTEXT_MANIFEST_EVENT,
728
+ observedAt: typeof now === 'function' ? now() : new Date().toISOString(),
729
+ producerVersion: CONTEXT_MANIFEST_PRODUCER,
730
+ contractVersion: CONTRACT_VERSION,
731
+ privacyClass: 'internal',
732
+ artifactRefs: [artRef.path],
733
+ safePayload: {
734
+ runId: effectiveRunId,
735
+ mode: isNonEmptyString(manifest.mode) ? manifest.mode : 'unknown',
736
+ policyVersion: isNonEmptyString(manifest.policyVersion)
737
+ ? manifest.policyVersion : 'unknown',
738
+ itemCount: manifest.items.length,
739
+ includedCount: manifest.items.filter((i) => i && i.included === true).length,
740
+ excludedCount: manifest.items.filter((i) => i && i.included !== true).length,
741
+ tokensUsed: Number.isInteger(manifest.tokensUsed) ? manifest.tokensUsed : null,
742
+ manifestRef,
743
+ },
744
+ };
745
+ const { seq } = await appendEvent(dir, event);
746
+ return { seq, manifestRef };
747
+ }