kiro-spec-engine 1.47.32 → 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,16 +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/moqui-baseline 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
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`.
775
777
  - Run result includes `recommendations` with executable follow-up commands (for example, auto-generated `--continue-from <session>` on failed/incomplete batches).
776
- - Gate defaults: `--min-spec-success-rate` defaults to `100`, `--max-risk-level` defaults to `high`, and ontology validation requirement is enabled by default.
777
- - 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.
778
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).
779
781
  - `--window` (2-50, default `2`) returns multi-run `series`, `window_trend`, and `aggregates` for broader regression visibility.
780
782
  - Regression JSON now includes `risk_layers` (low/medium/high/unknown buckets with per-layer session list and quality aggregates).
@@ -782,11 +784,11 @@ Dual-track handoff integration:
782
784
  - Markdown report includes `Trend Series` (ASCII success/ontology bars per session) and `Risk Layer View`.
783
785
  - `--out` writes the generated regression report using the selected format.
784
786
  - Output includes `recommendations` to guide next action when trend degrades or risk escalates.
785
- - `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/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.
786
788
  - Default evidence file is `.kiro/reports/release-evidence/handoff-runs.json`.
787
789
  - `--window` (1-50, default `5`) controls how many recent sessions are aggregated in review.
788
790
  - JSON output includes `current_overview`, `aggregates.status_counts`, `aggregates.gate_pass_rate_percent`, and `risk_layers`.
789
- - Markdown output includes `Current Gate`, `Current Ontology`, `Current Regression`, `Current Moqui Baseline`, `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`.
790
792
  - Add `--release-draft <path>` to auto-generate a release notes draft and evidence review markdown in one run.
791
793
  - `--release-version` sets draft version tag (defaults to `v<package.json version>`), and `--release-date` accepts `YYYY-MM-DD` (default: current UTC date).
792
794
  - Use `--review-out <path>` to override the generated evidence review markdown path (default `.kiro/reports/release-evidence/handoff-evidence-review.md`).
@@ -14,6 +14,8 @@ KSE defaults already enforce the baseline below:
14
14
 
15
15
  - `kse auto handoff run`: ontology validation is required by default.
16
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%`).
17
19
  - `kse scene package-publish-batch`:
18
20
  - ontology validation required by default
19
21
  - batch ontology gate defaults:
@@ -23,6 +25,8 @@ KSE defaults already enforce the baseline below:
23
25
  Emergency bypass exists but is not recommended:
24
26
 
25
27
  - `--no-require-ontology-validation`
28
+ - `--no-require-moqui-baseline`
29
+ - `--no-require-capability-coverage`
26
30
 
27
31
  ## One-Shot Intake Flow
28
32
 
@@ -65,8 +69,11 @@ Required artifacts for each intake batch:
65
69
  - `.kiro/reports/moqui-template-baseline.md`
66
70
  - `.kiro/reports/release-evidence/moqui-template-baseline.json`
67
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`
68
74
  - `.kiro/reports/handoff-runs/<session>.json`
69
75
  - `.kiro/reports/scene-package-ontology-batch.json`
76
+ - `.kiro/auto/moqui-remediation.lines` (when baseline/coverage gaps exist)
70
77
  - `.kiro/templates/scene-packages/registry.json`
71
78
  - gate output/evidence linked from release notes or handoff summary
72
79
 
@@ -21,6 +21,9 @@ const AUTO_HANDOFF_RELEASE_EVIDENCE_DIR = '.kiro/reports/release-evidence';
21
21
  const AUTO_HANDOFF_RELEASE_GATE_HISTORY_FILE = '.kiro/reports/release-evidence/release-gate-history.json';
22
22
  const AUTO_HANDOFF_MOQUI_BASELINE_JSON_FILE = '.kiro/reports/release-evidence/moqui-template-baseline.json';
23
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';
24
27
 
