forgecraft-mcp 1.4.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +67 -0
- package/README.md +527 -525
- package/dist/analyzers/anchors/anchor-loader.d.ts +47 -0
- package/dist/analyzers/anchors/anchor-loader.d.ts.map +1 -0
- package/dist/analyzers/anchors/anchor-loader.js +113 -0
- package/dist/analyzers/anchors/anchor-loader.js.map +1 -0
- package/dist/analyzers/anti-pattern.d.ts.map +1 -1
- package/dist/analyzers/anti-pattern.js +38 -26
- package/dist/analyzers/anti-pattern.js.map +1 -1
- package/dist/analyzers/completeness-helpers.d.ts +5 -0
- package/dist/analyzers/completeness-helpers.d.ts.map +1 -1
- package/dist/analyzers/completeness-helpers.js +17 -0
- package/dist/analyzers/completeness-helpers.js.map +1 -1
- package/dist/analyzers/completeness.d.ts.map +1 -1
- package/dist/analyzers/completeness.js +4 -4
- package/dist/analyzers/completeness.js.map +1 -1
- package/dist/analyzers/gs-scorer.d.ts +3 -1
- package/dist/analyzers/gs-scorer.d.ts.map +1 -1
- package/dist/analyzers/gs-scorer.js +5 -2
- package/dist/analyzers/gs-scorer.js.map +1 -1
- package/dist/analyzers/package-json.d.ts.map +1 -1
- package/dist/analyzers/package-json.js +194 -34
- package/dist/analyzers/package-json.js.map +1 -1
- package/dist/analyzers/scorers/composable-scorer.d.ts +4 -2
- package/dist/analyzers/scorers/composable-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/composable-scorer.js +50 -2
- package/dist/analyzers/scorers/composable-scorer.js.map +1 -1
- package/dist/analyzers/scorers/executable-scorer.d.ts +3 -2
- package/dist/analyzers/scorers/executable-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/executable-scorer.js +64 -4
- package/dist/analyzers/scorers/executable-scorer.js.map +1 -1
- package/dist/analyzers/scorers/scorer-utils.d.ts +5 -3
- package/dist/analyzers/scorers/scorer-utils.d.ts.map +1 -1
- package/dist/analyzers/scorers/scorer-utils.js +34 -9
- package/dist/analyzers/scorers/scorer-utils.js.map +1 -1
- package/dist/analyzers/scorers/self-describing-scorer.d.ts +7 -4
- package/dist/analyzers/scorers/self-describing-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/self-describing-scorer.js +17 -18
- package/dist/analyzers/scorers/self-describing-scorer.js.map +1 -1
- package/dist/cli/help.js +51 -51
- package/dist/disciplines/catalog.d.ts +16 -0
- package/dist/disciplines/catalog.d.ts.map +1 -0
- package/dist/disciplines/catalog.js +196 -0
- package/dist/disciplines/catalog.js.map +1 -0
- package/dist/disciplines/runner.d.ts +13 -0
- package/dist/disciplines/runner.d.ts.map +1 -0
- package/dist/disciplines/runner.js +35 -0
- package/dist/disciplines/runner.js.map +1 -0
- package/dist/registry/composer.d.ts.map +1 -1
- package/dist/registry/composer.js +9 -4
- package/dist/registry/composer.js.map +1 -1
- package/dist/registry/loader-tag.d.ts.map +1 -1
- package/dist/registry/loader-tag.js +1 -0
- package/dist/registry/loader-tag.js.map +1 -1
- package/dist/registry/remote-gates.js +1 -1
- package/dist/registry/remote-gates.js.map +1 -1
- package/dist/registry/renderer-skeletons.js +92 -92
- package/dist/registry/sentinel-renderer.js +299 -20
- package/dist/registry/sentinel-renderer.js.map +1 -1
- package/dist/sentinel/detect.d.ts +41 -0
- package/dist/sentinel/detect.d.ts.map +1 -0
- package/dist/sentinel/detect.js +122 -0
- package/dist/sentinel/detect.js.map +1 -0
- package/dist/sentinel/write.d.ts +54 -0
- package/dist/sentinel/write.d.ts.map +1 -0
- package/dist/sentinel/write.js +75 -0
- package/dist/sentinel/write.js.map +1 -0
- package/dist/shared/cnt-health.d.ts +16 -0
- package/dist/shared/cnt-health.d.ts.map +1 -1
- package/dist/shared/cnt-health.js +55 -8
- package/dist/shared/cnt-health.js.map +1 -1
- package/dist/shared/config.d.ts +14 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +45 -0
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/gs-score-logger.js +6 -6
- package/dist/shared/hook-installer.d.ts +58 -0
- package/dist/shared/hook-installer.d.ts.map +1 -0
- package/dist/shared/hook-installer.js +316 -0
- package/dist/shared/hook-installer.js.map +1 -0
- package/dist/shared/project-gates-helpers.d.ts +9 -0
- package/dist/shared/project-gates-helpers.d.ts.map +1 -1
- package/dist/shared/project-gates-helpers.js +35 -0
- package/dist/shared/project-gates-helpers.js.map +1 -1
- package/dist/shared/types/config.d.ts +7 -1
- package/dist/shared/types/config.d.ts.map +1 -1
- package/dist/shared/types/gates.d.ts +34 -0
- package/dist/shared/types/gates.d.ts.map +1 -1
- package/dist/shared/types/project.d.ts +68 -2
- package/dist/shared/types/project.d.ts.map +1 -1
- package/dist/shared/types/project.js +1 -0
- package/dist/shared/types/project.js.map +1 -1
- package/dist/shared/types/templates.d.ts +8 -1
- package/dist/shared/types/templates.d.ts.map +1 -1
- package/dist/shared/types/verify.d.ts +51 -1
- package/dist/shared/types/verify.d.ts.map +1 -1
- package/dist/shared/types/verify.js +37 -1
- package/dist/shared/types/verify.js.map +1 -1
- package/dist/tools/add-hook.d.ts.map +1 -1
- package/dist/tools/add-hook.js +8 -1
- package/dist/tools/add-hook.js.map +1 -1
- package/dist/tools/add-module.js +123 -123
- package/dist/tools/advice-registry.d.ts.map +1 -1
- package/dist/tools/advice-registry.js +108 -18
- package/dist/tools/advice-registry.js.map +1 -1
- package/dist/tools/advise-session-advisor.d.ts +16 -0
- package/dist/tools/advise-session-advisor.d.ts.map +1 -0
- package/dist/tools/advise-session-advisor.js +89 -0
- package/dist/tools/advise-session-advisor.js.map +1 -0
- package/dist/tools/advise-session-signals.d.ts +21 -0
- package/dist/tools/advise-session-signals.d.ts.map +1 -0
- package/dist/tools/advise-session-signals.js +113 -0
- package/dist/tools/advise-session-signals.js.map +1 -0
- package/dist/tools/advise-session.d.ts +22 -0
- package/dist/tools/advise-session.d.ts.map +1 -0
- package/dist/tools/advise-session.js +31 -0
- package/dist/tools/advise-session.js.map +1 -0
- package/dist/tools/analyze-harness.d.ts +18 -0
- package/dist/tools/analyze-harness.d.ts.map +1 -0
- package/dist/tools/analyze-harness.js +298 -0
- package/dist/tools/analyze-harness.js.map +1 -0
- package/dist/tools/audit.d.ts.map +1 -1
- package/dist/tools/audit.js +19 -0
- package/dist/tools/audit.js.map +1 -1
- package/dist/tools/change-request.d.ts +53 -0
- package/dist/tools/change-request.d.ts.map +1 -0
- package/dist/tools/change-request.js +395 -0
- package/dist/tools/change-request.js.map +1 -0
- package/dist/tools/check-cascade-contracts.d.ts +13 -0
- package/dist/tools/check-cascade-contracts.d.ts.map +1 -1
- package/dist/tools/check-cascade-contracts.js +73 -2
- package/dist/tools/check-cascade-contracts.js.map +1 -1
- package/dist/tools/check-cascade-report.js +64 -64
- package/dist/tools/check-cascade-steps.d.ts +3 -0
- package/dist/tools/check-cascade-steps.d.ts.map +1 -1
- package/dist/tools/check-cascade-steps.js +104 -15
- package/dist/tools/check-cascade-steps.js.map +1 -1
- package/dist/tools/check-cascade.d.ts +4 -3
- package/dist/tools/check-cascade.d.ts.map +1 -1
- package/dist/tools/check-cascade.js +30 -12
- package/dist/tools/check-cascade.js.map +1 -1
- package/dist/tools/check-derivation-chain.d.ts +37 -0
- package/dist/tools/check-derivation-chain.d.ts.map +1 -0
- package/dist/tools/check-derivation-chain.js +418 -0
- package/dist/tools/check-derivation-chain.js.map +1 -0
- package/dist/tools/check-spec-consistency.d.ts +25 -0
- package/dist/tools/check-spec-consistency.d.ts.map +1 -0
- package/dist/tools/check-spec-consistency.js +339 -0
- package/dist/tools/check-spec-consistency.js.map +1 -0
- package/dist/tools/check-t4.d.ts +54 -0
- package/dist/tools/check-t4.d.ts.map +1 -0
- package/dist/tools/check-t4.js +305 -0
- package/dist/tools/check-t4.js.map +1 -0
- package/dist/tools/close-cycle.d.ts +11 -0
- package/dist/tools/close-cycle.d.ts.map +1 -1
- package/dist/tools/close-cycle.js +364 -4
- package/dist/tools/close-cycle.js.map +1 -1
- package/dist/tools/cnt-add-routing.d.ts +31 -0
- package/dist/tools/cnt-add-routing.d.ts.map +1 -0
- package/dist/tools/cnt-add-routing.js +99 -0
- package/dist/tools/cnt-add-routing.js.map +1 -0
- package/dist/tools/configure-mcp.d.ts.map +1 -1
- package/dist/tools/configure-mcp.js +52 -2
- package/dist/tools/configure-mcp.js.map +1 -1
- package/dist/tools/consolidate-status.d.ts +31 -0
- package/dist/tools/consolidate-status.d.ts.map +1 -1
- package/dist/tools/consolidate-status.js +105 -0
- package/dist/tools/consolidate-status.js.map +1 -1
- package/dist/tools/executable-gates.d.ts +52 -0
- package/dist/tools/executable-gates.d.ts.map +1 -0
- package/dist/tools/executable-gates.js +333 -0
- package/dist/tools/executable-gates.js.map +1 -0
- package/dist/tools/extract-adrs-from-spec.d.ts +33 -0
- package/dist/tools/extract-adrs-from-spec.d.ts.map +1 -0
- package/dist/tools/extract-adrs-from-spec.js +410 -0
- package/dist/tools/extract-adrs-from-spec.js.map +1 -0
- package/dist/tools/extract-adrs-history.d.ts +47 -0
- package/dist/tools/extract-adrs-history.d.ts.map +1 -0
- package/dist/tools/extract-adrs-history.js +265 -0
- package/dist/tools/extract-adrs-history.js.map +1 -0
- package/dist/tools/forgecraft-dispatch-extended.d.ts.map +1 -1
- package/dist/tools/forgecraft-dispatch-extended.js +137 -0
- package/dist/tools/forgecraft-dispatch-extended.js.map +1 -1
- package/dist/tools/forgecraft-dispatch.d.ts.map +1 -1
- package/dist/tools/forgecraft-dispatch.js +16 -0
- package/dist/tools/forgecraft-dispatch.js.map +1 -1
- package/dist/tools/forgecraft-schema-params.d.ts +174 -2
- package/dist/tools/forgecraft-schema-params.d.ts.map +1 -1
- package/dist/tools/forgecraft-schema-params.js +197 -0
- package/dist/tools/forgecraft-schema-params.js.map +1 -1
- package/dist/tools/forgecraft-schema.d.ts +179 -7
- package/dist/tools/forgecraft-schema.d.ts.map +1 -1
- package/dist/tools/forgecraft-schema.js +37 -0
- package/dist/tools/forgecraft-schema.js.map +1 -1
- package/dist/tools/generate-adr.js +6 -6
- package/dist/tools/generate-adr.js.map +1 -1
- package/dist/tools/generate-decision.d.ts +77 -0
- package/dist/tools/generate-decision.d.ts.map +1 -0
- package/dist/tools/generate-decision.js +162 -0
- package/dist/tools/generate-decision.js.map +1 -0
- package/dist/tools/generate-env-probe.d.ts +49 -0
- package/dist/tools/generate-env-probe.d.ts.map +1 -0
- package/dist/tools/generate-env-probe.js +365 -0
- package/dist/tools/generate-env-probe.js.map +1 -0
- package/dist/tools/generate-harness.d.ts +53 -0
- package/dist/tools/generate-harness.d.ts.map +1 -0
- package/dist/tools/generate-harness.js +395 -0
- package/dist/tools/generate-harness.js.map +1 -0
- package/dist/tools/generate-roadmap.d.ts +1 -1
- package/dist/tools/generate-roadmap.d.ts.map +1 -1
- package/dist/tools/generate-roadmap.js +38 -4
- package/dist/tools/generate-roadmap.js.map +1 -1
- package/dist/tools/generate-session-prompt.d.ts +3 -3
- package/dist/tools/generate-session-prompt.d.ts.map +1 -1
- package/dist/tools/generate-session-prompt.js +9 -1
- package/dist/tools/generate-session-prompt.js.map +1 -1
- package/dist/tools/generate-slo-probe.d.ts +53 -0
- package/dist/tools/generate-slo-probe.d.ts.map +1 -0
- package/dist/tools/generate-slo-probe.js +366 -0
- package/dist/tools/generate-slo-probe.js.map +1 -0
- package/dist/tools/layer-status-gates.d.ts +24 -0
- package/dist/tools/layer-status-gates.d.ts.map +1 -0
- package/dist/tools/layer-status-gates.js +151 -0
- package/dist/tools/layer-status-gates.js.map +1 -0
- package/dist/tools/layer-status.d.ts +126 -0
- package/dist/tools/layer-status.d.ts.map +1 -0
- package/dist/tools/layer-status.js +647 -0
- package/dist/tools/layer-status.js.map +1 -0
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +9 -5
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/postcondition-coverage.d.ts +57 -0
- package/dist/tools/postcondition-coverage.d.ts.map +1 -0
- package/dist/tools/postcondition-coverage.js +256 -0
- package/dist/tools/postcondition-coverage.js.map +1 -0
- package/dist/tools/probe-runners.d.ts +21 -0
- package/dist/tools/probe-runners.d.ts.map +1 -0
- package/dist/tools/probe-runners.js +246 -0
- package/dist/tools/probe-runners.js.map +1 -0
- package/dist/tools/probe-templates.d.ts +27 -0
- package/dist/tools/probe-templates.d.ts.map +1 -0
- package/dist/tools/probe-templates.js +279 -0
- package/dist/tools/probe-templates.js.map +1 -0
- package/dist/tools/propose-session.d.ts +28 -0
- package/dist/tools/propose-session.d.ts.map +1 -0
- package/dist/tools/propose-session.js +333 -0
- package/dist/tools/propose-session.js.map +1 -0
- package/dist/tools/refresh-output.js +14 -14
- package/dist/tools/review-stubs.d.ts +29 -0
- package/dist/tools/review-stubs.d.ts.map +1 -0
- package/dist/tools/review-stubs.js +173 -0
- package/dist/tools/review-stubs.js.map +1 -0
- package/dist/tools/roadmap-builder.d.ts +49 -1
- package/dist/tools/roadmap-builder.d.ts.map +1 -1
- package/dist/tools/roadmap-builder.js +210 -5
- package/dist/tools/roadmap-builder.js.map +1 -1
- package/dist/tools/run-env-probe.d.ts +57 -0
- package/dist/tools/run-env-probe.d.ts.map +1 -0
- package/dist/tools/run-env-probe.js +270 -0
- package/dist/tools/run-env-probe.js.map +1 -0
- package/dist/tools/run-harness.d.ts +52 -0
- package/dist/tools/run-harness.d.ts.map +1 -0
- package/dist/tools/run-harness.js +279 -0
- package/dist/tools/run-harness.js.map +1 -0
- package/dist/tools/run-slo-probe.d.ts +50 -0
- package/dist/tools/run-slo-probe.d.ts.map +1 -0
- package/dist/tools/run-slo-probe.js +281 -0
- package/dist/tools/run-slo-probe.js.map +1 -0
- package/dist/tools/scaffold-spec-stubs.js +115 -115
- package/dist/tools/scaffold-templates.js +62 -62
- package/dist/tools/scaffold-writer.d.ts.map +1 -1
- package/dist/tools/scaffold-writer.js +9 -0
- package/dist/tools/scaffold-writer.js.map +1 -1
- package/dist/tools/score-rubric.d.ts +19 -0
- package/dist/tools/score-rubric.d.ts.map +1 -0
- package/dist/tools/score-rubric.js +411 -0
- package/dist/tools/score-rubric.js.map +1 -0
- package/dist/tools/session-prompt-builders.d.ts +20 -0
- package/dist/tools/session-prompt-builders.d.ts.map +1 -1
- package/dist/tools/session-prompt-builders.js +78 -5
- package/dist/tools/session-prompt-builders.js.map +1 -1
- package/dist/tools/session-prompt-sections.d.ts +4 -2
- package/dist/tools/session-prompt-sections.d.ts.map +1 -1
- package/dist/tools/session-prompt-sections.js +22 -10
- package/dist/tools/session-prompt-sections.js.map +1 -1
- package/dist/tools/setup-artifact-writers.d.ts +69 -4
- package/dist/tools/setup-artifact-writers.d.ts.map +1 -1
- package/dist/tools/setup-artifact-writers.js +681 -5
- package/dist/tools/setup-artifact-writers.js.map +1 -1
- package/dist/tools/setup-cnt-builders.d.ts.map +1 -1
- package/dist/tools/setup-cnt-builders.js +162 -34
- package/dist/tools/setup-cnt-builders.js.map +1 -1
- package/dist/tools/setup-monitoring.d.ts +41 -0
- package/dist/tools/setup-monitoring.d.ts.map +1 -0
- package/dist/tools/setup-monitoring.js +364 -0
- package/dist/tools/setup-monitoring.js.map +1 -0
- package/dist/tools/setup-phase1.d.ts.map +1 -1
- package/dist/tools/setup-phase1.js +14 -1
- package/dist/tools/setup-phase1.js.map +1 -1
- package/dist/tools/setup-phase2.d.ts +14 -0
- package/dist/tools/setup-phase2.d.ts.map +1 -1
- package/dist/tools/setup-phase2.js +130 -3
- package/dist/tools/setup-phase2.js.map +1 -1
- package/dist/tools/setup-project.d.ts +8 -0
- package/dist/tools/setup-project.d.ts.map +1 -1
- package/dist/tools/setup-project.js +52 -2
- package/dist/tools/setup-project.js.map +1 -1
- package/dist/tools/spec-parser-tags.d.ts.map +1 -1
- package/dist/tools/spec-parser-tags.js +1 -0
- package/dist/tools/spec-parser-tags.js.map +1 -1
- package/dist/tools/verify-formatter.d.ts.map +1 -1
- package/dist/tools/verify-formatter.js +15 -1
- package/dist/tools/verify-formatter.js.map +1 -1
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +3 -0
- package/dist/tools/verify.js.map +1 -1
- package/package.json +98 -89
- package/templates/analytics/instructions.yaml +37 -37
- package/templates/analytics/mcp-servers.yaml +11 -11
- package/templates/analytics/structure.yaml +25 -25
- package/templates/api/harness/uc-template.hurl +20 -0
- package/templates/api/instructions.yaml +231 -231
- package/templates/api/mcp-servers.yaml +22 -22
- package/templates/api/nfr.yaml +23 -23
- package/templates/api/review.yaml +103 -103
- package/templates/api/structure.yaml +34 -34
- package/templates/api/verification.yaml +132 -132
- package/templates/cli/instructions.yaml +31 -31
- package/templates/cli/mcp-servers.yaml +11 -11
- package/templates/cli/review.yaml +53 -53
- package/templates/cli/structure.yaml +16 -16
- package/templates/data-lineage/instructions.yaml +28 -28
- package/templates/data-lineage/mcp-servers.yaml +22 -22
- package/templates/data-pipeline/instructions.yaml +84 -84
- package/templates/data-pipeline/mcp-servers.yaml +13 -13
- package/templates/data-pipeline/nfr.yaml +39 -39
- package/templates/data-pipeline/structure.yaml +23 -23
- package/templates/docs-manifest.yaml +227 -0
- package/templates/fintech/hooks.yaml +55 -55
- package/templates/fintech/instructions.yaml +112 -112
- package/templates/fintech/mcp-servers.yaml +13 -13
- package/templates/fintech/nfr.yaml +46 -46
- package/templates/fintech/playbook.yaml +210 -210
- package/templates/fintech/verification.yaml +239 -239
- package/templates/game/harness/uc-template.sim.ts +29 -0
- package/templates/game/instructions.yaml +289 -289
- package/templates/game/mcp-servers.yaml +38 -38
- package/templates/game/nfr.yaml +64 -64
- package/templates/game/playbook.yaml +214 -214
- package/templates/game/review.yaml +97 -97
- package/templates/game/structure.yaml +67 -67
- package/templates/game/verification.yaml +174 -174
- package/templates/healthcare/instructions.yaml +42 -42
- package/templates/healthcare/mcp-servers.yaml +13 -13
- package/templates/healthcare/nfr.yaml +47 -47
- package/templates/hipaa/instructions.yaml +41 -41
- package/templates/hipaa/mcp-servers.yaml +13 -13
- package/templates/infra/instructions.yaml +104 -104
- package/templates/infra/mcp-servers.yaml +20 -20
- package/templates/infra/nfr.yaml +46 -46
- package/templates/infra/review.yaml +65 -65
- package/templates/infra/structure.yaml +25 -25
- package/templates/library/instructions.yaml +36 -36
- package/templates/library/mcp-servers.yaml +20 -20
- package/templates/library/review.yaml +56 -56
- package/templates/library/structure.yaml +19 -19
- package/templates/medallion-architecture/instructions.yaml +41 -41
- package/templates/medallion-architecture/mcp-servers.yaml +22 -22
- package/templates/ml/instructions.yaml +85 -85
- package/templates/ml/mcp-servers.yaml +11 -11
- package/templates/ml/nfr.yaml +39 -39
- package/templates/ml/structure.yaml +25 -25
- package/templates/ml/verification.yaml +156 -156
- package/templates/mobile/instructions.yaml +44 -44
- package/templates/mobile/mcp-servers.yaml +11 -11
- package/templates/mobile/nfr.yaml +49 -49
- package/templates/mobile/structure.yaml +27 -27
- package/templates/mobile/verification.yaml +121 -121
- package/templates/observability-xray/instructions.yaml +40 -40
- package/templates/observability-xray/mcp-servers.yaml +15 -15
- package/templates/realtime/instructions.yaml +42 -42
- package/templates/realtime/mcp-servers.yaml +13 -13
- package/templates/soc2/instructions.yaml +41 -41
- package/templates/soc2/mcp-servers.yaml +24 -24
- package/templates/social/instructions.yaml +43 -43
- package/templates/social/mcp-servers.yaml +24 -24
- package/templates/state-machine/instructions.yaml +42 -42
- package/templates/state-machine/mcp-servers.yaml +11 -11
- package/templates/tools-registry.yaml +164 -164
- package/templates/universal/claude-md-blocks/layer-navigation.md +20 -0
- package/templates/universal/claude-md-blocks/nfr-contracts.md +22 -0
- package/templates/universal/hooks.yaml +879 -723
- package/templates/universal/instructions.yaml +1692 -1692
- package/templates/universal/mcp-servers.yaml +50 -50
- package/templates/universal/nfr.yaml +197 -197
- package/templates/universal/reference.yaml +326 -326
- package/templates/universal/review.yaml +204 -204
- package/templates/universal/skills.yaml +262 -262
- package/templates/universal/structure.yaml +67 -67
- package/templates/universal/verification.yaml +416 -416
- package/templates/web-next/hooks.yaml +114 -0
- package/templates/web-next/instructions.yaml +106 -0
- package/templates/web-react/harness/uc-template.spec.ts +35 -0
- package/templates/web-react/hooks.yaml +156 -44
- package/templates/web-react/instructions.yaml +296 -207
- package/templates/web-react/mcp-servers.yaml +20 -20
- package/templates/web-react/nfr.yaml +27 -27
- package/templates/web-react/review.yaml +94 -94
- package/templates/web-react/structure.yaml +46 -46
- package/templates/web-react/verification.yaml +126 -126
- package/templates/web-static/hooks.yaml +85 -0
- package/templates/web-static/instructions.yaml +204 -115
- package/templates/web-static/mcp-servers.yaml +20 -20
- package/templates/web3/instructions.yaml +44 -44
- package/templates/web3/mcp-servers.yaml +11 -11
- package/templates/web3/verification.yaml +159 -159
- package/templates/zero-trust/instructions.yaml +41 -41
- package/templates/zero-trust/mcp-servers.yaml +15 -15
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* layer_status tool handler.
|
|
3
|
+
*
|
|
4
|
+
* Reports L1–L4 completion per use case by reading docs/use-cases.md
|
|
5
|
+
* and scanning for harness probes, infra config, and monitoring artifacts.
|
|
6
|
+
* Works on any project with docs/use-cases.md — does not require forgecraft.yaml.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { computePostconditionCoverage, formatCoverageTable, } from "./postcondition-coverage.js";
|
|
12
|
+
import { detectL1GateViolations, } from "./layer-status-gates.js";
|
|
13
|
+
// ── Harness run helpers ───────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Read harness-run.json for full summary (passed/failed/notFound + timestamp).
|
|
16
|
+
* Returns null when missing or unparseable.
|
|
17
|
+
*/
|
|
18
|
+
function readHarnessRunForSummary(projectDir) {
|
|
19
|
+
const runJsonPath = join(projectDir, ".forgecraft", "harness-run.json");
|
|
20
|
+
if (!existsSync(runJsonPath))
|
|
21
|
+
return null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = readFileSync(runJsonPath, "utf-8");
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check tests/harness/ and .forgecraft/harness-run.json for probe file count
|
|
32
|
+
* and last run timestamp.
|
|
33
|
+
*/
|
|
34
|
+
export function buildHarnessRunSummary(projectDir) {
|
|
35
|
+
const harnessDir = join(projectDir, "tests", "harness");
|
|
36
|
+
let probeFilesFound = 0;
|
|
37
|
+
if (existsSync(harnessDir)) {
|
|
38
|
+
try {
|
|
39
|
+
const entries = readdirSync(harnessDir);
|
|
40
|
+
probeFilesFound = entries.filter((e) => e.endsWith(".spec.ts") ||
|
|
41
|
+
e.endsWith(".hurl") ||
|
|
42
|
+
e.endsWith(".sh") ||
|
|
43
|
+
e.endsWith(".sim.ts")).length;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
let lastRunTimestamp = null;
|
|
50
|
+
const runJsonPath = join(projectDir, ".forgecraft", "harness-run.json");
|
|
51
|
+
if (existsSync(runJsonPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(runJsonPath, "utf-8");
|
|
54
|
+
const parsed = JSON.parse(raw);
|
|
55
|
+
lastRunTimestamp = parsed.timestamp ?? null;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// ignore
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { probeFilesFound, lastRunTimestamp };
|
|
62
|
+
}
|
|
63
|
+
// ── Schema ───────────────────────────────────────────────────────────
|
|
64
|
+
export const layerStatusSchema = z.object({
|
|
65
|
+
project_dir: z
|
|
66
|
+
.string()
|
|
67
|
+
.describe("Absolute path to the project root directory."),
|
|
68
|
+
});
|
|
69
|
+
// ── UC parsing ────────────────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Parse use case records from docs/use-cases.md content.
|
|
72
|
+
* Extracts UC-NNN id and title from `## UC-NNN: Title` headers.
|
|
73
|
+
*/
|
|
74
|
+
export function parseUseCases(content) {
|
|
75
|
+
const ucs = [];
|
|
76
|
+
const lines = content.split("\n");
|
|
77
|
+
for (const line of lines) {
|
|
78
|
+
const match = /^##\s+(UC-\d{3}):\s+(.+)$/.exec(line.trim());
|
|
79
|
+
if (match) {
|
|
80
|
+
ucs.push({ id: match[1], title: match[2].trim() });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return ucs;
|
|
84
|
+
}
|
|
85
|
+
// ── L1 check ─────────────────────────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Check whether any test file in the project references a given UC id.
|
|
88
|
+
*/
|
|
89
|
+
function hasTestsForUc(projectDir, ucId) {
|
|
90
|
+
const testDirs = ["tests", "test", "src", "__tests__", "spec"];
|
|
91
|
+
const lowerId = ucId.toLowerCase();
|
|
92
|
+
for (const dir of testDirs) {
|
|
93
|
+
const dirPath = join(projectDir, dir);
|
|
94
|
+
if (!existsSync(dirPath))
|
|
95
|
+
continue;
|
|
96
|
+
if (scanDirForText(dirPath, lowerId, 0))
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Recursively scan a directory for files containing the given text.
|
|
103
|
+
* Limited to depth 4 to avoid traversing large trees.
|
|
104
|
+
*/
|
|
105
|
+
function scanDirForText(dirPath, text, depth) {
|
|
106
|
+
if (depth > 4)
|
|
107
|
+
return false;
|
|
108
|
+
let entries;
|
|
109
|
+
try {
|
|
110
|
+
entries = readdirSync(dirPath);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const full = join(dirPath, entry);
|
|
117
|
+
try {
|
|
118
|
+
const stat = statSync(full);
|
|
119
|
+
if (stat.isDirectory()) {
|
|
120
|
+
if (scanDirForText(full, text, depth + 1))
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
else if (entry.endsWith(".ts") ||
|
|
124
|
+
entry.endsWith(".js") ||
|
|
125
|
+
entry.endsWith(".test.ts")) {
|
|
126
|
+
const content = readFileSync(full, "utf-8");
|
|
127
|
+
if (content.toLowerCase().includes(text))
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// skip unreadable entries
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build L1 status for all parsed use cases.
|
|
139
|
+
* L1 = UC is documented. Test coverage is surfaced as a sub-check.
|
|
140
|
+
*/
|
|
141
|
+
export function buildL1Status(projectDir, ucs) {
|
|
142
|
+
return ucs.map((uc) => ({
|
|
143
|
+
...uc,
|
|
144
|
+
documented: true,
|
|
145
|
+
testsFound: hasTestsForUc(projectDir, uc.id),
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
// ── L2 check ─────────────────────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Read probe types from a harness YAML file.
|
|
151
|
+
* Non-throwing — returns empty array on parse failure.
|
|
152
|
+
*/
|
|
153
|
+
function readProbeTypes(probePath) {
|
|
154
|
+
try {
|
|
155
|
+
const raw = readFileSync(probePath, "utf-8");
|
|
156
|
+
const types = [];
|
|
157
|
+
for (const line of raw.split("\n")) {
|
|
158
|
+
const m = /^\s+type:\s+(\S+)/.exec(line);
|
|
159
|
+
if (m)
|
|
160
|
+
types.push(m[1]);
|
|
161
|
+
}
|
|
162
|
+
return [...new Set(types)];
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Read per-UC results from harness-run.json.
|
|
170
|
+
* Returns a map of ucId -> status string, or empty map on failure.
|
|
171
|
+
*/
|
|
172
|
+
function readHarnessRunResults(projectDir) {
|
|
173
|
+
const runJsonPath = join(projectDir, ".forgecraft", "harness-run.json");
|
|
174
|
+
if (!existsSync(runJsonPath))
|
|
175
|
+
return new Map();
|
|
176
|
+
try {
|
|
177
|
+
const raw = readFileSync(runJsonPath, "utf-8");
|
|
178
|
+
const parsed = JSON.parse(raw);
|
|
179
|
+
const map = new Map();
|
|
180
|
+
for (const r of parsed.results ?? []) {
|
|
181
|
+
map.set(r.ucId.toUpperCase(), r.status);
|
|
182
|
+
}
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return new Map();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check tests/harness/ for happy-path and error probe files for a UC.
|
|
191
|
+
*/
|
|
192
|
+
function readHarnessProbeFiles(harnessDir, ucId) {
|
|
193
|
+
const lower = ucId.toLowerCase().replace(/_/g, "-");
|
|
194
|
+
const EXTENSIONS = [".spec.ts", ".hurl", ".sh", ".sim.ts"];
|
|
195
|
+
if (!existsSync(harnessDir)) {
|
|
196
|
+
return { hasHappyProbe: false, errorProbeCount: 0 };
|
|
197
|
+
}
|
|
198
|
+
let entries = [];
|
|
199
|
+
try {
|
|
200
|
+
entries = readdirSync(harnessDir);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return { hasHappyProbe: false, errorProbeCount: 0 };
|
|
204
|
+
}
|
|
205
|
+
const hasHappyProbe = EXTENSIONS.some((ext) => entries.includes(`${lower}-happy${ext}`));
|
|
206
|
+
const errorProbeCount = entries.filter((e) => {
|
|
207
|
+
const base = e.replace(/\.(spec\.ts|hurl|sh|sim\.ts)$/, "");
|
|
208
|
+
return base.startsWith(`${lower}-error-`);
|
|
209
|
+
}).length;
|
|
210
|
+
return { hasHappyProbe, errorProbeCount };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Build L2 status for all use cases.
|
|
214
|
+
* L2 = .forgecraft/harness/uc-NNN.yaml exists.
|
|
215
|
+
*/
|
|
216
|
+
export function buildL2Status(projectDir, ucs) {
|
|
217
|
+
const harnessDir = join(projectDir, "tests", "harness");
|
|
218
|
+
const runResults = readHarnessRunResults(projectDir);
|
|
219
|
+
return ucs.map((uc) => {
|
|
220
|
+
const probeFile = join(projectDir, ".forgecraft", "harness", `${uc.id.toLowerCase()}.yaml`);
|
|
221
|
+
const hasProbe = existsSync(probeFile);
|
|
222
|
+
const { hasHappyProbe, errorProbeCount } = readHarnessProbeFiles(harnessDir, uc.id);
|
|
223
|
+
const lastRunStatus = runResults.get(uc.id.toUpperCase()) ?? null;
|
|
224
|
+
return {
|
|
225
|
+
...uc,
|
|
226
|
+
hasProbe,
|
|
227
|
+
probeTypes: hasProbe ? readProbeTypes(probeFile) : [],
|
|
228
|
+
hasHappyProbe,
|
|
229
|
+
errorProbeCount,
|
|
230
|
+
lastRunStatus,
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// ── L3 check ─────────────────────────────────────────────────────────
|
|
235
|
+
const L3_CHECKS = [
|
|
236
|
+
["CI config", [".github/workflows", ".gitlab-ci.yml", "Procfile"]],
|
|
237
|
+
["Test command", ["package.json", "Makefile", "Cargo.toml"]],
|
|
238
|
+
["Env schema", [".env.example", ".env.schema", "src/config"]],
|
|
239
|
+
[
|
|
240
|
+
"Deployment config",
|
|
241
|
+
["Dockerfile", "docker-compose.yml", "render.yaml", "fly.toml"],
|
|
242
|
+
],
|
|
243
|
+
];
|
|
244
|
+
/**
|
|
245
|
+
* Evaluate L3 (environment/infrastructure) readiness.
|
|
246
|
+
*/
|
|
247
|
+
export function buildL3Status(projectDir) {
|
|
248
|
+
const checks = {};
|
|
249
|
+
for (const [label, paths] of L3_CHECKS) {
|
|
250
|
+
checks[label] = paths.some((p) => existsSync(join(projectDir, p)));
|
|
251
|
+
}
|
|
252
|
+
const passing = Object.values(checks).filter(Boolean).length;
|
|
253
|
+
const total = Object.keys(checks).length;
|
|
254
|
+
let status = passing === 0 ? "not-started" : passing === total ? "complete" : "partial";
|
|
255
|
+
// Incorporate env-probe-run.json evidence
|
|
256
|
+
const envProbeRunPath = join(projectDir, ".forgecraft", "env-probe-run.json");
|
|
257
|
+
let envProbeEvidence = null;
|
|
258
|
+
if (existsSync(envProbeRunPath)) {
|
|
259
|
+
try {
|
|
260
|
+
envProbeEvidence = JSON.parse(readFileSync(envProbeRunPath, "utf-8"));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
/* ignore */
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (envProbeEvidence !== null) {
|
|
267
|
+
if (envProbeEvidence.passed > 0 && envProbeEvidence.failed === 0) {
|
|
268
|
+
status = "complete";
|
|
269
|
+
}
|
|
270
|
+
else if (envProbeEvidence.failed > 0) {
|
|
271
|
+
status = "partial";
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return { status, checks, envProbeEvidence };
|
|
275
|
+
}
|
|
276
|
+
// ── L4 check ─────────────────────────────────────────────────────────
|
|
277
|
+
const L4_HEALTH_PATHS = [
|
|
278
|
+
".forgecraft/health",
|
|
279
|
+
"src/health",
|
|
280
|
+
"health",
|
|
281
|
+
"src/monitoring",
|
|
282
|
+
];
|
|
283
|
+
const L4_DRIFT_PATHS = [".forgecraft/monitoring", "src/drift", "monitoring"];
|
|
284
|
+
/**
|
|
285
|
+
* Evaluate L4 (self-monitoring/drift detection) readiness.
|
|
286
|
+
*/
|
|
287
|
+
export function buildL4Status(projectDir) {
|
|
288
|
+
const healthProbes = L4_HEALTH_PATHS.some((p) => existsSync(join(projectDir, p)));
|
|
289
|
+
const driftDetection = L4_DRIFT_PATHS.some((p) => existsSync(join(projectDir, p)));
|
|
290
|
+
const checks = {
|
|
291
|
+
"Health probes": healthProbes,
|
|
292
|
+
"Drift detection": driftDetection,
|
|
293
|
+
};
|
|
294
|
+
const passing = Object.values(checks).filter(Boolean).length;
|
|
295
|
+
let status = passing === 0 ? "not-started" : passing === 2 ? "complete" : "partial";
|
|
296
|
+
// Incorporate slo-probe-run.json evidence
|
|
297
|
+
const sloProbeRunPath = join(projectDir, ".forgecraft", "slo-probe-run.json");
|
|
298
|
+
let sloProbeEvidence = null;
|
|
299
|
+
if (existsSync(sloProbeRunPath)) {
|
|
300
|
+
try {
|
|
301
|
+
sloProbeEvidence = JSON.parse(readFileSync(sloProbeRunPath, "utf-8"));
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
/* ignore */
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (sloProbeEvidence !== null) {
|
|
308
|
+
if (sloProbeEvidence.passed > 0 && sloProbeEvidence.failed === 0) {
|
|
309
|
+
status = "complete";
|
|
310
|
+
}
|
|
311
|
+
else if (sloProbeEvidence.failed > 0) {
|
|
312
|
+
status = "partial";
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { status, checks, sloProbeEvidence };
|
|
316
|
+
}
|
|
317
|
+
// ── Core data builder ─────────────────────────────────────────────────
|
|
318
|
+
/**
|
|
319
|
+
* Build the full layer report for a project directory.
|
|
320
|
+
* All reads are non-throwing — missing artifacts produce empty/default fields.
|
|
321
|
+
*
|
|
322
|
+
* @param projectDir - Absolute path to project root
|
|
323
|
+
* @returns Structured layer report
|
|
324
|
+
*/
|
|
325
|
+
/**
|
|
326
|
+
* Parse UC records from canonical docs/use-cases/ directory.
|
|
327
|
+
* Each file is named UC-NNN-slug.md; the title is taken from the first H1.
|
|
328
|
+
*/
|
|
329
|
+
function parseUseCaseDirectory(projectDir) {
|
|
330
|
+
const ucDir = join(projectDir, "docs", "use-cases");
|
|
331
|
+
if (!existsSync(ucDir))
|
|
332
|
+
return [];
|
|
333
|
+
let files;
|
|
334
|
+
try {
|
|
335
|
+
files = readdirSync(ucDir).filter((f) => /^UC-\d{3,4}-.+\.md$/i.test(f));
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
const ucs = [];
|
|
341
|
+
for (const file of files.sort()) {
|
|
342
|
+
const idMatch = /^(UC-\d{3,4})/i.exec(file);
|
|
343
|
+
if (!idMatch)
|
|
344
|
+
continue;
|
|
345
|
+
const id = idMatch[1].toUpperCase();
|
|
346
|
+
let title = file
|
|
347
|
+
.replace(/^UC-\d{3,4}-/, "")
|
|
348
|
+
.replace(/\.md$/, "")
|
|
349
|
+
.replace(/-/g, " ");
|
|
350
|
+
try {
|
|
351
|
+
const content = readFileSync(join(ucDir, file), "utf-8");
|
|
352
|
+
const h1 = /^#\s+(.+)$/m.exec(content);
|
|
353
|
+
if (h1)
|
|
354
|
+
title = h1[1].trim();
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// use filename-derived title
|
|
358
|
+
}
|
|
359
|
+
ucs.push({ id, title });
|
|
360
|
+
}
|
|
361
|
+
return ucs;
|
|
362
|
+
}
|
|
363
|
+
export function buildLayerReport(projectDir) {
|
|
364
|
+
let ucs = [];
|
|
365
|
+
// Try canonical directory first (docs/use-cases/UC-*.md), then legacy monolith
|
|
366
|
+
const ucDir = join(projectDir, "docs", "use-cases");
|
|
367
|
+
const useCasesPath = join(projectDir, "docs", "use-cases.md");
|
|
368
|
+
const dirUcs = parseUseCaseDirectory(projectDir);
|
|
369
|
+
if (dirUcs.length > 0) {
|
|
370
|
+
ucs = dirUcs;
|
|
371
|
+
}
|
|
372
|
+
else if (existsSync(useCasesPath)) {
|
|
373
|
+
try {
|
|
374
|
+
const content = readFileSync(useCasesPath, "utf-8");
|
|
375
|
+
ucs = parseUseCases(content);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// leave ucs empty
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
void ucDir; // referenced above via parseUseCaseDirectory
|
|
382
|
+
const l1 = buildL1Status(projectDir, ucs);
|
|
383
|
+
const l2 = buildL2Status(projectDir, ucs);
|
|
384
|
+
const { status: l3, checks: l3Checks, envProbeEvidence, } = buildL3Status(projectDir);
|
|
385
|
+
const { status: l4, checks: l4Checks, sloProbeEvidence, } = buildL4Status(projectDir);
|
|
386
|
+
// Derive project name from forgecraft.yaml or fallback to dirname
|
|
387
|
+
const projectName = deriveProjectName(projectDir);
|
|
388
|
+
const l1GateViolations = detectL1GateViolations(projectDir);
|
|
389
|
+
return {
|
|
390
|
+
projectName,
|
|
391
|
+
projectDir,
|
|
392
|
+
generatedAt: new Date().toISOString(),
|
|
393
|
+
ucs,
|
|
394
|
+
l1,
|
|
395
|
+
l2,
|
|
396
|
+
l3,
|
|
397
|
+
l3Checks,
|
|
398
|
+
l4,
|
|
399
|
+
l4Checks,
|
|
400
|
+
l1GateViolations,
|
|
401
|
+
envProbeEvidence,
|
|
402
|
+
sloProbeEvidence,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Read project name from forgecraft.yaml if present, otherwise use dirname.
|
|
407
|
+
*/
|
|
408
|
+
function deriveProjectName(projectDir) {
|
|
409
|
+
const yamlPath = join(projectDir, "forgecraft.yaml");
|
|
410
|
+
if (existsSync(yamlPath)) {
|
|
411
|
+
try {
|
|
412
|
+
const content = readFileSync(yamlPath, "utf-8");
|
|
413
|
+
const m = /^project_name:\s*(.+)/m.exec(content);
|
|
414
|
+
if (m)
|
|
415
|
+
return m[1].trim();
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// fallback
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return projectDir.split(/[\\/]/).filter(Boolean).pop() ?? "project";
|
|
422
|
+
}
|
|
423
|
+
// ── Formatter ─────────────────────────────────────────────────────────
|
|
424
|
+
/**
|
|
425
|
+
* Format a LayerReport as a human-readable markdown block.
|
|
426
|
+
*/
|
|
427
|
+
export function formatLayerReport(report) {
|
|
428
|
+
const lines = [
|
|
429
|
+
`## Layer Status — ${report.projectName}`,
|
|
430
|
+
``,
|
|
431
|
+
`_Generated: ${report.generatedAt}_`,
|
|
432
|
+
``,
|
|
433
|
+
];
|
|
434
|
+
const total = report.ucs.length;
|
|
435
|
+
if (total === 0) {
|
|
436
|
+
lines.push("_No use cases found in docs/use-cases.md_");
|
|
437
|
+
lines.push("");
|
|
438
|
+
lines.push("To begin tracking: create `docs/use-cases.md` with `## UC-001: ...` headers.");
|
|
439
|
+
appendLayerSummaryNoUcs(lines, report);
|
|
440
|
+
return lines.join("\n");
|
|
441
|
+
}
|
|
442
|
+
// L1
|
|
443
|
+
const unboundUcs = report.l1.filter((u) => !u.testsFound);
|
|
444
|
+
const testBindingPct = total > 0 ? Math.round(((total - unboundUcs.length) / total) * 100) : 100;
|
|
445
|
+
lines.push("### L1: Blueprint");
|
|
446
|
+
lines.push(`${total}/${total} use cases documented`);
|
|
447
|
+
lines.push(`${total - unboundUcs.length}/${total} use cases have test binding (${testBindingPct}%)`);
|
|
448
|
+
lines.push("");
|
|
449
|
+
lines.push("| UC | Title | Documented | Tests Found |");
|
|
450
|
+
lines.push("|---|---|---|---|");
|
|
451
|
+
for (const uc of report.l1) {
|
|
452
|
+
const tests = uc.testsFound ? "✅" : "❌";
|
|
453
|
+
lines.push(`| ${uc.id} | ${uc.title} | ✅ | ${tests} |`);
|
|
454
|
+
}
|
|
455
|
+
lines.push("");
|
|
456
|
+
if (unboundUcs.length > 0) {
|
|
457
|
+
lines.push(`> ⚠️ **${unboundUcs.length} use case(s) have no test binding** — tests pass but these UCs have no test file that references their ID.`);
|
|
458
|
+
lines.push(`> This means the UCs are documentation, not contracts. Add a test that imports or references \`${unboundUcs[0].id}\` (even as a describe label) to establish the binding.`);
|
|
459
|
+
lines.push(`> Unbound: ${unboundUcs.map((u) => u.id).join(", ")}`);
|
|
460
|
+
lines.push("");
|
|
461
|
+
}
|
|
462
|
+
// L1 gate violations
|
|
463
|
+
if (report.l1GateViolations.length > 0) {
|
|
464
|
+
lines.push("**Active L1 gate violations:**");
|
|
465
|
+
for (const v of report.l1GateViolations) {
|
|
466
|
+
lines.push(`- ❌ ${v.gateId}: ${v.message}`);
|
|
467
|
+
}
|
|
468
|
+
lines.push("");
|
|
469
|
+
}
|
|
470
|
+
// L2
|
|
471
|
+
const l2Passing = report.l2.filter((u) => u.hasProbe).length;
|
|
472
|
+
const l2Pct = total > 0 ? Math.round((l2Passing / total) * 100) : 0;
|
|
473
|
+
const harnessSummary = buildHarnessRunSummary(report.projectDir ?? "");
|
|
474
|
+
const lastRunStr = harnessSummary.lastRunTimestamp ?? "never run";
|
|
475
|
+
// Scenario counts across all UCs
|
|
476
|
+
const happyCount = report.l2.filter((u) => u.hasHappyProbe).length;
|
|
477
|
+
const errorTotal = report.l2.reduce((sum, u) => sum + u.errorProbeCount, 0);
|
|
478
|
+
lines.push("### L2: Behavioral Harness");
|
|
479
|
+
lines.push(`${l2Passing}/${total} use cases have harness specs (.forgecraft/harness/)`);
|
|
480
|
+
lines.push(`${harnessSummary.probeFilesFound}/${total} have executable probe files (tests/harness/)`);
|
|
481
|
+
lines.push(`Last harness run: ${lastRunStr}`);
|
|
482
|
+
lines.push("");
|
|
483
|
+
lines.push("| UC | Title | Happy | Error Paths | Last Run |");
|
|
484
|
+
lines.push("|---|---|---|---|---|");
|
|
485
|
+
for (const uc of report.l2) {
|
|
486
|
+
const happyCell = uc.hasHappyProbe
|
|
487
|
+
? "✅"
|
|
488
|
+
: uc.hasProbe
|
|
489
|
+
? "❌ missing"
|
|
490
|
+
: "❌ missing";
|
|
491
|
+
const errorCell = uc.errorProbeCount > 0
|
|
492
|
+
? `${uc.errorProbeCount} probe${uc.errorProbeCount === 1 ? "" : "s"}`
|
|
493
|
+
: "0 probes ⚠️";
|
|
494
|
+
const lastRunCell = uc.lastRunStatus
|
|
495
|
+
? uc.lastRunStatus === "pass"
|
|
496
|
+
? "✅ pass"
|
|
497
|
+
: `❌ ${uc.lastRunStatus}`
|
|
498
|
+
: "—";
|
|
499
|
+
lines.push(`| ${uc.id} | ${uc.title} | ${happyCell} | ${errorCell} | ${lastRunCell} |`);
|
|
500
|
+
}
|
|
501
|
+
lines.push("");
|
|
502
|
+
lines.push(`**Scenario coverage**: ${happyCount}/${total} happy paths | ${errorTotal}/${total * 3} error paths`);
|
|
503
|
+
if (harnessSummary.lastRunTimestamp) {
|
|
504
|
+
const harnessRun = readHarnessRunForSummary(report.projectDir ?? "");
|
|
505
|
+
if (harnessRun) {
|
|
506
|
+
lines.push(`**Last harness run**: ${harnessRun.timestamp} (${harnessRun.passed} passed / ${harnessRun.failed} failed / ${harnessRun.notFound} not run)`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
lines.push("");
|
|
510
|
+
// Postcondition coverage
|
|
511
|
+
const coverage = computePostconditionCoverage(report.projectDir ?? "", report.ucs);
|
|
512
|
+
const hollowCount = coverage.filter((c) => c.hollow).length;
|
|
513
|
+
const uncoveredCount = coverage.filter((c) => c.coverageRatio < 0.4 && c.probeFiles.length > 0).length;
|
|
514
|
+
lines.push("**Postcondition Coverage** (assertions in probe files vs. spec postconditions):");
|
|
515
|
+
lines.push(formatCoverageTable(coverage));
|
|
516
|
+
if (hollowCount > 0) {
|
|
517
|
+
lines.push(`⚠️ ${hollowCount} hollow probe(s) — pass with zero assertions. Add assertion checks.`);
|
|
518
|
+
}
|
|
519
|
+
if (uncoveredCount > 0) {
|
|
520
|
+
lines.push(`⚠️ ${uncoveredCount} UC(s) have probes but assertion count < 40% of postconditions.`);
|
|
521
|
+
}
|
|
522
|
+
lines.push("");
|
|
523
|
+
lines.push(`**L2 coverage: ${l2Pct}% — ${total - l2Passing} use case(s) need probe definitions**`);
|
|
524
|
+
lines.push("");
|
|
525
|
+
lines.push("To add a probe: create `.forgecraft/harness/uc-NNN.yaml` with at least one probe entry.");
|
|
526
|
+
lines.push("");
|
|
527
|
+
// L3
|
|
528
|
+
lines.push("### L3: Environment & Infrastructure");
|
|
529
|
+
for (const [label, passing] of Object.entries(report.l3Checks)) {
|
|
530
|
+
lines.push(`- ${passing ? "✅" : "❌"} ${label}`);
|
|
531
|
+
}
|
|
532
|
+
if (report.envProbeEvidence !== null) {
|
|
533
|
+
const ev = report.envProbeEvidence;
|
|
534
|
+
lines.push(`- Env probe evidence: ✅ ${ev.passed} passed / ${ev.failed} failed (last run: ${ev.timestamp})`);
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
lines.push(`- Env probe evidence: ⚠️ not yet run — call run_env_probe`);
|
|
538
|
+
}
|
|
539
|
+
lines.push("");
|
|
540
|
+
// L4
|
|
541
|
+
lines.push("### L4: Self-Monitoring");
|
|
542
|
+
for (const [label, passing] of Object.entries(report.l4Checks)) {
|
|
543
|
+
lines.push(`- ${passing ? "✅" : "❌"} ${label}`);
|
|
544
|
+
}
|
|
545
|
+
if (report.sloProbeEvidence !== null) {
|
|
546
|
+
const ev = report.sloProbeEvidence;
|
|
547
|
+
lines.push(`- SLO probe evidence: ✅ ${ev.passed} passed / ${ev.failed} failed (last run: ${ev.timestamp})`);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
lines.push(`- SLO probe evidence: ⚠️ not yet run — call run_slo_probe`);
|
|
551
|
+
}
|
|
552
|
+
lines.push("");
|
|
553
|
+
// Summary
|
|
554
|
+
const l3Score = report.l3 === "complete"
|
|
555
|
+
? "complete"
|
|
556
|
+
: report.l3 === "partial"
|
|
557
|
+
? "partial"
|
|
558
|
+
: "not-started";
|
|
559
|
+
const l4Score = report.l4 === "complete"
|
|
560
|
+
? "complete"
|
|
561
|
+
: report.l4 === "partial"
|
|
562
|
+
? "partial"
|
|
563
|
+
: "not-started";
|
|
564
|
+
const l1Pct = 100;
|
|
565
|
+
lines.push("### Summary");
|
|
566
|
+
lines.push("| Layer | Status | Score |");
|
|
567
|
+
lines.push("|---|---|---|");
|
|
568
|
+
lines.push(`| L1 Blueprint | ${total}/${total} | ${l1Pct}% |`);
|
|
569
|
+
lines.push(`| L2 Harness | ${l2Passing}/${total} | ${l2Pct}% |`);
|
|
570
|
+
lines.push(`| L3 Environment | ${l3Score} | — |`);
|
|
571
|
+
lines.push(`| L4 Monitoring | ${l4Score} | — |`);
|
|
572
|
+
lines.push("");
|
|
573
|
+
// Next action
|
|
574
|
+
const nextAction = deriveNextAction(report, l2Passing, total);
|
|
575
|
+
lines.push(`**Next action**: ${nextAction}`);
|
|
576
|
+
return lines.join("\n");
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Append layer summary for projects with no use cases.
|
|
580
|
+
*/
|
|
581
|
+
function appendLayerSummaryNoUcs(lines, report) {
|
|
582
|
+
lines.push("");
|
|
583
|
+
lines.push("### L3: Environment & Infrastructure");
|
|
584
|
+
for (const [label, passing] of Object.entries(report.l3Checks)) {
|
|
585
|
+
lines.push(`- ${passing ? "✅" : "❌"} ${label}`);
|
|
586
|
+
}
|
|
587
|
+
if (report.envProbeEvidence !== null) {
|
|
588
|
+
const ev = report.envProbeEvidence;
|
|
589
|
+
lines.push(`- Env probe evidence: ✅ ${ev.passed} passed / ${ev.failed} failed (last run: ${ev.timestamp})`);
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
lines.push(`- Env probe evidence: ⚠️ not yet run — call run_env_probe`);
|
|
593
|
+
}
|
|
594
|
+
lines.push("");
|
|
595
|
+
lines.push("### L4: Self-Monitoring");
|
|
596
|
+
for (const [label, passing] of Object.entries(report.l4Checks)) {
|
|
597
|
+
lines.push(`- ${passing ? "✅" : "❌"} ${label}`);
|
|
598
|
+
}
|
|
599
|
+
if (report.sloProbeEvidence !== null) {
|
|
600
|
+
const ev = report.sloProbeEvidence;
|
|
601
|
+
lines.push(`- SLO probe evidence: ✅ ${ev.passed} passed / ${ev.failed} failed (last run: ${ev.timestamp})`);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
lines.push(`- SLO probe evidence: ⚠️ not yet run — call run_slo_probe`);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Derive the highest-impact next action from the current layer report.
|
|
609
|
+
*/
|
|
610
|
+
function deriveNextAction(report, l2Passing, total) {
|
|
611
|
+
if (total === 0) {
|
|
612
|
+
return "Create docs/use-cases.md with formal UC entries to begin layer tracking.";
|
|
613
|
+
}
|
|
614
|
+
if (l2Passing < total) {
|
|
615
|
+
const missing = report.l2
|
|
616
|
+
.filter((u) => !u.hasProbe)
|
|
617
|
+
.map((u) => u.id)
|
|
618
|
+
.slice(0, 3)
|
|
619
|
+
.join(", ");
|
|
620
|
+
return `Add L2 harness probes for: ${missing}. Create .forgecraft/harness/uc-NNN.yaml.`;
|
|
621
|
+
}
|
|
622
|
+
if (report.l3 === "not-started") {
|
|
623
|
+
return "Add L3 infrastructure config: CI workflow, Dockerfile, or .env.example.";
|
|
624
|
+
}
|
|
625
|
+
if (report.l3 === "partial") {
|
|
626
|
+
return "Complete L3 environment coverage: add missing CI, env schema, or deployment config.";
|
|
627
|
+
}
|
|
628
|
+
if (report.l4 === "not-started") {
|
|
629
|
+
return "Add L4 monitoring: create .forgecraft/health/ or src/health/ with probe definitions.";
|
|
630
|
+
}
|
|
631
|
+
return "All layers complete. Run close_cycle to finalize the cycle.";
|
|
632
|
+
}
|
|
633
|
+
// ── Handler ───────────────────────────────────────────────────────────
|
|
634
|
+
/**
|
|
635
|
+
* Handler for the layer_status action.
|
|
636
|
+
*
|
|
637
|
+
* @param args - Validated input with project_dir
|
|
638
|
+
* @returns MCP-style tool result with formatted layer report
|
|
639
|
+
*/
|
|
640
|
+
export async function layerStatusHandler(args) {
|
|
641
|
+
const projectDir = resolve(args.project_dir);
|
|
642
|
+
const report = buildLayerReport(projectDir);
|
|
643
|
+
return {
|
|
644
|
+
content: [{ type: "text", text: formatLayerReport(report) }],
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
//# sourceMappingURL=layer-status.js.map
|