@ngockhoale/ukit 3.3.3 → 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.
- package/CHANGELOG.md +40 -0
- package/manifests/engineConformance.yaml +17 -1
- package/manifests/hostCapabilities.yaml +68 -1
- package/manifests/platform.full.yaml +138 -0
- package/manifests/platform.user.yaml +255 -3
- package/package.json +1 -1
- package/scripts/bench/subagent-orchestrator-corpus.mjs +275 -0
- package/scripts/bench/subagent-orchestrator-eval.mjs +565 -0
- package/scripts/probe/codex-capability-probe.mjs +169 -0
- package/src/cli/commands/doctor.js +168 -0
- package/src/cli/commands/indexTools.js +7 -0
- package/src/cli/commands/metrics.js +66 -2
- package/src/cli/commands/playbook.js +4 -4
- package/src/cli/commands/vm.js +49 -8
- package/src/core/agentRuntime/adapters.js +328 -27
- package/src/core/agentRuntime/artifacts.js +89 -0
- package/src/core/agentRuntime/context.js +345 -1
- package/src/core/agentRuntime/contract.js +296 -0
- package/src/core/agentRuntime/eventStore.js +176 -0
- package/src/core/agentRuntime/shadowRun.js +481 -5
- package/src/core/agentRuntime/telemetry.js +121 -0
- package/src/core/observability/emit/lifecycle.js +68 -1
- package/src/core/observability/emit/sessionBoot.js +393 -0
- package/src/core/observability/privacy/allowlist.js +10 -1
- package/src/core/observability/schema/registry.js +10 -0
- package/src/core/runtimeConfig.js +133 -0
- package/src/core/userPlaybooks.js +18 -3
- package/src/decision/registry.js +19 -0
- package/src/diagnostics/feedbackEvents.js +7 -4
- package/src/diagnostics/routeOutcomes.js +51 -6
- package/src/diagnostics/skillAccuracy.js +43 -3
- package/src/index/crossCheckMatrix.js +412 -0
- package/src/index/fixLoopEscalation.js +453 -0
- package/src/index/playbookRegistry.js +691 -0
- package/src/index/reviewPolicy.js +368 -0
- package/src/index/routeResolver.js +915 -0
- package/src/index/sessionHistoryExtractor.js +359 -0
- package/src/index/taskRouting.js +764 -581
- package/src/index/tierSelection.js +308 -0
- package/src/index/verificationMap.js +404 -0
- package/template_project/.claude/hooks/observability-emit.mjs +14 -0
- package/template_project/.claude/hooks/record-execution.mjs +19 -1
- package/template_project/.claude/hooks/skill-router.sh +691 -25
- package/template_project/.claude/hooks/verification-guard.sh +230 -1
- package/template_project/.claude/settings.json +2 -2
- package/template_project/.claude/ukit/index/cross-check-matrix.mjs +415 -0
- package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +456 -0
- package/template_project/.claude/ukit/index/playbook-registry.mjs +690 -0
- package/template_project/.claude/ukit/index/review-panel-aggregate.mjs +20 -2
- package/template_project/.claude/ukit/index/review-policy.mjs +376 -0
- package/template_project/.claude/ukit/index/route-resolver.mjs +1059 -0
- package/template_project/.claude/ukit/index/route-task.mjs +1253 -846
- package/template_project/.claude/ukit/index/session-history-extractor.mjs +362 -0
- package/template_project/.claude/ukit/index/tier-selection.mjs +309 -0
- package/template_project/.claude/ukit/index/verification-map.mjs +403 -0
- package/template_project/.claude/ukit/index/worktree-sweep.mjs +195 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +789 -11
- package/template_project/.claude/ukit/runtime/observability-emit.mjs +1102 -0
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +9 -1
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +149 -5
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +323 -6
- package/template_project/.codex/README.md +8 -0
- package/template_project/.omp/hooks/pre/ukit-bridge.js +8 -1
- package/template_project/ukit/README.md +1 -1
- package/template_project/ukit/storage/config.json +20 -0
- package/template_user/playbooks/architecture-decision.md +28 -0
- package/template_user/playbooks/autonomous-run.md +43 -0
- package/template_user/playbooks/autopilot-full.md +59 -0
- package/template_user/playbooks/autopilot-stack.md +54 -0
- package/template_user/playbooks/babysit.md +39 -0
- package/template_user/playbooks/bug-fix.md +3 -1
- package/template_user/playbooks/{issue-implementation.md → feature-implementation.md} +4 -2
- package/template_user/playbooks/hillclimb.md +44 -0
- package/template_user/playbooks/investigation.md +21 -0
- package/template_user/playbooks/migration.md +21 -0
- package/template_user/playbooks/open-pr.md +48 -0
- package/template_user/playbooks/orchestrate.md +45 -0
- package/template_user/playbooks/performance.md +33 -0
- package/template_user/playbooks/prototype.md +28 -0
- package/template_user/playbooks/refactor.md +19 -0
- package/template_user/playbooks/release.md +28 -0
- package/template_user/playbooks/runtime-forensics.md +23 -0
- package/template_user/playbooks/session-pickup.md +31 -0
- package/template_user/playbooks/shipping.md +53 -0
- package/template_user/playbooks/skill-evaluation.md +48 -0
- package/template_user/playbooks/small-feature.md +20 -0
- package/template_user/playbooks/verification-map.json +153 -0
- package/template_user/playbooks/verification.md +22 -0
- 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
|
+
}
|