@danielblomma/cortex-mcp 2.0.16 → 2.0.19
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 +6 -4
- package/bin/cortex.mjs +75 -10
- package/package.json +2 -1
- package/scaffold/.context/config.yaml +2 -3
- package/scaffold/.githooks/_cortex-update-runner.sh +2 -2
- package/scaffold/mcp/package-lock.json +59 -60
- package/scaffold/mcp/package.json +2 -2
- package/scaffold/mcp/src/cli/stage.ts +15 -18
- package/scaffold/mcp/src/core/validators/builtins.ts +24 -4
- package/scaffold/mcp/src/core/workflow/index.ts +1 -0
- package/scaffold/mcp/src/core/workflow/mcp-tools.ts +15 -25
- package/scaffold/mcp/src/core/workflow/resolution.ts +113 -0
- package/scaffold/mcp/src/embeddings.ts +1 -1
- package/scaffold/mcp/src/enterprise/index.ts +16 -1
- package/scaffold/mcp/src/enterprise/reviews/policy-selection.ts +65 -0
- package/scaffold/mcp/src/enterprise/reviews/push.ts +55 -4
- package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +305 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +40 -47
- package/scaffold/mcp/src/enterprise/tools/harness.ts +2 -0
- package/scaffold/mcp/src/enterprise/workflow/state.ts +21 -0
- package/scaffold/mcp/src/graph.ts +3 -3
- package/scaffold/mcp/tests/enterprise-review-trust.test.mjs +318 -0
- package/scaffold/mcp/tests/fixtures/org-skillz-contract.json +59 -0
- package/scaffold/mcp/tests/fixtures/review-trust-contract.json +111 -0
- package/scaffold/mcp/tests/review-policy-selection.test.mjs +85 -0
- package/scaffold/mcp/tests/review-trust-contract.test.mjs +167 -0
- package/scaffold/mcp/tests/review-validator-source.test.mjs +52 -0
- package/scaffold/mcp/tests/skill-sync-checker.test.mjs +277 -0
- package/scaffold/mcp/tests/workflow-cli.test.mjs +102 -1
- package/scaffold/mcp/tests/workflow-mcp-tools.test.mjs +22 -0
- package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +56 -0
- package/scaffold/scripts/bootstrap.sh +9 -8
- package/scaffold/scripts/context.sh +1 -1
- package/scaffold/scripts/dashboard.mjs +3 -1
- package/scaffold/scripts/dashboard.sh +1 -1
- package/scaffold/scripts/doctor.sh +2 -1
- package/scaffold/scripts/embed.sh +2 -1
- package/scaffold/scripts/ingest.mjs +3 -1
- package/scaffold/scripts/ingest.sh +3 -2
- package/scaffold/scripts/install-git-hooks.sh +2 -1
- package/scaffold/scripts/load-kuzu.sh +2 -2
- package/scaffold/scripts/load-ryu.sh +2 -1
- package/scaffold/scripts/memory-compile.mjs +2 -2
- package/scaffold/scripts/memory-compile.sh +3 -2
- package/scaffold/scripts/memory-lint.mjs +2 -2
- package/scaffold/scripts/memory-lint.sh +3 -2
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +0 -1
- package/scaffold/scripts/parsers/package-lock.json +0 -1
- package/scaffold/scripts/refresh.sh +2 -2
- package/scaffold/scripts/status.sh +6 -5
- package/scaffold/scripts/update-context.sh +5 -5
- package/scaffold/scripts/watch.sh +7 -8
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { advanceStage, createRun, getRunState } from "./run-lifecycle.js";
|
|
3
3
|
import { composeStageEnvelope } from "./envelope.js";
|
|
4
|
-
import {
|
|
5
|
-
import { loadSyncedWorkflows } from "./synced-registry.js";
|
|
4
|
+
import { resolveWorkflowDefinition } from "./resolution.js";
|
|
6
5
|
import {
|
|
7
6
|
stageOverrideSchema,
|
|
8
7
|
stageStatusSchema,
|
|
@@ -88,33 +87,18 @@ export function resolveProjectRoot(): string {
|
|
|
88
87
|
export type WorkflowToolContext = {
|
|
89
88
|
cwd: string;
|
|
90
89
|
workflows?: Record<string, WorkflowDefinition>;
|
|
90
|
+
bundledFallbackPolicy?: "allow" | "warn" | "block";
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
function resolveWorkflow(
|
|
94
|
-
workflowId: string,
|
|
95
|
-
registry: Record<string, WorkflowDefinition> | undefined,
|
|
96
|
-
): WorkflowDefinition {
|
|
97
|
-
// When the caller passes an explicit registry, it wins outright (used
|
|
98
|
-
// by tests). Otherwise we merge bundled defaults with the org-authored
|
|
99
|
-
// workflows the daemon has synced into ~/.cortex/workflows.local.json,
|
|
100
|
-
// with the synced ones taking precedence on workflow_id collisions so
|
|
101
|
-
// org overrides actually override.
|
|
102
|
-
const workflows =
|
|
103
|
-
registry ?? { ...DEFAULT_WORKFLOWS, ...loadSyncedWorkflows() };
|
|
104
|
-
const workflow = workflows[workflowId];
|
|
105
|
-
if (!workflow) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Unknown workflow_id: ${workflowId}. Available: ${Object.keys(workflows).join(", ") || "<none>"}`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return workflow;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
93
|
export function runWorkflowStart(
|
|
114
94
|
input: WorkflowStartInputT,
|
|
115
95
|
ctx: WorkflowToolContext,
|
|
116
96
|
) {
|
|
117
|
-
const
|
|
97
|
+
const resolved = resolveWorkflowDefinition(input.workflow_id, {
|
|
98
|
+
registry: ctx.workflows,
|
|
99
|
+
bundledFallbackPolicy: ctx.bundledFallbackPolicy,
|
|
100
|
+
});
|
|
101
|
+
const workflow = resolved.workflow;
|
|
118
102
|
const state = createRun({
|
|
119
103
|
cwd: ctx.cwd,
|
|
120
104
|
taskId: input.task_id,
|
|
@@ -129,6 +113,8 @@ export function runWorkflowStart(
|
|
|
129
113
|
return {
|
|
130
114
|
state,
|
|
131
115
|
envelope,
|
|
116
|
+
workflow_source: resolved.source,
|
|
117
|
+
warnings: resolved.warnings,
|
|
132
118
|
};
|
|
133
119
|
}
|
|
134
120
|
|
|
@@ -142,7 +128,9 @@ export function runWorkflowAdvance(
|
|
|
142
128
|
`No run state found for task ${input.task_id}. Call cortex.workflow.start first.`,
|
|
143
129
|
);
|
|
144
130
|
}
|
|
145
|
-
const workflow =
|
|
131
|
+
const workflow = resolveWorkflowDefinition(state.workflow_id, {
|
|
132
|
+
registry: ctx.workflows,
|
|
133
|
+
}).workflow;
|
|
146
134
|
const stage = workflow.stages.find((s) => s.name === input.stage);
|
|
147
135
|
if (!stage) {
|
|
148
136
|
throw new Error(`Stage ${input.stage} is not defined in workflow ${workflow.id}`);
|
|
@@ -220,7 +208,9 @@ export function runWorkflowEnvelope(
|
|
|
220
208
|
`No run state found for task ${input.task_id}. Call cortex.workflow.start first.`,
|
|
221
209
|
);
|
|
222
210
|
}
|
|
223
|
-
const workflow =
|
|
211
|
+
const workflow = resolveWorkflowDefinition(state.workflow_id, {
|
|
212
|
+
registry: ctx.workflows,
|
|
213
|
+
}).workflow;
|
|
224
214
|
const envelope = composeStageEnvelope({
|
|
225
215
|
cwd: ctx.cwd,
|
|
226
216
|
taskId: input.task_id,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { DEFAULT_WORKFLOWS } from "./default-workflows.js";
|
|
2
|
+
import { loadSyncedWorkflows } from "./synced-registry.js";
|
|
3
|
+
import type { WorkflowDefinition } from "./schemas.js";
|
|
4
|
+
|
|
5
|
+
export type WorkflowSource = "bundled" | "synced" | "injected";
|
|
6
|
+
|
|
7
|
+
export type WorkflowResolutionWarning = {
|
|
8
|
+
code: "bundled-workflow-fallback";
|
|
9
|
+
workflow_id: string;
|
|
10
|
+
message: string;
|
|
11
|
+
bundled_available: boolean;
|
|
12
|
+
synced_available: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type WorkflowResolution = {
|
|
16
|
+
workflow: WorkflowDefinition;
|
|
17
|
+
source: WorkflowSource;
|
|
18
|
+
warnings: WorkflowResolutionWarning[];
|
|
19
|
+
bundled_ids: string[];
|
|
20
|
+
synced_ids: string[];
|
|
21
|
+
available_ids: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ResolveWorkflowOptions = {
|
|
25
|
+
registry?: Record<string, WorkflowDefinition>;
|
|
26
|
+
syncedDir?: string;
|
|
27
|
+
emitBundledFallbackWarning?: boolean;
|
|
28
|
+
bundledFallbackPolicy?: "allow" | "warn" | "block";
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function sortIds(ids: Iterable<string>): string[] {
|
|
32
|
+
return [...ids].sort((left, right) => left.localeCompare(right));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatIds(ids: string[]): string {
|
|
36
|
+
return ids.length > 0 ? ids.join(", ") : "<none>";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveWorkflowDefinition(
|
|
40
|
+
workflowId: string,
|
|
41
|
+
options: ResolveWorkflowOptions = {},
|
|
42
|
+
): WorkflowResolution {
|
|
43
|
+
if (options.registry) {
|
|
44
|
+
const availableIds = sortIds(Object.keys(options.registry));
|
|
45
|
+
const workflow = options.registry[workflowId];
|
|
46
|
+
if (!workflow) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unknown workflow_id: ${workflowId}. Available injected: ${formatIds(availableIds)}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
workflow,
|
|
53
|
+
source: "injected",
|
|
54
|
+
warnings: [],
|
|
55
|
+
bundled_ids: [],
|
|
56
|
+
synced_ids: [],
|
|
57
|
+
available_ids: availableIds,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const bundled = DEFAULT_WORKFLOWS;
|
|
62
|
+
const synced = loadSyncedWorkflows(options.syncedDir);
|
|
63
|
+
const bundledIds = sortIds(Object.keys(bundled));
|
|
64
|
+
const syncedIds = sortIds(Object.keys(synced));
|
|
65
|
+
const merged = { ...bundled, ...synced };
|
|
66
|
+
const availableIds = sortIds(Object.keys(merged));
|
|
67
|
+
const workflow = merged[workflowId];
|
|
68
|
+
|
|
69
|
+
if (!workflow) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Unknown workflow_id: ${workflowId}. Available bundled: ${formatIds(bundledIds)}. ` +
|
|
72
|
+
`Available synced: ${formatIds(syncedIds)}. Available all: ${formatIds(availableIds)}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const source: WorkflowSource = Object.prototype.hasOwnProperty.call(synced, workflowId)
|
|
77
|
+
? "synced"
|
|
78
|
+
: "bundled";
|
|
79
|
+
const warnings: WorkflowResolutionWarning[] = [];
|
|
80
|
+
const fallbackPolicy = options.bundledFallbackPolicy ?? "allow";
|
|
81
|
+
|
|
82
|
+
if (source === "bundled" && fallbackPolicy === "block") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Workflow "${workflowId}" is only available from the bundled registry, ` +
|
|
85
|
+
"but enforced govern mode requires a synced org workflow. " +
|
|
86
|
+
`Available synced: ${formatIds(syncedIds)}.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
source === "bundled" &&
|
|
92
|
+
(options.emitBundledFallbackWarning || fallbackPolicy === "warn")
|
|
93
|
+
) {
|
|
94
|
+
warnings.push({
|
|
95
|
+
code: "bundled-workflow-fallback",
|
|
96
|
+
workflow_id: workflowId,
|
|
97
|
+
bundled_available: true,
|
|
98
|
+
synced_available: syncedIds.length > 0,
|
|
99
|
+
message:
|
|
100
|
+
`Workflow "${workflowId}" was resolved from the bundled registry because ` +
|
|
101
|
+
"no synced org workflow with that id exists in the local cache.",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
workflow,
|
|
107
|
+
source,
|
|
108
|
+
warnings,
|
|
109
|
+
bundled_ids: bundledIds,
|
|
110
|
+
synced_ids: syncedIds,
|
|
111
|
+
available_ids: availableIds,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -101,7 +101,7 @@ export function loadEmbeddingIndex(): EmbeddingIndex {
|
|
|
101
101
|
embeddingsCache = {
|
|
102
102
|
model: null,
|
|
103
103
|
vectors: new Map(),
|
|
104
|
-
warning: "Embedding index missing (run:
|
|
104
|
+
warning: "Embedding index missing (run: cortex embed)"
|
|
105
105
|
};
|
|
106
106
|
return embeddingsCache;
|
|
107
107
|
}
|
|
@@ -17,7 +17,8 @@ import { registerEnterpriseTools } from "./tools/enterprise.js";
|
|
|
17
17
|
import { registerHarnessTools } from "./tools/harness.js";
|
|
18
18
|
import { pushViolations, setViolationPushContext } from "./violations/push.js";
|
|
19
19
|
import { pushReviewResults, setReviewPushContext } from "./reviews/push.js";
|
|
20
|
-
import { setWorkflowPushContext } from "./workflow/push.js";
|
|
20
|
+
import { pushWorkflowSnapshot, setWorkflowPushContext } from "./workflow/push.js";
|
|
21
|
+
import { hasWorkflowState, loadWorkflowState } from "./workflow/state.js";
|
|
21
22
|
|
|
22
23
|
const require = createRequire(import.meta.url);
|
|
23
24
|
const pkg = require("../package.json") as { version: string };
|
|
@@ -32,6 +33,7 @@ let activeAuditWriter: AuditWriter | null = null;
|
|
|
32
33
|
let activeInstanceId: string | null = null;
|
|
33
34
|
let activeSessionId: string | null = null;
|
|
34
35
|
let activeRepo: string | null = null;
|
|
36
|
+
let activeContextDir: string | null = null;
|
|
35
37
|
|
|
36
38
|
async function flushComplianceQueues(
|
|
37
39
|
config: EnterpriseConfig,
|
|
@@ -64,6 +66,15 @@ async function flushComplianceQueues(
|
|
|
64
66
|
if (!result.success) {
|
|
65
67
|
process.stderr.write(`[cortex-enterprise] ${reason} reviews push failed: ${result.error}\n`);
|
|
66
68
|
}
|
|
69
|
+
if (result.attempted && activeContextDir && hasWorkflowState(activeContextDir)) {
|
|
70
|
+
const workflowState = loadWorkflowState(activeContextDir);
|
|
71
|
+
const workflowResult = await pushWorkflowSnapshot(baseUrl, apiKey, workflowState);
|
|
72
|
+
if (!workflowResult.success) {
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
`[cortex-enterprise] ${reason} workflow snapshot refresh after review push failed: ${workflowResult.error}\n`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
} catch (err) {
|
|
68
79
|
process.stderr.write(`[cortex-enterprise] ${reason} reviews push error: ${err}\n`);
|
|
69
80
|
}
|
|
@@ -113,6 +124,7 @@ export function shutdown(): void {
|
|
|
113
124
|
activeInstanceId = null;
|
|
114
125
|
activeSessionId = null;
|
|
115
126
|
activeRepo = null;
|
|
127
|
+
activeContextDir = null;
|
|
116
128
|
setAuditPushContext({});
|
|
117
129
|
setViolationPushContext({});
|
|
118
130
|
setReviewPushContext({});
|
|
@@ -248,6 +260,7 @@ export async function onSessionEvent(event: SessionEvent): Promise<void> {
|
|
|
248
260
|
export async function register(server: McpServer): Promise<void> {
|
|
249
261
|
const projectRoot = process.env.CORTEX_PROJECT_ROOT?.trim() || process.cwd();
|
|
250
262
|
const contextDir = path.join(projectRoot, ".context");
|
|
263
|
+
activeContextDir = contextDir;
|
|
251
264
|
|
|
252
265
|
const config = loadEnterpriseConfig(contextDir);
|
|
253
266
|
const activation = resolveEnterpriseActivation(config);
|
|
@@ -295,6 +308,8 @@ export async function register(server: McpServer): Promise<void> {
|
|
|
295
308
|
repo: activeRepo ?? undefined,
|
|
296
309
|
instance_id: activeInstanceId ?? undefined,
|
|
297
310
|
session_id: activeSessionId ?? undefined,
|
|
311
|
+
context_dir: contextDir,
|
|
312
|
+
project_root: projectRoot,
|
|
298
313
|
});
|
|
299
314
|
setWorkflowPushContext({
|
|
300
315
|
repo: activeRepo ?? undefined,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { OrgPolicy } from "../../core/policy/store.js";
|
|
2
|
+
import {
|
|
3
|
+
getGenericEvaluator,
|
|
4
|
+
getValidator,
|
|
5
|
+
type EnforcedPolicy,
|
|
6
|
+
} from "../../core/validators/engine.js";
|
|
7
|
+
import type { ReviewSkippedPolicy } from "./trust-state.js";
|
|
8
|
+
|
|
9
|
+
export const DEFERRED_CODE_REVIEW_REASON =
|
|
10
|
+
"Current context.review invocation is the review being recorded; validate this policy from workflow state on the next run.";
|
|
11
|
+
|
|
12
|
+
export type PartitionedReviewPolicies = {
|
|
13
|
+
enforced: EnforcedPolicy[];
|
|
14
|
+
skipped: ReviewSkippedPolicy[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function partitionReviewPolicies(
|
|
18
|
+
policies: OrgPolicy[],
|
|
19
|
+
): PartitionedReviewPolicies {
|
|
20
|
+
const enforced: EnforcedPolicy[] = [];
|
|
21
|
+
const skipped: ReviewSkippedPolicy[] = [];
|
|
22
|
+
|
|
23
|
+
for (const policy of policies) {
|
|
24
|
+
if (!policy.enforce) continue;
|
|
25
|
+
|
|
26
|
+
if (policy.id === "require-code-review") {
|
|
27
|
+
skipped.push({
|
|
28
|
+
policy_id: policy.id,
|
|
29
|
+
kind: policy.kind ?? null,
|
|
30
|
+
type: policy.type ?? null,
|
|
31
|
+
reason: DEFERRED_CODE_REVIEW_REASON,
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (policy.type) {
|
|
37
|
+
if (!getGenericEvaluator(policy.type)) {
|
|
38
|
+
skipped.push({
|
|
39
|
+
policy_id: policy.id,
|
|
40
|
+
kind: policy.kind ?? null,
|
|
41
|
+
type: policy.type,
|
|
42
|
+
reason: `No evaluator registered for type "${policy.type}"`,
|
|
43
|
+
});
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
} else if (!getValidator(policy.id)) {
|
|
47
|
+
skipped.push({
|
|
48
|
+
policy_id: policy.id,
|
|
49
|
+
kind: policy.kind ?? null,
|
|
50
|
+
type: null,
|
|
51
|
+
reason: "No executable validator registered for this policy",
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
enforced.push({
|
|
57
|
+
id: policy.id,
|
|
58
|
+
type: policy.type ?? null,
|
|
59
|
+
config: policy.config ?? null,
|
|
60
|
+
severity: policy.severity ?? "block",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { enforced, skipped };
|
|
65
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { recordReviewDeliveryStatus } from "./trust-state.js";
|
|
2
|
+
|
|
1
3
|
export type ReviewPushItem = {
|
|
2
4
|
policy_id: string;
|
|
3
5
|
pass: boolean;
|
|
@@ -11,12 +13,16 @@ export type ReviewPushContext = {
|
|
|
11
13
|
repo?: string;
|
|
12
14
|
instance_id?: string;
|
|
13
15
|
session_id?: string;
|
|
16
|
+
context_dir?: string;
|
|
17
|
+
project_root?: string;
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export type ReviewPushResult = {
|
|
17
21
|
success: boolean;
|
|
18
22
|
count: number;
|
|
19
23
|
error?: string;
|
|
24
|
+
attempted?: boolean;
|
|
25
|
+
delivery_status?: "accepted" | "failed";
|
|
20
26
|
};
|
|
21
27
|
|
|
22
28
|
const pending: ReviewPushItem[] = [];
|
|
@@ -26,6 +32,10 @@ export function setReviewPushContext(context: ReviewPushContext): void {
|
|
|
26
32
|
activeContext = { ...context };
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
export function getReviewPushContext(): ReviewPushContext {
|
|
36
|
+
return { ...activeContext };
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
export function queueReviewResult(item: ReviewPushItem): void {
|
|
30
40
|
pending.push(item);
|
|
31
41
|
}
|
|
@@ -39,11 +49,18 @@ export async function pushReviewResults(
|
|
|
39
49
|
apiKey: string,
|
|
40
50
|
): Promise<ReviewPushResult> {
|
|
41
51
|
if (pending.length === 0) {
|
|
42
|
-
return { success: true, count: 0 };
|
|
52
|
+
return { success: true, count: 0, attempted: false };
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
const reviewsUrl = `${baseUrl.replace(/\/$/, "")}/api/v1/reviews/push`;
|
|
46
56
|
const batch = pending.splice(0, 100);
|
|
57
|
+
const attemptedAt = new Date().toISOString();
|
|
58
|
+
if (activeContext.context_dir) {
|
|
59
|
+
recordReviewDeliveryStatus(activeContext.context_dir, "pushed", {
|
|
60
|
+
reviewCount: batch.length,
|
|
61
|
+
attemptedAt,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
47
64
|
|
|
48
65
|
try {
|
|
49
66
|
const response = await fetch(reviewsUrl, {
|
|
@@ -64,16 +81,50 @@ export async function pushReviewResults(
|
|
|
64
81
|
|
|
65
82
|
if (!response.ok) {
|
|
66
83
|
pending.unshift(...batch);
|
|
67
|
-
|
|
84
|
+
if (activeContext.context_dir) {
|
|
85
|
+
recordReviewDeliveryStatus(activeContext.context_dir, "failed", {
|
|
86
|
+
reviewCount: batch.length,
|
|
87
|
+
attemptedAt,
|
|
88
|
+
error: `HTTP ${response.status}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
count: 0,
|
|
94
|
+
error: `HTTP ${response.status}`,
|
|
95
|
+
attempted: true,
|
|
96
|
+
delivery_status: "failed",
|
|
97
|
+
};
|
|
68
98
|
}
|
|
69
99
|
|
|
70
|
-
|
|
100
|
+
if (activeContext.context_dir) {
|
|
101
|
+
recordReviewDeliveryStatus(activeContext.context_dir, "accepted", {
|
|
102
|
+
reviewCount: batch.length,
|
|
103
|
+
attemptedAt,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
count: batch.length,
|
|
109
|
+
attempted: true,
|
|
110
|
+
delivery_status: "accepted",
|
|
111
|
+
};
|
|
71
112
|
} catch (err) {
|
|
72
113
|
pending.unshift(...batch);
|
|
114
|
+
const error = err instanceof Error ? err.message : "unknown error";
|
|
115
|
+
if (activeContext.context_dir) {
|
|
116
|
+
recordReviewDeliveryStatus(activeContext.context_dir, "failed", {
|
|
117
|
+
reviewCount: batch.length,
|
|
118
|
+
attemptedAt,
|
|
119
|
+
error,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
73
122
|
return {
|
|
74
123
|
success: false,
|
|
75
124
|
count: 0,
|
|
76
|
-
error
|
|
125
|
+
error,
|
|
126
|
+
attempted: true,
|
|
127
|
+
delivery_status: "failed",
|
|
77
128
|
};
|
|
78
129
|
}
|
|
79
130
|
}
|