kiro-spec-engine 1.47.31 → 1.47.33

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.
@@ -765,15 +765,18 @@ Dual-track handoff integration:
765
765
  - `kse auto handoff plan --manifest <path> [--out <path>] [--strict] [--strict-warnings] [--json]`: parse handoff manifest (source project, specs, templates, known gaps) and generate an executable KSE integration phase plan.
766
766
  - `kse auto handoff queue --manifest <path> [--out <path>] [--append] [--no-include-known-gaps] [--dry-run] [--json]`: generate close-loop batch goal queue from handoff manifest and optionally persist line-based queue file (default `.kiro/auto/handoff-goals.lines`).
767
767
  - `kse auto handoff template-diff --manifest <path> [--json]`: compare manifest templates against local template exports/registry and report `missing_in_local` and `extra_in_local`.
768
- - `kse auto handoff run --manifest <path> [--out <path>] [--queue-out <path>] [--append] [--no-include-known-gaps] [--continue-from <session|latest|file>] [--continue-strategy <auto|pending|failed-only>] [--dry-run] [--strict] [--strict-warnings] [--no-dependency-batching] [--min-spec-success-rate <n>] [--max-risk-level <level>] [--no-require-ontology-validation] [--release-evidence-window <n>] [--json]`: execute handoff end-to-end (`plan -> queue -> close-loop-batch -> observability`) with automatic report archive to `.kiro/reports/handoff-runs/<session>.json`.
768
+ - `kse auto handoff run --manifest <path> [--out <path>] [--queue-out <path>] [--append] [--no-include-known-gaps] [--continue-from <session|latest|file>] [--continue-strategy <auto|pending|failed-only>] [--dry-run] [--strict] [--strict-warnings] [--no-dependency-batching] [--min-spec-success-rate <n>] [--max-risk-level <level>] [--no-require-ontology-validation] [--no-require-moqui-baseline] [--min-capability-coverage <n>] [--no-require-capability-coverage] [--release-evidence-window <n>] [--json]`: execute handoff end-to-end (`plan -> queue -> close-loop-batch -> observability`) with automatic report archive to `.kiro/reports/handoff-runs/<session>.json`.
769
769
  - Default mode is dependency-aware: spec integration goals are grouped into dependency batches and executed in topological order.
770
770
  - `--continue-from` resumes pending goals from an existing handoff run report (`latest`, session id, or JSON file path). For safety, KSE enforces manifest-path consistency between the previous report and current run.
771
771
  - `--continue-strategy auto|pending|failed-only` controls resumed scope. `auto` (default) derives the best strategy from prior run state (`pending` when unprocessed/planned goals exist, otherwise `failed-only` for pure failure replay).
772
- - Non-dry runs auto-merge release evidence into `.kiro/reports/release-evidence/handoff-runs.json` with session-level gate/ontology/regression snapshots. Merge failures are recorded as warnings without aborting the run.
772
+ - Non-dry runs auto-merge release evidence into `.kiro/reports/release-evidence/handoff-runs.json` with session-level gate/ontology/regression/moqui-baseline/capability-coverage snapshots. Merge failures are recorded as warnings without aborting the run.
773
773
  - `--release-evidence-window` controls trend snapshot window size (2-50, default `5`) used in merged release evidence (`latest_trend_window` and per-session `trend_window`).
774
+ - Run output includes `moqui_baseline` snapshot by default, with artifacts at `.kiro/reports/release-evidence/moqui-template-baseline.json` and `.kiro/reports/release-evidence/moqui-template-baseline.md`.
775
+ - Run output includes `moqui_capability_coverage` snapshot by default (when manifest `capabilities` is declared), with artifacts at `.kiro/reports/release-evidence/moqui-capability-coverage.json` and `.kiro/reports/release-evidence/moqui-capability-coverage.md`.
776
+ - When Moqui baseline/capability gates fail, KSE auto-generates remediation queue lines at `.kiro/auto/moqui-remediation.lines`.
774
777
  - Run result includes `recommendations` with executable follow-up commands (for example, auto-generated `--continue-from <session>` on failed/incomplete batches).
775
- - Gate defaults: `--min-spec-success-rate` defaults to `100`, `--max-risk-level` defaults to `high`, and ontology validation requirement is enabled by default.
776
- - Use `--no-require-ontology-validation` only for emergency bypass; default behavior fails fast at precheck if manifest ontology evidence is missing or not passed.
778
+ - Gate defaults: `--min-spec-success-rate` defaults to `100`, `--max-risk-level` defaults to `high`, ontology validation requirement is enabled by default, Moqui baseline requirement is enabled by default, and capability coverage minimum defaults to `100` when manifest `capabilities` is declared.
779
+ - Use `--no-require-ontology-validation`, `--no-require-moqui-baseline`, or `--no-require-capability-coverage` only for emergency bypass.
777
780
  - `kse auto handoff regression [--session-id <id|latest>] [--window <n>] [--format <json|markdown>] [--out <path>] [--json]`: compare one handoff run report with its previous run and output trend deltas (success-rate/risk/failed-goals/elapsed time).
778
781
  - `--window` (2-50, default `2`) returns multi-run `series`, `window_trend`, and `aggregates` for broader regression visibility.
779
782
  - Regression JSON now includes `risk_layers` (low/medium/high/unknown buckets with per-layer session list and quality aggregates).
@@ -781,11 +784,11 @@ Dual-track handoff integration:
781
784
  - Markdown report includes `Trend Series` (ASCII success/ontology bars per session) and `Risk Layer View`.
782
785
  - `--out` writes the generated regression report using the selected format.
783
786
  - Output includes `recommendations` to guide next action when trend degrades or risk escalates.
784
- - `kse auto handoff evidence [--file <path>] [--session-id <id|latest>] [--window <n>] [--format <json|markdown>] [--out <path>] [--json]`: quick-review merged release evidence and render current-batch gate/ontology/regression/risk-layer overview.
787
+ - `kse auto handoff evidence [--file <path>] [--session-id <id|latest>] [--window <n>] [--format <json|markdown>] [--out <path>] [--json]`: quick-review merged release evidence and render current-batch gate/ontology/regression/moqui-baseline/capability-coverage/risk-layer overview.
785
788
  - Default evidence file is `.kiro/reports/release-evidence/handoff-runs.json`.
786
789
  - `--window` (1-50, default `5`) controls how many recent sessions are aggregated in review.
787
790
  - JSON output includes `current_overview`, `aggregates.status_counts`, `aggregates.gate_pass_rate_percent`, and `risk_layers`.
788
- - Markdown output includes `Current Gate`, `Current Ontology`, `Current Regression`, `Trend Series`, and `Risk Layer View`.
791
+ - Markdown output includes `Current Gate`, `Current Ontology`, `Current Regression`, `Current Moqui Baseline`, `Current Capability Coverage`, `Trend Series`, and `Risk Layer View`.
789
792
  - Add `--release-draft <path>` to auto-generate a release notes draft and evidence review markdown in one run.
790
793
  - `--release-version` sets draft version tag (defaults to `v<package.json version>`), and `--release-date` accepts `YYYY-MM-DD` (default: current UTC date).
791
794
  - Use `--review-out <path>` to override the generated evidence review markdown path (default `.kiro/reports/release-evidence/handoff-evidence-review.md`).
@@ -13,6 +13,9 @@ This playbook defines a generic (project-agnostic) path to absorb Moqui capabili
13
13
  KSE defaults already enforce the baseline below:
14
14
 
15
15
  - `kse auto handoff run`: ontology validation is required by default.
16
+ - `kse auto handoff run`: generates Moqui baseline snapshot by default and appends it to release-evidence sessions.
17
+ - `kse auto handoff run`: requires Moqui baseline portfolio pass by default.
18
+ - `kse auto handoff run`: evaluates capability coverage matrix by default when manifest `capabilities` is declared (default minimum `100%`).
16
19
  - `kse scene package-publish-batch`:
17
20
  - ontology validation required by default
18
21
  - batch ontology gate defaults:
@@ -22,6 +25,8 @@ KSE defaults already enforce the baseline below:
22
25
  Emergency bypass exists but is not recommended:
23
26
 
24
27
  - `--no-require-ontology-validation`
28
+ - `--no-require-moqui-baseline`
29
+ - `--no-require-capability-coverage`
25
30
 
26
31
  ## One-Shot Intake Flow
27
32
 
@@ -62,8 +67,13 @@ Required artifacts for each intake batch:
62
67
 
63
68
  - `.kiro/reports/moqui-template-baseline.json`
64
69
  - `.kiro/reports/moqui-template-baseline.md`
70
+ - `.kiro/reports/release-evidence/moqui-template-baseline.json`
71
+ - `.kiro/reports/release-evidence/moqui-template-baseline.md`
72
+ - `.kiro/reports/release-evidence/moqui-capability-coverage.json`
73
+ - `.kiro/reports/release-evidence/moqui-capability-coverage.md`
65
74
  - `.kiro/reports/handoff-runs/<session>.json`
66
75
  - `.kiro/reports/scene-package-ontology-batch.json`
76
+ - `.kiro/auto/moqui-remediation.lines` (when baseline/coverage gaps exist)
67
77
  - `.kiro/templates/scene-packages/registry.json`
68
78
  - gate output/evidence linked from release notes or handoff summary
69
79
 
@@ -9,6 +9,7 @@ const { analyzeGoalSemantics } = require('../auto/semantic-decomposer');
9
9
  const fs = require('fs-extra');
10
10
  const path = require('path');
11
11
  const chalk = require('chalk');
12
+ const { spawnSync } = require('child_process');
12
13
 
13
14
  const AUTO_ARCHIVE_SCHEMA_VERSION = '1.0';
14
15
  const AUTO_ARCHIVE_SCHEMA_SUPPORTED_VERSIONS = new Set([AUTO_ARCHIVE_SCHEMA_VERSION]);
@@ -18,6 +19,11 @@ const AUTO_HANDOFF_RELEASE_EVIDENCE_FILE = '.kiro/reports/release-evidence/hando
18
19
  const AUTO_HANDOFF_EVIDENCE_REVIEW_DEFAULT_FILE = '.kiro/reports/release-evidence/handoff-evidence-review.md';