25
28
  /**
26
29
  * Register auto commands
@@ -1664,6 +1667,22 @@ function registerAutoCommands(program) {
1664
1667
  console.log(chalk.gray(` Portfolio: ${moquiSummary.portfolio_passed === true ? 'pass' : 'fail'} | avg=${scoreText} | valid-rate=${validRateText}`));
1665
1668
  }
1666
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
+ }
1667
1686
  if (result.output_file) {
1668
1687
  console.log(chalk.gray(` Output: ${result.output_file}`));
1669
1688
  }
@@ -1765,6 +1784,11 @@ function registerAutoCommands(program) {
1765
1784
  .option('--max-undecided-decisions <n>', 'Gate: maximum allowed undecided decisions (optional)', parseInt)
1766
1785
  .option('--require-ontology-validation', 'Gate: require manifest ontology_validation to be present and passed (default: enabled)')
1767
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)')
1768
1792
  .option('--release-evidence-window <n>', 'Release evidence trend window size (2-50, default: 5)', parseInt)
1769
1793
  .option('--json', 'Output machine-readable JSON')
1770
1794
  .action(async (options) => {
@@ -1804,6 +1828,24 @@ function registerAutoCommands(program) {
1804
1828
  console.log(chalk.gray(` Baseline report: ${result.moqui_baseline.output.json}`));
1805
1829
  }
1806
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
+ }
1807
1849
  if (result.output_file) {
1808
1850
  console.log(chalk.gray(` Report: ${result.output_file}`));
1809
1851
  }
@@ -6911,6 +6953,16 @@ function normalizeAutoHandoffManifest(payload = {}) {
6911
6953
  validationWarnings.push('templates is empty');
6912
6954
  }
6913
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
+
6914
6966
  const knownGapCollected = collectKnownGaps(payload.known_gaps);
6915
6967
  validationWarnings.push(...knownGapCollected.warnings);
6916
6968
 
@@ -6932,6 +6984,7 @@ function normalizeAutoHandoffManifest(payload = {}) {
6932
6984
  spec_descriptors: specsCollected.descriptors,
6933
6985
  dependency_batches: dependencyBatches,
6934
6986
  templates: templatesCollected.values,
6987
+ capabilities: capabilitiesCollected.values,
6935
6988
  known_gaps: knownGapCollected.gaps,
6936
6989
  ontology_validation: ontologyValidation,
6937
6990
  next_batch: nextBatch,
@@ -7039,11 +7092,13 @@ async function buildAutoHandoffPlan(projectPath, options = {}) {
7039
7092
  timestamp: handoff.timestamp,
7040
7093
  spec_count: handoff.specs.length,
7041
7094
  template_count: handoff.templates.length,
7095
+ capability_count: Array.isArray(handoff.capabilities) ? handoff.capabilities.length : 0,
7042
7096
  known_gap_count: handoff.known_gaps.length,
7043
7097
  specs: handoff.specs,
7044
7098
  spec_descriptors: handoff.spec_descriptors,
7045
7099
  dependency_batches: handoff.dependency_batches,
7046
7100
  templates: handoff.templates,
7101
+ capabilities: handoff.capabilities,
7047
7102
  known_gaps: handoff.known_gaps,
7048
7103
  ontology_validation: handoff.ontology_validation,
7049
7104
  next_batch: handoff.next_batch
@@ -8923,6 +8978,19 @@ function buildAutoHandoffEvidenceSnapshot(entry = {}) {
8923
8978
  ),
8924
8979
  ontology_business_rule_pass_rate_percent: toNumber(ontologyMetrics.business_rule_pass_rate_percent),
8925
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
+ ),
8926
8994
  generated_at: normalizeHandoffText(entry.merged_at)
8927
8995
  };
8928
8996
  }
@@ -8985,6 +9053,18 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
8985
9053
  const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
8986
9054
  ? moquiCompare.failed_templates
8987
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
+ : [];
8988
9068
  const window = payload.window || { requested: 5, actual: 0 };
8989
9069
  const series = Array.isArray(payload.series) ? payload.series : [];
8990
9070
  const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
@@ -8999,7 +9079,11 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
8999
9079
  const failedGoals = formatAutoHandoffRegressionValue(item.failed_goals);
9000
9080
  const successBar = renderAutoHandoffRegressionAsciiBar(item.spec_success_rate_percent, 100, 20);
9001
9081
  const ontologyBar = renderAutoHandoffRegressionAsciiBar(item.ontology_quality_score, 100, 20);
9002
- 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
+ );
9003
9087
  })
9004
9088
  : ['- None'];
9005
9089
  const riskLayerLines = ['low', 'medium', 'high', 'unknown'].map(level => {
@@ -9062,6 +9146,21 @@ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
9062
9146
  `- Recovered templates: ${Array.isArray(moquiFailedTemplates.recovered) && moquiFailedTemplates.recovered.length > 0 ? moquiFailedTemplates.recovered.join(', ') : 'none'}`,
9063
9147
  `- Baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
9064
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
+ '',
9065
9164
  '## Trend Series',
9066
9165
  '',
9067
9166
  ...trendSeriesLines,
@@ -9168,6 +9267,18 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9168
9267
  const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
9169
9268
  ? moquiCompare.failed_templates
9170
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
+ : [];
9171
9282
  const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
9172
9283
  ? payload.risk_layers
9173
9284
  : {};
@@ -9219,6 +9330,13 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9219
9330
  `- Moqui baseline avg score delta: ${formatAutoHandoffRegressionValue(moquiDeltas.avg_score)}`,
9220
9331
  `- Moqui baseline valid-rate delta: ${formatAutoHandoffRegressionValue(moquiDeltas.valid_rate_percent)}%`,
9221
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'}`,
9222
9340
  '',
9223
9341
  '## Status Breakdown',
9224
9342
  '',
@@ -9239,6 +9357,8 @@ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}) {
9239
9357
  `- Release evidence JSON: ${formatAutoHandoffRegressionValue(payload.evidence_file)}`,
9240
9358
  `- Moqui baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
9241
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)}`,
9242
9362
  '',
9243
9363
  '## Recommendations'
9244
9364
  ];
@@ -9454,6 +9574,17 @@ function normalizeHandoffMinOntologyScore(scoreCandidate) {
9454
9574
  return Number(parsed.toFixed(2));
9455
9575
  }
9456
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
+
9457
9588
  function normalizeHandoffOptionalNonNegativeInteger(valueCandidate, optionName) {
9458
9589
  if (valueCandidate === undefined || valueCandidate === null || valueCandidate === '') {
9459
9590
  return null;
@@ -9481,6 +9612,7 @@ function buildAutoHandoffRunPolicy(options = {}) {
9481
9612
  min_spec_success_rate: normalizeHandoffMinSpecSuccessRate(options.minSpecSuccessRate),
9482
9613
  max_risk_level: normalizeHandoffRiskLevel(options.maxRiskLevel),
9483
9614
  min_ontology_score: normalizeHandoffMinOntologyScore(options.minOntologyScore),
9615
+ min_capability_coverage_percent: normalizeHandoffMinCapabilityCoverage(options.minCapabilityCoverage),
9484
9616
  max_unmapped_rules: normalizeHandoffOptionalNonNegativeInteger(
9485
9617
  options.maxUnmappedRules,
9486
9618
  '--max-unmapped-rules'
@@ -9490,6 +9622,8 @@ function buildAutoHandoffRunPolicy(options = {}) {
9490
9622
  '--max-undecided-decisions'
9491
9623
  ),
9492
9624
  require_ontology_validation: options.requireOntologyValidation !== false,
9625
+ require_moqui_baseline: options.requireMoquiBaseline !== false,
9626
+ require_capability_coverage: options.requireCapabilityCoverage !== false,
9493
9627
  dependency_batching: options.dependencyBatching !== false,
9494
9628
  release_evidence_window: normalizeHandoffReleaseEvidenceWindow(options.releaseEvidenceWindow)
9495
9629
  };
@@ -9793,6 +9927,84 @@ function evaluateAutoHandoffOntologyGateReasons(policy = {}, ontology = {}) {
9793
9927
  return reasons;
9794
9928
  }
9795
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
+
9796
10008
  function collectHandoffBlockers(resultItem) {
9797
10009
  const blockers = [];
9798
10010
  if (!resultItem) {
@@ -9877,6 +10089,12 @@ function evaluateAutoHandoffRunGates(context = {}) {
9877
10089
  present: false,
9878
10090
  passed: false
9879
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;
9880
10098
  const kpi = context.programKpi || {
9881
10099
  risk_level: 'high'
9882
10100
  };
@@ -9902,6 +10120,8 @@ function evaluateAutoHandoffRunGates(context = {}) {
9902
10120
  }
9903
10121
 
9904
10122
  reasons.push(...evaluateAutoHandoffOntologyGateReasons(policy, ontology));
10123
+ reasons.push(...evaluateAutoHandoffMoquiBaselineGateReasons(policy, moquiBaseline));
10124
+ reasons.push(...evaluateAutoHandoffCapabilityCoverageGateReasons(policy, capabilityCoverage));
9905
10125
 
9906
10126
  return {
9907
10127
  passed: reasons.length === 0,
@@ -9935,6 +10155,18 @@ function evaluateAutoHandoffRunGates(context = {}) {
9935
10155
  Number(ontology && ontology.metrics ? ontology.metrics.decision_resolved_rate_percent : null)
9936
10156
  )
9937
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)
9938
10170
  : null
9939
10171
  },
9940
10172
  reasons
@@ -10024,6 +10256,14 @@ function buildAutoHandoffRunRecommendations(projectPath, result) {
10024
10256
  push('kse auto governance stats --days 14 --json');
10025
10257
  }
10026
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
+
10027
10267
  const moquiBaseline = result && result.moqui_baseline && typeof result.moqui_baseline === 'object'
10028
10268
  ? result.moqui_baseline
10029
10269
  : null;
@@ -10043,6 +10283,20 @@ function buildAutoHandoffRunRecommendations(projectPath, result) {
10043
10283
  );
10044
10284
  }
10045
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
+
10046
10300
  return recommendations;
10047
10301
  }
10048
10302
 
@@ -10145,6 +10399,18 @@ function buildAutoHandoffReleaseEvidenceEntry(projectPath, result, reportFile =
10145
10399
  const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
10146
10400
  ? moquiCompare.failed_templates
10147
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
+ : [];
10148
10414
  const batchSummary = result && result.batch_summary && typeof result.batch_summary === 'object'
10149
10415
  ? result.batch_summary
10150
10416
  : {};
@@ -10239,6 +10505,34 @@ function buildAutoHandoffReleaseEvidenceEntry(projectPath, result, reportFile =
10239
10505
  markdown: normalizeHandoffText(moquiBaseline && moquiBaseline.output ? moquiBaseline.output.markdown : null)
10240
10506
  }
10241
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
+ },
10242
10536
  batch_summary: {
10243
10537
  status: normalizeHandoffText(batchSummary.status),
10244
10538
  total_goals: toNumber(batchSummary.total_goals),
@@ -10478,6 +10772,8 @@ async function buildAutoHandoffMoquiBaselineSnapshot(projectPath) {
10478
10772
  previous_template_root: compare.previous_template_root || null,
10479
10773
  deltas: compare.deltas || null,
10480
10774
  failed_templates: {
10775
+ previous: Array.isArray(failedTemplates.previous) ? failedTemplates.previous : [],
10776
+ current: Array.isArray(failedTemplates.current) ? failedTemplates.current : [],
10481
10777
  newly_failed: Array.isArray(failedTemplates.newly_failed) ? failedTemplates.newly_failed : [],
10482
10778
  recovered: Array.isArray(failedTemplates.recovered) ? failedTemplates.recovered : []
10483
10779
  }
@@ -10491,6 +10787,377 @@ async function buildAutoHandoffMoquiBaselineSnapshot(projectPath) {
10491
10787
  };
10492
10788
  }
10493
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
+
10494
11161
  async function runAutoHandoff(projectPath, options = {}) {
10495
11162
  const startedAtMs = Date.now();
10496
11163
  const result = {
@@ -10513,6 +11180,8 @@ async function runAutoHandoff(projectPath, options = {}) {
10513
11180
  spec_status: null,
10514
11181
  ontology_validation: null,
10515
11182
  moqui_baseline: null,
11183
+ moqui_capability_coverage: null,
11184
+ remediation_queue: null,
10516
11185
  gates: null,
10517
11186
  regression: null,
10518
11187
  release_evidence: null,
@@ -10554,6 +11223,85 @@ async function runAutoHandoff(projectPath, options = {}) {
10554
11223
  throw error;
10555
11224
  }
10556
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
+
10557
11305
  const queuePhase = beginAutoHandoffRunPhase(result, 'queue', 'Queue generation');
10558
11306
  let queue = null;
10559
11307
  try {
@@ -10614,30 +11362,13 @@ async function runAutoHandoff(projectPath, options = {}) {
10614
11362
  null,
10615
11363
  continuationBaselineSummary
10616
11364
  );
10617
- const baselinePhase = beginAutoHandoffRunPhase(result, 'moqui-baseline', 'Moqui template baseline scorecard');
10618
- try {
10619
- result.moqui_baseline = await buildAutoHandoffMoquiBaselineSnapshot(projectPath);
10620
- completeAutoHandoffRunPhase(
10621
- baselinePhase,
10622
- buildAutoHandoffMoquiBaselinePhaseDetails(result.moqui_baseline)
10623
- );
10624
- if (result.moqui_baseline && result.moqui_baseline.status === 'error') {
10625
- result.warnings.push(`moqui baseline generation failed: ${result.moqui_baseline.error || 'unknown error'}`);
10626
- }
10627
- } catch (baselineError) {
10628
- failAutoHandoffRunPhase(baselinePhase, baselineError);
10629
- result.moqui_baseline = {
10630
- status: 'error',
10631
- generated: false,
10632
- error: baselineError && baselineError.message ? baselineError.message : `${baselineError}`
10633
- };
10634
- result.warnings.push(`moqui baseline generation failed: ${result.moqui_baseline.error}`);
10635
- }
10636
11365
  result.gates = evaluateAutoHandoffRunGates({
10637
11366
  policy: result.policy,
10638
11367
  dryRun: true,
10639
11368
  specStatus: result.spec_status,
10640
11369
  ontology: result.ontology_validation,
11370
+ moquiBaseline: result.moqui_baseline,
11371
+ capabilityCoverage: result.moqui_capability_coverage,
10641
11372
  programKpi: {
10642
11373
  risk_level: 'low'
10643
11374
  }
@@ -10693,31 +11424,13 @@ async function runAutoHandoff(projectPath, options = {}) {
10693
11424
  throw error;
10694
11425
  }
10695
11426
 
10696
- const baselinePhase = beginAutoHandoffRunPhase(result, 'moqui-baseline', 'Moqui template baseline scorecard');
10697
- try {
10698
- result.moqui_baseline = await buildAutoHandoffMoquiBaselineSnapshot(projectPath);
10699
- completeAutoHandoffRunPhase(
10700
- baselinePhase,
10701
- buildAutoHandoffMoquiBaselinePhaseDetails(result.moqui_baseline)
10702
- );
10703
- if (result.moqui_baseline && result.moqui_baseline.status === 'error') {
10704
- result.warnings.push(`moqui baseline generation failed: ${result.moqui_baseline.error || 'unknown error'}`);
10705
- }
10706
- } catch (baselineError) {
10707
- failAutoHandoffRunPhase(baselinePhase, baselineError);
10708
- result.moqui_baseline = {
10709
- status: 'error',
10710
- generated: false,
10711
- error: baselineError && baselineError.message ? baselineError.message : `${baselineError}`
10712
- };
10713
- result.warnings.push(`moqui baseline generation failed: ${result.moqui_baseline.error}`);
10714
- }
10715
-
10716
11427
  result.gates = evaluateAutoHandoffRunGates({
10717
11428
  policy: result.policy,
10718
11429
  dryRun: false,
10719
11430
  specStatus: result.spec_status,
10720
11431
  ontology: result.ontology_validation,
11432
+ moquiBaseline: result.moqui_baseline,
11433
+ capabilityCoverage: result.moqui_capability_coverage,
10721
11434
  programKpi: buildProgramKpiSnapshot(result.batch_summary || {})
10722
11435
  });
10723
11436
  if (!result.gates.passed) {
@@ -10731,6 +11444,7 @@ async function runAutoHandoff(projectPath, options = {}) {
10731
11444
  result.completed_at = new Date().toISOString();
10732
11445
  result.elapsed_ms = Math.max(0, Date.now() - startedAtMs);
10733
11446
  result.regression = await buildAutoHandoffRegression(projectPath, result);
11447
+ result.remediation_queue = await maybeWriteAutoHandoffMoquiRemediationQueue(projectPath, result);
10734
11448
  result.recommendations = buildAutoHandoffRunRecommendations(projectPath, result);
10735
11449
  await writeAutoHandoffRunReport(projectPath, result, options.out);
10736
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.32",
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": {