onto-mcp 0.3.0 → 0.3.2
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/.onto/authority/core-lexicon.yaml +12 -0
- package/.onto/domains/software-engineering/competency_qs.md +192 -63
- package/.onto/domains/software-engineering/concepts.md +67 -5
- package/.onto/domains/software-engineering/conciseness_rules.md +22 -2
- package/.onto/domains/software-engineering/dependency_rules.md +78 -8
- package/.onto/domains/software-engineering/domain_scope.md +181 -150
- package/.onto/domains/software-engineering/extension_cases.md +318 -542
- package/.onto/domains/software-engineering/logic_rules.md +75 -3
- package/.onto/domains/software-engineering/problem_framing_profile.md +29 -2
- package/.onto/domains/software-engineering/prompt_interface.md +122 -0
- package/.onto/domains/software-engineering/structure_spec.md +53 -4
- package/.onto/principles/llm-native-development-guideline.md +20 -0
- package/.onto/principles/productization-charter.md +6 -0
- package/.onto/processes/evolve/material-kind-adapter-contract.md +6 -0
- package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +468 -81
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +177 -0
- package/.onto/processes/reconstruct/source-profile-contract.md +39 -6
- package/.onto/processes/reconstruct/top-level-concept-discovery-contract.md +387 -0
- package/.onto/processes/review/binding-contract.md +8 -0
- package/.onto/processes/review/lens-registry.md +16 -0
- package/.onto/processes/review/pre-dispatch-contracts.md +34 -13
- package/.onto/processes/review/productized-live-path.md +3 -1
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +185 -0
- package/.onto/processes/shared/target-material-kind-contract.md +24 -2
- package/.onto/roles/axiology.md +7 -2
- package/AGENTS.md +4 -2
- package/README.md +52 -29
- package/dist/core-api/reconstruct-api.js +92 -5
- package/dist/core-api/review-api.js +1744 -371
- package/dist/core-runtime/cli/mock-review-unit-executor.js +17 -0
- package/dist/core-runtime/cli/render-review-final-output.js +9 -0
- package/dist/core-runtime/cli/review-invoke.js +387 -55
- package/dist/core-runtime/cli/run-review-prompt-execution.js +361 -90
- package/dist/core-runtime/path-boundary.js +58 -0
- package/dist/core-runtime/pipeline-execution-ledger.js +100 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +33 -1
- package/dist/core-runtime/reconstruct/materialize-preparation.js +54 -4
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +342 -0
- package/dist/core-runtime/reconstruct/post-seed-validation.js +630 -0
- package/dist/core-runtime/reconstruct/record.js +105 -1
- package/dist/core-runtime/reconstruct/run.js +1594 -38
- package/dist/core-runtime/reconstruct/seed-candidate-validation.js +29 -0
- package/dist/core-runtime/review/continuation-plan.js +160 -0
- package/dist/core-runtime/review/execution-plan-boundary.js +123 -0
- package/dist/core-runtime/review/materializers.js +8 -3
- package/dist/core-runtime/review/pipeline-execution-ledger.js +250 -0
- package/dist/core-runtime/review/review-artifact-utils.js +15 -2
- package/dist/core-runtime/review/review-invocation-runner.js +604 -0
- package/dist/core-runtime/target-material-kind.js +43 -5
- package/dist/mcp/server.js +289 -59
- package/dist/mcp/tool-schemas.js +28 -2
- package/package.json +4 -2
- package/.onto/domains/llm-native-development/competency_qs.md +0 -430
- package/.onto/domains/llm-native-development/concepts.md +0 -242
- package/.onto/domains/llm-native-development/conciseness_rules.md +0 -163
- package/.onto/domains/llm-native-development/dependency_rules.md +0 -216
- package/.onto/domains/llm-native-development/domain_scope.md +0 -197
- package/.onto/domains/llm-native-development/extension_cases.md +0 -474
- package/.onto/domains/llm-native-development/logic_rules.md +0 -123
- package/.onto/domains/llm-native-development/prompt_interface.md +0 -49
- package/.onto/domains/llm-native-development/structure_spec.md +0 -245
|
@@ -14,6 +14,7 @@ import { REVIEW_EXECUTION_STEP_IDS, REVIEW_PROGRESS_TOTAL_STEPS, } from "../revi
|
|
|
14
14
|
import { printOntoReleaseChannelNotice } from "../release-channel/release-channel.js";
|
|
15
15
|
import { executeReviewViaCodexNested } from "./codex-nested-dispatch.js";
|
|
16
16
|
import { ReviewStructuredFailureError, writeAndThrowStructuredFailureRecord, } from "../review/failure-records.js";
|
|
17
|
+
import { assertReviewExecutionPlanSessionBoundary, } from "../review/execution-plan-boundary.js";
|
|
17
18
|
function errorMessage(error) {
|
|
18
19
|
return error instanceof Error ? error.message : String(error);
|
|
19
20
|
}
|
|
@@ -143,6 +144,7 @@ const DEFAULT_LENS_MAX_RETRIES = 10;
|
|
|
143
144
|
/** Base delay for bounded linear retries. Synthesize reuses the base delay. */
|
|
144
145
|
const DEFAULT_LENS_RETRY_INITIAL_DELAY_MS = 8000;
|
|
145
146
|
const DEFAULT_REVIEW_UNIT_TIMEOUT_MS = 600_000;
|
|
147
|
+
const REVIEW_CANCEL_REQUEST_FILENAME = "review-cancel-request.yaml";
|
|
146
148
|
class ReviewUnitTimeoutError extends Error {
|
|
147
149
|
timeoutMs;
|
|
148
150
|
constructor(unitId, timeoutMs) {
|
|
@@ -215,6 +217,12 @@ function haltArtifactFields(haltPhase, outcome) {
|
|
|
215
217
|
function sleep(ms) {
|
|
216
218
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
217
219
|
}
|
|
220
|
+
async function readReviewCancelRequest(sessionRoot) {
|
|
221
|
+
const cancelPath = path.join(sessionRoot, REVIEW_CANCEL_REQUEST_FILENAME);
|
|
222
|
+
if (!(await fileExists(cancelPath)))
|
|
223
|
+
return null;
|
|
224
|
+
return readYamlDocument(cancelPath);
|
|
225
|
+
}
|
|
218
226
|
async function ensureNonEmptyOutputFile(outputPath) {
|
|
219
227
|
if (!(await fileExists(outputPath))) {
|
|
220
228
|
throw new Error(`Executor did not create output file: ${outputPath}`);
|
|
@@ -308,9 +316,14 @@ async function invokeExecutor(executorConfig, projectRoot, sessionRoot, dispatch
|
|
|
308
316
|
? combinedMessage
|
|
309
317
|
: `Executor exited with code ${exitCode} for ${dispatch.unit_id}`);
|
|
310
318
|
}
|
|
319
|
+
if (stderr.trim().length > 0) {
|
|
320
|
+
console.warn(`[review runner warning] ${dispatch.unit_id}: ${stderr.trim()}`);
|
|
321
|
+
}
|
|
311
322
|
await ensureNonEmptyOutputFile(dispatch.output_path);
|
|
312
323
|
}
|
|
313
324
|
function toUnitExecutionResult(outcome) {
|
|
325
|
+
if (outcome.preservedResult)
|
|
326
|
+
return outcome.preservedResult;
|
|
314
327
|
return {
|
|
315
328
|
unit_id: outcome.dispatch.unit_id,
|
|
316
329
|
unit_kind: outcome.dispatch.unit_kind,
|
|
@@ -326,6 +339,45 @@ function toUnitExecutionResult(outcome) {
|
|
|
326
339
|
failure_message: outcome.failure?.message ?? null,
|
|
327
340
|
};
|
|
328
341
|
}
|
|
342
|
+
function allUnitExecutionResults(artifact) {
|
|
343
|
+
if (!artifact)
|
|
344
|
+
return [];
|
|
345
|
+
return [
|
|
346
|
+
...artifact.lens_execution_results,
|
|
347
|
+
...(artifact.issue_artifact_execution_results ?? []),
|
|
348
|
+
...(artifact.deliberation_execution_results ?? []),
|
|
349
|
+
...(artifact.synthesize_execution_result
|
|
350
|
+
? [artifact.synthesize_execution_result]
|
|
351
|
+
: []),
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
function outcomeFromPreviousResult(result) {
|
|
355
|
+
const startedAtMs = Date.parse(result.started_at);
|
|
356
|
+
const completedAtMs = Date.parse(result.completed_at);
|
|
357
|
+
return {
|
|
358
|
+
dispatch: {
|
|
359
|
+
unit_id: result.unit_id,
|
|
360
|
+
unit_kind: result.unit_kind,
|
|
361
|
+
packet_path: result.packet_path,
|
|
362
|
+
output_path: result.output_path,
|
|
363
|
+
},
|
|
364
|
+
success: result.status === "completed",
|
|
365
|
+
startedAtMs: Number.isFinite(startedAtMs) ? startedAtMs : Date.now(),
|
|
366
|
+
completedAtMs: Number.isFinite(completedAtMs) ? completedAtMs : Date.now(),
|
|
367
|
+
...(result.status === "failed"
|
|
368
|
+
? {
|
|
369
|
+
failure: {
|
|
370
|
+
unit_id: result.unit_id,
|
|
371
|
+
unit_kind: result.unit_kind,
|
|
372
|
+
packet_path: result.packet_path,
|
|
373
|
+
output_path: result.output_path,
|
|
374
|
+
message: result.failure_message ?? "Previous unit failed.",
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
: {}),
|
|
378
|
+
preservedResult: result,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
329
381
|
function deriveExecutionStatus(params) {
|
|
330
382
|
if (!params.synthesisExecuted) {
|
|
331
383
|
return "halted_partial";
|
|
@@ -959,7 +1011,7 @@ async function writeReviewRunManifest(executionPlan, artifact, reviewExecutionPr
|
|
|
959
1011
|
synthesize: reviewExecutionProfile.synthesize,
|
|
960
1012
|
deliberation: reviewExecutionProfile.deliberation,
|
|
961
1013
|
runtime_route: {
|
|
962
|
-
execution_realization:
|
|
1014
|
+
execution_realization: route.execution_realization,
|
|
963
1015
|
host_runtime: route.artifact_host_runtime,
|
|
964
1016
|
worker_executor: route.executor,
|
|
965
1017
|
runtime_provider: route.resolved_provider,
|
|
@@ -1149,6 +1201,22 @@ function issueArtifactProgress(artifactId) {
|
|
|
1149
1201
|
const spec = issueArtifactSpec(artifactId);
|
|
1150
1202
|
return { step: spec.progress_step, label: spec.progress_label };
|
|
1151
1203
|
}
|
|
1204
|
+
function issueArtifactOutputPath(executionPlan, artifactId) {
|
|
1205
|
+
switch (artifactId) {
|
|
1206
|
+
case "finding-ledger":
|
|
1207
|
+
return executionPlan.finding_ledger_path;
|
|
1208
|
+
case "finding-relation-graph":
|
|
1209
|
+
return executionPlan.finding_relation_graph_path;
|
|
1210
|
+
case "issue-ledger":
|
|
1211
|
+
return executionPlan.issue_ledger_path;
|
|
1212
|
+
case "issue-stance-matrix":
|
|
1213
|
+
return executionPlan.issue_stance_matrix_path;
|
|
1214
|
+
case "deliberation-plan":
|
|
1215
|
+
return executionPlan.deliberation_plan_path;
|
|
1216
|
+
case "problem-framing":
|
|
1217
|
+
return executionPlan.problem_framing_path;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1152
1220
|
async function runIssueArtifactDispatch(args) {
|
|
1153
1221
|
const seat = await writeIssueArtifactPromptPacket({
|
|
1154
1222
|
artifactId: args.artifactId,
|
|
@@ -1289,7 +1357,17 @@ async function runControlledLensDeliberation(args) {
|
|
|
1289
1357
|
label: "lens deliberation responses",
|
|
1290
1358
|
details: [`participating_lens_count=${deliberationDispatches.length}`],
|
|
1291
1359
|
});
|
|
1360
|
+
const shouldRunUnit = (unitId) => args.runUnitIds === undefined || args.runUnitIds.has(unitId);
|
|
1361
|
+
const preservedOutcomeForDispatch = (dispatch) => {
|
|
1362
|
+
const result = args.preservedResultsByUnitId?.get(dispatch.unit_id);
|
|
1363
|
+
if (!result || result.status !== "completed") {
|
|
1364
|
+
throw new Error(`Cannot preserve continuation unit without a completed prior result: ${dispatch.unit_id}`);
|
|
1365
|
+
}
|
|
1366
|
+
return outcomeFromPreviousResult(result);
|
|
1367
|
+
};
|
|
1292
1368
|
for (const dispatch of deliberationDispatches) {
|
|
1369
|
+
if (!shouldRunUnit(dispatch.unit_id))
|
|
1370
|
+
continue;
|
|
1293
1371
|
const lensId = dispatch.unit_id.replace(/^deliberation-/, "");
|
|
1294
1372
|
const ownOutput = lensOutputById.get(lensId);
|
|
1295
1373
|
if (!ownOutput) {
|
|
@@ -1323,6 +1401,10 @@ async function runControlledLensDeliberation(args) {
|
|
|
1323
1401
|
const dispatch = deliberationDispatches[currentIndex];
|
|
1324
1402
|
if (!dispatch)
|
|
1325
1403
|
return;
|
|
1404
|
+
if (!shouldRunUnit(dispatch.unit_id)) {
|
|
1405
|
+
deliberationOutcomes[currentIndex] = preservedOutcomeForDispatch(dispatch);
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1326
1408
|
deliberationOutcomes[currentIndex] = await runSingleDispatchWithRetries({
|
|
1327
1409
|
projectRoot,
|
|
1328
1410
|
sessionRoot,
|
|
@@ -1349,41 +1431,47 @@ async function runControlledLensDeliberation(args) {
|
|
|
1349
1431
|
content: await fs.readFile(dispatch.output_path, "utf8"),
|
|
1350
1432
|
};
|
|
1351
1433
|
}));
|
|
1352
|
-
const teamleadPacketText = buildTeamleadControlledDeliberationPrompt({
|
|
1353
|
-
session_id: executionPlan.session_id,
|
|
1354
|
-
output_path: executionPlan.deliberation_output_path,
|
|
1355
|
-
lens_outputs: lensOutputs,
|
|
1356
|
-
lens_deliberation_responses: lensDeliberationResponses,
|
|
1357
|
-
...(issueArtifactContext ? { issue_artifact_context: issueArtifactContext } : {}),
|
|
1358
|
-
});
|
|
1359
|
-
await fs.writeFile(executionPlan.teamlead_deliberation_prompt_packet_path, `${teamleadPacketText.trimEnd()}\n`, "utf8");
|
|
1360
|
-
await registerGeneratedPromptPacketRefForDispatch({
|
|
1361
|
-
executionPlan,
|
|
1362
|
-
consumerId: "controlled-deliberation",
|
|
1363
|
-
packetPath: executionPlan.teamlead_deliberation_prompt_packet_path,
|
|
1364
|
-
});
|
|
1365
1434
|
const teamleadDispatch = {
|
|
1366
1435
|
unit_id: "controlled-deliberation",
|
|
1367
1436
|
unit_kind: "deliberation",
|
|
1368
1437
|
packet_path: executionPlan.teamlead_deliberation_prompt_packet_path,
|
|
1369
1438
|
output_path: executionPlan.deliberation_output_path,
|
|
1370
1439
|
};
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
executionPlan,
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1440
|
+
let teamleadOutcome;
|
|
1441
|
+
if (shouldRunUnit(teamleadDispatch.unit_id)) {
|
|
1442
|
+
const teamleadPacketText = buildTeamleadControlledDeliberationPrompt({
|
|
1443
|
+
session_id: executionPlan.session_id,
|
|
1444
|
+
output_path: executionPlan.deliberation_output_path,
|
|
1445
|
+
lens_outputs: lensOutputs,
|
|
1446
|
+
lens_deliberation_responses: lensDeliberationResponses,
|
|
1447
|
+
...(issueArtifactContext ? { issue_artifact_context: issueArtifactContext } : {}),
|
|
1448
|
+
});
|
|
1449
|
+
await fs.writeFile(executionPlan.teamlead_deliberation_prompt_packet_path, `${teamleadPacketText.trimEnd()}\n`, "utf8");
|
|
1450
|
+
await registerGeneratedPromptPacketRefForDispatch({
|
|
1451
|
+
executionPlan,
|
|
1452
|
+
consumerId: "controlled-deliberation",
|
|
1453
|
+
packetPath: executionPlan.teamlead_deliberation_prompt_packet_path,
|
|
1454
|
+
});
|
|
1455
|
+
await emitReviewProgress({
|
|
1456
|
+
executionPlan,
|
|
1457
|
+
step: 10,
|
|
1458
|
+
label: "teamlead controlled deliberation",
|
|
1459
|
+
details: [`output_path=${executionPlan.deliberation_output_path}`],
|
|
1460
|
+
});
|
|
1461
|
+
teamleadOutcome = await runSingleDispatchWithRetries({
|
|
1462
|
+
projectRoot,
|
|
1463
|
+
sessionRoot,
|
|
1464
|
+
executionPlan,
|
|
1465
|
+
executorConfig: teamleadExecutorConfig,
|
|
1466
|
+
dispatch: teamleadDispatch,
|
|
1467
|
+
maxRetries: 1,
|
|
1468
|
+
retryInitialDelayMs: DEFAULT_LENS_RETRY_INITIAL_DELAY_MS,
|
|
1469
|
+
unitTimeoutMs,
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
teamleadOutcome = preservedOutcomeForDispatch(teamleadDispatch);
|
|
1474
|
+
}
|
|
1387
1475
|
if (!teamleadOutcome.success) {
|
|
1388
1476
|
throw new ReviewControlledDeliberationDispatchError(`Teamlead controlled deliberation failed: ${teamleadOutcome.failure?.message ?? "unknown error"}`, [...completedDeliberationOutcomes, teamleadOutcome], teamleadOutcome);
|
|
1389
1477
|
}
|
|
@@ -1402,9 +1490,42 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1402
1490
|
const sessionRoot = path.resolve(params.sessionRoot);
|
|
1403
1491
|
const executionPlanPath = path.join(sessionRoot, "execution-plan.yaml");
|
|
1404
1492
|
const executionPlan = await readYamlDocument(executionPlanPath);
|
|
1493
|
+
await assertReviewExecutionPlanSessionBoundary({ sessionRoot, executionPlan });
|
|
1405
1494
|
const executionStartedAtMs = Date.now();
|
|
1406
|
-
|
|
1407
|
-
|
|
1495
|
+
const continuationPlan = params.continuationPlan;
|
|
1496
|
+
const continuationMode = continuationPlan !== undefined;
|
|
1497
|
+
const continuationRunUnitIds = new Set([
|
|
1498
|
+
...(continuationPlan?.frontierUnits ?? []),
|
|
1499
|
+
...(continuationPlan?.downstreamUnits ?? []),
|
|
1500
|
+
]
|
|
1501
|
+
.filter((unit) => unit.dispatchDecision === "run")
|
|
1502
|
+
.map((unit) => unit.unitId));
|
|
1503
|
+
const previousExecutionResult = continuationMode && await fileExists(executionPlan.execution_result_path)
|
|
1504
|
+
? await readYamlDocument(executionPlan.execution_result_path)
|
|
1505
|
+
: null;
|
|
1506
|
+
const previousResultsByUnitId = new Map(allUnitExecutionResults(previousExecutionResult).map((result) => [
|
|
1507
|
+
result.unit_id,
|
|
1508
|
+
result,
|
|
1509
|
+
]));
|
|
1510
|
+
const shouldRunUnit = (unitId) => !continuationMode || continuationRunUnitIds.has(unitId);
|
|
1511
|
+
const preservedOutcomeForDispatch = (dispatch) => {
|
|
1512
|
+
const result = previousResultsByUnitId.get(dispatch.unit_id);
|
|
1513
|
+
if (!result || result.status !== "completed") {
|
|
1514
|
+
throw new Error(`Cannot preserve continuation unit without a completed prior result: ${dispatch.unit_id}`);
|
|
1515
|
+
}
|
|
1516
|
+
return outcomeFromPreviousResult(result);
|
|
1517
|
+
};
|
|
1518
|
+
if (continuationMode) {
|
|
1519
|
+
await appendMarkdownLogEntry(executionPlan.error_log_path, "runner continuation mode", [
|
|
1520
|
+
`frontier_units: ${continuationPlan.frontierUnits.map((unit) => unit.unitId).join(", ")}`,
|
|
1521
|
+
`downstream_units: ${continuationPlan.downstreamUnits.map((unit) => unit.unitId).join(", ")}`,
|
|
1522
|
+
`preserved_artifact_count: ${continuationPlan.preservedArtifactRefs.length}`,
|
|
1523
|
+
].join("\n"));
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
await resetExecutionOutputs(executionPlan);
|
|
1527
|
+
await pruneGeneratedPromptPacketRefs(executionPlan);
|
|
1528
|
+
}
|
|
1408
1529
|
await ensureReviewContextManifestReadyForDispatch(executionPlan);
|
|
1409
1530
|
await emitReviewProgress({
|
|
1410
1531
|
executionPlan,
|
|
@@ -1453,6 +1574,77 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1453
1574
|
}
|
|
1454
1575
|
const executionOutcomes = new Array(lensDispatches.length);
|
|
1455
1576
|
let nextLensIndex = 0;
|
|
1577
|
+
async function haltForCancellation(args) {
|
|
1578
|
+
const completedLensOutcomes = executionOutcomes.filter(isSuccessfulOutcome);
|
|
1579
|
+
const successfulLensDispatches = completedLensOutcomes.map((outcome) => outcome.dispatch);
|
|
1580
|
+
const executionFailures = executionOutcomes
|
|
1581
|
+
.filter(isFailureOutcome)
|
|
1582
|
+
.map((outcome) => outcome.failure);
|
|
1583
|
+
const degradedLensIds = executionFailures
|
|
1584
|
+
.filter((failure) => failure.unit_kind === "lens")
|
|
1585
|
+
.map((failure) => failure.unit_id);
|
|
1586
|
+
const haltReason = `Review cancelled by MCP request: ${args.cancelRequest.reason}`;
|
|
1587
|
+
await appendMarkdownLogEntry(executionPlan.error_log_path, "runner cancelled", [
|
|
1588
|
+
haltReason,
|
|
1589
|
+
`requested_at: ${args.cancelRequest.requested_at}`,
|
|
1590
|
+
`phase: ${args.phase}`,
|
|
1591
|
+
].join("\n"));
|
|
1592
|
+
const executionCompletedAtMs = Date.now();
|
|
1593
|
+
await writeExecutionResultArtifact(executionPlan, {
|
|
1594
|
+
session_id: executionPlan.session_id,
|
|
1595
|
+
session_root: sessionRoot,
|
|
1596
|
+
execution_realization: executionPlan.execution_realization,
|
|
1597
|
+
host_runtime: executionPlan.host_runtime,
|
|
1598
|
+
review_mode: executionPlan.review_mode,
|
|
1599
|
+
execution_status: "halted_partial",
|
|
1600
|
+
execution_started_at: isoFromTimestamp(executionStartedAtMs),
|
|
1601
|
+
execution_completed_at: isoFromTimestamp(executionCompletedAtMs),
|
|
1602
|
+
total_duration_ms: Math.max(0, executionCompletedAtMs - executionStartedAtMs),
|
|
1603
|
+
max_concurrent_lenses: maxConcurrentLenses,
|
|
1604
|
+
observed_dispatch_width: observedDispatchWidth,
|
|
1605
|
+
planned_lens_ids: lensDispatches.map((dispatch) => dispatch.unit_id),
|
|
1606
|
+
participating_lens_ids: successfulLensDispatches.map((dispatch) => dispatch.unit_id),
|
|
1607
|
+
degraded_lens_ids: degradedLensIds,
|
|
1608
|
+
excluded_lens_ids: lensDispatches
|
|
1609
|
+
.map((dispatch) => dispatch.unit_id)
|
|
1610
|
+
.filter((lensId) => !successfulLensDispatches.some((dispatch) => dispatch.unit_id === lensId) &&
|
|
1611
|
+
!degradedLensIds.includes(lensId)),
|
|
1612
|
+
executed_lens_count: successfulLensDispatches.length,
|
|
1613
|
+
synthesis_executed: false,
|
|
1614
|
+
deliberation_status: args.deliberationStatus ?? "not_performed",
|
|
1615
|
+
halt_reason: haltReason,
|
|
1616
|
+
...haltArtifactFields("cancellation", null),
|
|
1617
|
+
error_log_path: executionPlan.error_log_path,
|
|
1618
|
+
lens_completion_barrier_ref: executionPlan.lens_completion_barrier_path ??
|
|
1619
|
+
path.join(sessionRoot, "lens-completion-barrier.yaml"),
|
|
1620
|
+
lens_execution_results: executionOutcomes
|
|
1621
|
+
.filter((outcome) => outcome !== undefined)
|
|
1622
|
+
.map(toUnitExecutionResult),
|
|
1623
|
+
issue_artifact_execution_results: args.issueArtifactOutcomes?.map(toUnitExecutionResult) ?? [],
|
|
1624
|
+
deliberation_execution_results: args.deliberationExecutionResults?.map(toUnitExecutionResult) ?? [],
|
|
1625
|
+
synthesize_execution_result: args.synthesizeOutcome
|
|
1626
|
+
? toUnitExecutionResult(args.synthesizeOutcome)
|
|
1627
|
+
: null,
|
|
1628
|
+
}, params.reviewExecutionProfile);
|
|
1629
|
+
return {
|
|
1630
|
+
session_root: sessionRoot,
|
|
1631
|
+
executed_lens_count: successfulLensDispatches.length,
|
|
1632
|
+
synthesis_output_path: executionPlan.synthesis_output_path,
|
|
1633
|
+
participating_lens_ids: successfulLensDispatches.map((dispatch) => dispatch.unit_id),
|
|
1634
|
+
degraded_lens_ids: degradedLensIds,
|
|
1635
|
+
synthesis_executed: false,
|
|
1636
|
+
error_log_path: executionPlan.error_log_path,
|
|
1637
|
+
halt_reason: haltReason,
|
|
1638
|
+
...haltArtifactFields("cancellation", null),
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
const initialCancelRequest = await readReviewCancelRequest(sessionRoot);
|
|
1642
|
+
if (initialCancelRequest) {
|
|
1643
|
+
return haltForCancellation({
|
|
1644
|
+
cancelRequest: initialCancelRequest,
|
|
1645
|
+
phase: "before_lens_dispatch",
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1456
1648
|
async function runLensWorker(workerIndex) {
|
|
1457
1649
|
// Stagger initial dispatch to avoid thundering-herd on external APIs.
|
|
1458
1650
|
// Only the very first dispatch of each worker is staggered; subsequent
|
|
@@ -1471,6 +1663,13 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1471
1663
|
if (!dispatch) {
|
|
1472
1664
|
return;
|
|
1473
1665
|
}
|
|
1666
|
+
if (await readReviewCancelRequest(sessionRoot)) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
if (!shouldRunUnit(dispatch.unit_id)) {
|
|
1670
|
+
executionOutcomes[currentIndex] = preservedOutcomeForDispatch(dispatch);
|
|
1671
|
+
continue;
|
|
1672
|
+
}
|
|
1474
1673
|
console.log(`[review runner] starting ${dispatch.unit_kind}: ${dispatch.unit_id}`);
|
|
1475
1674
|
await appendExecutionProgress(executionPlan.error_log_path, `runner dispatch started: ${dispatch.unit_id}`, [
|
|
1476
1675
|
`unit_id: ${dispatch.unit_id}`,
|
|
@@ -1539,7 +1738,8 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1539
1738
|
}
|
|
1540
1739
|
}
|
|
1541
1740
|
}
|
|
1542
|
-
if (
|
|
1741
|
+
if (!continuationMode &&
|
|
1742
|
+
params.reviewExecutionProfile?.mode === "nested-workers" &&
|
|
1543
1743
|
params.reviewExecutionProfile.worker_executor === "codex") {
|
|
1544
1744
|
console.log("[review runner] mode=nested-workers worker_executor=codex");
|
|
1545
1745
|
await appendExecutionProgress(executionPlan.error_log_path, "runner profile dispatch: nested-workers", [
|
|
@@ -1611,6 +1811,13 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1611
1811
|
else {
|
|
1612
1812
|
await Promise.all(Array.from({ length: Math.min(maxConcurrentLenses, lensDispatches.length) }, async (_, workerIndex) => runLensWorker(workerIndex)));
|
|
1613
1813
|
}
|
|
1814
|
+
const postLensCancelRequest = await readReviewCancelRequest(sessionRoot);
|
|
1815
|
+
if (postLensCancelRequest) {
|
|
1816
|
+
return haltForCancellation({
|
|
1817
|
+
cancelRequest: postLensCancelRequest,
|
|
1818
|
+
phase: "after_lens_dispatch",
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1614
1821
|
const successfulLensDispatches = executionOutcomes
|
|
1615
1822
|
.filter(isSuccessfulOutcome)
|
|
1616
1823
|
.map((outcome) => outcome.dispatch);
|
|
@@ -1755,6 +1962,24 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1755
1962
|
};
|
|
1756
1963
|
};
|
|
1757
1964
|
for (const artifactId of PRE_DELIBERATION_ISSUE_ARTIFACT_IDS) {
|
|
1965
|
+
const cancelRequest = await readReviewCancelRequest(sessionRoot);
|
|
1966
|
+
if (cancelRequest) {
|
|
1967
|
+
return haltForCancellation({
|
|
1968
|
+
cancelRequest,
|
|
1969
|
+
phase: `before_issue_artifact:${artifactId}`,
|
|
1970
|
+
issueArtifactOutcomes,
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
if (!shouldRunUnit(artifactId)) {
|
|
1974
|
+
issueArtifactOutcomes.push(preservedOutcomeForDispatch({
|
|
1975
|
+
unit_id: artifactId,
|
|
1976
|
+
unit_kind: "issue_artifact",
|
|
1977
|
+
packet_path: executionPlan.issue_artifact_prompt_packet_seats.find((seat) => seat.artifact_id === artifactId)?.packet_path ??
|
|
1978
|
+
path.join(executionPlan.prompt_packets_root, issueArtifactSpec(artifactId).prompt_packet_file_name),
|
|
1979
|
+
output_path: issueArtifactOutputPath(executionPlan, artifactId),
|
|
1980
|
+
}));
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1758
1983
|
try {
|
|
1759
1984
|
issueArtifactOutcomes.push(await runIssueArtifactDispatch({
|
|
1760
1985
|
projectRoot,
|
|
@@ -1778,6 +2003,14 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1778
2003
|
executionPlan,
|
|
1779
2004
|
});
|
|
1780
2005
|
let controlledDeliberation;
|
|
2006
|
+
const preDeliberationCancelRequest = await readReviewCancelRequest(sessionRoot);
|
|
2007
|
+
if (preDeliberationCancelRequest) {
|
|
2008
|
+
return haltForCancellation({
|
|
2009
|
+
cancelRequest: preDeliberationCancelRequest,
|
|
2010
|
+
phase: "before_controlled_deliberation",
|
|
2011
|
+
issueArtifactOutcomes,
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
1781
2014
|
try {
|
|
1782
2015
|
controlledDeliberation = await runControlledLensDeliberation({
|
|
1783
2016
|
projectRoot,
|
|
@@ -1789,6 +2022,12 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1789
2022
|
maxConcurrentLenses,
|
|
1790
2023
|
unitTimeoutMs,
|
|
1791
2024
|
issueArtifactContext,
|
|
2025
|
+
...(continuationMode
|
|
2026
|
+
? {
|
|
2027
|
+
runUnitIds: continuationRunUnitIds,
|
|
2028
|
+
preservedResultsByUnitId: previousResultsByUnitId,
|
|
2029
|
+
}
|
|
2030
|
+
: {}),
|
|
1792
2031
|
});
|
|
1793
2032
|
}
|
|
1794
2033
|
catch (error) {
|
|
@@ -1856,32 +2095,43 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1856
2095
|
...haltArtifactFields("controlled_lens_deliberation", failedDeliberationOutcome),
|
|
1857
2096
|
};
|
|
1858
2097
|
}
|
|
1859
|
-
|
|
1860
|
-
issueArtifactOutcomes.push(
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
executionPlan
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
lensOutputPaths,
|
|
1867
|
-
deliberationResponsePaths: controlledDeliberation.deliberationDispatches.map((dispatch) => dispatch.output_path),
|
|
1868
|
-
deliberationOutputPath: executionPlan.deliberation_output_path,
|
|
1869
|
-
problemFramingProfileRef: await resolveProblemFramingProfileRef({
|
|
1870
|
-
projectRoot,
|
|
1871
|
-
executionPlan,
|
|
1872
|
-
}),
|
|
1873
|
-
unitTimeoutMs,
|
|
2098
|
+
if (!shouldRunUnit("problem-framing")) {
|
|
2099
|
+
issueArtifactOutcomes.push(preservedOutcomeForDispatch({
|
|
2100
|
+
unit_id: "problem-framing",
|
|
2101
|
+
unit_kind: "issue_artifact",
|
|
2102
|
+
packet_path: executionPlan.issue_artifact_prompt_packet_seats.find((seat) => seat.artifact_id === "problem-framing")?.packet_path ??
|
|
2103
|
+
path.join(executionPlan.prompt_packets_root, issueArtifactSpec("problem-framing").prompt_packet_file_name),
|
|
2104
|
+
output_path: executionPlan.problem_framing_path,
|
|
1874
2105
|
}));
|
|
1875
2106
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2107
|
+
else {
|
|
2108
|
+
try {
|
|
2109
|
+
issueArtifactOutcomes.push(await runIssueArtifactDispatch({
|
|
2110
|
+
projectRoot,
|
|
2111
|
+
sessionRoot,
|
|
2112
|
+
executionPlan,
|
|
2113
|
+
executorConfig: teamleadExecutorConfig,
|
|
2114
|
+
artifactId: "problem-framing",
|
|
2115
|
+
lensOutputPaths,
|
|
2116
|
+
deliberationResponsePaths: controlledDeliberation.deliberationDispatches.map((dispatch) => dispatch.output_path),
|
|
2117
|
+
deliberationOutputPath: executionPlan.deliberation_output_path,
|
|
2118
|
+
problemFramingProfileRef: await resolveProblemFramingProfileRef({
|
|
2119
|
+
projectRoot,
|
|
2120
|
+
executionPlan,
|
|
2121
|
+
}),
|
|
2122
|
+
unitTimeoutMs,
|
|
2123
|
+
}));
|
|
2124
|
+
}
|
|
2125
|
+
catch (error) {
|
|
2126
|
+
return haltAfterIssueArtifactFailure({
|
|
2127
|
+
error,
|
|
2128
|
+
deliberationStatus: "performed",
|
|
2129
|
+
deliberationExecutionResults: [
|
|
2130
|
+
...controlledDeliberation.deliberationOutcomes,
|
|
2131
|
+
controlledDeliberation.teamleadOutcome,
|
|
2132
|
+
],
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
1885
2135
|
}
|
|
1886
2136
|
const synthesizePacketRuntimePath = path.join(executionPlan.prompt_packets_root, "synthesize.runtime.prompt.md");
|
|
1887
2137
|
const synthesizePacketText = await fs.readFile(executionPlan.synthesize_prompt_packet_path, "utf8");
|
|
@@ -1905,47 +2155,68 @@ export async function executeReviewPromptExecution(params) {
|
|
|
1905
2155
|
packet_path: synthesizePacketRuntimePath,
|
|
1906
2156
|
output_path: executionPlan.synthesis_output_path,
|
|
1907
2157
|
};
|
|
1908
|
-
await
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2158
|
+
const preSynthesizeCancelRequest = await readReviewCancelRequest(sessionRoot);
|
|
2159
|
+
if (preSynthesizeCancelRequest) {
|
|
2160
|
+
return haltForCancellation({
|
|
2161
|
+
cancelRequest: preSynthesizeCancelRequest,
|
|
2162
|
+
phase: "before_synthesize",
|
|
2163
|
+
issueArtifactOutcomes,
|
|
2164
|
+
deliberationExecutionResults: [
|
|
2165
|
+
...controlledDeliberation.deliberationOutcomes,
|
|
2166
|
+
controlledDeliberation.teamleadOutcome,
|
|
2167
|
+
],
|
|
2168
|
+
deliberationStatus: "performed",
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
if (shouldRunUnit("synthesize")) {
|
|
2172
|
+
await emitReviewProgress({
|
|
2173
|
+
executionPlan,
|
|
2174
|
+
step: 12,
|
|
2175
|
+
label: "synthesize and write execution result",
|
|
2176
|
+
details: [`participating_lens_count=${successfulLensDispatches.length}`],
|
|
2177
|
+
});
|
|
2178
|
+
console.log("[review runner] starting synthesize: synthesize");
|
|
2179
|
+
await appendExecutionProgress(executionPlan.error_log_path, "runner dispatch started: synthesize", [
|
|
2180
|
+
`unit_id: ${synthesizeDispatch.unit_id}`,
|
|
2181
|
+
`unit_kind: ${synthesizeDispatch.unit_kind}`,
|
|
2182
|
+
`packet_path: ${synthesizeDispatch.packet_path}`,
|
|
2183
|
+
`output_path: ${synthesizeDispatch.output_path}`,
|
|
2184
|
+
]);
|
|
2185
|
+
}
|
|
1921
2186
|
const synthesizeStartedAtMs = Date.now();
|
|
1922
2187
|
const synthesizeMaxRetries = 1;
|
|
1923
2188
|
let synthesizeOutcome = null;
|
|
1924
2189
|
let synthesizeLastError = undefined;
|
|
1925
2190
|
let synthesizeSucceeded = false;
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
}
|
|
1932
|
-
catch (error) {
|
|
1933
|
-
synthesizeLastError = error;
|
|
1934
|
-
if (!isReviewUnitTimeoutError(error) && attempt < synthesizeMaxRetries) {
|
|
1935
|
-
const retryDelay = DEFAULT_LENS_RETRY_INITIAL_DELAY_MS;
|
|
1936
|
-
console.log(`[review runner] synthesize attempt ${attempt + 1} failed, retrying in ${retryDelay}ms...`);
|
|
1937
|
-
await appendExecutionProgress(executionPlan.error_log_path, "runner synthesize retry", [
|
|
1938
|
-
`attempt: ${attempt + 1}/${synthesizeMaxRetries}`,
|
|
1939
|
-
`retry_delay_ms: ${retryDelay}`,
|
|
1940
|
-
`error: ${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`,
|
|
1941
|
-
]);
|
|
1942
|
-
await sleep(retryDelay);
|
|
1943
|
-
}
|
|
1944
|
-
if (isReviewUnitTimeoutError(error))
|
|
2191
|
+
if (shouldRunUnit("synthesize")) {
|
|
2192
|
+
for (let attempt = 0; attempt <= synthesizeMaxRetries; attempt++) {
|
|
2193
|
+
try {
|
|
2194
|
+
await invokeExecutor(synthesizeExecutorConfig, projectRoot, sessionRoot, synthesizeDispatch, unitTimeoutMs);
|
|
2195
|
+
synthesizeSucceeded = true;
|
|
1945
2196
|
break;
|
|
2197
|
+
}
|
|
2198
|
+
catch (error) {
|
|
2199
|
+
synthesizeLastError = error;
|
|
2200
|
+
if (!isReviewUnitTimeoutError(error) && attempt < synthesizeMaxRetries) {
|
|
2201
|
+
const retryDelay = DEFAULT_LENS_RETRY_INITIAL_DELAY_MS;
|
|
2202
|
+
console.log(`[review runner] synthesize attempt ${attempt + 1} failed, retrying in ${retryDelay}ms...`);
|
|
2203
|
+
await appendExecutionProgress(executionPlan.error_log_path, "runner synthesize retry", [
|
|
2204
|
+
`attempt: ${attempt + 1}/${synthesizeMaxRetries}`,
|
|
2205
|
+
`retry_delay_ms: ${retryDelay}`,
|
|
2206
|
+
`error: ${error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200)}`,
|
|
2207
|
+
]);
|
|
2208
|
+
await sleep(retryDelay);
|
|
2209
|
+
}
|
|
2210
|
+
if (isReviewUnitTimeoutError(error))
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
1946
2213
|
}
|
|
1947
2214
|
}
|
|
1948
|
-
|
|
2215
|
+
else {
|
|
2216
|
+
synthesizeOutcome = preservedOutcomeForDispatch(synthesizeDispatch);
|
|
2217
|
+
synthesizeSucceeded = true;
|
|
2218
|
+
}
|
|
2219
|
+
if (synthesizeSucceeded && synthesizeOutcome === null) {
|
|
1949
2220
|
synthesizeOutcome = {
|
|
1950
2221
|
dispatch: synthesizeDispatch,
|
|
1951
2222
|
success: true,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function isPathInsideRoot(root, candidate) {
|
|
5
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
6
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
7
|
+
}
|
|
8
|
+
export async function realpathIfExists(targetPath) {
|
|
9
|
+
try {
|
|
10
|
+
return await fs.realpath(targetPath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function realpathIfExistsSync(targetPath) {
|
|
17
|
+
try {
|
|
18
|
+
return fsSync.realpathSync(targetPath);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function realpathNearestExisting(targetPath) {
|
|
25
|
+
let current = path.resolve(targetPath);
|
|
26
|
+
while (true) {
|
|
27
|
+
const real = await realpathIfExists(current);
|
|
28
|
+
if (real)
|
|
29
|
+
return real;
|
|
30
|
+
const parent = path.dirname(current);
|
|
31
|
+
if (parent === current)
|
|
32
|
+
return null;
|
|
33
|
+
current = parent;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function isPathInsideRootRealpathAwareSync(root, candidate) {
|
|
37
|
+
const realRoot = realpathIfExistsSync(root) ?? path.resolve(root);
|
|
38
|
+
const realCandidate = realpathIfExistsSync(candidate) ?? path.resolve(candidate);
|
|
39
|
+
return isPathInsideRoot(realRoot, realCandidate);
|
|
40
|
+
}
|
|
41
|
+
export async function assertPathInsideRoot(args) {
|
|
42
|
+
const root = path.resolve(args.root);
|
|
43
|
+
const candidate = path.resolve(args.candidate);
|
|
44
|
+
if (!isPathInsideRoot(root, candidate)) {
|
|
45
|
+
throw new Error(`${args.label} escapes allowed root: ${candidate}`);
|
|
46
|
+
}
|
|
47
|
+
const realRoot = (await realpathIfExists(root)) ?? root;
|
|
48
|
+
const realCandidate = await realpathIfExists(candidate);
|
|
49
|
+
if (realCandidate && !isPathInsideRoot(realRoot, realCandidate)) {
|
|
50
|
+
throw new Error(`${args.label} realpath escapes allowed root: ${realCandidate}`);
|
|
51
|
+
}
|
|
52
|
+
if (!realCandidate) {
|
|
53
|
+
const nearest = await realpathNearestExisting(path.dirname(candidate));
|
|
54
|
+
if (nearest && !isPathInsideRoot(realRoot, nearest)) {
|
|
55
|
+
throw new Error(`${args.label} parent realpath escapes allowed root: ${nearest}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|