19
20
  const AUTO_HANDOFF_RELEASE_EVIDENCE_DIR = '.kiro/reports/release-evidence';
20
21
  const AUTO_HANDOFF_RELEASE_GATE_HISTORY_FILE = '.kiro/reports/release-evidence/release-gate-history.json';
22
+ const AUTO_HANDOFF_MOQUI_BASELINE_JSON_FILE = '.kiro/reports/release-evidence/moqui-template-baseline.json';
23
+ const AUTO_HANDOFF_MOQUI_BASELINE_MARKDOWN_FILE = '.kiro/reports/release-evidence/moqui-template-baseline.md';
24
+ const AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_JSON_FILE = '.kiro/reports/release-evidence/moqui-capability-coverage.json';
25
+ const AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_MARKDOWN_FILE = '.kiro/reports/release-evidence/moqui-capability-coverage.md';
26
+ const AUTO_HANDOFF_MOQUI_REMEDIATION_QUEUE_FILE = '.kiro/auto/moqui-remediation.lines';
21
27
 
22
28
  /**
23
29
  * Register auto commands
@@ -1647,6 +1653,36 @@ function registerAutoCommands(program) {
1647
1653
  if (result.current_overview && result.current_overview.gate) {
1648
1654
  console.log(chalk.gray(` Gate passed: ${result.current_overview.gate.passed ? 'yes' : 'no'}`));
1649
1655
  }
1656
+ if (result.current_overview && result.current_overview.moqui_baseline) {
1657
+ const moquiBaseline = result.current_overview.moqui_baseline;
1658
+ const moquiSummary = moquiBaseline && moquiBaseline.summary ? moquiBaseline.summary : null;
1659
+ console.log(chalk.gray(` Moqui baseline: ${moquiBaseline.status || 'n/a'}`));
1660
+ if (moquiSummary) {
1661
+ const scoreText = Number.isFinite(Number(moquiSummary.avg_score))
1662
+ ? `${moquiSummary.avg_score}`
1663
+ : 'n/a';
1664
+ const validRateText = Number.isFinite(Number(moquiSummary.valid_rate_percent))
1665
+ ? `${moquiSummary.valid_rate_percent}%`
1666
+ : 'n/a';
1667
+ console.log(chalk.gray(` Portfolio: ${moquiSummary.portfolio_passed === true ? 'pass' : 'fail'} | avg=${scoreText} | valid-rate=${validRateText}`));
1668
+ }
1669
+ }
1670
+ if (result.current_overview && result.current_overview.capability_coverage) {
1671
+ const capabilityCoverage = result.current_overview.capability_coverage;
1672
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary ? capabilityCoverage.summary : null;
1673
+ console.log(chalk.gray(` Capability coverage: ${capabilityCoverage.status || 'n/a'}`));
1674
+ if (capabilitySummary) {
1675
+ const coverageText = Number.isFinite(Number(capabilitySummary.coverage_percent))
1676
+ ? `${capabilitySummary.coverage_percent}%`
1677
+ : 'n/a';
1678
+ console.log(
1679
+ chalk.gray(
1680
+ ` Passed: ${capabilitySummary.passed === true ? 'yes' : 'no'} | ` +
1681
+ `coverage=${coverageText} | min=${capabilitySummary.min_required_percent}%`
1682
+ )
1683
+ );
1684
+ }
1685
+ }
1650
1686
  if (result.output_file) {
1651
1687
  console.log(chalk.gray(` Output: ${result.output_file}`));
1652
1688
  }
@@ -1748,6 +1784,11 @@ function registerAutoCommands(program) {
1748
1784
  .option('--max-undecided-decisions <n>', 'Gate: maximum allowed undecided decisions (optional)', parseInt)
1749
1785
  .option('--require-ontology-validation', 'Gate: require manifest ontology_validation to be present and passed (default: enabled)')
1750
1786
  .option('--no-require-ontology-validation', 'Gate: disable manifest ontology_validation requirement (not recommended)')
1787
+ .option('--require-moqui-baseline', 'Gate: require Moqui baseline portfolio to pass (default: enabled)')
1788
+ .option('--no-require-moqui-baseline', 'Gate: disable Moqui baseline portfolio requirement (not recommended)')
1789
+ .option('--min-capability-coverage <n>', 'Gate: minimum Moqui capability coverage percent (default: 100)', parseFloat)
1790
+ .option('--require-capability-coverage', 'Gate: require capability coverage threshold when capabilities are declared (default: enabled)')
1791
+ .option('--no-require-capability-coverage', 'Gate: disable capability coverage requirement (not recommended)')
1751
1792
  .option('--release-evidence-window <n>', 'Release evidence trend window size (2-50, default: 5)', parseInt)
1752
1793
  .option('--json', 'Output machine-readable JSON')
1753
1794
  .action(async (options) => {
@@ -1771,6 +1812,40 @@ function registerAutoCommands(program) {
1771
1812
  if (result.gates) {
1772
1813
  console.log(chalk.gray(` Gate passed: ${result.gates.passed ? 'yes' : 'no'}`));
1773
1814
  }
1815
+ if (result.moqui_baseline) {
1816
+ console.log(chalk.gray(` Moqui baseline: ${result.moqui_baseline.status || 'unknown'}`));
1817
+ if (result.moqui_baseline.summary) {
1818
+ const baselineSummary = result.moqui_baseline.summary;
1819
+ const scoreText = Number.isFinite(Number(baselineSummary.avg_score))
1820
+ ? `${baselineSummary.avg_score}`
1821
+ : 'n/a';
1822
+ const validRateText = Number.isFinite(Number(baselineSummary.valid_rate_percent))
1823
+ ? `${baselineSummary.valid_rate_percent}%`
1824
+ : 'n/a';
1825
+ console.log(chalk.gray(` Portfolio: ${baselineSummary.portfolio_passed ? 'pass' : 'fail'} | avg=${scoreText} | valid-rate=${validRateText}`));
1826
+ }
1827
+ if (result.moqui_baseline.output && result.moqui_baseline.output.json) {
1828
+ console.log(chalk.gray(` Baseline report: ${result.moqui_baseline.output.json}`));
1829
+ }
1830
+ }
1831
+ if (result.moqui_capability_coverage) {
1832
+ console.log(chalk.gray(` Capability coverage: ${result.moqui_capability_coverage.status || 'unknown'}`));
1833
+ if (result.moqui_capability_coverage.summary) {
1834
+ const coverageSummary = result.moqui_capability_coverage.summary;
1835
+ const coverageText = Number.isFinite(Number(coverageSummary.coverage_percent))
1836
+ ? `${coverageSummary.coverage_percent}%`
1837
+ : 'n/a';
1838
+ console.log(
1839
+ chalk.gray(
1840
+ ` Passed: ${coverageSummary.passed === true ? 'yes' : 'no'} | ` +
1841
+ `coverage=${coverageText} | min=${coverageSummary.min_required_percent}%`
1842
+ )
1843
+ );
1844
+ }
1845
+ }
1846
+ if (result.remediation_queue && result.remediation_queue.file) {
1847
+ console.log(chalk.gray(` Remediation queue: ${toAutoHandoffCliPath(process.cwd(), result.remediation_queue.file)} (${result.remediation_queue.goal_count})`));
1848
+ }
1774
1849
  if (result.output_file) {
1775
1850
  console.log(chalk.gray(` Report: ${result.output_file}`));
1776
1851
  }
@@ -6878,6 +6953,16 @@ function normalizeAutoHandoffManifest(payload = {}) {
6878
6953
  validationWarnings.push('templates is empty');
6879
6954
  }
6880
6955
 
6956
+ const capabilitiesCollected = collectUniqueIdentifiers(
6957
+ payload.capabilities,
6958
+ ['name', 'capability', 'capability_name', 'id', 'capability.id', 'capability.name'],
6959
+ 'capabilities'
6960
+ );
6961
+ validationWarnings.push(...capabilitiesCollected.warnings);
6962
+ if (capabilitiesCollected.values.length === 0) {
6963
+ validationWarnings.push('capabilities is empty; capability coverage gate will be skipped unless capabilities are declared');
6964
+ }
6965
+
6881
6966
  const knownGapCollected = collectKnownGaps(payload.known_gaps);
6882
6967
  validationWarnings.push(...knownGapCollected.warnings);
6883
6968
 
@@ -6899,6 +6984,7 @@ function normalizeAutoHandoffManifest(payload = {}) {
6899
6984
  spec_descriptors: specsCollected.descriptors,
6900
6985
  dependency_batches: dependencyBatches,
6901
6986
  templates: templatesCollected.values,
6987
+ capabilities: capabilitiesCollected.values,
6902
6988
  known_gaps: knownGapCollected.gaps,
6903
6989
  ontology_validation: ontologyValidation,
6904
6990
  next_batch: nextBatch,
@@ -7006,11 +7092,13 @@ async function buildAutoHandoffPlan(projectPath, options = {}) {
7006
7092
  timestamp: handoff.timestamp,
7007
7093
  spec_count: handoff.specs.length,
7008
7094
  template_count: handoff.templates.length,
7095
+ capability_count: Array.isArray(handoff.capabilities) ? handoff.capabilities.length : 0,
7009
7096
  known_gap_count: handoff.known_gaps.length,
7010
7097
  specs: handoff.specs,
7011
7098
  spec_descriptors: handoff.spec_descriptors,
7012
7099
  dependency_batches: handoff.dependency_batches,
7013
7100
  templates: handoff.templates,
7101
+ capabilities: handoff.capabilities,
7014
7102
  known_gaps: handoff.known_gaps,
7015
7103
  ontology_validation: handoff.ontology_validation,
7016
7104
  next_batch: handoff.next_batch
@@ -8890,6 +8978,19 @@ function buildAutoHandoffEvidenceSnapshot(entry = {}) {
8890
8978
  ),
8891
8979
  ontology_business_rule_pass_rate_percent: toNumber(ontologyMetrics.business_rule_pass_rate_percent),
8892
8980
  ontology_decision_resolved_rate_percent: toNumber(ontologyMetrics.decision_resolved_rate_percent),
8981
+ capability_coverage_percent: toNumber(
8982
+ entry &&
8983
+ entry.capability_coverage &&
8984
+ entry.capability_coverage.summary
8985
+ ? entry.capability_coverage.summary.coverage_percent
8986
+ : null
8987
+ ),
8988
+ capability_coverage_passed: Boolean(
8989
+ entry &&
8990
+ entry.capability_coverage &&
8991
+ entry.capability_coverage.summary &&
8992
+ entry.capability_coverage.summary.passed === true
8993
+ ),
8893
8994
  generated_at: normalizeHandoffText(entry.merged_at)
8894
8995
  };
8895
8996
  }
@@ -8937,6 +9038,33 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
8937
9038
  const regression = currentOverview.regression && typeof currentOverview.regression === 'object'
8938
9039
  ? currentOverview.regression
8939
9040
  : {};
9041
+ const moquiBaseline = currentOverview.moqui_baseline && typeof currentOverview.moqui_baseline === 'object'
9042
+ ? currentOverview.moqui_baseline
9043
+ : {};
9044
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
9045
+ ? moquiBaseline.summary
9046
+ : {};
9047
+ const moquiCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
9048
+ ? moquiBaseline.compare
9049
+ : {};
9050
+ const moquiDeltas = moquiCompare && moquiCompare.deltas && typeof moquiCompare.deltas === 'object'
9051
+ ? moquiCompare.deltas
9052
+ : {};
9053
+ const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
9054
+ ? moquiCompare.failed_templates
9055
+ : {};
9056
+ const capabilityCoverage = currentOverview.capability_coverage && typeof currentOverview.capability_coverage === 'object'
9057
+ ? currentOverview.capability_coverage
9058
+ : {};
9059
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
9060
+ ? capabilityCoverage.summary
9061
+ : {};
9062
+ const capabilityCompare = capabilityCoverage && capabilityCoverage.compare && typeof capabilityCoverage.compare === 'object'
9063
+ ? capabilityCoverage.compare
9064
+ : {};
9065
+ const capabilityGaps = Array.isArray(capabilityCoverage && capabilityCoverage.gaps)
9066
+ ? capabilityCoverage.gaps
9067
+ : [];
8940
9068
  const window = payload.window || { requested: 5, actual: 0 };
8941
9069
  const series = Array.isArray(payload.series) ? payload.series : [];
8942
9070
  const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
@@ -8951,7 +9079,11 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
8951
9079
  const failedGoals = formatAutoHandoffRegressionValue(item.failed_goals);
8952
9080
  const successBar = renderAutoHandoffRegressionAsciiBar(item.spec_success_rate_percent, 100, 20);
8953
9081
  const ontologyBar = renderAutoHandoffRegressionAsciiBar(item.ontology_quality_score, 100, 20);
8954
- return `- ${sessionId} | ${mergedAt} | risk=${riskLevel} | failed=${failedGoals} | success=${successBar} | ontology=${ontologyBar}`;
9082
+ const capabilityBar = renderAutoHandoffRegressionAsciiBar(item.capability_coverage_percent, 100, 20);
9083
+ return (
9084
+ `- ${sessionId} | ${mergedAt} | risk=${riskLevel} | failed=${failedGoals} | ` +
9085
+ `success=${successBar} | ontology=${ontologyBar} | capability=${capabilityBar}`
9086
+ );
8955
9087
  })
8956
9088
  : ['- None'];
8957
9089
  const riskLayerLines = ['low', 'medium', 'high', 'unknown'].map(level => {
@@ -9001,6 +9133,34 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
9001
9133
  `- Delta risk rank: ${formatAutoHandoffRegressionValue(regression.delta && regression.delta.risk_level_rank)}`,
9002
9134
  `- Delta failed goals: ${formatAutoHandoffRegressionValue(regression.delta && regression.delta.failed_goals)}`,
9003
9135
  '',
9136
+ '## Current Moqui Baseline',
9137
+ '',
9138
+ `- Status: ${formatAutoHandoffRegressionValue(moquiBaseline.status)}`,
9139
+ `- Portfolio passed: ${moquiSummary.portfolio_passed === true ? 'yes' : (moquiSummary.portfolio_passed === false ? 'no' : 'n/a')}`,
9140
+ `- Avg score: ${formatAutoHandoffRegressionValue(moquiSummary.avg_score)}`,
9141
+ `- Valid-rate: ${formatAutoHandoffRegressionValue(moquiSummary.valid_rate_percent)}%`,
9142
+ `- Baseline failed templates: ${formatAutoHandoffRegressionValue(moquiSummary.baseline_failed)}`,
9143
+ `- Delta avg score: ${formatAutoHandoffRegressionValue(moquiDeltas.avg_score)}`,
9144
+ `- Delta valid-rate: ${formatAutoHandoffRegressionValue(moquiDeltas.valid_rate_percent)}%`,
9145
+ `- Newly failed templates: ${Array.isArray(moquiFailedTemplates.newly_failed) && moquiFailedTemplates.newly_failed.length > 0 ? moquiFailedTemplates.newly_failed.join(', ') : 'none'}`,
9146
+ `- Recovered templates: ${Array.isArray(moquiFailedTemplates.recovered) && moquiFailedTemplates.recovered.length > 0 ? moquiFailedTemplates.recovered.join(', ') : 'none'}`,
9147
+ `- Baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
9148
+ '',
9149
+ '## Current Capability Coverage',
9150
+ '',
9151
+ `- Status: ${formatAutoHandoffRegressionValue(capabilityCoverage.status)}`,
9152
+ `- Passed: ${capabilitySummary.passed === true ? 'yes' : (capabilitySummary.passed === false ? 'no' : 'n/a')}`,
9153
+ `- Coverage: ${formatAutoHandoffRegressionValue(capabilitySummary.coverage_percent)}%`,
9154
+ `- Min required: ${formatAutoHandoffRegressionValue(capabilitySummary.min_required_percent)}%`,
9155
+ `- Covered capabilities: ${formatAutoHandoffRegressionValue(capabilitySummary.covered_capabilities)}`,
9156
+ `- Uncovered capabilities: ${formatAutoHandoffRegressionValue(capabilitySummary.uncovered_capabilities)}`,
9157
+ `- Delta coverage: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_coverage_percent)}%`,
9158
+ `- Delta covered capabilities: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_covered_capabilities)}`,
9159
+ `- Newly covered: ${Array.isArray(capabilityCompare.newly_covered) && capabilityCompare.newly_covered.length > 0 ? capabilityCompare.newly_covered.join(', ') : 'none'}`,
9160
+ `- Newly uncovered: ${Array.isArray(capabilityCompare.newly_uncovered) && capabilityCompare.newly_uncovered.length > 0 ? capabilityCompare.newly_uncovered.join(', ') : 'none'}`,
9161
+ `- Capability gaps: ${capabilityGaps.length > 0 ? capabilityGaps.join(', ') : 'none'}`,
9162
+ `- Coverage JSON: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.json)}`,
9163
+ '',
9004
9164
  '## Trend Series',
9005
9165
  '',
9006
9166
  ...trendSeriesLines,
@@ -9092,6 +9252,33 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9092
9252
  const regression = currentOverview.regression && typeof currentOverview.regression === 'object'
9093
9253
  ? currentOverview.regression
9094
9254
  : {};
9255
+ const moquiBaseline = currentOverview.moqui_baseline && typeof currentOverview.moqui_baseline === 'object'
9256
+ ? currentOverview.moqui_baseline
9257
+ : {};
9258
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
9259
+ ? moquiBaseline.summary
9260
+ : {};
9261
+ const moquiCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
9262
+ ? moquiBaseline.compare
9263
+ : {};
9264
+ const moquiDeltas = moquiCompare && moquiCompare.deltas && typeof moquiCompare.deltas === 'object'
9265
+ ? moquiCompare.deltas
9266
+ : {};
9267
+ const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
9268
+ ? moquiCompare.failed_templates
9269
+ : {};
9270
+ const capabilityCoverage = currentOverview.capability_coverage && typeof currentOverview.capability_coverage === 'object'
9271
+ ? currentOverview.capability_coverage
9272
+ : {};
9273
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
9274
+ ? capabilityCoverage.summary
9275
+ : {};
9276
+ const capabilityCompare = capabilityCoverage && capabilityCoverage.compare && typeof capabilityCoverage.compare === 'object'
9277
+ ? capabilityCoverage.compare
9278
+ : {};
9279
+ const capabilityGaps = Array.isArray(capabilityCoverage && capabilityCoverage.gaps)
9280
+ ? capabilityCoverage.gaps
9281
+ : [];
9095
9282
  const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
9096
9283
  ? payload.risk_layers
9097
9284
  : {};
@@ -9136,6 +9323,20 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9136
9323
  `- Regression trend: ${formatAutoHandoffRegressionValue(regression.trend, formatAutoHandoffRegressionValue(payload.trend))}`,
9137
9324
  `- Window trend: ${formatAutoHandoffRegressionValue(payload.window_trend && payload.window_trend.trend)}`,
9138
9325
  `- Gate pass rate (window): ${formatAutoHandoffRegressionValue(payload.aggregates && payload.aggregates.gate_pass_rate_percent)}%`,
9326
+ `- Moqui baseline portfolio passed: ${moquiSummary.portfolio_passed === true ? 'yes' : (moquiSummary.portfolio_passed === false ? 'no' : 'n/a')}`,
9327
+ `- Moqui baseline avg score: ${formatAutoHandoffRegressionValue(moquiSummary.avg_score)}`,
9328
+ `- Moqui baseline valid-rate: ${formatAutoHandoffRegressionValue(moquiSummary.valid_rate_percent)}%`,
9329
+ `- Moqui baseline failed templates: ${formatAutoHandoffRegressionValue(moquiSummary.baseline_failed)}`,
9330
+ `- Moqui baseline avg score delta: ${formatAutoHandoffRegressionValue(moquiDeltas.avg_score)}`,
9331
+ `- Moqui baseline valid-rate delta: ${formatAutoHandoffRegressionValue(moquiDeltas.valid_rate_percent)}%`,
9332
+ `- Moqui newly failed templates: ${Array.isArray(moquiFailedTemplates.newly_failed) && moquiFailedTemplates.newly_failed.length > 0 ? moquiFailedTemplates.newly_failed.join(', ') : 'none'}`,
9333
+ `- Capability coverage status: ${formatAutoHandoffRegressionValue(capabilityCoverage.status)}`,
9334
+ `- Capability coverage passed: ${capabilitySummary.passed === true ? 'yes' : (capabilitySummary.passed === false ? 'no' : 'n/a')}`,
9335
+ `- Capability coverage: ${formatAutoHandoffRegressionValue(capabilitySummary.coverage_percent)}%`,
9336
+ `- Capability min required: ${formatAutoHandoffRegressionValue(capabilitySummary.min_required_percent)}%`,
9337
+ `- Capability coverage delta: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_coverage_percent)}%`,
9338
+ `- Capability newly uncovered: ${Array.isArray(capabilityCompare.newly_uncovered) && capabilityCompare.newly_uncovered.length > 0 ? capabilityCompare.newly_uncovered.join(', ') : 'none'}`,
9339
+ `- Capability gaps: ${capabilityGaps.length > 0 ? capabilityGaps.join(', ') : 'none'}`,
9139
9340
  '',
9140
9341
  '## Status Breakdown',
9141
9342
  '',
@@ -9154,6 +9355,10 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9154
9355
  `- Evidence review report: ${reviewFile || 'n/a'}`,
9155
9356
  `- Handoff report: ${formatAutoHandoffRegressionValue(currentOverview.handoff_report_file)}`,
9156
9357
  `- Release evidence JSON: ${formatAutoHandoffRegressionValue(payload.evidence_file)}`,
9358
+ `- Moqui baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
9359
+ `- Moqui baseline markdown: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.markdown)}`,
9360
+ `- Capability coverage JSON: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.json)}`,
9361
+ `- Capability coverage markdown: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.markdown)}`,
9157
9362
  '',
9158
9363
  '## Recommendations'
9159
9364
  ];
@@ -9369,6 +9574,17 @@ function normalizeHandoffMinOntologyScore(scoreCandidate) {
9369
9574
  return Number(parsed.toFixed(2));
9370
9575
  }
9371
9576
 
9577
+ function normalizeHandoffMinCapabilityCoverage(coverageCandidate) {
9578
+ if (coverageCandidate === undefined || coverageCandidate === null) {
9579
+ return 100;
9580
+ }
9581
+ const parsed = Number(coverageCandidate);
9582
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
9583
+ throw new Error('--min-capability-coverage must be a number between 0 and 100.');
9584
+ }
9585
+ return Number(parsed.toFixed(2));
9586
+ }
9587
+
9372
9588
  function normalizeHandoffOptionalNonNegativeInteger(valueCandidate, optionName) {
9373
9589
  if (valueCandidate === undefined || valueCandidate === null || valueCandidate === '') {
9374
9590
  return null;
@@ -9396,6 +9612,7 @@ function buildAutoHandoffRunPolicy(options = {}) {
9396
9612
  min_spec_success_rate: normalizeHandoffMinSpecSuccessRate(options.minSpecSuccessRate),
9397
9613
  max_risk_level: normalizeHandoffRiskLevel(options.maxRiskLevel),
9398
9614
  min_ontology_score: normalizeHandoffMinOntologyScore(options.minOntologyScore),
9615
+ min_capability_coverage_percent: normalizeHandoffMinCapabilityCoverage(options.minCapabilityCoverage),
9399
9616
  max_unmapped_rules: normalizeHandoffOptionalNonNegativeInteger(
9400
9617
  options.maxUnmappedRules,
9401
9618
  '--max-unmapped-rules'
@@ -9405,6 +9622,8 @@ function buildAutoHandoffRunPolicy(options = {}) {
9405
9622
  '--max-undecided-decisions'
9406
9623
  ),
9407
9624
  require_ontology_validation: options.requireOntologyValidation !== false,
9625
+ require_moqui_baseline: options.requireMoquiBaseline !== false,
9626
+ require_capability_coverage: options.requireCapabilityCoverage !== false,
9408
9627
  dependency_batching: options.dependencyBatching !== false,
9409
9628
  release_evidence_window: normalizeHandoffReleaseEvidenceWindow(options.releaseEvidenceWindow)
9410
9629
  };
@@ -9708,6 +9927,84 @@ function evaluateAutoHandoffOntologyGateReasons(policy = {}, ontology = {}) {
9708
9927
  return reasons;
9709
9928
  }
9710
9929
 
9930
+ function evaluateAutoHandoffMoquiBaselineGateReasons(policy = {}, moquiBaseline = null) {
9931
+ const reasons = [];
9932
+ if (policy.require_moqui_baseline !== true) {
9933
+ return reasons;
9934
+ }
9935
+
9936
+ const baseline = moquiBaseline && typeof moquiBaseline === 'object'
9937
+ ? moquiBaseline
9938
+ : null;
9939
+ const summary = baseline && baseline.summary && typeof baseline.summary === 'object'
9940
+ ? baseline.summary
9941
+ : {};
9942
+ const status = `${baseline && baseline.status ? baseline.status : 'missing'}`.trim().toLowerCase();
9943
+ if (!baseline || baseline.generated !== true) {
9944
+ const reason = baseline && baseline.reason ? baseline.reason : 'moqui baseline snapshot missing';
9945
+ reasons.push(`moqui baseline unavailable: ${reason}`);
9946
+ return reasons;
9947
+ }
9948
+ if (status === 'error') {
9949
+ reasons.push(`moqui baseline errored: ${baseline.error || 'unknown error'}`);
9950
+ return reasons;
9951
+ }
9952
+ if (summary.portfolio_passed !== true) {
9953
+ const avgScore = Number(summary.avg_score);
9954
+ const validRate = Number(summary.valid_rate_percent);
9955
+ reasons.push(
9956
+ `moqui baseline portfolio not passed (avg_score=${Number.isFinite(avgScore) ? avgScore : 'n/a'}, ` +
9957
+ `valid_rate=${Number.isFinite(validRate) ? `${validRate}%` : 'n/a'})`
9958
+ );
9959
+ }
9960
+ return reasons;
9961
+ }
9962
+
9963
+ function evaluateAutoHandoffCapabilityCoverageGateReasons(policy = {}, capabilityCoverage = null) {
9964
+ const reasons = [];
9965
+ if (policy.require_capability_coverage !== true) {
9966
+ return reasons;
9967
+ }
9968
+
9969
+ const coverage = capabilityCoverage && typeof capabilityCoverage === 'object'
9970
+ ? capabilityCoverage
9971
+ : null;
9972
+ if (!coverage) {
9973
+ reasons.push('capability coverage snapshot missing');
9974
+ return reasons;
9975
+ }
9976
+ if (coverage.status === 'error') {
9977
+ reasons.push(`capability coverage errored: ${coverage.error || 'unknown error'}`);
9978
+ return reasons;
9979
+ }
9980
+ if (coverage.status === 'skipped') {
9981
+ const totalCapabilities = Number(
9982
+ coverage &&
9983
+ coverage.summary &&
9984
+ coverage.summary.total_capabilities !== undefined
9985
+ ? coverage.summary.total_capabilities
9986
+ : 0
9987
+ );
9988
+ if (Number.isFinite(totalCapabilities) && totalCapabilities <= 0) {
9989
+ return reasons;
9990
+ }
9991
+ reasons.push(`capability coverage skipped: ${coverage.reason || 'unknown reason'}`);
9992
+ return reasons;
9993
+ }
9994
+
9995
+ const summary = coverage.summary && typeof coverage.summary === 'object'
9996
+ ? coverage.summary
9997
+ : {};
9998
+ const coveragePercent = Number(summary.coverage_percent);
9999
+ const minCoverage = Number(policy.min_capability_coverage_percent);
10000
+ if (!Number.isFinite(coveragePercent)) {
10001
+ reasons.push('capability_coverage_percent unavailable');
10002
+ } else if (Number.isFinite(minCoverage) && coveragePercent < minCoverage) {
10003
+ reasons.push(`capability_coverage_percent ${coveragePercent} < required ${minCoverage}`);
10004
+ }
10005
+ return reasons;
10006
+ }
10007
+
9711
10008
  function collectHandoffBlockers(resultItem) {
9712
10009
  const blockers = [];
9713
10010
  if (!resultItem) {
@@ -9792,6 +10089,12 @@ function evaluateAutoHandoffRunGates(context = {}) {
9792
10089
  present: false,
9793
10090
  passed: false
9794
10091
  };
10092
+ const moquiBaseline = context.moquiBaseline && typeof context.moquiBaseline === 'object'
10093
+ ? context.moquiBaseline
10094
+ : null;
10095
+ const capabilityCoverage = context.capabilityCoverage && typeof context.capabilityCoverage === 'object'
10096
+ ? context.capabilityCoverage
10097
+ : null;
9795
10098
  const kpi = context.programKpi || {
9796
10099
  risk_level: 'high'
9797
10100
  };
@@ -9817,6 +10120,8 @@ function evaluateAutoHandoffRunGates(context = {}) {
9817
10120
  }
9818
10121
 
9819
10122
  reasons.push(...evaluateAutoHandoffOntologyGateReasons(policy, ontology));
10123
+ reasons.push(...evaluateAutoHandoffMoquiBaselineGateReasons(policy, moquiBaseline));
10124
+ reasons.push(...evaluateAutoHandoffCapabilityCoverageGateReasons(policy, capabilityCoverage));
9820
10125
 
9821
10126
  return {
9822
10127
  passed: reasons.length === 0,
@@ -9850,6 +10155,18 @@ function evaluateAutoHandoffRunGates(context = {}) {
9850
10155
  Number(ontology && ontology.metrics ? ontology.metrics.decision_resolved_rate_percent : null)
9851
10156
  )
9852
10157
  ? Number(ontology.metrics.decision_resolved_rate_percent)
10158
+ : null,
10159
+ moqui_baseline_status: normalizeHandoffText(moquiBaseline && moquiBaseline.status),
10160
+ moqui_baseline_portfolio_passed: Boolean(
10161
+ moquiBaseline &&
10162
+ moquiBaseline.summary &&
10163
+ moquiBaseline.summary.portfolio_passed === true
10164
+ ),
10165
+ capability_coverage_status: normalizeHandoffText(capabilityCoverage && capabilityCoverage.status),
10166
+ capability_coverage_percent: Number.isFinite(
10167
+ Number(capabilityCoverage && capabilityCoverage.summary ? capabilityCoverage.summary.coverage_percent : null)
10168
+ )
10169
+ ? Number(capabilityCoverage.summary.coverage_percent)
9853
10170
  : null
9854
10171
  },
9855
10172
  reasons
@@ -9939,6 +10256,47 @@ function buildAutoHandoffRunRecommendations(projectPath, result) {
9939
10256
  push('kse auto governance stats --days 14 --json');
9940
10257
  }
9941
10258
 
10259
+ if (result && result.remediation_queue && result.remediation_queue.file) {
10260
+ push(
10261
+ `kse auto close-loop-batch ${quoteCliArg(
10262
+ toAutoHandoffCliPath(projectPath, result.remediation_queue.file)
10263
+ )} --format lines --json`
10264
+ );
10265
+ }
10266
+
10267
+ const moquiBaseline = result && result.moqui_baseline && typeof result.moqui_baseline === 'object'
10268
+ ? result.moqui_baseline
10269
+ : null;
10270
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
10271
+ ? moquiBaseline.summary
10272
+ : null;
10273
+ if (moquiBaseline && moquiBaseline.status === 'error') {
10274
+ push('kse scene moqui-baseline --json');
10275
+ } else if (moquiSummary && moquiSummary.portfolio_passed === false) {
10276
+ push(
10277
+ 'kse scene moqui-baseline --include-all ' +
10278
+ '--compare-with .kiro/reports/release-evidence/moqui-template-baseline.json --json'
10279
+ );
10280
+ push(
10281
+ 'kse scene package-publish-batch --manifest docs/handoffs/handoff-manifest.json ' +
10282
+ '--dry-run --ontology-task-queue-out .kiro/auto/ontology-remediation.lines --json'
10283
+ );
10284
+ }
10285
+
10286
+ const capabilityCoverage = result && result.moqui_capability_coverage && typeof result.moqui_capability_coverage === 'object'
10287
+ ? result.moqui_capability_coverage
10288
+ : null;
10289
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
10290
+ ? capabilityCoverage.summary
10291
+ : null;
10292
+ if (capabilityCoverage && capabilityCoverage.status === 'error') {
10293
+ push('declare manifest capabilities and rerun `kse auto handoff run` to rebuild capability coverage evidence');
10294
+ } else if (capabilitySummary && capabilitySummary.passed === false) {
10295
+ push('complete uncovered moqui capabilities and rerun `kse auto handoff run --json`');
10296
+ } else if (capabilityCoverage && capabilityCoverage.status === 'skipped') {
10297
+ push('declare `capabilities` in handoff manifest to enable machine-checkable moqui capability coverage');
10298
+ }
10299
+
9942
10300
  return recommendations;
9943
10301
  }
9944
10302
 
@@ -10026,6 +10384,33 @@ function buildAutoHandoffReleaseEvidenceEntry(projectPath, result, reportFile =
10026
10384
  const regressionDelta = regression && regression.delta && typeof regression.delta === 'object'
10027
10385
  ? regression.delta
10028
10386
  : {};
10387
+ const moquiBaseline = result && result.moqui_baseline && typeof result.moqui_baseline === 'object'
10388
+ ? result.moqui_baseline
10389
+ : {};
10390
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
10391
+ ? moquiBaseline.summary
10392
+ : {};
10393
+ const moquiCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
10394
+ ? moquiBaseline.compare
10395
+ : {};
10396
+ const moquiDeltas = moquiCompare && moquiCompare.deltas && typeof moquiCompare.deltas === 'object'
10397
+ ? moquiCompare.deltas
10398
+ : {};
10399
+ const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
10400
+ ? moquiCompare.failed_templates
10401
+ : {};
10402
+ const capabilityCoverage = result && result.moqui_capability_coverage && typeof result.moqui_capability_coverage === 'object'
10403
+ ? result.moqui_capability_coverage
10404
+ : {};
10405
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
10406
+ ? capabilityCoverage.summary
10407
+ : {};
10408
+ const capabilityCompare = capabilityCoverage && capabilityCoverage.compare && typeof capabilityCoverage.compare === 'object'
10409
+ ? capabilityCoverage.compare
10410
+ : {};
10411
+ const capabilityGaps = Array.isArray(capabilityCoverage && capabilityCoverage.gaps)
10412
+ ? capabilityCoverage.gaps
10413
+ : [];
10029
10414
  const batchSummary = result && result.batch_summary && typeof result.batch_summary === 'object'
10030
10415
  ? result.batch_summary
10031
10416
  : {};
@@ -10084,6 +10469,70 @@ function buildAutoHandoffReleaseEvidenceEntry(projectPath, result, reportFile =
10084
10469
  ontology_undecided_decisions: toNumber(regressionDelta.ontology_undecided_decisions)
10085
10470
  }
10086
10471
  },
10472
+ moqui_baseline: {
10473
+ status: normalizeHandoffText(moquiBaseline.status),
10474
+ generated: moquiBaseline.generated === true,
10475
+ reason: normalizeHandoffText(moquiBaseline.reason),
10476
+ error: normalizeHandoffText(moquiBaseline.error),
10477
+ summary: {
10478
+ total_templates: toNumber(moquiSummary.total_templates),
10479
+ scoped_templates: toNumber(moquiSummary.scoped_templates),
10480
+ avg_score: toNumber(moquiSummary.avg_score),
10481
+ valid_rate_percent: toNumber(moquiSummary.valid_rate_percent),
10482
+ baseline_passed: toNumber(moquiSummary.baseline_passed),
10483
+ baseline_failed: toNumber(moquiSummary.baseline_failed),
10484
+ portfolio_passed: moquiSummary.portfolio_passed === true
10485
+ },
10486
+ compare: Object.keys(moquiCompare).length === 0
10487
+ ? null
10488
+ : {
10489
+ previous_generated_at: normalizeHandoffText(moquiCompare.previous_generated_at),
10490
+ previous_template_root: normalizeHandoffText(moquiCompare.previous_template_root),
10491
+ deltas: {
10492
+ scoped_templates: toNumber(moquiDeltas.scoped_templates),
10493
+ avg_score: toNumber(moquiDeltas.avg_score),
10494
+ valid_rate_percent: toNumber(moquiDeltas.valid_rate_percent),
10495
+ baseline_passed: toNumber(moquiDeltas.baseline_passed),
10496
+ baseline_failed: toNumber(moquiDeltas.baseline_failed)
10497
+ },
10498
+ failed_templates: {
10499
+ newly_failed: Array.isArray(moquiFailedTemplates.newly_failed) ? moquiFailedTemplates.newly_failed : [],
10500
+ recovered: Array.isArray(moquiFailedTemplates.recovered) ? moquiFailedTemplates.recovered : []
10501
+ }
10502
+ },
10503
+ output: {
10504
+ json: normalizeHandoffText(moquiBaseline && moquiBaseline.output ? moquiBaseline.output.json : null),
10505
+ markdown: normalizeHandoffText(moquiBaseline && moquiBaseline.output ? moquiBaseline.output.markdown : null)
10506
+ }
10507
+ },
10508
+ capability_coverage: {
10509
+ status: normalizeHandoffText(capabilityCoverage.status),
10510
+ generated: capabilityCoverage.generated === true,
10511
+ reason: normalizeHandoffText(capabilityCoverage.reason),
10512
+ error: normalizeHandoffText(capabilityCoverage.error),
10513
+ summary: {
10514
+ total_capabilities: toNumber(capabilitySummary.total_capabilities),
10515
+ covered_capabilities: toNumber(capabilitySummary.covered_capabilities),
10516
+ uncovered_capabilities: toNumber(capabilitySummary.uncovered_capabilities),
10517
+ coverage_percent: toNumber(capabilitySummary.coverage_percent),
10518
+ min_required_percent: toNumber(capabilitySummary.min_required_percent),
10519
+ passed: capabilitySummary.passed === true
10520
+ },
10521
+ compare: Object.keys(capabilityCompare).length === 0
10522
+ ? null
10523
+ : {
10524
+ previous_generated_at: normalizeHandoffText(capabilityCompare.previous_generated_at),
10525
+ delta_coverage_percent: toNumber(capabilityCompare.delta_coverage_percent),
10526
+ delta_covered_capabilities: toNumber(capabilityCompare.delta_covered_capabilities),
10527
+ newly_covered: Array.isArray(capabilityCompare.newly_covered) ? capabilityCompare.newly_covered : [],
10528
+ newly_uncovered: Array.isArray(capabilityCompare.newly_uncovered) ? capabilityCompare.newly_uncovered : []
10529
+ },
10530
+ gaps: capabilityGaps,
10531
+ output: {
10532
+ json: normalizeHandoffText(capabilityCoverage && capabilityCoverage.output ? capabilityCoverage.output.json : null),
10533
+ markdown: normalizeHandoffText(capabilityCoverage && capabilityCoverage.output ? capabilityCoverage.output.markdown : null)
10534
+ }
10535
+ },
10087
10536
  batch_summary: {
10088
10537
  status: normalizeHandoffText(batchSummary.status),
10089
10538
  total_goals: toNumber(batchSummary.total_goals),
@@ -10218,6 +10667,497 @@ async function writeAutoHandoffRunReport(projectPath, result, outCandidate = nul
10218
10667
  await maybeWriteOutput(result, defaultFile, projectPath);
10219
10668
  }
10220
10669
 
10670
+ function buildAutoHandoffMoquiBaselinePhaseDetails(payload) {
10671
+ const baseline = payload && typeof payload === 'object' ? payload : {};
10672
+ const summary = baseline.summary && typeof baseline.summary === 'object' ? baseline.summary : null;
10673
+ return {
10674
+ status: baseline.status || 'unknown',
10675
+ generated: baseline.generated === true,
10676
+ output: baseline.output || null,
10677
+ portfolio_passed: summary ? summary.portfolio_passed === true : null,
10678
+ avg_score: summary && Number.isFinite(Number(summary.avg_score))
10679
+ ? Number(summary.avg_score)
10680
+ : null,
10681
+ valid_rate_percent: summary && Number.isFinite(Number(summary.valid_rate_percent))
10682
+ ? Number(summary.valid_rate_percent)
10683
+ : null
10684
+ };
10685
+ }
10686
+
10687
+ async function buildAutoHandoffMoquiBaselineSnapshot(projectPath) {
10688
+ const scriptPath = path.join(projectPath, 'scripts', 'moqui-template-baseline-report.js');
10689
+ if (!(await fs.pathExists(scriptPath))) {
10690
+ return {
10691
+ status: 'skipped',
10692
+ generated: false,
10693
+ reason: `baseline script missing: ${toAutoHandoffCliPath(projectPath, scriptPath)}`
10694
+ };
10695
+ }
10696
+
10697
+ const outputJsonPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_BASELINE_JSON_FILE);
10698
+ const outputMarkdownPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_BASELINE_MARKDOWN_FILE);
10699
+ await fs.ensureDir(path.dirname(outputJsonPath));
10700
+
10701
+ const scriptArgs = [
10702
+ scriptPath,
10703
+ '--out', outputJsonPath,
10704
+ '--markdown-out', outputMarkdownPath,
10705
+ '--json'
10706
+ ];
10707
+
10708
+ if (await fs.pathExists(outputJsonPath)) {
10709
+ scriptArgs.push('--compare-with', outputJsonPath);
10710
+ }
10711
+
10712
+ const execution = spawnSync(process.execPath, scriptArgs, {
10713
+ cwd: projectPath,
10714
+ encoding: 'utf8'
10715
+ });
10716
+
10717
+ const stdout = typeof execution.stdout === 'string' ? execution.stdout.trim() : '';
10718
+ const stderr = typeof execution.stderr === 'string' ? execution.stderr.trim() : '';
10719
+
10720
+ if (execution.error) {
10721
+ return {
10722
+ status: 'error',
10723
+ generated: false,
10724
+ error: execution.error.message
10725
+ };
10726
+ }
10727
+
10728
+ if (execution.status !== 0) {
10729
+ return {
10730
+ status: 'error',
10731
+ generated: false,
10732
+ error: stderr || stdout || `baseline script exited with code ${execution.status}`
10733
+ };
10734
+ }
10735
+
10736
+ let reportPayload = null;
10737
+ try {
10738
+ reportPayload = stdout ? JSON.parse(stdout) : await fs.readJson(outputJsonPath);
10739
+ } catch (error) {
10740
+ return {
10741
+ status: 'error',
10742
+ generated: false,
10743
+ error: `failed to parse baseline payload: ${error.message}`
10744
+ };
10745
+ }
10746
+
10747
+ const summary = reportPayload && reportPayload.summary && typeof reportPayload.summary === 'object'
10748
+ ? reportPayload.summary
10749
+ : {};
10750
+ const compare = reportPayload && reportPayload.compare && typeof reportPayload.compare === 'object'
10751
+ ? reportPayload.compare
10752
+ : null;
10753
+ const failedTemplates = compare && compare.failed_templates && typeof compare.failed_templates === 'object'
10754
+ ? compare.failed_templates
10755
+ : {};
10756
+
10757
+ return {
10758
+ status: summary.portfolio_passed === true ? 'passed' : 'failed',
10759
+ generated: true,
10760
+ summary: {
10761
+ total_templates: Number(summary.total_templates) || 0,
10762
+ scoped_templates: Number(summary.scoped_templates) || 0,
10763
+ avg_score: Number.isFinite(Number(summary.avg_score)) ? Number(summary.avg_score) : null,
10764
+ valid_rate_percent: Number.isFinite(Number(summary.valid_rate_percent)) ? Number(summary.valid_rate_percent) : null,
10765
+ baseline_passed: Number(summary.baseline_passed) || 0,
10766
+ baseline_failed: Number(summary.baseline_failed) || 0,
10767
+ portfolio_passed: summary.portfolio_passed === true
10768
+ },
10769
+ compare: compare
10770
+ ? {
10771
+ previous_generated_at: compare.previous_generated_at || null,
10772
+ previous_template_root: compare.previous_template_root || null,
10773
+ deltas: compare.deltas || null,
10774
+ failed_templates: {
10775
+ previous: Array.isArray(failedTemplates.previous) ? failedTemplates.previous : [],
10776
+ current: Array.isArray(failedTemplates.current) ? failedTemplates.current : [],
10777
+ newly_failed: Array.isArray(failedTemplates.newly_failed) ? failedTemplates.newly_failed : [],
10778
+ recovered: Array.isArray(failedTemplates.recovered) ? failedTemplates.recovered : []
10779
+ }
10780
+ }
10781
+ : null,
10782
+ output: {
10783
+ json: toAutoHandoffCliPath(projectPath, outputJsonPath),
10784
+ markdown: toAutoHandoffCliPath(projectPath, outputMarkdownPath)
10785
+ },
10786
+ warnings: stderr ? [stderr] : []
10787
+ };
10788
+ }
10789
+
10790
+ function normalizeMoquiCapabilityToken(value) {
10791
+ if (value === undefined || value === null) {
10792
+ return null;
10793
+ }
10794
+ const normalized = `${value}`
10795
+ .trim()
10796
+ .toLowerCase()
10797
+ .replace(/[^a-z0-9]+/g, '-')
10798
+ .replace(/^-+|-+$/g, '');
10799
+ return normalized.length > 0 ? normalized : null;
10800
+ }
10801
+
10802
+ function tokenizeMoquiCapability(value) {
10803
+ const normalized = normalizeMoquiCapabilityToken(value);
10804
+ if (!normalized) {
10805
+ return [];
10806
+ }
10807
+ return normalized.split('-').map(item => item.trim()).filter(Boolean);
10808
+ }
10809
+
10810
+ function moquiCapabilityMatch(expected, provided) {
10811
+ const left = normalizeMoquiCapabilityToken(expected);
10812
+ const right = normalizeMoquiCapabilityToken(provided);
10813
+ if (!left || !right) {
10814
+ return false;
10815
+ }
10816
+ if (left === right) {
10817
+ return true;
10818
+ }
10819
+ if ((left.length >= 8 && left.includes(right)) || (right.length >= 8 && right.includes(left))) {
10820
+ return true;
10821
+ }
10822
+ const leftTokens = tokenizeMoquiCapability(left);
10823
+ const rightTokens = tokenizeMoquiCapability(right);
10824
+ if (leftTokens.length === 0 || rightTokens.length === 0) {
10825
+ return false;
10826
+ }
10827
+ const rightSet = new Set(rightTokens);
10828
+ const overlap = leftTokens.filter(item => rightSet.has(item)).length;
10829
+ return overlap >= 2;
10830
+ }
10831
+
10832
+ function renderMoquiCapabilityCoverageMarkdown(report = {}) {
10833
+ const summary = report.summary && typeof report.summary === 'object'
10834
+ ? report.summary
10835
+ : {};
10836
+ const coverage = Array.isArray(report.coverage) ? report.coverage : [];
10837
+ const compare = report.compare && typeof report.compare === 'object' ? report.compare : null;
10838
+ const lines = [
10839
+ '# Moqui Capability Coverage Report',
10840
+ '',
10841
+ `- Generated at: ${report.generated_at || 'n/a'}`,
10842
+ `- Expected capabilities: ${summary.total_capabilities !== undefined ? summary.total_capabilities : 'n/a'}`,
10843
+ `- Covered capabilities: ${summary.covered_capabilities !== undefined ? summary.covered_capabilities : 'n/a'}`,
10844
+ `- Uncovered capabilities: ${summary.uncovered_capabilities !== undefined ? summary.uncovered_capabilities : 'n/a'}`,
10845
+ `- Coverage: ${summary.coverage_percent !== undefined && summary.coverage_percent !== null ? `${summary.coverage_percent}%` : 'n/a'}`,
10846
+ `- Min required: ${summary.min_required_percent !== undefined && summary.min_required_percent !== null ? `${summary.min_required_percent}%` : 'n/a'}`,
10847
+ `- Passed: ${summary.passed === true ? 'yes' : 'no'}`,
10848
+ '',
10849
+ '## Capability Matrix',
10850
+ '',
10851
+ '| Capability | Covered | Matched Templates |',
10852
+ '| --- | --- | --- |'
10853
+ ];
10854
+
10855
+ for (const item of coverage) {
10856
+ const matchedTemplates = Array.isArray(item.matched_templates) && item.matched_templates.length > 0
10857
+ ? item.matched_templates.join(', ')
10858
+ : 'none';
10859
+ lines.push(`| ${item.capability} | ${item.covered ? 'yes' : 'no'} | ${matchedTemplates} |`);
10860
+ }
10861
+
10862
+ if (compare) {
10863
+ lines.push('');
10864
+ lines.push('## Trend vs Previous');
10865
+ lines.push('');
10866
+ lines.push(`- Previous generated at: ${compare.previous_generated_at || 'n/a'}`);
10867
+ lines.push(`- Delta coverage: ${compare.delta_coverage_percent !== null && compare.delta_coverage_percent !== undefined ? `${compare.delta_coverage_percent}%` : 'n/a'}`);
10868
+ lines.push(`- Delta covered capabilities: ${compare.delta_covered_capabilities !== null && compare.delta_covered_capabilities !== undefined ? compare.delta_covered_capabilities : 'n/a'}`);
10869
+ lines.push(`- Newly covered: ${Array.isArray(compare.newly_covered) && compare.newly_covered.length > 0 ? compare.newly_covered.join(', ') : 'none'}`);
10870
+ lines.push(`- Newly uncovered: ${Array.isArray(compare.newly_uncovered) && compare.newly_uncovered.length > 0 ? compare.newly_uncovered.join(', ') : 'none'}`);
10871
+ }
10872
+
10873
+ return `${lines.join('\n')}\n`;
10874
+ }
10875
+
10876
+ function buildCapabilityCoverageComparison(currentPayload, previousPayload) {
10877
+ const currentSummary = currentPayload && currentPayload.summary ? currentPayload.summary : {};
10878
+ const previousSummary = previousPayload && previousPayload.summary ? previousPayload.summary : {};
10879
+ const currentCoverage = Array.isArray(currentPayload && currentPayload.coverage) ? currentPayload.coverage : [];
10880
+ const previousCoverage = Array.isArray(previousPayload && previousPayload.coverage) ? previousPayload.coverage : [];
10881
+ const currentCovered = new Set(
10882
+ currentCoverage.filter(item => item && item.covered === true).map(item => item.capability)
10883
+ );
10884
+ const previousCovered = new Set(
10885
+ previousCoverage.filter(item => item && item.covered === true).map(item => item.capability)
10886
+ );
10887
+ const newlyCovered = Array.from(currentCovered).filter(item => !previousCovered.has(item)).sort();
10888
+ const newlyUncovered = Array.from(previousCovered).filter(item => !currentCovered.has(item)).sort();
10889
+ const toDelta = (current, previous) => {
10890
+ if (!Number.isFinite(Number(current)) || !Number.isFinite(Number(previous))) {
10891
+ return null;
10892
+ }
10893
+ return Number((Number(current) - Number(previous)).toFixed(2));
10894
+ };
10895
+ return {
10896
+ previous_generated_at: previousPayload && previousPayload.generated_at ? previousPayload.generated_at : null,
10897
+ delta_coverage_percent: toDelta(currentSummary.coverage_percent, previousSummary.coverage_percent),
10898
+ delta_covered_capabilities: toDelta(currentSummary.covered_capabilities, previousSummary.covered_capabilities),
10899
+ newly_covered: newlyCovered,
10900
+ newly_uncovered: newlyUncovered
10901
+ };
10902
+ }
10903
+
10904
+ async function loadLatestMoquiCapabilityCoverageReport(projectPath) {
10905
+ const reportPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_JSON_FILE);
10906
+ if (!(await fs.pathExists(reportPath))) {
10907
+ return null;
10908
+ }
10909
+ try {
10910
+ const payload = await fs.readJson(reportPath);
10911
+ return payload && typeof payload === 'object' ? payload : null;
10912
+ } catch (_error) {
10913
+ return null;
10914
+ }
10915
+ }
10916
+
10917
+ async function buildAutoHandoffCapabilityCoverageSnapshot(projectPath, handoff = null, policy = {}) {
10918
+ const expectedRaw = Array.isArray(handoff && handoff.capabilities)
10919
+ ? handoff.capabilities
10920
+ : [];
10921
+ const expected = Array.from(
10922
+ new Set(
10923
+ expectedRaw
10924
+ .map(item => normalizeMoquiCapabilityToken(item))
10925
+ .filter(Boolean)
10926
+ )
10927
+ );
10928
+ if (expected.length === 0) {
10929
+ return {
10930
+ status: 'skipped',
10931
+ generated: false,
10932
+ reason: 'manifest capabilities not declared',
10933
+ summary: {
10934
+ total_capabilities: 0,
10935
+ covered_capabilities: 0,
10936
+ uncovered_capabilities: 0,
10937
+ coverage_percent: null,
10938
+ min_required_percent: Number(policy.min_capability_coverage_percent),
10939
+ passed: true
10940
+ },
10941
+ coverage: [],
10942
+ gaps: []
10943
+ };
10944
+ }
10945
+
10946
+ const templateRoot = path.join(projectPath, '.kiro', 'templates', 'scene-packages');
10947
+ if (!(await fs.pathExists(templateRoot))) {
10948
+ return {
10949
+ status: 'skipped',
10950
+ generated: false,
10951
+ reason: `template library not found: ${toAutoHandoffCliPath(projectPath, templateRoot)}`,
10952
+ summary: {
10953
+ total_capabilities: expected.length,
10954
+ covered_capabilities: 0,
10955
+ uncovered_capabilities: expected.length,
10956
+ coverage_percent: 0,
10957
+ min_required_percent: Number(policy.min_capability_coverage_percent),
10958
+ passed: false
10959
+ },
10960
+ coverage: expected.map(item => ({ capability: item, covered: false, matched_templates: [], matched_provides: [] })),
10961
+ gaps: expected
10962
+ };
10963
+ }
10964
+
10965
+ const templateEntries = await fs.readdir(templateRoot);
10966
+ const templates = [];
10967
+ for (const entry of templateEntries) {
10968
+ const templateDir = path.join(templateRoot, entry);
10969
+ let stat = null;
10970
+ try {
10971
+ stat = await fs.stat(templateDir);
10972
+ } catch (_error) {
10973
+ stat = null;
10974
+ }
10975
+ if (!stat || !stat.isDirectory()) {
10976
+ continue;
10977
+ }
10978
+ const contractFile = path.join(templateDir, 'scene-package.json');
10979
+ if (!(await fs.pathExists(contractFile))) {
10980
+ continue;
10981
+ }
10982
+ try {
10983
+ const payload = await fs.readJson(contractFile);
10984
+ const providesRaw = [];
10985
+ const contractProvides = payload && payload.contract && payload.contract.capabilities && payload.contract.capabilities.provides;
10986
+ const rootProvides = payload && payload.capabilities && payload.capabilities.provides;
10987
+ if (Array.isArray(contractProvides)) {
10988
+ providesRaw.push(...contractProvides);
10989
+ }
10990
+ if (Array.isArray(rootProvides)) {
10991
+ providesRaw.push(...rootProvides);
10992
+ }
10993
+ const provides = Array.from(
10994
+ new Set(
10995
+ providesRaw
10996
+ .map(item => normalizeMoquiCapabilityToken(item))
10997
+ .filter(Boolean)
10998
+ )
10999
+ );
11000
+ templates.push({
11001
+ template_id: entry,
11002
+ provides
11003
+ });
11004
+ } catch (_error) {
11005
+ // Ignore malformed template package entries.
11006
+ }
11007
+ }
11008
+
11009
+ const coverage = expected.map(capability => {
11010
+ const matchedTemplates = [];
11011
+ const matchedProvides = [];
11012
+ for (const template of templates) {
11013
+ const providedMatched = template.provides.filter(item => moquiCapabilityMatch(capability, item));
11014
+ if (providedMatched.length > 0) {
11015
+ matchedTemplates.push(template.template_id);
11016
+ matchedProvides.push(...providedMatched);
11017
+ }
11018
+ }
11019
+ const uniqueProvides = Array.from(new Set(matchedProvides)).sort();
11020
+ return {
11021
+ capability,
11022
+ covered: matchedTemplates.length > 0,
11023
+ matched_templates: Array.from(new Set(matchedTemplates)).sort(),
11024
+ matched_provides: uniqueProvides
11025
+ };
11026
+ });
11027
+
11028
+ const coveredCount = coverage.filter(item => item.covered).length;
11029
+ const uncovered = coverage.filter(item => !item.covered).map(item => item.capability);
11030
+ const coveragePercent = expected.length > 0
11031
+ ? Number(((coveredCount / expected.length) * 100).toFixed(2))
11032
+ : null;
11033
+ const minRequiredPercent = Number(policy.min_capability_coverage_percent);
11034
+ const passed = Number.isFinite(coveragePercent) && Number.isFinite(minRequiredPercent)
11035
+ ? coveragePercent >= minRequiredPercent
11036
+ : false;
11037
+
11038
+ const payload = {
11039
+ mode: 'moqui-capability-coverage',
11040
+ generated_at: new Date().toISOString(),
11041
+ expected_capabilities: expected,
11042
+ summary: {
11043
+ total_capabilities: expected.length,
11044
+ covered_capabilities: coveredCount,
11045
+ uncovered_capabilities: expected.length - coveredCount,
11046
+ coverage_percent: coveragePercent,
11047
+ min_required_percent: Number.isFinite(minRequiredPercent) ? minRequiredPercent : 100,
11048
+ passed
11049
+ },
11050
+ coverage,
11051
+ gaps: uncovered
11052
+ };
11053
+
11054
+ const previousPayload = await loadLatestMoquiCapabilityCoverageReport(projectPath);
11055
+ if (previousPayload) {
11056
+ payload.compare = buildCapabilityCoverageComparison(payload, previousPayload);
11057
+ }
11058
+
11059
+ const outputJsonPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_JSON_FILE);
11060
+ const outputMarkdownPath = path.join(projectPath, AUTO_HANDOFF_MOQUI_CAPABILITY_COVERAGE_MARKDOWN_FILE);
11061
+ await fs.ensureDir(path.dirname(outputJsonPath));
11062
+ await fs.writeJson(outputJsonPath, payload, { spaces: 2 });
11063
+ await fs.writeFile(outputMarkdownPath, renderMoquiCapabilityCoverageMarkdown(payload), 'utf8');
11064
+
11065
+ return {
11066
+ status: 'evaluated',
11067
+ generated: true,
11068
+ summary: payload.summary,
11069
+ coverage: payload.coverage,
11070
+ gaps: payload.gaps,
11071
+ compare: payload.compare || null,
11072
+ output: {
11073
+ json: toAutoHandoffCliPath(projectPath, outputJsonPath),
11074
+ markdown: toAutoHandoffCliPath(projectPath, outputMarkdownPath)
11075
+ }
11076
+ };
11077
+ }
11078
+
11079
+ function collectAutoHandoffMoquiRemediationGoals(result) {
11080
+ const goals = [];
11081
+ const seen = new Set();
11082
+ const pushGoal = value => {
11083
+ const text = `${value || ''}`.trim();
11084
+ if (!text || seen.has(text)) {
11085
+ return;
11086
+ }
11087
+ seen.add(text);
11088
+ goals.push(text);
11089
+ };
11090
+
11091
+ const moquiBaseline = result && result.moqui_baseline && typeof result.moqui_baseline === 'object'
11092
+ ? result.moqui_baseline
11093
+ : null;
11094
+ const baselineSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
11095
+ ? moquiBaseline.summary
11096
+ : null;
11097
+ const baselineCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
11098
+ ? moquiBaseline.compare
11099
+ : null;
11100
+ const baselineFailedTemplates = baselineCompare && baselineCompare.failed_templates && typeof baselineCompare.failed_templates === 'object'
11101
+ ? baselineCompare.failed_templates
11102
+ : {};
11103
+
11104
+ if (moquiBaseline && moquiBaseline.status === 'error') {
11105
+ pushGoal('repair moqui baseline generation pipeline and regenerate baseline evidence');
11106
+ } else if (baselineSummary && baselineSummary.portfolio_passed === false) {
11107
+ pushGoal(
11108
+ `raise moqui baseline portfolio score (avg=${baselineSummary.avg_score || 'n/a'}, ` +
11109
+ `valid-rate=${baselineSummary.valid_rate_percent || 'n/a'}%) to pass thresholds`
11110
+ );
11111
+ const targetTemplates = Array.isArray(baselineFailedTemplates.current)
11112
+ ? baselineFailedTemplates.current
11113
+ : [];
11114
+ for (const templateId of targetTemplates) {
11115
+ pushGoal(`remediate moqui template ${templateId} ontology semantics and close baseline gaps`);
11116
+ }
11117
+ }
11118
+
11119
+ const capabilityCoverage = result && result.moqui_capability_coverage && typeof result.moqui_capability_coverage === 'object'
11120
+ ? result.moqui_capability_coverage
11121
+ : null;
11122
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
11123
+ ? capabilityCoverage.summary
11124
+ : null;
11125
+ const capabilityGaps = capabilityCoverage && Array.isArray(capabilityCoverage.gaps)
11126
+ ? capabilityCoverage.gaps
11127
+ : [];
11128
+ if (
11129
+ capabilityCoverage &&
11130
+ capabilityCoverage.status === 'evaluated' &&
11131
+ capabilitySummary &&
11132
+ capabilitySummary.passed === false
11133
+ ) {
11134
+ pushGoal(
11135
+ `increase moqui capability coverage to >=${capabilitySummary.min_required_percent}% ` +
11136
+ `(current=${capabilitySummary.coverage_percent || 0}%)`
11137
+ );
11138
+ for (const capability of capabilityGaps) {
11139
+ pushGoal(`implement scene template or ontology mapping for moqui capability ${capability}`);
11140
+ }
11141
+ }
11142
+
11143
+ return goals;
11144
+ }
11145
+
11146
+ async function maybeWriteAutoHandoffMoquiRemediationQueue(projectPath, result) {
11147
+ const goals = collectAutoHandoffMoquiRemediationGoals(result);
11148
+ if (goals.length === 0) {
11149
+ return null;
11150
+ }
11151
+ const queuePath = path.join(projectPath, AUTO_HANDOFF_MOQUI_REMEDIATION_QUEUE_FILE);
11152
+ await fs.ensureDir(path.dirname(queuePath));
11153
+ await fs.writeFile(queuePath, `${goals.join('\n')}\n`, 'utf8');
11154
+ return {
11155
+ file: queuePath,
11156
+ goal_count: goals.length,
11157
+ goals
11158
+ };
11159
+ }
11160
+
10221
11161
  async function runAutoHandoff(projectPath, options = {}) {
10222
11162
  const startedAtMs = Date.now();
10223
11163
  const result = {
@@ -10239,6 +11179,9 @@ async function runAutoHandoff(projectPath, options = {}) {
10239
11179
  observability_snapshot: null,
10240
11180
  spec_status: null,
10241
11181
  ontology_validation: null,
11182
+ moqui_baseline: null,
11183
+ moqui_capability_coverage: null,
11184
+ remediation_queue: null,
10242
11185
  gates: null,
10243
11186
  regression: null,
10244
11187
  release_evidence: null,
@@ -10280,6 +11223,85 @@ async function runAutoHandoff(projectPath, options = {}) {
10280
11223
  throw error;
10281
11224
  }
10282
11225
 
11226
+ const baselinePhase = beginAutoHandoffRunPhase(result, 'moqui-baseline', 'Moqui template baseline scorecard');
11227
+ try {
11228
+ result.moqui_baseline = await buildAutoHandoffMoquiBaselineSnapshot(projectPath);
11229
+ completeAutoHandoffRunPhase(
11230
+ baselinePhase,
11231
+ buildAutoHandoffMoquiBaselinePhaseDetails(result.moqui_baseline)
11232
+ );
11233
+ if (result.moqui_baseline && result.moqui_baseline.status === 'error') {
11234
+ result.warnings.push(`moqui baseline generation failed: ${result.moqui_baseline.error || 'unknown error'}`);
11235
+ }
11236
+ const moquiBaselineGateReasons = evaluateAutoHandoffMoquiBaselineGateReasons(
11237
+ result.policy,
11238
+ result.moqui_baseline
11239
+ );
11240
+ if (moquiBaselineGateReasons.length > 0) {
11241
+ throw new Error(`handoff moqui baseline gate failed: ${moquiBaselineGateReasons.join('; ')}`);
11242
+ }
11243
+ } catch (baselineError) {
11244
+ failAutoHandoffRunPhase(baselinePhase, baselineError);
11245
+ if (!result.moqui_baseline) {
11246
+ result.moqui_baseline = {
11247
+ status: 'error',
11248
+ generated: false,
11249
+ error: baselineError && baselineError.message ? baselineError.message : `${baselineError}`
11250
+ };
11251
+ }
11252
+ throw baselineError;
11253
+ }
11254
+
11255
+ const capabilityCoveragePhase = beginAutoHandoffRunPhase(
11256
+ result,
11257
+ 'moqui-capability-coverage',
11258
+ 'Moqui capability coverage matrix'
11259
+ );
11260
+ try {
11261
+ result.moqui_capability_coverage = await buildAutoHandoffCapabilityCoverageSnapshot(
11262
+ projectPath,
11263
+ result.handoff,
11264
+ result.policy
11265
+ );
11266
+ completeAutoHandoffRunPhase(capabilityCoveragePhase, {
11267
+ status: result.moqui_capability_coverage.status || 'unknown',
11268
+ coverage_percent: Number.isFinite(
11269
+ Number(
11270
+ result.moqui_capability_coverage &&
11271
+ result.moqui_capability_coverage.summary
11272
+ ? result.moqui_capability_coverage.summary.coverage_percent
11273
+ : null
11274
+ )
11275
+ )
11276
+ ? Number(result.moqui_capability_coverage.summary.coverage_percent)
11277
+ : null,
11278
+ passed: Boolean(
11279
+ result.moqui_capability_coverage &&
11280
+ result.moqui_capability_coverage.summary &&
11281
+ result.moqui_capability_coverage.summary.passed === true
11282
+ )
11283
+ });
11284
+ const capabilityCoverageGateReasons = evaluateAutoHandoffCapabilityCoverageGateReasons(
11285
+ result.policy,
11286
+ result.moqui_capability_coverage
11287
+ );
11288
+ if (capabilityCoverageGateReasons.length > 0) {
11289
+ throw new Error(`handoff capability coverage gate failed: ${capabilityCoverageGateReasons.join('; ')}`);
11290
+ }
11291
+ } catch (capabilityCoverageError) {
11292
+ failAutoHandoffRunPhase(capabilityCoveragePhase, capabilityCoverageError);
11293
+ if (!result.moqui_capability_coverage) {
11294
+ result.moqui_capability_coverage = {
11295
+ status: 'error',
11296
+ generated: false,
11297
+ error: capabilityCoverageError && capabilityCoverageError.message
11298
+ ? capabilityCoverageError.message
11299
+ : `${capabilityCoverageError}`
11300
+ };
11301
+ }
11302
+ throw capabilityCoverageError;
11303
+ }
11304
+
10283
11305
  const queuePhase = beginAutoHandoffRunPhase(result, 'queue', 'Queue generation');
10284
11306
  let queue = null;
10285
11307
  try {
@@ -10345,6 +11367,8 @@ async function runAutoHandoff(projectPath, options = {}) {
10345
11367
  dryRun: true,
10346
11368
  specStatus: result.spec_status,
10347
11369
  ontology: result.ontology_validation,
11370
+ moquiBaseline: result.moqui_baseline,
11371
+ capabilityCoverage: result.moqui_capability_coverage,
10348
11372
  programKpi: {
10349
11373
  risk_level: 'low'
10350
11374
  }
@@ -10405,6 +11429,8 @@ async function runAutoHandoff(projectPath, options = {}) {
10405
11429
  dryRun: false,
10406
11430
  specStatus: result.spec_status,
10407
11431
  ontology: result.ontology_validation,
11432
+ moquiBaseline: result.moqui_baseline,
11433
+ capabilityCoverage: result.moqui_capability_coverage,
10408
11434
  programKpi: buildProgramKpiSnapshot(result.batch_summary || {})
10409
11435
  });
10410
11436
  if (!result.gates.passed) {
@@ -10418,6 +11444,7 @@ async function runAutoHandoff(projectPath, options = {}) {
10418
11444
  result.completed_at = new Date().toISOString();
10419
11445
  result.elapsed_ms = Math.max(0, Date.now() - startedAtMs);
10420
11446
  result.regression = await buildAutoHandoffRegression(projectPath, result);
11447
+ result.remediation_queue = await maybeWriteAutoHandoffMoquiRemediationQueue(projectPath, result);
10421
11448
  result.recommendations = buildAutoHandoffRunRecommendations(projectPath, result);
10422
11449
  await writeAutoHandoffRunReport(projectPath, result, options.out);
10423
11450
  if (result.dry_run) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiro-spec-engine",
3
- "version": "1.47.31",
3
+ "version": "1.47.33",
4
4
  "description": "kiro-spec-engine (kse) - A CLI tool and npm package for spec-driven development with AI coding assistants. NOT the Kiro IDE desktop application.",
5
5
  "main": "index.js",
6
6
  "bin": {