@mcoda/core 0.1.27 → 0.1.28

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.
@@ -13,6 +13,7 @@ import { TaskOrderingService } from "../backlog/TaskOrderingService.js";
13
13
  import { QaTestCommandBuilder } from "../execution/QaTestCommandBuilder.js";
14
14
  import { createEpicKeyGenerator, createStoryKeyGenerator, createTaskKeyGenerator, } from "./KeyHelpers.js";
15
15
  import { TaskSufficiencyService } from "./TaskSufficiencyService.js";
16
+ import { SdsPreflightService } from "./SdsPreflightService.js";
16
17
  const formatBullets = (items, fallback) => {
17
18
  if (!items || items.length === 0)
18
19
  return `- ${fallback}`;
@@ -257,6 +258,14 @@ const extractMarkdownHeadings = (value, limit) => {
257
258
  !line.startsWith("*")) {
258
259
  headings.push(line);
259
260
  }
261
+ else {
262
+ const numberedHeading = line.match(/^(\d+(?:\.\d+)+)\s+(.+)$/);
263
+ if (numberedHeading) {
264
+ const headingText = `${numberedHeading[1]} ${numberedHeading[2]}`.trim();
265
+ if (/[a-z]/i.test(headingText))
266
+ headings.push(headingText);
267
+ }
268
+ }
260
269
  if (headings.length >= limit)
261
270
  break;
262
271
  }
@@ -398,6 +407,89 @@ const extractJsonObjects = (value) => {
398
407
  }
399
408
  return results;
400
409
  };
410
+ const normalizeAgentFailoverEvents = (value) => {
411
+ if (!Array.isArray(value))
412
+ return [];
413
+ const events = [];
414
+ for (const entry of value) {
415
+ if (!isPlainObject(entry))
416
+ continue;
417
+ if (typeof entry.type !== "string" || entry.type.trim().length === 0)
418
+ continue;
419
+ events.push({ ...entry });
420
+ }
421
+ return events;
422
+ };
423
+ const mergeAgentFailoverEvents = (left, right) => {
424
+ if (!left.length)
425
+ return right;
426
+ if (!right.length)
427
+ return left;
428
+ const seen = new Set();
429
+ const merged = [];
430
+ const signature = (event) => [
431
+ event.type ?? "",
432
+ event.fromAgentId ?? "",
433
+ event.toAgentId ?? "",
434
+ event.at ?? "",
435
+ event.until ?? "",
436
+ event.durationMs ?? "",
437
+ ].join("|");
438
+ for (const event of [...left, ...right]) {
439
+ const key = signature(event);
440
+ if (seen.has(key))
441
+ continue;
442
+ seen.add(key);
443
+ merged.push(event);
444
+ }
445
+ return merged;
446
+ };
447
+ const mergeAgentInvocationMetadata = (current, incoming) => {
448
+ if (!current && !incoming)
449
+ return undefined;
450
+ if (!incoming)
451
+ return current;
452
+ if (!current)
453
+ return { ...incoming };
454
+ const merged = { ...current, ...incoming };
455
+ const currentEvents = normalizeAgentFailoverEvents(current.failoverEvents);
456
+ const incomingEvents = normalizeAgentFailoverEvents(incoming.failoverEvents);
457
+ if (currentEvents.length > 0 || incomingEvents.length > 0) {
458
+ merged.failoverEvents = mergeAgentFailoverEvents(currentEvents, incomingEvents);
459
+ }
460
+ return merged;
461
+ };
462
+ const summarizeAgentFailoverEvent = (event) => {
463
+ const type = String(event.type ?? "unknown");
464
+ if (type === "switch_agent") {
465
+ const from = typeof event.fromAgentId === "string" ? event.fromAgentId : "unknown";
466
+ const to = typeof event.toAgentId === "string" ? event.toAgentId : "unknown";
467
+ return `switch_agent ${from} -> ${to}`;
468
+ }
469
+ if (type === "sleep_until_reset") {
470
+ const duration = typeof event.durationMs === "number" && Number.isFinite(event.durationMs)
471
+ ? `${Math.round(event.durationMs / 1000)}s`
472
+ : "unknown duration";
473
+ const until = typeof event.until === "string" ? event.until : "unknown";
474
+ return `sleep_until_reset ${duration} (until ${until})`;
475
+ }
476
+ if (type === "stream_restart_after_limit") {
477
+ const from = typeof event.fromAgentId === "string" ? event.fromAgentId : "unknown";
478
+ return `stream_restart_after_limit from ${from}`;
479
+ }
480
+ return type;
481
+ };
482
+ const resolveTerminalFailoverAgentId = (events, fallbackAgentId) => {
483
+ for (let index = events.length - 1; index >= 0; index -= 1) {
484
+ const event = events[index];
485
+ if (event?.type !== "switch_agent")
486
+ continue;
487
+ if (typeof event.toAgentId === "string" && event.toAgentId.trim().length > 0) {
488
+ return event.toAgentId;
489
+ }
490
+ }
491
+ return fallbackAgentId;
492
+ };
401
493
  const compactNarrative = (value, fallback, maxLines = 5) => {
402
494
  if (!value || value.trim().length === 0)
403
495
  return fallback;
@@ -721,6 +813,7 @@ export class CreateTasksService {
721
813
  this.ratingService = deps.ratingService;
722
814
  this.taskOrderingFactory = deps.taskOrderingFactory ?? TaskOrderingService.create;
723
815
  this.taskSufficiencyFactory = deps.taskSufficiencyFactory ?? TaskSufficiencyService.create;
816
+ this.sdsPreflightFactory = deps.sdsPreflightFactory ?? SdsPreflightService.create;
724
817
  }
725
818
  static async create(workspace) {
726
819
  const repo = await GlobalRepository.create();
@@ -742,6 +835,7 @@ export class CreateTasksService {
742
835
  workspaceRepo,
743
836
  routingService,
744
837
  taskSufficiencyFactory: TaskSufficiencyService.create,
838
+ sdsPreflightFactory: SdsPreflightService.create,
745
839
  });
746
840
  }
