| 416 | if (!skill) return { skill: null, context: null, decision: null }; |
| 417 | const context = options.activationContext || config.loadActivationContext(projectRoot, { |
| 418 | runtime: options.runtime, |
| 419 | runtimeId: options.runtimeId, |
| 420 | env: options.env, |
| 421 | }); |
| 422 | return { |
| 423 | skill, |
| 424 | context: { |
| 425 | status: context.status, |
| 426 | persisted: context.persisted !== false, |
| 427 | reasonCode: context.reasonCode, |
| 428 | receiptPath: context.receiptPath, |
| 429 | }, |
| 430 | decision: config.preflightRoute(context, skill), |
| 431 | }; |
| 432 | } |
| 433 | |
| 434 | function buildPreview(input, options = {}) { |
| 435 | const projectRoot = path.resolve(options.projectRoot || process.cwd()); |
| 436 | const selected = selectRoute(input, { |
| 437 | mode: 'preview', |
| 438 | routeOverride: options.routeOverride, |
| 439 | projectRoot, |
| 440 | }); |
| 441 | const dirty = options.gitDirty === undefined ? gitDirty(projectRoot) : Boolean(options.gitDirty); |
| 442 | let boundary = { |
| 443 | canRunNow: false, |
| 444 | boundary: 'semantic-classification-required', |
| 445 | risk: 'low', |
| 446 | approval: 'Run the full /do protocol to inspect active state, discover project skills, and semantically classify before execution.', |
| 447 | }; |
| 448 | let activation = { skill: null, context: null, decision: null }; |
| 449 | |
| 450 | if (selected.final) { |
| 451 | boundary = boundaryFor(selected.selected, dirty); |
| 452 | activation = activationBoundary(projectRoot, selected.selected, options); |
| 453 | if (activation.decision |
| 454 | && !['enabled', 'degraded'].includes(activation.decision.status)) { |
| 455 | boundary = { |
| 456 | canRunNow: false, |
| 457 | boundary: 'product-bundle-activation', |