sentinelayer-cli 0.46.5 → 0.48.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/README.md +47 -3
- package/package.json +12 -7
- package/skills/omar-gate-setup/SKILL.md +103 -0
- package/skills/senti-agent-identity/SKILL.md +80 -0
- package/skills/senti-judge/SKILL.md +139 -0
- package/skills/senti-room-notifications/SKILL.md +73 -0
- package/skills/senti-room-notifications/listen-wrapper.cjs +150 -0
- package/skills/senti-room-notifications/package.json +1 -0
- package/skills/senti-room-notifications/test-classifier.js +109 -0
- package/skills/senti-room-notifications/test-receipt.js +54 -0
- package/skills/sentinelayer-cli/SKILL.md +222 -0
- package/src/agents/devtestbot/runner.js +22 -11
- package/src/agents/devtestbot/tool.js +24 -11
- package/src/agents/jules/fix-cycle.js +7 -1
- package/src/agents/jules/tools/runtime-audit.js +17 -12
- package/src/agents/shared-tools/dispatch-core.js +10 -5
- package/src/ai/aidenid.js +4 -2
- package/src/ai/domain-target-store.js +11 -5
- package/src/ai/identity-store.js +11 -5
- package/src/ai/site-store.js +8 -4
- package/src/audit/orchestrator.js +21 -14
- package/src/audit/package.js +6 -4
- package/src/audit/persona-loop.js +7 -0
- package/src/audit/replay.js +4 -2
- package/src/auth/gate.js +32 -1
- package/src/auth/session-store.js +37 -0
- package/src/board/evidence-resolvers.js +3 -4
- package/src/cli.js +23 -3
- package/src/commands/ai/identity-lifecycle.js +30 -26
- package/src/commands/ai/provision-governance.js +32 -24
- package/src/commands/ai/shared.js +6 -4
- package/src/commands/audit.js +14 -12
- package/src/commands/board.js +2 -2
- package/src/commands/chat.js +8 -6
- package/src/commands/judge.js +125 -0
- package/src/commands/legacy-args.js +1 -0
- package/src/commands/mcp.js +45 -52
- package/src/commands/omargate.js +2 -1
- package/src/commands/plugin.js +11 -3
- package/src/commands/review.js +9 -4
- package/src/commands/scan.js +7 -7
- package/src/commands/session.js +516 -30
- package/src/commands/skills.js +45 -0
- package/src/commands/watch.js +9 -7
- package/src/config/output-filesystem.js +156 -0
- package/src/config/schema.js +4 -2
- package/src/config/service.js +40 -4
- package/src/cost/history.js +15 -9
- package/src/daemon/artifact-lineage.js +15 -12
- package/src/daemon/assignment-ledger.js +31 -25
- package/src/daemon/ast-drift.js +7 -3
- package/src/daemon/budget-governor.js +26 -20
- package/src/daemon/error-worker.js +28 -22
- package/src/daemon/fix-cycle.js +5 -4
- package/src/daemon/hybrid-mapper.js +19 -16
- package/src/daemon/ingest-refresh.js +4 -0
- package/src/daemon/jira-lifecycle.js +21 -17
- package/src/daemon/operator-control.js +18 -15
- package/src/daemon/reliability-lane.js +18 -15
- package/src/daemon/scope-engine.js +12 -8
- package/src/daemon/watchdog.js +15 -12
- package/src/engram/build-state.js +24 -3
- package/src/engram/capability-index.js +367 -0
- package/src/engram/compile-graph.js +1063 -0
- package/src/engram/contracts/action-receipt.js +82 -0
- package/src/engram/contracts/capability-index.js +259 -0
- package/src/engram/contracts/capability.js +377 -0
- package/src/engram/contracts/content-hash.js +8 -0
- package/src/engram/contracts/evidence.js +258 -0
- package/src/engram/contracts/graph.js +594 -0
- package/src/engram/contracts/identity.js +8 -0
- package/src/engram/contracts/index.js +149 -0
- package/src/engram/contracts/media-asset.js +85 -0
- package/src/engram/contracts/money.js +249 -0
- package/src/engram/contracts/source-snapshot.js +57 -0
- package/src/engram/contracts/task-context-pack.js +114 -0
- package/src/engram/contracts/temporal-segment.js +72 -0
- package/src/engram/contracts/versions.js +96 -0
- package/src/engram/extract-html.js +692 -0
- package/src/engram/graph-store.js +877 -0
- package/src/engram/index.js +98 -14
- package/src/engram/ingest-site.js +132 -0
- package/src/engram/live-state.js +1180 -0
- package/src/engram/namespace.js +34 -1
- package/src/engram/retrieve-site.js +753 -0
- package/src/engram/safe-reader.js +597 -0
- package/src/engram/source-intake.js +251 -0
- package/src/engram/store.js +227 -9
- package/src/ingest/engine.js +21 -28
- package/src/judge/verdict.js +305 -0
- package/src/legacy-cli.js +96 -19
- package/src/mcp/bridge-inputs.js +25 -4
- package/src/mcp/cli-command-tools.js +70 -0
- package/src/mcp/registry.js +17 -13
- package/src/mcp/session-stdio-server.js +297 -19
- package/src/memory/blackboard.js +5 -4
- package/src/net/gh-api-path.js +35 -9
- package/src/net/test-egress-guard.js +16 -4
- package/src/plugin/manifest.js +12 -8
- package/src/review/ai-review.js +110 -10
- package/src/review/investor-dd-devtestbot.js +17 -6
- package/src/review/investor-dd-orchestrator.js +21 -13
- package/src/review/local-review.js +26 -16
- package/src/review/omargate-cache.js +5 -5
- package/src/review/omargate-orchestrator.js +89 -17
- package/src/review/replay.js +9 -4
- package/src/review/report.js +22 -13
- package/src/review/typesafe-severity.js +201 -0
- package/src/session/admission-auth.js +34 -7
- package/src/session/admission.js +110 -15
- package/src/session/attachment-summary.js +77 -0
- package/src/session/edit-summary.js +19 -0
- package/src/session/invitations.js +65 -1
- package/src/session/mentions.js +17 -9
- package/src/session/message-edits.js +5 -4
- package/src/session/preview.js +10 -2
- package/src/session/recap.js +3 -2
- package/src/session/room-files.js +519 -0
- package/src/session/route-classes.js +22 -8
- package/src/session/setup-guides.js +4 -0
- package/src/session/standup.js +192 -0
- package/src/session/sync.js +229 -10
- package/src/session/tickets.js +41 -0
- package/src/session/transcript.js +2 -1
- package/src/session/wake/resolve-target.js +7 -3
- package/src/skills/listener-runtime.cjs +92 -0
- package/src/skills/service.js +207 -0
- package/src/swarm/factory.js +5 -5
- package/src/swarm/pentest.js +9 -9
- package/src/swarm/report.js +4 -4
- package/src/swarm/runtime.js +15 -12
- package/src/swarm/scenario-dsl.js +7 -5
- package/src/telemetry/ledger.js +16 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fsp from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import { resolveOutputRoot } from "../config/service.js";
|
|
4
|
+
import { resolveOutputRoot, resolveOutputContext } from "../config/service.js";
|
|
5
5
|
|
|
6
6
|
const CACHE_SCHEMA_VERSION = "1.0.0";
|
|
7
7
|
const CACHE_KIND = "omargate-deterministic-cache";
|
|
@@ -178,7 +178,7 @@ export async function writeOmarGateDeterministicCache({
|
|
|
178
178
|
reportPath = "",
|
|
179
179
|
} = {}) {
|
|
180
180
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
181
|
-
const outputRoot = await
|
|
181
|
+
const { outputRoot, outputFs } = await resolveOutputContext({
|
|
182
182
|
cwd: normalizedTargetPath,
|
|
183
183
|
outputDirOverride: outputDir,
|
|
184
184
|
env: process.env,
|
|
@@ -191,7 +191,7 @@ export async function writeOmarGateDeterministicCache({
|
|
|
191
191
|
const runDirectory = path.join(outputRoot, "runs", normalizedRunId);
|
|
192
192
|
const artifactPath = path.join(runDirectory, "deterministic.json");
|
|
193
193
|
const latestPath = path.join(outputRoot, "runs", LATEST_INDEX_NAME);
|
|
194
|
-
await
|
|
194
|
+
await outputFs.mkdir(runDirectory, { recursive: true });
|
|
195
195
|
|
|
196
196
|
const cache = {
|
|
197
197
|
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
@@ -212,8 +212,8 @@ export async function writeOmarGateDeterministicCache({
|
|
|
212
212
|
reportPath: normalizeString(reportPath),
|
|
213
213
|
},
|
|
214
214
|
};
|
|
215
|
-
await
|
|
216
|
-
await
|
|
215
|
+
await outputFs.writeFile(artifactPath, `${JSON.stringify(cache, null, 2)}\n`, "utf-8");
|
|
216
|
+
await outputFs.writeFile(
|
|
217
217
|
latestPath,
|
|
218
218
|
`${JSON.stringify(
|
|
219
219
|
{
|
|
@@ -9,6 +9,7 @@ import { randomUUID } from "node:crypto";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
|
|
11
11
|
import { runAiReviewLayer } from "./ai-review.js";
|
|
12
|
+
import { summarizeSeverityEvaluations, normalizeSeverityClassifier, parseSeverityEvaluationMaxCost } from "./typesafe-severity.js";
|
|
12
13
|
import { buildPersonaReviewPrompt, PERSONA_IDS } from "./persona-prompts.js";
|
|
13
14
|
import { resolveFilteredPersonas, resolveScanMode } from "./scan-modes.js";
|
|
14
15
|
import { reconcileReviewFindings } from "./report.js";
|
|
@@ -388,6 +389,13 @@ function resolveAiResultCostUsd(result = {}) {
|
|
|
388
389
|
);
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
function incompleteReviewReason(result = {}) {
|
|
393
|
+
if (result.reviewValidity?.valid === false || result.parser === "fallback_text" || !Array.isArray(result.findings)) {
|
|
394
|
+
return "invalid_review_response";
|
|
395
|
+
}
|
|
396
|
+
return result.budget?.blocking === true ? "budget_blocked" : null;
|
|
397
|
+
}
|
|
398
|
+
|
|
391
399
|
function buildAiCallEvidence(result = {}, { dryRun = false } = {}) {
|
|
392
400
|
if (dryRun) {
|
|
393
401
|
return {
|
|
@@ -462,11 +470,15 @@ function buildPersonaHealthFindings(personaHealth = {}) {
|
|
|
462
470
|
const aiCallEvidence = personaHealth.aiCallEvidence || {};
|
|
463
471
|
const okCount = Number(personaHealth.ok || 0);
|
|
464
472
|
const unverifiedOkCount = Number(personaHealth.unverifiedOk || 0);
|
|
473
|
+
const skippedCount = Number(personaHealth.skipped || 0);
|
|
474
|
+
const incompleteCount = Number(personaHealth.incomplete || 0);
|
|
465
475
|
const allPersonasFailed = total > 0 && errorCount === total;
|
|
466
476
|
const severity = allPersonasFailed ? "P0" : "P1";
|
|
467
477
|
let message = `Omar AI coverage degraded: ${errorCount}/${total} dispatched personas errored (${Math.round(errorRatio * 100)}%).`;
|
|
468
478
|
if (allPersonasFailed) {
|
|
469
479
|
message = `Omar AI coverage failed closed: all ${total} dispatched personas errored before producing findings.`;
|
|
480
|
+
} else if (skippedCount > 0 || incompleteCount > 0) {
|
|
481
|
+
message = `Omar AI coverage incomplete: ${skippedCount}/${total} requested personas were skipped and ${incompleteCount}/${total} returned incomplete partition, budget, or structured-response coverage.`;
|
|
470
482
|
} else if (errorCount === 0 && unverifiedOkCount > 0) {
|
|
471
483
|
message = `Omar AI coverage failed closed: ${unverifiedOkCount}/${okCount} successful personas lacked token-bearing usage, billing-ledger, or priced-call evidence.`;
|
|
472
484
|
}
|
|
@@ -484,12 +496,13 @@ function buildPersonaHealthFindings(personaHealth = {}) {
|
|
|
484
496
|
confidenceFloor: 0,
|
|
485
497
|
sources: ["orchestrator"],
|
|
486
498
|
remediation:
|
|
487
|
-
"
|
|
499
|
+
"Resolve the reported provider, budget, or usage-evidence failure and rerun every requested persona and partition. Partial coverage cannot satisfy a full AI review gate.",
|
|
488
500
|
evidence: warnings,
|
|
489
501
|
personaHealth: {
|
|
490
502
|
ok: Number(personaHealth.ok || 0),
|
|
491
503
|
error: errorCount,
|
|
492
|
-
skipped:
|
|
504
|
+
skipped: skippedCount,
|
|
505
|
+
incomplete: incompleteCount,
|
|
493
506
|
total,
|
|
494
507
|
errorRatio,
|
|
495
508
|
totalCostUsd,
|
|
@@ -562,6 +575,7 @@ async function runOmarPersonaSwarm({
|
|
|
562
575
|
outputDir,
|
|
563
576
|
provider,
|
|
564
577
|
model,
|
|
578
|
+
severityClassifier = "off",
|
|
565
579
|
perPersonaCost,
|
|
566
580
|
dryRun,
|
|
567
581
|
onEvent,
|
|
@@ -670,6 +684,7 @@ async function runOmarPersonaSwarm({
|
|
|
670
684
|
outputDir,
|
|
671
685
|
provider: provider || undefined,
|
|
672
686
|
model: model || undefined,
|
|
687
|
+
...(severityClassifier === "typesafe" ? { severityClassifier } : {}),
|
|
673
688
|
sessionId: usageSessionId || `${subagentRunId}-ai`,
|
|
674
689
|
maxCostUsd: budget.maxCostUsd,
|
|
675
690
|
systemPrompt,
|
|
@@ -717,6 +732,7 @@ async function runOmarPersonaSwarm({
|
|
|
717
732
|
});
|
|
718
733
|
const costUsd = resolveAiResultCostUsd(result);
|
|
719
734
|
const aiCallEvidence = buildAiCallEvidence(result, { dryRun });
|
|
735
|
+
const incompleteReason = incompleteReviewReason(result);
|
|
720
736
|
|
|
721
737
|
if (onEvent) {
|
|
722
738
|
for (const finding of findings) {
|
|
@@ -739,6 +755,7 @@ async function runOmarPersonaSwarm({
|
|
|
739
755
|
partitionCount: partitions.length,
|
|
740
756
|
fileCount: scopedFiles.length,
|
|
741
757
|
findings: findings.length,
|
|
758
|
+
status: incompleteReason ? "incomplete" : "ok",
|
|
742
759
|
summary: result?.summary || summarizeFindings(findings),
|
|
743
760
|
costUsd,
|
|
744
761
|
durationMs: Date.now() - subagentStart,
|
|
@@ -753,7 +770,7 @@ async function runOmarPersonaSwarm({
|
|
|
753
770
|
}
|
|
754
771
|
|
|
755
772
|
return {
|
|
756
|
-
status: "ok",
|
|
773
|
+
status: incompleteReason ? "incomplete" : "ok",
|
|
757
774
|
subagentIndex,
|
|
758
775
|
agentId: subagentIdentity.id,
|
|
759
776
|
files: scopedFiles,
|
|
@@ -761,8 +778,11 @@ async function runOmarPersonaSwarm({
|
|
|
761
778
|
summary: result?.summary || summarizeFindings(findings),
|
|
762
779
|
costUsd,
|
|
763
780
|
aiCallEvidence,
|
|
781
|
+
reason: incompleteReason,
|
|
782
|
+
reviewValidity: result.reviewValidity || null,
|
|
764
783
|
model: result?.model || model || null,
|
|
765
784
|
billing: result?.billing || null,
|
|
785
|
+
...(severityClassifier === "typesafe" ? { severityClassification: result?.severityClassification || null } : {}),
|
|
766
786
|
artifacts: result?.artifacts || null,
|
|
767
787
|
durationMs: Date.now() - subagentStart,
|
|
768
788
|
};
|
|
@@ -827,6 +847,14 @@ async function runOmarPersonaSwarm({
|
|
|
827
847
|
const summary = summarizeFindings(findings);
|
|
828
848
|
const okCount = settledSubagents.filter((result) => result.status === "ok").length;
|
|
829
849
|
const errorCount = settledSubagents.filter((result) => result.status === "error").length;
|
|
850
|
+
const completedCount = settledSubagents.filter((result) =>
|
|
851
|
+
result.status === "ok" && (dryRun || Number(result.aiCallEvidence?.confirmedCalls || 0) > 0)
|
|
852
|
+
).length;
|
|
853
|
+
const coverage = {
|
|
854
|
+
requested: partitions.length,
|
|
855
|
+
completed: completedCount,
|
|
856
|
+
incomplete: partitions.length - completedCount,
|
|
857
|
+
};
|
|
830
858
|
const usageLedgerErrors = settledSubagents.filter((result) =>
|
|
831
859
|
result.status === "error" && isUsageLedgerFailure(result.error)
|
|
832
860
|
);
|
|
@@ -875,6 +903,7 @@ async function runOmarPersonaSwarm({
|
|
|
875
903
|
subagentCount: settledSubagents.length,
|
|
876
904
|
ok: okCount,
|
|
877
905
|
error: errorCount,
|
|
906
|
+
coverage,
|
|
878
907
|
findings: findings.length,
|
|
879
908
|
summary,
|
|
880
909
|
totalCostUsd,
|
|
@@ -892,20 +921,22 @@ async function runOmarPersonaSwarm({
|
|
|
892
921
|
|
|
893
922
|
return {
|
|
894
923
|
personaId,
|
|
895
|
-
|
|
924
|
+
...(severityClassifier === "typesafe" ? { severityClassification: summarizeSeverityEvaluations(settledSubagents) } : {}),
|
|
925
|
+
status: coverage.incomplete === 0 ? "ok" : errorCount === partitions.length ? "error" : "incomplete",
|
|
896
926
|
findings,
|
|
897
927
|
summary,
|
|
898
928
|
costUsd: totalCostUsd,
|
|
899
929
|
aiCallEvidence,
|
|
900
930
|
model: model || null,
|
|
901
931
|
durationMs: Date.now() - startedAt,
|
|
902
|
-
error:
|
|
932
|
+
error: coverage.incomplete === 0 ? null : settledSubagents.find((result) => result.error)?.error || "Requested partition coverage is incomplete.",
|
|
903
933
|
swarm: {
|
|
904
934
|
runId: swarmRunId,
|
|
905
935
|
decision,
|
|
906
936
|
subagentCount: settledSubagents.length,
|
|
907
937
|
ok: okCount,
|
|
908
938
|
error: errorCount,
|
|
939
|
+
coverage,
|
|
909
940
|
partitionSizes: partitions.map((files) => files.length),
|
|
910
941
|
blackboardEntries: blackboard.length,
|
|
911
942
|
subagents: settledSubagents.map((result) => ({
|
|
@@ -919,6 +950,9 @@ async function runOmarPersonaSwarm({
|
|
|
919
950
|
billing: result.billing || null,
|
|
920
951
|
durationMs: result.durationMs || 0,
|
|
921
952
|
error: result.error || null,
|
|
953
|
+
reason: result.reason || null,
|
|
954
|
+
reviewValidity: result.reviewValidity || null,
|
|
955
|
+
verified: dryRun ? null : Number(result.aiCallEvidence?.confirmedCalls || 0) > 0,
|
|
922
956
|
artifacts: result.artifacts || null,
|
|
923
957
|
})),
|
|
924
958
|
},
|
|
@@ -934,7 +968,7 @@ async function runOmarPersonaSwarm({
|
|
|
934
968
|
* @param {number} [options.maxParallel] - Max concurrent persona calls (default 4)
|
|
935
969
|
* @param {string} [options.provider] - LLM provider override
|
|
936
970
|
* @param {string} [options.model] - LLM model override
|
|
937
|
-
* @param {number} [options.maxCostUsd] -
|
|
971
|
+
* @param {number} [options.maxCostUsd] - Completed-persona cost stop threshold (default 5.0), not a pre-dispatch spending cap
|
|
938
972
|
* @param {boolean} [options.dryRun] - Dry-run mode (no LLM calls)
|
|
939
973
|
* @param {string} [options.outputDir] - Output directory override
|
|
940
974
|
* @param {object} [options.deterministic] - Deterministic scan results
|
|
@@ -951,6 +985,7 @@ export async function runOmarGateOrchestrator({
|
|
|
951
985
|
maxParallel = 4,
|
|
952
986
|
provider = "",
|
|
953
987
|
model = "",
|
|
988
|
+
severityClassifier = "off",
|
|
954
989
|
maxCostUsd = 5.0,
|
|
955
990
|
dryRun = false,
|
|
956
991
|
outputDir = "",
|
|
@@ -963,6 +998,8 @@ export async function runOmarGateOrchestrator({
|
|
|
963
998
|
usageRecorder = undefined,
|
|
964
999
|
aiReviewRunner = runAiReviewLayer,
|
|
965
1000
|
} = {}) {
|
|
1001
|
+
normalizeSeverityClassifier(severityClassifier);
|
|
1002
|
+
if (severityClassifier === "typesafe") maxCostUsd = parseSeverityEvaluationMaxCost(maxCostUsd, 5);
|
|
966
1003
|
const runId = `omargate-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
967
1004
|
const startTime = Date.now();
|
|
968
1005
|
|
|
@@ -980,6 +1017,12 @@ export async function runOmarGateOrchestrator({
|
|
|
980
1017
|
const { mode, personas: basePersonas } = resolved;
|
|
981
1018
|
const droppedPersonas = resolved.dropped || [];
|
|
982
1019
|
const unknownPersonas = resolved.unknown || [];
|
|
1020
|
+
if (unknownPersonas.length > 0) {
|
|
1021
|
+
throw new Error("Unknown requested Omar persona IDs; the requested review cannot be completed.");
|
|
1022
|
+
}
|
|
1023
|
+
if (basePersonas.length === 0) {
|
|
1024
|
+
throw new Error("Requested Omar persona roster is empty; AI coverage cannot be completed.");
|
|
1025
|
+
}
|
|
983
1026
|
|
|
984
1027
|
if (onEvent && (droppedPersonas.length > 0 || unknownPersonas.length > 0)) {
|
|
985
1028
|
onEvent(createAgentEvent({
|
|
@@ -1003,6 +1046,9 @@ export async function runOmarGateOrchestrator({
|
|
|
1003
1046
|
manualInclude: Array.isArray(includeOnly) && includeOnly.length > 0,
|
|
1004
1047
|
});
|
|
1005
1048
|
const personas = personaRouting.effectivePersonas;
|
|
1049
|
+
if (personas.length === 0) {
|
|
1050
|
+
throw new Error("Effective Omar persona roster is empty; AI coverage cannot be completed.");
|
|
1051
|
+
}
|
|
1006
1052
|
|
|
1007
1053
|
if (onEvent && personaRouting.enabled) {
|
|
1008
1054
|
onEvent(createAgentEvent({
|
|
@@ -1052,7 +1098,8 @@ export async function runOmarGateOrchestrator({
|
|
|
1052
1098
|
const detFindings = deterministic?.findings || [];
|
|
1053
1099
|
|
|
1054
1100
|
// Per-persona cost budget = global / effective persona count (with minimum floor).
|
|
1055
|
-
const
|
|
1101
|
+
const evaluationWithoutCostCap = severityClassifier === "typesafe" && maxCostUsd === 0;
|
|
1102
|
+
const perPersonaCost = evaluationWithoutCostCap ? 0 : personas.length > 0 ? Math.max(0.25, maxCostUsd / personas.length) : 0;
|
|
1056
1103
|
let runningCostUsd = 0;
|
|
1057
1104
|
|
|
1058
1105
|
const personaResults = await runWithConcurrency(personas, maxParallel, async (personaId) => {
|
|
@@ -1067,7 +1114,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1067
1114
|
};
|
|
1068
1115
|
|
|
1069
1116
|
// Global budget check — skip remaining personas if exhausted
|
|
1070
|
-
if (runningCostUsd >= maxCostUsd) {
|
|
1117
|
+
if (!evaluationWithoutCostCap && runningCostUsd >= maxCostUsd) {
|
|
1071
1118
|
if (onEvent) {
|
|
1072
1119
|
onEvent(createAgentEvent({
|
|
1073
1120
|
event: "persona_skipped",
|
|
@@ -1128,6 +1175,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1128
1175
|
outputDir,
|
|
1129
1176
|
provider,
|
|
1130
1177
|
model,
|
|
1178
|
+
severityClassifier,
|
|
1131
1179
|
perPersonaCost,
|
|
1132
1180
|
dryRun,
|
|
1133
1181
|
onEvent,
|
|
@@ -1151,6 +1199,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1151
1199
|
identity,
|
|
1152
1200
|
error: swarmResult.error || "all subagents failed",
|
|
1153
1201
|
swarm: swarmResult.swarm,
|
|
1202
|
+
status: swarmResult.status,
|
|
1154
1203
|
},
|
|
1155
1204
|
runId,
|
|
1156
1205
|
}));
|
|
@@ -1163,6 +1212,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1163
1212
|
identity,
|
|
1164
1213
|
findings: swarmResult.findings.length,
|
|
1165
1214
|
summary: swarmResult.summary,
|
|
1215
|
+
status: swarmResult.status,
|
|
1166
1216
|
costUsd: personaCost,
|
|
1167
1217
|
durationMs: Date.now() - personaStart,
|
|
1168
1218
|
swarm: swarmResult.swarm,
|
|
@@ -1203,6 +1253,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1203
1253
|
outputDir,
|
|
1204
1254
|
provider: provider || undefined,
|
|
1205
1255
|
model: model || undefined,
|
|
1256
|
+
...(severityClassifier === "typesafe" ? { severityClassifier } : {}),
|
|
1206
1257
|
sessionId: usageSessionId || `${runId}-${personaId}-ai`,
|
|
1207
1258
|
maxCostUsd: perPersonaCost,
|
|
1208
1259
|
systemPrompt,
|
|
@@ -1231,6 +1282,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1231
1282
|
}));
|
|
1232
1283
|
const personaCost = resolveAiResultCostUsd(result);
|
|
1233
1284
|
const aiCallEvidence = buildAiCallEvidence(result, { dryRun });
|
|
1285
|
+
const incompleteReason = incompleteReviewReason(result);
|
|
1234
1286
|
|
|
1235
1287
|
if (onEvent) {
|
|
1236
1288
|
for (const finding of findings) {
|
|
@@ -1262,6 +1314,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1262
1314
|
personaId,
|
|
1263
1315
|
identity,
|
|
1264
1316
|
findings: findings.length,
|
|
1317
|
+
status: incompleteReason ? "incomplete" : "ok",
|
|
1265
1318
|
summary: result?.summary || {},
|
|
1266
1319
|
costUsd: personaCost,
|
|
1267
1320
|
durationMs: Date.now() - personaStart,
|
|
@@ -1274,13 +1327,16 @@ export async function runOmarGateOrchestrator({
|
|
|
1274
1327
|
|
|
1275
1328
|
return {
|
|
1276
1329
|
personaId,
|
|
1277
|
-
status: "ok",
|
|
1330
|
+
status: incompleteReason ? "incomplete" : "ok",
|
|
1278
1331
|
findings,
|
|
1279
1332
|
summary: result?.summary || { P0: 0, P1: 0, P2: 0, P3: 0 },
|
|
1280
1333
|
costUsd: personaCost,
|
|
1281
1334
|
aiCallEvidence,
|
|
1335
|
+
reason: incompleteReason,
|
|
1336
|
+
reviewValidity: result.reviewValidity || null,
|
|
1282
1337
|
model: result?.model || model || null,
|
|
1283
1338
|
billing: result?.billing || null,
|
|
1339
|
+
...(severityClassifier === "typesafe" ? { severityClassification: result?.severityClassification || null } : {}),
|
|
1284
1340
|
artifacts: result?.artifacts || null,
|
|
1285
1341
|
durationMs: Date.now() - personaStart,
|
|
1286
1342
|
};
|
|
@@ -1366,6 +1422,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1366
1422
|
// billing can still carry token-bearing usage and provider-cost receipts.
|
|
1367
1423
|
const personaErrorCount = settled.filter((r) => r.status === "error").length;
|
|
1368
1424
|
const personaSkippedCount = settled.filter((r) => r.status === "skipped").length;
|
|
1425
|
+
const personaIncompleteCount = settled.filter((r) => r.status === "incomplete").length;
|
|
1369
1426
|
const personaOkCount = settled.filter((r) => r.status === "ok").length;
|
|
1370
1427
|
const personaVerifiedOkCount = settled.filter(
|
|
1371
1428
|
(r) => r.status === "ok" && Number(r.aiCallEvidence?.confirmedCalls || 0) > 0
|
|
@@ -1373,34 +1430,44 @@ export async function runOmarGateOrchestrator({
|
|
|
1373
1430
|
const personaUnverifiedOkCount = personaOkCount - personaVerifiedOkCount;
|
|
1374
1431
|
const totalPersonas = settled.length;
|
|
1375
1432
|
const errorRatio = totalPersonas > 0 ? personaErrorCount / totalPersonas : 0;
|
|
1376
|
-
const aiCoverageHealthy =
|
|
1377
|
-
|
|
1378
|
-
|
|
1433
|
+
const aiCoverageHealthy = personaOkCount === totalPersonas &&
|
|
1434
|
+
(dryRun || personaUnverifiedOkCount === 0);
|
|
1435
|
+
const coverage = {
|
|
1436
|
+
status: aiCoverageHealthy ? (dryRun ? "simulated" : "complete") : "incomplete",
|
|
1437
|
+
requestedPersonas: totalPersonas,
|
|
1438
|
+
completedPersonas: dryRun ? personaOkCount : personaVerifiedOkCount,
|
|
1439
|
+
};
|
|
1379
1440
|
|
|
1380
1441
|
const personaHealth = {
|
|
1381
1442
|
ok: personaOkCount,
|
|
1382
1443
|
error: personaErrorCount,
|
|
1383
1444
|
skipped: personaSkippedCount,
|
|
1445
|
+
incomplete: personaIncompleteCount,
|
|
1384
1446
|
total: totalPersonas,
|
|
1385
1447
|
verifiedOk: personaVerifiedOkCount,
|
|
1386
1448
|
unverifiedOk: personaUnverifiedOkCount,
|
|
1387
1449
|
errorRatio,
|
|
1388
1450
|
totalCostUsd: totalCost,
|
|
1389
1451
|
aiCallEvidence,
|
|
1390
|
-
healthy: aiCoverageHealthy
|
|
1452
|
+
healthy: aiCoverageHealthy,
|
|
1391
1453
|
warnings: [],
|
|
1392
1454
|
};
|
|
1393
|
-
if (
|
|
1394
|
-
if (
|
|
1455
|
+
if (totalPersonas > 0) {
|
|
1456
|
+
if (personaErrorCount === totalPersonas) {
|
|
1395
1457
|
personaHealth.warnings.push(
|
|
1396
1458
|
`ALL ${totalPersonas} personas errored. AI coverage is ZERO. Re-check auth (sl auth login) or LLM proxy config.`
|
|
1397
1459
|
);
|
|
1398
|
-
} else if (
|
|
1460
|
+
} else if (personaErrorCount > 0) {
|
|
1399
1461
|
personaHealth.warnings.push(
|
|
1400
1462
|
`${personaErrorCount}/${totalPersonas} personas errored (${Math.round(errorRatio * 100)}%). AI coverage is degraded.`
|
|
1401
1463
|
);
|
|
1402
1464
|
}
|
|
1403
|
-
if (
|
|
1465
|
+
if (personaSkippedCount > 0 || personaIncompleteCount > 0) {
|
|
1466
|
+
personaHealth.warnings.push(
|
|
1467
|
+
`${personaSkippedCount} requested personas skipped; ${personaIncompleteCount} returned incomplete partition, budget, or structured-response coverage. Full AI coverage is incomplete.`
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
if (!dryRun && personaUnverifiedOkCount > 0) {
|
|
1404
1471
|
personaHealth.warnings.push(
|
|
1405
1472
|
`${personaUnverifiedOkCount}/${personaOkCount} successful personas lacked token-bearing usage, billing-ledger, or priced-call evidence.`
|
|
1406
1473
|
);
|
|
@@ -1416,6 +1483,7 @@ export async function runOmarGateOrchestrator({
|
|
|
1416
1483
|
const result = {
|
|
1417
1484
|
runId,
|
|
1418
1485
|
mode,
|
|
1486
|
+
...(severityClassifier === "typesafe" ? { severityClassification: summarizeSeverityEvaluations(settled) } : {}),
|
|
1419
1487
|
basePersonas,
|
|
1420
1488
|
roster,
|
|
1421
1489
|
personaRouting,
|
|
@@ -1430,11 +1498,15 @@ export async function runOmarGateOrchestrator({
|
|
|
1430
1498
|
durationMs: r.durationMs,
|
|
1431
1499
|
model: r.model || null,
|
|
1432
1500
|
billing: r.billing || null,
|
|
1501
|
+
...(severityClassifier === "typesafe" ? { severityClassification: r.severityClassification || null } : {}),
|
|
1433
1502
|
error: r.error || null,
|
|
1503
|
+
reason: r.reason || null,
|
|
1504
|
+
reviewValidity: r.reviewValidity || null,
|
|
1434
1505
|
swarm: r.swarm || null,
|
|
1435
1506
|
artifacts: r.artifacts || null,
|
|
1436
1507
|
})),
|
|
1437
1508
|
personaHealth,
|
|
1509
|
+
coverage,
|
|
1438
1510
|
findings: reconciledFindings,
|
|
1439
1511
|
findingsBySource: {
|
|
1440
1512
|
deterministic: detFindings.length,
|
package/src/review/replay.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requireOutputFileSystem } from "../config/output-filesystem.js";
|
|
1
2
|
import { spawnSync } from "node:child_process";
|
|
2
3
|
import fsp from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
@@ -74,6 +75,7 @@ function normalizeInvocation(invocation = {}) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
export async function writeReviewRunContext({
|
|
78
|
+
outputFs,
|
|
77
79
|
runDirectory,
|
|
78
80
|
runId,
|
|
79
81
|
targetPath,
|
|
@@ -81,6 +83,7 @@ export async function writeReviewRunContext({
|
|
|
81
83
|
invocation = {},
|
|
82
84
|
replay = {},
|
|
83
85
|
} = {}) {
|
|
86
|
+
requireOutputFileSystem(outputFs);
|
|
84
87
|
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
85
88
|
const context = {
|
|
86
89
|
schemaVersion: "1.0.0",
|
|
@@ -96,8 +99,8 @@ export async function writeReviewRunContext({
|
|
|
96
99
|
},
|
|
97
100
|
};
|
|
98
101
|
const contextPath = path.join(normalizedRunDirectory, REVIEW_RUN_CONTEXT_FILE);
|
|
99
|
-
await
|
|
100
|
-
await
|
|
102
|
+
await outputFs.mkdir(normalizedRunDirectory, { recursive: true });
|
|
103
|
+
await outputFs.writeFile(contextPath, `${JSON.stringify(context, null, 2)}\n`, "utf-8");
|
|
101
104
|
return {
|
|
102
105
|
context,
|
|
103
106
|
contextPath,
|
|
@@ -208,11 +211,13 @@ function sanitizeRunId(value) {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
export async function writeReviewComparisonArtifact({
|
|
214
|
+
outputFs,
|
|
211
215
|
runDirectory,
|
|
212
216
|
baseRunId,
|
|
213
217
|
candidateRunId,
|
|
214
218
|
comparison,
|
|
215
219
|
} = {}) {
|
|
220
|
+
requireOutputFileSystem(outputFs);
|
|
216
221
|
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
217
222
|
const fileName = `REVIEW_COMPARISON_${sanitizeRunId(baseRunId)}_vs_${sanitizeRunId(
|
|
218
223
|
candidateRunId
|
|
@@ -225,8 +230,8 @@ export async function writeReviewComparisonArtifact({
|
|
|
225
230
|
candidateRunId: normalizeString(candidateRunId),
|
|
226
231
|
comparison,
|
|
227
232
|
};
|
|
228
|
-
await
|
|
229
|
-
await
|
|
233
|
+
await outputFs.mkdir(normalizedRunDirectory, { recursive: true });
|
|
234
|
+
await outputFs.writeFile(artifactPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
|
|
230
235
|
return {
|
|
231
236
|
artifactPath,
|
|
232
237
|
payload,
|
package/src/review/report.js
CHANGED
|
@@ -4,7 +4,8 @@ import fsp from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
-
import { resolveOutputRoot } from "../config/service.js";
|
|
7
|
+
import { resolveOutputRoot, resolveOutputContext } from "../config/service.js";
|
|
8
|
+
import { bindOutputFileSystem, requireOutputFileSystem } from "../config/output-filesystem.js";
|
|
8
9
|
|
|
9
10
|
const REVIEW_REPORT_JSON = "REVIEW_REPORT.json";
|
|
10
11
|
const REVIEW_REPORT_MD = "REVIEW_REPORT.md";
|
|
@@ -523,16 +524,18 @@ export async function buildUnifiedReviewReport({
|
|
|
523
524
|
}
|
|
524
525
|
|
|
525
526
|
export async function writeUnifiedReviewArtifacts({
|
|
527
|
+
outputFs,
|
|
526
528
|
runDirectory,
|
|
527
529
|
report,
|
|
528
530
|
markdown,
|
|
529
531
|
} = {}) {
|
|
532
|
+
requireOutputFileSystem(outputFs);
|
|
530
533
|
const normalizedRunDirectory = path.resolve(String(runDirectory || "."));
|
|
531
|
-
await
|
|
534
|
+
await outputFs.mkdir(normalizedRunDirectory, { recursive: true });
|
|
532
535
|
const reportJsonPath = path.join(normalizedRunDirectory, REVIEW_REPORT_JSON);
|
|
533
536
|
const reportMarkdownPath = path.join(normalizedRunDirectory, REVIEW_REPORT_MD);
|
|
534
|
-
await
|
|
535
|
-
await
|
|
537
|
+
await outputFs.writeFile(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf-8");
|
|
538
|
+
await outputFs.writeFile(reportMarkdownPath, `${String(markdown || "").trim()}\n`, "utf-8");
|
|
536
539
|
return {
|
|
537
540
|
reportJsonPath,
|
|
538
541
|
reportMarkdownPath,
|
|
@@ -617,6 +620,7 @@ export async function loadUnifiedReviewReport({
|
|
|
617
620
|
outputDir = "",
|
|
618
621
|
env,
|
|
619
622
|
} = {}) {
|
|
623
|
+
const { outputFs } = await resolveOutputContext({ cwd: path.resolve(String(targetPath || ".")), outputDirOverride: outputDir, env });
|
|
620
624
|
const runDirectory = await resolveRunDirectory({ targetPath, runId, outputDir, env });
|
|
621
625
|
const reportJsonPath = path.join(runDirectory, REVIEW_REPORT_JSON);
|
|
622
626
|
const reportMarkdownPath = path.join(runDirectory, REVIEW_REPORT_MD);
|
|
@@ -624,14 +628,14 @@ export async function loadUnifiedReviewReport({
|
|
|
624
628
|
const decisionsPath = path.join(runDirectory, REVIEW_DECISIONS_JSON);
|
|
625
629
|
const decisions = await loadDecisions(decisionsPath);
|
|
626
630
|
const resolvedReport = applyDecisionsToReport(report, decisions.decisions);
|
|
627
|
-
return {
|
|
631
|
+
return bindOutputFileSystem({
|
|
628
632
|
runDirectory,
|
|
629
633
|
reportJsonPath,
|
|
630
634
|
reportMarkdownPath,
|
|
631
635
|
decisionsPath,
|
|
632
636
|
decisions,
|
|
633
637
|
report: resolvedReport,
|
|
634
|
-
};
|
|
638
|
+
}, outputFs);
|
|
635
639
|
}
|
|
636
640
|
|
|
637
641
|
export async function recordReviewDecision({
|
|
@@ -659,6 +663,7 @@ export async function recordReviewDecision({
|
|
|
659
663
|
outputDir,
|
|
660
664
|
env,
|
|
661
665
|
});
|
|
666
|
+
const { outputFs } = loaded;
|
|
662
667
|
const finding = (loaded.report.findings || []).find((item) => item.findingId === normalizedFindingId);
|
|
663
668
|
if (!finding) {
|
|
664
669
|
throw new Error(`Finding '${normalizedFindingId}' not found in run ${loaded.report.runId}.`);
|
|
@@ -678,12 +683,12 @@ export async function recordReviewDecision({
|
|
|
678
683
|
[normalizedFindingId]: decision,
|
|
679
684
|
},
|
|
680
685
|
};
|
|
681
|
-
await
|
|
686
|
+
await outputFs.writeFile(loaded.decisionsPath, `${JSON.stringify(nextDecisions, null, 2)}\n`, "utf-8");
|
|
682
687
|
|
|
683
688
|
const nextReport = applyDecisionsToReport(loaded.report, nextDecisions.decisions);
|
|
684
689
|
const nextMarkdown = composeReportMarkdown(nextReport);
|
|
685
|
-
await
|
|
686
|
-
await
|
|
690
|
+
await outputFs.writeFile(loaded.reportJsonPath, `${JSON.stringify(nextReport, null, 2)}\n`, "utf-8");
|
|
691
|
+
await outputFs.writeFile(loaded.reportMarkdownPath, `${nextMarkdown.trim()}\n`, "utf-8");
|
|
687
692
|
|
|
688
693
|
return {
|
|
689
694
|
runId: nextReport.runId,
|
|
@@ -821,10 +826,14 @@ export async function exportUnifiedReviewReport({
|
|
|
821
826
|
outputDir,
|
|
822
827
|
env,
|
|
823
828
|
});
|
|
824
|
-
const
|
|
825
|
-
?
|
|
829
|
+
const explicitOutput = normalizeString(outputFile)
|
|
830
|
+
? await resolveOutputContext({ cwd: path.resolve(String(targetPath || ".")), outputDirOverride: outputFile, env })
|
|
831
|
+
: null;
|
|
832
|
+
const resolvedOutputPath = explicitOutput
|
|
833
|
+
? explicitOutput.outputRoot
|
|
826
834
|
: resolveDefaultExportPath(loaded.runDirectory, normalizedFormat);
|
|
827
835
|
|
|
836
|
+
const outputFs = requireOutputFileSystem(explicitOutput ? explicitOutput.outputFs : loaded.outputFs);
|
|
828
837
|
let serialized = "";
|
|
829
838
|
if (normalizedFormat === "json") {
|
|
830
839
|
serialized = `${JSON.stringify(loaded.report, null, 2)}\n`;
|
|
@@ -836,8 +845,8 @@ export async function exportUnifiedReviewReport({
|
|
|
836
845
|
serialized = `${buildGithubAnnotations(loaded.report)}\n`;
|
|
837
846
|
}
|
|
838
847
|
|
|
839
|
-
await
|
|
840
|
-
await
|
|
848
|
+
await outputFs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
|
|
849
|
+
await outputFs.writeFile(resolvedOutputPath, serialized, "utf-8");
|
|
841
850
|
|
|
842
851
|
return {
|
|
843
852
|
runId: loaded.report.runId,
|