747
841
  async close() {
@@ -830,6 +924,20 @@ export class CreateTasksService {
830
924
  const resolved = path.isAbsolute(input) ? input : path.join(this.workspace.workspaceRoot, input);
831
925
  return path.resolve(resolved).toLowerCase();
832
926
  }
927
+ mergeDocInputs(primary, extras) {
928
+ const merged = [];
929
+ const seen = new Set();
930
+ for (const input of [...primary, ...extras]) {
931
+ if (!input?.trim())
932
+ continue;
933
+ const key = this.normalizeDocInputForSet(input);
934
+ if (seen.has(key))
935
+ continue;
936
+ seen.add(key);
937
+ merged.push(input);
938
+ }
939
+ return merged;
940
+ }
833
941
  docIdentity(doc) {
834
942
  const pathKey = `${doc.path ?? ""}`.trim().toLowerCase();
835
943
  const idKey = `${doc.id ?? ""}`.trim().toLowerCase();
@@ -2100,8 +2208,11 @@ export class CreateTasksService {
2100
2208
  const segmentHeadings = (doc.segments ?? [])
2101
2209
  .map((segment) => segment.heading?.trim())
2102
2210
  .filter((heading) => Boolean(heading));
2211
+ const segmentContentHeadings = (doc.segments ?? [])
2212
+ .flatMap((segment) => extractMarkdownHeadings(segment.content ?? "", Math.max(6, Math.ceil(limit / 4))))
2213
+ .slice(0, limit);
2103
2214
  const contentHeadings = extractMarkdownHeadings(doc.content ?? "", limit);
2104
- for (const heading of [...segmentHeadings, ...contentHeadings]) {
2215
+ for (const heading of [...segmentHeadings, ...segmentContentHeadings, ...contentHeadings]) {
2105
2216
  const normalized = heading.replace(/[`*_]/g, "").trim();
2106
2217
  if (!normalized)
2107
2218
  continue;
@@ -2345,6 +2456,7 @@ export class CreateTasksService {
2345
2456
  async invokeAgentWithRetry(agent, prompt, action, stream, jobId, commandRunId, metadata) {
2346
2457
  const startedAt = Date.now();
2347
2458
  let output = "";
2459
+ let invocationMetadata;
2348
2460
  const logChunk = async (chunk) => {
2349
2461
  if (!chunk)
2350
2462
  return;
@@ -2352,17 +2464,51 @@ export class CreateTasksService {
2352
2464
  if (stream)
2353
2465
  process.stdout.write(chunk);
2354
2466
  };
2467
+ const baseInvocationMetadata = {
2468
+ command: "create-tasks",
2469
+ action,
2470
+ phase: `create_tasks_${action}`,
2471
+ };
2472
+ const logFailoverEvents = async (events) => {
2473
+ if (!events.length)
2474
+ return;
2475
+ for (const event of events) {
2476
+ await this.jobService.appendLog(jobId, `[create-tasks] agent failover (${action}): ${summarizeAgentFailoverEvent(event)}\n`);
2477
+ }
2478
+ };
2479
+ const resolveUsageAgent = async (events) => {
2480
+ const agentId = resolveTerminalFailoverAgentId(events, agent.id);
2481
+ if (agentId === agent.id) {
2482
+ return { id: agent.id, defaultModel: agent.defaultModel };
2483
+ }
2484
+ try {
2485
+ const resolved = await this.agentService.resolveAgent(agentId);
2486
+ return { id: resolved.id, defaultModel: resolved.defaultModel };
2487
+ }
2488
+ catch (error) {
2489
+ await this.jobService.appendLog(jobId, `[create-tasks] unable to resolve failover agent (${agentId}) for usage accounting: ${error instanceof Error ? error.message : String(error)}\n`);
2490
+ return { id: agent.id, defaultModel: agent.defaultModel };
2491
+ }
2492
+ };
2355
2493
  try {
2356
2494
  if (stream) {
2357
- const gen = await this.agentService.invokeStream(agent.id, { input: prompt });
2495
+ const gen = await this.agentService.invokeStream(agent.id, {
2496
+ input: prompt,
2497
+ metadata: baseInvocationMetadata,
2498
+ });
2358
2499
  for await (const chunk of gen) {
2359
2500
  output += chunk.output ?? "";
2501
+ invocationMetadata = mergeAgentInvocationMetadata(invocationMetadata, chunk.metadata);
2360
2502
  await logChunk(chunk.output);
2361
2503
  }
2362
2504
  }
2363
2505
  else {
2364
- const result = await this.agentService.invoke(agent.id, { input: prompt });
2506
+ const result = await this.agentService.invoke(agent.id, {
2507
+ input: prompt,
2508
+ metadata: baseInvocationMetadata,
2509
+ });
2365
2510
  output = result.output ?? "";
2511
+ invocationMetadata = mergeAgentInvocationMetadata(invocationMetadata, result.metadata);
2366
2512
  await logChunk(output);
2367
2513
  }
2368
2514
  }
@@ -2379,10 +2525,22 @@ export class CreateTasksService {
2379
2525
  `Original content:\n${output}`,
2380
2526
  ].join("\n\n");
2381
2527
  try {
2382
- const fix = await this.agentService.invoke(agent.id, { input: fixPrompt });
2528
+ let retryInvocationMetadata;
2529
+ const fix = await this.agentService.invoke(agent.id, {
2530
+ input: fixPrompt,
2531
+ metadata: {
2532
+ ...baseInvocationMetadata,
2533
+ attempt: 2,
2534
+ stage: "json_repair",
2535
+ },
2536
+ });
2383
2537
  output = fix.output ?? "";
2538
+ retryInvocationMetadata = mergeAgentInvocationMetadata(retryInvocationMetadata, fix.metadata);
2384
2539
  parsed = extractJson(output);
2385
2540
  if (parsed) {
2541
+ const failoverEvents = normalizeAgentFailoverEvents(retryInvocationMetadata?.failoverEvents);
2542
+ await logFailoverEvents(failoverEvents);
2543
+ const usageAgent = await resolveUsageAgent(failoverEvents);
2386
2544
  const promptTokens = estimateTokens(prompt);
2387
2545
  const completionTokens = estimateTokens(output);
2388
2546
  const durationSeconds = (Date.now() - startedAt) / 1000;
@@ -2391,8 +2549,8 @@ export class CreateTasksService {
2391
2549
  workspaceId: this.workspace.workspaceId,
2392
2550
  jobId,
2393
2551
  commandRunId,
2394
- agentId: agent.id,
2395
- modelName: agent.defaultModel,
2552
+ agentId: usageAgent.id,
2553
+ modelName: usageAgent.defaultModel,
2396
2554
  promptTokens,
2397
2555
  completionTokens,
2398
2556
  tokensPrompt: promptTokens,
@@ -2403,6 +2561,7 @@ export class CreateTasksService {
2403
2561
  action: `create_tasks_${action}`,
2404
2562
  phase: `create_tasks_${action}`,
2405
2563
  attempt,
2564
+ failoverEvents: failoverEvents.length > 0 ? failoverEvents : undefined,
2406
2565
  ...(metadata ?? {}),
2407
2566
  },
2408
2567
  });
@@ -2416,6 +2575,9 @@ export class CreateTasksService {
2416
2575
  if (!parsed) {
2417
2576
  throw new Error(`Agent output was not valid JSON for ${action}`);
2418
2577
  }
2578
+ const failoverEvents = normalizeAgentFailoverEvents(invocationMetadata?.failoverEvents);
2579
+ await logFailoverEvents(failoverEvents);
2580
+ const usageAgent = await resolveUsageAgent(failoverEvents);
2419
2581
  const promptTokens = estimateTokens(prompt);
2420
2582
  const completionTokens = estimateTokens(output);
2421
2583
  const durationSeconds = (Date.now() - startedAt) / 1000;
@@ -2424,8 +2586,8 @@ export class CreateTasksService {
2424
2586
  workspaceId: this.workspace.workspaceId,
2425
2587
  jobId,
2426
2588
  commandRunId,
2427
- agentId: agent.id,
2428
- modelName: agent.defaultModel,
2589
+ agentId: usageAgent.id,
2590
+ modelName: usageAgent.defaultModel,
2429
2591
  promptTokens,
2430
2592
  completionTokens,
2431
2593
  tokensPrompt: promptTokens,
@@ -2436,6 +2598,7 @@ export class CreateTasksService {
2436
2598
  action: `create_tasks_${action}`,
2437
2599
  phase: `create_tasks_${action}`,
2438
2600
  attempt: 1,
2601
+ failoverEvents: failoverEvents.length > 0 ? failoverEvents : undefined,
2439
2602
  ...(metadata ?? {}),
2440
2603
  },
2441
2604
  });
@@ -3032,6 +3195,7 @@ export class CreateTasksService {
3032
3195
  inputs: options.inputs,
3033
3196
  agent: options.agentName,
3034
3197
  agentStream,
3198
+ sdsPreflightCommit: options.sdsPreflightCommit === true,
3035
3199
  },
3036
3200
  });
3037
3201
  let lastError;
@@ -3042,8 +3206,110 @@ export class CreateTasksService {
3042
3206
  name: options.projectKey,
3043
3207
  description: `Workspace project ${options.projectKey}`,
3044
3208
  });
3045
- const docs = await this.prepareDocs(options.inputs);
3046
- const { docSummary, warnings: docWarnings } = this.buildDocContext(docs);
3209
+ let sdsPreflight;
3210
+ let sdsPreflightError;
3211
+ if (this.sdsPreflightFactory) {
3212
+ let sdsPreflightCloseError;
3213
+ try {
3214
+ const preflightService = await this.sdsPreflightFactory(this.workspace);
3215
+ try {
3216
+ sdsPreflight = await preflightService.runPreflight({
3217
+ workspace: options.workspace,
3218
+ projectKey: options.projectKey,
3219
+ inputPaths: options.inputs,
3220
+ sdsPaths: options.inputs,
3221
+ writeArtifacts: true,
3222
+ applyToSds: true,
3223
+ commitAppliedChanges: options.sdsPreflightCommit === true,
3224
+ commitMessage: options.sdsPreflightCommitMessage,
3225
+ });
3226
+ }
3227
+ finally {
3228
+ try {
3229
+ await preflightService.close();
3230
+ }
3231
+ catch (closeError) {
3232
+ sdsPreflightCloseError = closeError?.message ?? String(closeError);
3233
+ await this.jobService.appendLog(job.id, `SDS preflight close warning: ${sdsPreflightCloseError}\n`);
3234
+ }
3235
+ }
3236
+ }
3237
+ catch (error) {
3238
+ sdsPreflightError = error?.message ?? String(error);
3239
+ }
3240
+ if (!sdsPreflight) {
3241
+ const message = `create-tasks blocked: SDS preflight failed before backlog generation (${sdsPreflightError ?? "unknown error"}).`;
3242
+ await this.jobService.writeCheckpoint(job.id, {
3243
+ stage: "sds_preflight",
3244
+ timestamp: new Date().toISOString(),
3245
+ details: {
3246
+ status: "failed",
3247
+ error: message,
3248
+ readyForPlanning: false,
3249
+ qualityStatus: undefined,
3250
+ sourceSdsCount: 0,
3251
+ issueCount: 0,
3252
+ blockingIssueCount: 0,
3253
+ questionCount: 0,
3254
+ requiredQuestionCount: 0,
3255
+ reportPath: undefined,
3256
+ openQuestionsPath: undefined,
3257
+ gapAddendumPath: undefined,
3258
+ warnings: [],
3259
+ },
3260
+ });
3261
+ throw new Error(message);
3262
+ }
3263
+ const preflightWarnings = uniqueStrings([
3264
+ ...(sdsPreflight.warnings ?? []),
3265
+ ...(sdsPreflightCloseError ? [`SDS preflight close warning: ${sdsPreflightCloseError}`] : []),
3266
+ ]);
3267
+ const blockingReasons = [];
3268
+ if (sdsPreflight.qualityStatus === "fail") {
3269
+ blockingReasons.push("SDS quality gates failed.");
3270
+ }
3271
+ if (sdsPreflight.blockingIssueCount > 0) {
3272
+ blockingReasons.push(`Blocking SDS issues: ${sdsPreflight.blockingIssueCount}.`);
3273
+ }
3274
+ if (sdsPreflight.requiredQuestionCount > 0) {
3275
+ blockingReasons.push(`Required open questions remaining: ${sdsPreflight.requiredQuestionCount}.`);
3276
+ }
3277
+ if (!sdsPreflight.readyForPlanning) {
3278
+ blockingReasons.push("SDS preflight reported planning context is not ready.");
3279
+ }
3280
+ if (blockingReasons.length > 0) {
3281
+ sdsPreflightError = blockingReasons.join(" ");
3282
+ }
3283
+ await this.jobService.writeCheckpoint(job.id, {
3284
+ stage: "sds_preflight",
3285
+ timestamp: new Date().toISOString(),
3286
+ details: {
3287
+ status: blockingReasons.length > 0 ? "blocked" : "succeeded",
3288
+ error: sdsPreflightError,
3289
+ readyForPlanning: sdsPreflight.readyForPlanning,
3290
+ qualityStatus: sdsPreflight.qualityStatus,
3291
+ sourceSdsCount: sdsPreflight.sourceSdsPaths.length,
3292
+ issueCount: sdsPreflight.issueCount,
3293
+ blockingIssueCount: sdsPreflight.blockingIssueCount,
3294
+ questionCount: sdsPreflight.questionCount,
3295
+ requiredQuestionCount: sdsPreflight.requiredQuestionCount,
3296
+ reportPath: sdsPreflight.reportPath,
3297
+ openQuestionsPath: sdsPreflight.openQuestionsPath,
3298
+ gapAddendumPath: sdsPreflight.gapAddendumPath,
3299
+ appliedToSds: sdsPreflight.appliedToSds,
3300
+ appliedSdsCount: sdsPreflight.appliedSdsPaths.length,
3301
+ commitHash: sdsPreflight.commitHash,
3302
+ warnings: preflightWarnings,
3303
+ },
3304
+ });
3305
+ if (blockingReasons.length > 0) {
3306
+ throw new Error(`create-tasks blocked by SDS preflight. ${blockingReasons.join(" ")} Report: ${sdsPreflight.reportPath}`);
3307
+ }
3308
+ }
3309
+ const preflightDocInputs = this.mergeDocInputs(options.inputs, sdsPreflight ? [...sdsPreflight.sourceSdsPaths, ...sdsPreflight.generatedDocPaths] : []);
3310
+ const docs = await this.prepareDocs(preflightDocInputs);
3311
+ const { docSummary, warnings: indexedDocWarnings } = this.buildDocContext(docs);
3312
+ const docWarnings = uniqueStrings([...(sdsPreflight?.warnings ?? []), ...indexedDocWarnings]);
3047
3313
  const discoveryGraph = this.buildServiceDependencyGraph({ epics: [], stories: [], tasks: [] }, docs);
3048
3314
  const projectBuildMethod = this.buildProjectConstructionMethod(docs, discoveryGraph);
3049
3315
  const projectBuildPlan = this.buildProjectPlanArtifact(options.projectKey, docs, discoveryGraph, projectBuildMethod);
@@ -3139,57 +3405,83 @@ export class CreateTasksService {
3139
3405
  let sufficiencyAudit;
3140
3406
  let sufficiencyAuditError;
3141
3407
  if (this.taskSufficiencyFactory) {
3408
+ let sufficiencyCloseError;
3142
3409
  try {
3143
3410
  const sufficiencyService = await this.taskSufficiencyFactory(this.workspace);
3144
3411
  try {
3145
- try {
3146
- sufficiencyAudit = await sufficiencyService.runAudit({
3147
- workspace: options.workspace,
3148
- projectKey: options.projectKey,
3149
- sourceCommand: "create-tasks",
3150
- });
3151
- }
3152
- catch (error) {
3153
- sufficiencyAuditError = error?.message ?? String(error);
3154
- await this.jobService.appendLog(job.id, `Task sufficiency audit failed; continuing with created backlog: ${sufficiencyAuditError}\n`);
3155
- }
3412
+ sufficiencyAudit = await sufficiencyService.runAudit({
3413
+ workspace: options.workspace,
3414
+ projectKey: options.projectKey,
3415
+ sourceCommand: "create-tasks",
3416
+ });
3156
3417
  }
3157
3418
  finally {
3158
3419
  try {
3159
3420
  await sufficiencyService.close();
3160
3421
  }
3161
3422
  catch (closeError) {
3162
- const closeMessage = closeError?.message ?? String(closeError);
3163
- const details = `Task sufficiency audit close failed; continuing with created backlog: ${closeMessage}`;
3164
- sufficiencyAuditError = sufficiencyAuditError ? `${sufficiencyAuditError}; ${details}` : details;
3165
- await this.jobService.appendLog(job.id, `${details}\n`);
3423
+ sufficiencyCloseError = closeError?.message ?? String(closeError);
3424
+ await this.jobService.appendLog(job.id, `Task sufficiency audit close warning: ${sufficiencyCloseError}\n`);
3166
3425
  }
3167
3426
  }
3168
3427
  }
3169
3428
  catch (error) {
3170
3429
  sufficiencyAuditError = error?.message ?? String(error);
3171
- await this.jobService.appendLog(job.id, `Task sufficiency audit setup failed; continuing with created backlog: ${sufficiencyAuditError}\n`);
3430
+ }
3431
+ if (!sufficiencyAudit) {
3432
+ const message = `create-tasks blocked: task sufficiency audit failed (${sufficiencyAuditError ?? "unknown error"}).`;
3433
+ await this.jobService.writeCheckpoint(job.id, {
3434
+ stage: "task_sufficiency_audit",
3435
+ timestamp: new Date().toISOString(),
3436
+ details: {
3437
+ status: "failed",
3438
+ error: message,
3439
+ jobId: undefined,
3440
+ commandRunId: undefined,
3441
+ satisfied: false,
3442
+ dryRun: undefined,
3443
+ totalTasksAdded: undefined,
3444
+ totalTasksUpdated: undefined,
3445
+ finalCoverageRatio: undefined,
3446
+ reportPath: undefined,
3447
+ remainingSectionCount: undefined,
3448
+ remainingFolderCount: undefined,
3449
+ remainingGapCount: undefined,
3450
+ warnings: [],
3451
+ },
3452
+ });
3453
+ throw new Error(message);
3454
+ }
3455
+ const sufficiencyWarnings = uniqueStrings([
3456
+ ...(sufficiencyAudit.warnings ?? []),
3457
+ ...(sufficiencyCloseError ? [`Task sufficiency audit close warning: ${sufficiencyCloseError}`] : []),
3458
+ ]);
3459
+ if (!sufficiencyAudit.satisfied) {
3460
+ sufficiencyAuditError = `SDS coverage target not reached (coverage=${sufficiencyAudit.finalCoverageRatio}, remaining gaps=${sufficiencyAudit.remainingGaps.total}).`;
3172
3461
  }
3173
3462
  await this.jobService.writeCheckpoint(job.id, {
3174
3463
  stage: "task_sufficiency_audit",
3175
3464
  timestamp: new Date().toISOString(),
3176
3465
  details: {
3177
- status: sufficiencyAudit ? "succeeded" : "failed",
3466
+ status: sufficiencyAudit.satisfied ? "succeeded" : "blocked",
3178
3467
  error: sufficiencyAuditError,
3179
- jobId: sufficiencyAudit?.jobId,
3180
- commandRunId: sufficiencyAudit?.commandRunId,
3181
- satisfied: sufficiencyAudit?.satisfied,
3182
- dryRun: sufficiencyAudit?.dryRun,
3183
- totalTasksAdded: sufficiencyAudit?.totalTasksAdded,
3184
- totalTasksUpdated: sufficiencyAudit?.totalTasksUpdated,
3185
- finalCoverageRatio: sufficiencyAudit?.finalCoverageRatio,
3186
- reportPath: sufficiencyAudit?.reportPath,
3187
- remainingSectionCount: sufficiencyAudit?.remainingSectionHeadings.length,
3188
- remainingFolderCount: sufficiencyAudit?.remainingFolderEntries.length,
3189
- remainingGapCount: sufficiencyAudit?.remainingGaps.total,
3190
- warnings: sufficiencyAudit?.warnings,
3468
+ jobId: sufficiencyAudit.jobId,
3469
+ commandRunId: sufficiencyAudit.commandRunId,
3470
+ satisfied: sufficiencyAudit.satisfied,
3471
+ dryRun: sufficiencyAudit.dryRun,
3472
+ totalTasksAdded: sufficiencyAudit.totalTasksAdded,
3473
+ totalTasksUpdated: sufficiencyAudit.totalTasksUpdated,
3474
+ finalCoverageRatio: sufficiencyAudit.finalCoverageRatio,
3475
+ reportPath: sufficiencyAudit.reportPath,
3476
+ remainingSectionCount: sufficiencyAudit.remainingSectionHeadings.length,
3477
+ remainingFolderCount: sufficiencyAudit.remainingFolderEntries.length,
3478
+ remainingGapCount: sufficiencyAudit.remainingGaps.total,
3479
+ warnings: sufficiencyWarnings,
3191
3480
  },
3192
3481
  });
3482
+ if (!sufficiencyAudit.satisfied) {
3483
+ throw new Error(`create-tasks blocked: task sufficiency audit did not reach full coverage. Report: ${sufficiencyAudit.reportPath}`);
3484
+ }
3193
3485
  }
3194
3486
  await this.jobService.updateJobStatus(job.id, "completed", {
3195
3487
  payload: {
@@ -3201,6 +3493,25 @@ export class CreateTasksService {
3201
3493
  planFolder: folder,
3202
3494
  planSource,
3203
3495
  fallbackReason,
3496
+ sdsPreflight: sdsPreflight
3497
+ ? {
3498
+ readyForPlanning: sdsPreflight.readyForPlanning,
3499
+ qualityStatus: sdsPreflight.qualityStatus,
3500
+ sourceSdsCount: sdsPreflight.sourceSdsPaths.length,
3501
+ issueCount: sdsPreflight.issueCount,
3502
+ blockingIssueCount: sdsPreflight.blockingIssueCount,
3503
+ questionCount: sdsPreflight.questionCount,
3504
+ requiredQuestionCount: sdsPreflight.requiredQuestionCount,
3505
+ appliedToSds: sdsPreflight.appliedToSds,
3506
+ appliedSdsPaths: sdsPreflight.appliedSdsPaths,
3507
+ commitHash: sdsPreflight.commitHash,
3508
+ reportPath: sdsPreflight.reportPath,
3509
+ openQuestionsPath: sdsPreflight.openQuestionsPath,
3510
+ gapAddendumPath: sdsPreflight.gapAddendumPath,
3511
+ warnings: sdsPreflight.warnings,
3512
+ }
3513
+ : undefined,
3514
+ sdsPreflightError,
3204
3515
  sufficiencyAudit: sufficiencyAudit
3205
3516
  ? {
3206
3517
  jobId: sufficiencyAudit.jobId,
@@ -0,0 +1,98 @@
1
+ import { WorkspaceResolution } from "../../workspace/WorkspaceManager.js";
2
+ import { type ReviewSeverity } from "../docs/review/ReviewTypes.js";
3
+ export interface SdsPreflightQuestionAnswer {
4
+ question: string;
5
+ normalized: string;
6
+ required: boolean;
7
+ target: string;
8
+ sourcePath?: string;
9
+ line?: number;
10
+ answer: string;
11
+ rationale: string;
12
+ assumptions: string[];
13
+ }
14
+ export interface SdsPreflightIssueSummary {
15
+ gateId: string;
16
+ gateName: string;
17
+ severity: ReviewSeverity;
18
+ category: string;
19
+ message: string;
20
+ remediation: string;
21
+ location: string;
22
+ }
23
+ export interface SdsPreflightResult {
24
+ projectKey: string;
25
+ generatedAt: string;
26
+ readyForPlanning: boolean;
27
+ qualityStatus: "pass" | "warn" | "fail";
28
+ sourceSdsPaths: string[];
29
+ reportPath: string;
30
+ openQuestionsPath: string;
31
+ gapAddendumPath: string;
32
+ generatedDocPaths: string[];
33
+ questionCount: number;
34
+ requiredQuestionCount: number;
35
+ issueCount: number;
36
+ blockingIssueCount: number;
37
+ appliedToSds: boolean;
38
+ appliedSdsPaths: string[];
39
+ commitHash?: string;
40
+ issues: SdsPreflightIssueSummary[];
41
+ questions: SdsPreflightQuestionAnswer[];
42
+ warnings: string[];
43
+ }
44
+ export interface SdsPreflightOptions {
45
+ workspace: WorkspaceResolution;
46
+ projectKey: string;
47
+ inputPaths?: string[];
48
+ sdsPaths?: string[];
49
+ writeArtifacts?: boolean;
50
+ applyToSds?: boolean;
51
+ commitAppliedChanges?: boolean;
52
+ commitMessage?: string;
53
+ }
54
+ export declare class SdsPreflightService {
55
+ private readonly workspace;
56
+ constructor(workspace: WorkspaceResolution);
57
+ static create(workspace: WorkspaceResolution): Promise<SdsPreflightService>;
58
+ close(): Promise<void>;
59
+ private walkCandidates;
60
+ private collectPathCandidates;
61
+ private discoverSdsPaths;
62
+ private isLikelySdsPath;
63
+ private resolveSdsPaths;
64
+ private buildArtifacts;
65
+ private getGateRunners;
66
+ private dedupeIssues;
67
+ private resolveWorkspacePath;
68
+ private collectSignalsFromContent;
69
+ private collectSignalsByPath;
70
+ private signalsForPath;
71
+ private answerForQuestion;
72
+ private managedFolderTreeSection;
73
+ private managedTechnologySection;
74
+ private managedPolicyTelemetrySection;
75
+ private managedOperationsSection;
76
+ private managedAdapterSection;
77
+ private shouldReplaceIssueLine;
78
+ private replacementForIssue;
79
+ private extractQuestionAnswers;
80
+ private issueSection;
81
+ private remediationLines;
82
+ private verificationLines;
83
+ private buildAddendum;
84
+ private buildQuestionsDoc;
85
+ private summarizeIssues;
86
+ private collectGateResults;
87
+ private issueMatchesPath;
88
+ private questionMatchesPath;
89
+ private normalizeResolvedText;
90
+ private buildLineReplacementsForPath;
91
+ private applyLineReplacements;
92
+ private buildManagedSdsBlock;
93
+ private upsertManagedSdsBlock;
94
+ private applyPreflightRemediationsToSds;
95
+ private commitAppliedSdsChanges;
96
+ runPreflight(options: SdsPreflightOptions): Promise<SdsPreflightResult>;
97
+ }
98
+ //# sourceMappingURL=SdsPreflightService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SdsPreflightService.d.ts","sourceRoot":"","sources":["../../../src/services/planning/SdsPreflightService.ts"],"names":[],"mappings":"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"}