icdev 0.0.3__py3-none-any.whl
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.
- args/agent_config.yaml +113 -0
- args/audit_regimes/cisa_sbd.json +381 -0
- args/audit_regimes/cmmc_l2.json +906 -0
- args/audit_regimes/dod_cssp.json +393 -0
- args/audit_regimes/dodi_5000_87.json +297 -0
- args/audit_regimes/fedramp_moderate.json +650 -0
- args/audit_regimes/ieee_1012.json +373 -0
- args/audit_regimes/nist_800_171.json +624 -0
- args/audit_regimes/nist_800_53.json +907 -0
- args/cloudforge_blueprints/aws_commercial.yaml +29 -0
- args/cloudforge_blueprints/aws_govcloud_il4.yaml +34 -0
- args/cloudforge_blueprints/aws_govcloud_il5.yaml +38 -0
- args/cloudforge_blueprints/azure_commercial.yaml +28 -0
- args/cloudforge_blueprints/azure_gov_il4.yaml +32 -0
- args/cloudforge_blueprints/azure_gov_il5.yaml +36 -0
- args/cloudforge_blueprints/gcp_commercial.yaml +28 -0
- args/cloudforge_blueprints/oci_commercial.yaml +28 -0
- args/cloudforge_config.yaml +231 -0
- args/cloudforge_runbook_templates/backup_verify.yaml +98 -0
- args/cloudforge_runbook_templates/dr_failover.yaml +107 -0
- args/cloudforge_runbook_templates/health_check.yaml +97 -0
- args/cloudforge_runbook_templates/incident_response.yaml +101 -0
- args/cloudforge_runbook_templates/migration_cutover.yaml +105 -0
- args/cloudforge_runbook_templates/patch_rollout.yaml +92 -0
- args/cloudforge_runbook_templates/zone_provision.yaml +93 -0
- args/code_pattern_config.yaml +151 -0
- args/code_quality_config.yaml +47 -0
- args/compliance_config.yaml +17 -0
- args/control_inheritance.yaml +177 -0
- args/csp_mcp_config.yaml +41 -0
- args/cui_markings.yaml +35 -0
- args/databridge_config.yaml +232 -0
- args/db_config.yaml +116 -0
- args/decision_tables/agent_trust_decision.yaml +143 -0
- args/decision_tables/ato_boundary_impact.yaml +132 -0
- args/decision_tables/deployment_approval.yaml +152 -0
- args/degradation_matrix.yaml +163 -0
- args/devsecops_config.yaml +286 -0
- args/endpoint_security_config.yaml +207 -0
- args/exit_criteria.yaml +102 -0
- args/feature_flags.yaml +235 -0
- args/file_access_tiers.yaml +88 -0
- args/forge_studio/blueprint_config.yaml +27 -0
- args/forge_studio/component_catalog.json +411 -0
- args/forge_studio/workflow_templates.yaml +103 -0
- args/govcon_config.yaml +41 -0
- args/harness_config.yaml +67 -0
- args/innovation_config.yaml +321 -0
- args/knowledge_graph_config.yaml +113 -0
- args/llm_config.yaml +222 -0
- args/marketplace_config.yaml +260 -0
- args/monitoring_config.yaml +127 -0
- args/mosa_config.yaml +190 -0
- args/observability_tracing_config.yaml +170 -0
- args/owasp_agentic_config.yaml +171 -0
- args/pipeline_gates.yaml +197 -0
- args/project_defaults.yaml +235 -0
- args/prompt_chains.yaml +163 -0
- args/rag_config.yaml +167 -0
- args/research_config.yaml +89 -0
- args/resilience_config.yaml +197 -0
- args/ricoas_config.yaml +191 -0
- args/security_gates.yaml +763 -0
- args/storage_config.yaml +63 -0
- args/writeguard_config.yaml +131 -0
- args/zta_config.yaml +247 -0
- context/__init__.py +6 -0
- context/agent/__init__.py +6 -0
- context/agent/response_schemas/__init__.py +6 -0
- context/agent/response_schemas/debate_position.json +46 -0
- context/agent/response_schemas/fitness_scorecard.json +74 -0
- context/agent/response_schemas/review_decision.json +39 -0
- context/agent/response_schemas/task_decomposition.json +82 -0
- context/agent/response_schemas/veto_decision.json +40 -0
- context/agentic/__init__.py +6 -0
- context/agentic/architecture_patterns.md +269 -0
- context/agentic/capability_registry.yaml +223 -0
- context/agentic/csp_integration.md +30 -0
- context/agentic/csp_mcp_registry.yaml +280 -0
- context/agentic/fitness_rubric.md +56 -0
- context/agentic/governance_baseline.md +205 -0
- context/ci/__init__.py +6 -0
- context/ci/worktree_templates.json +44 -0
- context/cloud/__init__.py +6 -0
- context/cloud/csp_service_registry.json +739 -0
- context/compliance/__init__.py +6 -0
- context/compliance/ai_rmf_crosswalk.yaml +226 -0
- context/compliance/atlas_mitigations.json +293 -0
- context/compliance/atlas_techniques.json +833 -0
- context/compliance/cisa_sbd_requirements.json +477 -0
- context/compliance/cjis_security_policy.json +522 -0
- context/compliance/cmmc_practices.json +2494 -0
- context/compliance/cmmc_report_template.md +142 -0
- context/compliance/cnssi_1253_overlay.json +109 -0
- context/compliance/control_crosswalk.json +1914 -0
- context/compliance/control_families/__init__.py +6 -0
- context/compliance/csp_certifications.json +251 -0
- context/compliance/cssp_report_template.md +193 -0
- context/compliance/cui_templates/__init__.py +6 -0
- context/compliance/cui_templates/banner_block.txt +4 -0
- context/compliance/cui_templates/code_header.txt +8 -0
- context/compliance/cui_templates/document_template.md +35 -0
- context/compliance/data_type_framework_map.json +321 -0
- context/compliance/data_type_registry.json +147 -0
- context/compliance/dod_cssp_8530.json +463 -0
- context/compliance/eu_ai_act_annex_iii.json +108 -0
- context/compliance/export_templates/__init__.py +6 -0
- context/compliance/export_templates/emass_controls.csv.j2 +4 -0
- context/compliance/export_templates/evidence_package.md.j2 +39 -0
- context/compliance/export_templates/executive_summary.md.j2 +55 -0
- context/compliance/export_templates/poam_tracking.csv.j2 +4 -0
- context/compliance/fedramp_20x_ksi_schemas.json +133 -0
- context/compliance/fedramp_high_baseline.json +4370 -0
- context/compliance/fedramp_moderate_baseline.json +2183 -0
- context/compliance/fedramp_report_template.md +181 -0
- context/compliance/fips_200_areas.json +362 -0
- context/compliance/gao_ai_accountability.json +262 -0
- context/compliance/hipaa_security_rule.json +720 -0
- context/compliance/hitrust_csf_v11.json +930 -0
- context/compliance/impact_level_profiles.json +251 -0
- context/compliance/incident_response_template.md +1110 -0
- context/compliance/iso27001_2022_controls.json +750 -0
- context/compliance/iso27001_nist_bridge.json +382 -0
- context/compliance/iso42001_controls.json +254 -0
- context/compliance/ivv_checklist_template.md +80 -0
- context/compliance/ivv_report_template.md +116 -0
- context/compliance/ivv_requirements.json +372 -0
- context/compliance/mosa_crosswalk.json +327 -0
- context/compliance/mosa_framework.json +250 -0
- context/compliance/narrative_templates/AC.md.j2 +101 -0
- context/compliance/narrative_templates/AU.md.j2 +106 -0
- context/compliance/narrative_templates/IA.md.j2 +104 -0
- context/compliance/narrative_templates/SC.md.j2 +102 -0
- context/compliance/narrative_templates/SI.md.j2 +111 -0
- context/compliance/narrative_templates/__init__.py +6 -0
- context/compliance/narrative_templates/default.md.j2 +50 -0
- context/compliance/narrative_templates/executive_summary.j2 +27 -0
- context/compliance/narrative_templates/poam_milestone.j2 +19 -0
- context/compliance/narrative_templates/ssp_section.j2 +11 -0
- context/compliance/nist_800_171_controls.json +1552 -0
- context/compliance/nist_800_207_crosswalk.json +399 -0
- context/compliance/nist_800_207_zta.json +258 -0
- context/compliance/nist_800_53.json +324 -0
- context/compliance/nist_ai_600_1_genai.json +326 -0
- context/compliance/nist_ai_rmf.json +206 -0
- context/compliance/nist_sp_800_60_types.json +1667 -0
- context/compliance/omb_m25_21_high_impact_ai.json +248 -0
- context/compliance/omb_m26_04_unbiased_ai.json +262 -0
- context/compliance/owasp_agentic_asi.json +133 -0
- context/compliance/owasp_agentic_threats.json +285 -0
- context/compliance/owasp_llm_top10.json +274 -0
- context/compliance/pci_dss_v4.json +510 -0
- context/compliance/poam_template.md +117 -0
- context/compliance/safeai_controls.json +512 -0
- context/compliance/sbd_report_template.md +77 -0
- context/compliance/siem_config_templates/__init__.py +6 -0
- context/compliance/siem_config_templates/filebeat.yml +213 -0
- context/compliance/siem_config_templates/log_sources.json +208 -0
- context/compliance/soc2_trust_criteria.json +661 -0
- context/compliance/ssp_template.md +432 -0
- context/compliance/stig_templates/__init__.py +6 -0
- context/compliance/stig_templates/webapp_stig.json +139 -0
- context/compliance/xai_requirements.json +108 -0
- context/dashboard/__init__.py +6 -0
- context/dashboard/nlq_examples.json +50 -0
- context/dashboard/schema_descriptions.json +23 -0
- context/icdev_methodology.md +100 -0
- context/integration/__init__.py +6 -0
- context/integration/approval_workflows.json +32 -0
- context/integration/gitlab_field_mappings.json +33 -0
- context/integration/jira_field_mappings.json +32 -0
- context/integration/reqif_export_schema.json +23 -0
- context/integration/servicenow_field_mappings.json +22 -0
- context/languages/__init__.py +6 -0
- context/languages/framework_patterns.json +205 -0
- context/languages/language_registry.json +279 -0
- context/llm/__init__.py +6 -0
- context/llm/example_provider.py +89 -0
- context/marketplace/assets/writeguard-core.yaml +100 -0
- context/marketplace/assets/writeguard-govcon.yaml +45 -0
- context/marketplace/assets/writeguard-style-guides.yaml +44 -0
- context/mbse/__init__.py +6 -0
- context/mbse/des_report_template.md +162 -0
- context/mbse/des_requirements.json +411 -0
- context/mbse/digital_thread_patterns.json +403 -0
- context/mbse/reqif_schema.json +280 -0
- context/mbse/sysml_element_types.json +432 -0
- context/oscal/NIST_SP-800-53_rev5_catalog.json +254987 -0
- context/oscal/README.md +43 -0
- context/patterns/__init__.py +6 -0
- context/profiles/__init__.py +6 -0
- context/profiles/dod_baseline_v1.yaml +145 -0
- context/profiles/fedramp_baseline_v1.yaml +143 -0
- context/profiles/financial_baseline_v1.yaml +142 -0
- context/profiles/healthcare_baseline_v1.yaml +135 -0
- context/profiles/law_enforcement_v1.yaml +129 -0
- context/profiles/startup_v1.yaml +134 -0
- context/rag/source_mappings.json +42 -0
- context/requirements/__init__.py +6 -0
- context/requirements/ambiguity_patterns.json +97 -0
- context/requirements/boundary_impact_rules.json +123 -0
- context/requirements/default_constitutions.json +67 -0
- context/requirements/document_extraction_rules.json +58 -0
- context/requirements/gap_patterns.json +108 -0
- context/requirements/readiness_rubric.json +78 -0
- context/requirements/red_alternative_patterns.json +210 -0
- context/requirements/safe_templates.json +72 -0
- context/requirements/spec_quality_checklist.json +122 -0
- context/research/regulatory_registry.json +114 -0
- context/research/verticals/cybersecurity.json +127 -0
- context/research/verticals/defense.json +104 -0
- context/research/verticals/fintech.json +125 -0
- context/research/verticals/healthcare.json +118 -0
- context/research/verticals/logistics.json +117 -0
- context/research/verticals/trading.json +145 -0
- context/simulation/__init__.py +6 -0
- context/simulation/architecture_patterns.json +36 -0
- context/simulation/coa_templates.json +38 -0
- context/simulation/cost_models.json +23 -0
- context/simulation/risk_categories.json +46 -0
- context/supply_chain/__init__.py +6 -0
- context/supply_chain/isa_templates.json +129 -0
- context/supply_chain/nist_800_161_controls.json +247 -0
- context/supply_chain/scrm_risk_matrix.json +147 -0
- context/templates/__init__.py +6 -0
- context/templates/ansible/__init__.py +6 -0
- context/templates/ansible/playbooks/__init__.py +6 -0
- context/templates/ansible/roles/__init__.py +6 -0
- context/templates/gitlab_ci/__init__.py +6 -0
- context/templates/grafana/__init__.py +6 -0
- context/templates/kubernetes/__init__.py +6 -0
- context/templates/project/__init__.py +6 -0
- context/templates/project/api/__init__.py +6 -0
- context/templates/project/cli/__init__.py +6 -0
- context/templates/project/data_pipeline/__init__.py +6 -0
- context/templates/project/iac/__init__.py +6 -0
- context/templates/project/javascript_frontend/__init__.py +6 -0
- context/templates/project/javascript_frontend/src/__init__.py +6 -0
- context/templates/project/javascript_frontend/tests/__init__.py +6 -0
- context/templates/project/microservice/__init__.py +6 -0
- context/templates/project/python_backend/__init__.py +6 -0
- context/templates/project/python_backend/src/__init__.py +6 -0
- context/templates/project/python_backend/tests/__init__.py +6 -0
- context/templates/project/python_backend/tests/features/__init__.py +6 -0
- context/templates/project/python_backend/tests/steps/__init__.py +6 -0
- context/templates/terraform/__init__.py +6 -0
- context/templates/terraform/govcloud_base/__init__.py +6 -0
- context/templates/terraform/modules/__init__.py +6 -0
- context/tone/__init__.py +6 -0
- context/writing/grammar_rules/common_errors.json +306 -0
- context/writing/grammar_rules/govcon_vocabulary.json +113 -0
- context/writing/style_guides/academic.yaml +43 -0
- context/writing/style_guides/business.yaml +42 -0
- context/writing/style_guides/government.yaml +59 -0
- context/writing/style_guides/proposal.yaml +58 -0
- context/writing/style_guides/technical.yaml +43 -0
- docs/adr/README.md +66 -0
- docs/adr/connector-forge-decisions.md +318 -0
- docs/adr/core-decisions.md +289 -0
- docs/adr/db-decisions.md +94 -0
- docs/adr/harness-decisions.md +122 -0
- docs/adr/innovation-decisions.md +262 -0
- docs/adr/marketplace-decisions.md +109 -0
- docs/adr/sbd-decisions.md +109 -0
- docs/adr/scale-engine-decisions.md +108 -0
- docs/adr/writeguard-decisions.md +136 -0
- docs/architecture/bounded-contexts.md +1032 -0
- docs/features/phase-65-writeguard.md +139 -0
- docs/features/phase-66-marketplace-commerce.md +79 -0
- docs/features/phase-67-knowledge-ingestion-rag-autodraft.md +97 -0
- docs/features/phase-68-enhanced-autodraft-pipeline.md +109 -0
- docs/features/phase-69-proposalai-marketplace-module.md +131 -0
- docs/features/phase-70-databridge.md +214 -0
- docs/features/phase-71-databridge-messaging.md +102 -0
- docs/implementation-plan-architecture-evolution.md +614 -0
- docs/marketplace/CONTRIBUTING.md +124 -0
- docs/marketplace/module_manifest_schema.yaml +83 -0
- docs/research/ai-architecture-patterns-2024-2026.md +1236 -0
- docs/research/app-builder-platform-analysis.md +582 -0
- docs/research/architecture-patterns-c4-ddd-agentic.md +871 -0
- docs/research/flowable-boat-competitive-analysis.md +426 -0
- docs/research/modern-dev-practices-2024-2026.md +1615 -0
- docs/research/secure-by-design-cloudyrion-adaptation.md +270 -0
- goals/agent_management.md +144 -0
- goals/ai_accountability.md +90 -0
- goals/ai_narratives.md +79 -0
- goals/ai_transparency.md +76 -0
- goals/ato_simulator.md +78 -0
- goals/audit_engine.md +177 -0
- goals/bite_sized_plans.md +225 -0
- goals/boundary_supply_chain.md +206 -0
- goals/brainstorming_gate.md +186 -0
- goals/build_app.md +604 -0
- goals/cato_live_evidence.md +77 -0
- goals/cloudforge.md +106 -0
- goals/code_intelligence.md +197 -0
- goals/compliance_workflow.md +858 -0
- goals/connector_forge.md +133 -0
- goals/databridge.md +128 -0
- goals/deploy_workflow.md +390 -0
- goals/developer_scorecard.md +78 -0
- goals/devsecops_workflow.md +408 -0
- goals/firmware_sbom.md +79 -0
- goals/forge_hub.md +78 -0
- goals/golden_path.md +77 -0
- goals/harness_engineering.md +91 -0
- goals/integration_testing.md +189 -0
- goals/knowledge_graph.md +128 -0
- goals/maintenance_audit.md +196 -0
- goals/manifest.md +50 -0
- goals/monitoring.md +126 -0
- goals/mosa_workflow.md +463 -0
- goals/multi_agent_orchestration.md +68 -0
- goals/observability_traceability_xai.md +154 -0
- goals/owasp_agentic_security.md +395 -0
- goals/pr_intelligence.md +78 -0
- goals/requirements_intake.md +213 -0
- goals/secure_by_design.md +135 -0
- goals/security_scan.md +381 -0
- goals/self_healing.md +120 -0
- goals/simulation_engine.md +111 -0
- goals/subagent_review.md +205 -0
- goals/systematic_debugging.md +257 -0
- goals/tdd_workflow.md +403 -0
- goals/template_exchange.md +77 -0
- goals/thread_heatmap.md +77 -0
- goals/threat_modeler.md +77 -0
- goals/verification_iron_law.md +192 -0
- goals/vsm_dashboard.md +76 -0
- goals/writeguard.md +89 -0
- goals/zero_trust_architecture.md +403 -0
- hardprompts/__init__.py +6 -0
- hardprompts/agent/__init__.py +6 -0
- hardprompts/agent/agentic_architect.md +100 -0
- hardprompts/agent/debate_prompt.md +32 -0
- hardprompts/agent/fitness_evaluation.md +48 -0
- hardprompts/agent/governance_review.md +214 -0
- hardprompts/agent/reviewer_prompt.md +34 -0
- hardprompts/agent/skill_design.md +172 -0
- hardprompts/agent/task_decomposition.md +275 -0
- hardprompts/agent/veto_check_prompt.md +33 -0
- hardprompts/architect/__init__.py +6 -0
- hardprompts/architect/api_design.md +283 -0
- hardprompts/architect/data_model.md +277 -0
- hardprompts/architect/system_design.md +180 -0
- hardprompts/builder/__init__.py +6 -0
- hardprompts/builder/code_generation.md +59 -0
- hardprompts/builder/refactor.md +58 -0
- hardprompts/builder/scaffold_project.md +69 -0
- hardprompts/builder/test_generation.md +87 -0
- hardprompts/ci/__init__.py +6 -0
- hardprompts/ci/worktree_setup.md +35 -0
- hardprompts/compliance/__init__.py +6 -0
- hardprompts/compliance/cmmc_assessment.md +63 -0
- hardprompts/compliance/cssp_assessment.md +75 -0
- hardprompts/compliance/cui_marking.md +86 -0
- hardprompts/compliance/fedramp_assessment.md +55 -0
- hardprompts/compliance/ivv_assessment.md +96 -0
- hardprompts/compliance/poam_generation.md +57 -0
- hardprompts/compliance/sbd_assessment.md +101 -0
- hardprompts/compliance/security_categorization.md +74 -0
- hardprompts/compliance/ssp_generation.md +56 -0
- hardprompts/compliance/stig_evaluation.md +63 -0
- hardprompts/dashboard/__init__.py +6 -0
- hardprompts/dashboard/nlq_system_prompt.md +26 -0
- hardprompts/infra/__init__.py +6 -0
- hardprompts/infra/k8s_manifests.md +118 -0
- hardprompts/infra/pipeline_generation.md +160 -0
- hardprompts/infra/terraform_generation.md +92 -0
- hardprompts/integration/__init__.py +6 -0
- hardprompts/integration/approval_review.md +17 -0
- hardprompts/integration/jira_mapping.md +25 -0
- hardprompts/integration/servicenow_mapping.md +14 -0
- hardprompts/knowledge/__init__.py +6 -0
- hardprompts/knowledge/pattern_detection.md +73 -0
- hardprompts/knowledge/recommendation_engine.md +90 -0
- hardprompts/knowledge/root_cause_analysis.md +91 -0
- hardprompts/maintenance/__init__.py +6 -0
- hardprompts/maintenance/maintenance_assessment.md +82 -0
- hardprompts/mbse/__init__.py +6 -0
- hardprompts/mbse/digital_thread.md +67 -0
- hardprompts/mbse/model_import.md +62 -0
- hardprompts/mbse/model_to_code.md +65 -0
- hardprompts/modernization/__init__.py +6 -0
- hardprompts/modernization/legacy_analysis.md +93 -0
- hardprompts/modernization/migration_planning.md +150 -0
- hardprompts/modernization/seven_r_assessment.md +107 -0
- hardprompts/proposal_draft.md +53 -0
- hardprompts/rag_citation.md +12 -0
- hardprompts/rag_rerank.md +31 -0
- hardprompts/requirements/__init__.py +6 -0
- hardprompts/requirements/bdd_generation.md +35 -0
- hardprompts/requirements/clarification_prioritization.md +29 -0
- hardprompts/requirements/decomposition.md +60 -0
- hardprompts/requirements/document_extraction.md +45 -0
- hardprompts/requirements/gap_detection.md +70 -0
- hardprompts/requirements/intake_conversation.md +101 -0
- hardprompts/requirements/readiness_assessment.md +39 -0
- hardprompts/requirements/spec_quality.md +33 -0
- hardprompts/requirements/traceability_analysis.md +23 -0
- hardprompts/security/__init__.py +6 -0
- hardprompts/security/endpoint_security.md +78 -0
- hardprompts/security/threat_model.md +70 -0
- hardprompts/security/vulnerability_assessment.md +81 -0
- hardprompts/simulation/__init__.py +6 -0
- hardprompts/simulation/architecture_impact.md +27 -0
- hardprompts/simulation/coa_alternative.md +27 -0
- hardprompts/simulation/coa_generation.md +25 -0
- hardprompts/simulation/compliance_impact.md +28 -0
- hardprompts/simulation/cost_estimation.md +33 -0
- hardprompts/simulation/risk_assessment.md +28 -0
- hardprompts/translation/code_translation.md +68 -0
- hardprompts/translation/dependency_suggestion.md +44 -0
- hardprompts/translation/test_translation.md +64 -0
- hardprompts/translation/translation_repair.md +59 -0
- icdev-0.0.3.dist-info/METADATA +909 -0
- icdev-0.0.3.dist-info/RECORD +1214 -0
- icdev-0.0.3.dist-info/WHEEL +5 -0
- icdev-0.0.3.dist-info/entry_points.txt +9 -0
- icdev-0.0.3.dist-info/licenses/LICENSE +201 -0
- icdev-0.0.3.dist-info/licenses/NOTICE +11 -0
- icdev-0.0.3.dist-info/top_level.txt +7 -0
- memory/MEMORY.md +52 -0
- memory/logs/2026-02-14.md +17 -0
- memory/logs/2026-03-03.md +2 -0
- memory/logs/__init__.py +1 -0
- tools/a2a/icdev_callback_client.py +210 -0
- tools/agent/cards/architect_card.json +29 -0
- tools/agent/cards/builder_card.json +34 -0
- tools/agent/cards/compliance_card.json +29 -0
- tools/agent/cards/connector_forge_card.json +49 -0
- tools/agent/cards/devsecops_zta_card.json +24 -0
- tools/agent/cards/knowledge_card.json +29 -0
- tools/agent/cards/monitor_card.json +29 -0
- tools/agent/cards/orchestrator_card.json +29 -0
- tools/agent/cards/requirements_analyst_card.json +24 -0
- tools/agent/cards/security_card.json +29 -0
- tools/agent/cards/simulation_card.json +24 -0
- tools/agent/cards/supply_chain_card.json +24 -0
- tools/analysis/__init__.py +1 -0
- tools/analysis/code_analyzer.py +770 -0
- tools/analysis/runtime_feedback.py +379 -0
- tools/analytics/__init__.py +2 -0
- tools/analytics/scorecard.py +538 -0
- tools/analytics/vsm_engine.py +612 -0
- tools/architecture/__init__.py +2 -0
- tools/architecture/adr_extractor.py +393 -0
- tools/audit/__init__.py +1 -0
- tools/audit/audit_logger.py +199 -0
- tools/audit/audit_query.py +153 -0
- tools/audit/decision_recorder.py +73 -0
- tools/audit_engine/__init__.py +12 -0
- tools/audit_engine/ai_advisor.py +906 -0
- tools/audit_engine/cli.py +286 -0
- tools/audit_engine/comparator.py +305 -0
- tools/audit_engine/eject_scaffolder.py +399 -0
- tools/audit_engine/engine.py +614 -0
- tools/audit_engine/git_fetcher.py +341 -0
- tools/audit_engine/regime_loader.py +200 -0
- tools/audit_engine/regime_updater.py +325 -0
- tools/audit_engine/report_card.py +289 -0
- tools/audit_engine/scanner.py +684 -0
- tools/audit_engine/self_heal.py +1042 -0
- tools/ci/__init__.py +2 -0
- tools/ci/connectors/__init__.py +2 -0
- tools/ci/connectors/base_connector.py +80 -0
- tools/ci/connectors/connector_registry.py +188 -0
- tools/ci/connectors/mattermost_connector.py +159 -0
- tools/ci/connectors/slack_connector.py +197 -0
- tools/ci/core/__init__.py +2 -0
- tools/ci/core/air_gap_detector.py +115 -0
- tools/ci/core/comment_handler.py +192 -0
- tools/ci/core/conversation_manager.py +480 -0
- tools/ci/core/event_envelope.py +500 -0
- tools/ci/core/event_router.py +444 -0
- tools/ci/core/failure_parser.py +397 -0
- tools/ci/core/recovery_engine.py +527 -0
- tools/ci/gate_enforcer.py +361 -0
- tools/ci/modules/__init__.py +2 -0
- tools/ci/modules/agent.py +271 -0
- tools/ci/modules/git_ops.py +175 -0
- tools/ci/modules/state.py +117 -0
- tools/ci/modules/vcs.py +303 -0
- tools/ci/modules/workflow_ops.py +295 -0
- tools/ci/modules/worktree.py +337 -0
- tools/ci/pipeline_config_generator.py +558 -0
- tools/ci/pr_intelligence.py +485 -0
- tools/ci/triggers/__init__.py +2 -0
- tools/ci/triggers/gitlab_task_monitor.py +327 -0
- tools/ci/triggers/poll_trigger.py +237 -0
- tools/ci/triggers/webhook_server.py +356 -0
- tools/ci/workflows/__init__.py +2 -0
- tools/ci/workflows/icdev_build.py +140 -0
- tools/ci/workflows/icdev_comply.py +284 -0
- tools/ci/workflows/icdev_document.py +152 -0
- tools/ci/workflows/icdev_e2e.py +188 -0
- tools/ci/workflows/icdev_patch.py +186 -0
- tools/ci/workflows/icdev_plan.py +202 -0
- tools/ci/workflows/icdev_plan_build.py +41 -0
- tools/ci/workflows/icdev_plan_build_test.py +46 -0
- tools/ci/workflows/icdev_plan_build_test_review.py +47 -0
- tools/ci/workflows/icdev_review.py +126 -0
- tools/ci/workflows/icdev_sdlc.py +261 -0
- tools/ci/workflows/icdev_test.py +240 -0
- tools/cli/__init__.py +1 -0
- tools/cli/output_formatter.py +756 -0
- tools/cloudforge/__init__.py +12 -0
- tools/cloudforge/airgap/__init__.py +2 -0
- tools/cloudforge/airgap/il_classifier.py +70 -0
- tools/cloudforge/airgap/offline_validator.py +42 -0
- tools/cloudforge/airgap/shift_emulator.py +155 -0
- tools/cloudforge/airgap/sneakernet.py +91 -0
- tools/cloudforge/cd_hub/__init__.py +2 -0
- tools/cloudforge/cd_hub/canary_deployer.py +88 -0
- tools/cloudforge/cd_hub/gitops_renderer.py +123 -0
- tools/cloudforge/cd_hub/hub_controller.py +143 -0
- tools/cloudforge/cd_hub/pipeline_bridge.py +30 -0
- tools/cloudforge/cd_hub/rollback_engine.py +29 -0
- tools/cloudforge/cd_hub/spoke_agent.py +51 -0
- tools/cloudforge/compliance/__init__.py +2 -0
- tools/cloudforge/compliance/ato_accelerator.py +272 -0
- tools/cloudforge/compliance/control_inheritor.py +127 -0
- tools/cloudforge/compliance/evidence_generator.py +129 -0
- tools/cloudforge/compliance/poam_bridge.py +41 -0
- tools/cloudforge/compliance/ssp_bridge.py +52 -0
- tools/cloudforge/compliance/stig_bridge.py +41 -0
- tools/cloudforge/container_forge/__init__.py +2 -0
- tools/cloudforge/container_forge/bigbang_renderer.py +85 -0
- tools/cloudforge/container_forge/hardener.py +169 -0
- tools/cloudforge/container_forge/image_scanner_bridge.py +33 -0
- tools/cloudforge/container_forge/runtime_policy.py +87 -0
- tools/cloudforge/container_forge/sbom_bridge.py +42 -0
- tools/cloudforge/finops/__init__.py +2 -0
- tools/cloudforge/finops/anomaly_detector.py +78 -0
- tools/cloudforge/finops/budget_tracker.py +96 -0
- tools/cloudforge/finops/chargeback.py +69 -0
- tools/cloudforge/finops/cost_collector.py +141 -0
- tools/cloudforge/finops/optimizer.py +55 -0
- tools/cloudforge/hybrid/__init__.py +2 -0
- tools/cloudforge/hybrid/connection_manager.py +141 -0
- tools/cloudforge/hybrid/dns_federator.py +56 -0
- tools/cloudforge/hybrid/health_monitor.py +108 -0
- tools/cloudforge/hybrid/identity_federator.py +53 -0
- tools/cloudforge/hybrid/network_bridge.py +68 -0
- tools/cloudforge/hybrid/topology_manager.py +147 -0
- tools/cloudforge/hybrid/workload_abstractor.py +92 -0
- tools/cloudforge/iac/__init__.py +2 -0
- tools/cloudforge/iac/drift_detector.py +154 -0
- tools/cloudforge/iac/module_library.py +265 -0
- tools/cloudforge/iac/opentofu_adapter.py +89 -0
- tools/cloudforge/iac/pulumi_renderer.py +292 -0
- tools/cloudforge/iac/state_backend.py +146 -0
- tools/cloudforge/iac/terraform_renderer.py +626 -0
- tools/cloudforge/landing_zone/__init__.py +2 -0
- tools/cloudforge/landing_zone/blueprint_loader.py +98 -0
- tools/cloudforge/landing_zone/blueprint_validator.py +113 -0
- tools/cloudforge/landing_zone/zone_provisioner.py +306 -0
- tools/cloudforge/landing_zone/zone_state.py +143 -0
- tools/cloudforge/mbse_thread/__init__.py +2 -0
- tools/cloudforge/mbse_thread/ato_thread_weaver.py +111 -0
- tools/cloudforge/mbse_thread/control_tracer.py +68 -0
- tools/cloudforge/mbse_thread/system_boundary.py +83 -0
- tools/cloudforge/metastore/__init__.py +2 -0
- tools/cloudforge/metastore/dependency_graph.py +202 -0
- tools/cloudforge/metastore/discovery.py +192 -0
- tools/cloudforge/metastore/registry.py +185 -0
- tools/cloudforge/metastore/rto_tracker.py +92 -0
- tools/cloudforge/metastore/runbook_linker.py +82 -0
- tools/cloudforge/migration/__init__.py +2 -0
- tools/cloudforge/migration/assessor.py +187 -0
- tools/cloudforge/migration/cutover_orchestrator.py +117 -0
- tools/cloudforge/migration/databridge_bridge.py +92 -0
- tools/cloudforge/migration/planner.py +98 -0
- tools/cloudforge/migration/risk_scorer.py +97 -0
- tools/cloudforge/migration/validation_runner.py +45 -0
- tools/cloudforge/migration/workload_inventory.py +107 -0
- tools/cloudforge/provider.py +319 -0
- tools/cloudforge/providers/__init__.py +2 -0
- tools/cloudforge/providers/aws_commercial.py +92 -0
- tools/cloudforge/providers/aws_govcloud.py +229 -0
- tools/cloudforge/providers/aws_secret.py +83 -0
- tools/cloudforge/providers/azure_commercial.py +80 -0
- tools/cloudforge/providers/azure_gov.py +91 -0
- tools/cloudforge/providers/azure_secret.py +71 -0
- tools/cloudforge/providers/gcp.py +102 -0
- tools/cloudforge/providers/oci.py +102 -0
- tools/cloudforge/registry.py +140 -0
- tools/cloudforge/runbooks/__init__.py +2 -0
- tools/cloudforge/runbooks/ai_generator.py +119 -0
- tools/cloudforge/runbooks/dag_validator.py +219 -0
- tools/cloudforge/runbooks/engine.py +470 -0
- tools/cloudforge/runbooks/models.py +99 -0
- tools/cloudforge/runbooks/snippet_library.py +158 -0
- tools/cloudforge/runbooks/template_loader.py +122 -0
- tools/cloudforge/runbooks/visualization.py +108 -0
- tools/cloudforge/siem/__init__.py +2 -0
- tools/cloudforge/siem/alert_rules.py +86 -0
- tools/cloudforge/siem/correlation_engine.py +61 -0
- tools/cloudforge/siem/log_aggregator.py +113 -0
- tools/cloudforge/siem/siem_dashboard_data.py +28 -0
- tools/cloudforge/supply_chain/__init__.py +2 -0
- tools/cloudforge/supply_chain/bridge.py +33 -0
- tools/cloudforge/supply_chain/iac_dependency_scanner.py +36 -0
- tools/cloudforge/supply_chain/provider_trust_scorer.py +54 -0
- tools/compat/__init__.py +21 -0
- tools/compat/cli_harmonizer.py +251 -0
- tools/compat/datetime_utils.py +18 -0
- tools/compat/db_utils.py +190 -0
- tools/compat/platform_utils.py +123 -0
- tools/compliance/__init__.py +1 -0
- tools/compliance/accountability_manager.py +391 -0
- tools/compliance/ai_accountability_audit.py +287 -0
- tools/compliance/ai_impact_assessor.py +267 -0
- tools/compliance/ai_incident_response.py +295 -0
- tools/compliance/ai_inventory_manager.py +233 -0
- tools/compliance/ai_reassessment_scheduler.py +250 -0
- tools/compliance/ai_transparency_audit.py +247 -0
- tools/compliance/atlas_assessor.py +276 -0
- tools/compliance/atlas_report_generator.py +1199 -0
- tools/compliance/base_assessor.py +591 -0
- tools/compliance/cato_live_engine.py +607 -0
- tools/compliance/cato_monitor.py +1371 -0
- tools/compliance/cato_scheduler.py +698 -0
- tools/compliance/cjis_assessor.py +76 -0
- tools/compliance/classification_manager.py +1340 -0
- tools/compliance/cmmc_assessor.py +1478 -0
- tools/compliance/cmmc_report_generator.py +1087 -0
- tools/compliance/compliance_detector.py +452 -0
- tools/compliance/compliance_exporter.py +418 -0
- tools/compliance/compliance_status.py +810 -0
- tools/compliance/control_mapper.py +488 -0
- tools/compliance/crosswalk_engine.py +1208 -0
- tools/compliance/cssp_assessor.py +1032 -0
- tools/compliance/cssp_evidence_collector.py +716 -0
- tools/compliance/cssp_report_generator.py +1103 -0
- tools/compliance/cui_marker.py +387 -0
- tools/compliance/diagram_validator.py +599 -0
- tools/compliance/emass/__init__.py +2 -0
- tools/compliance/emass/emass_client.py +822 -0
- tools/compliance/emass/emass_export.py +758 -0
- tools/compliance/emass/emass_sync.py +807 -0
- tools/compliance/eu_ai_act_classifier.py +193 -0
- tools/compliance/evidence_collector.py +459 -0
- tools/compliance/fairness_assessor.py +310 -0
- tools/compliance/fedramp_20x_ksi_emitter.py +692 -0
- tools/compliance/fedramp_assessor.py +1795 -0
- tools/compliance/fedramp_authorization_packager.py +137 -0
- tools/compliance/fedramp_ksi_generator.py +349 -0
- tools/compliance/fedramp_report_generator.py +1115 -0
- tools/compliance/fips199_categorizer.py +869 -0
- tools/compliance/fips200_validator.py +304 -0
- tools/compliance/firmware_sbom.py +646 -0
- tools/compliance/gao_ai_assessor.py +228 -0
- tools/compliance/gao_evidence_builder.py +302 -0
- tools/compliance/hipaa_assessor.py +78 -0
- tools/compliance/hitrust_assessor.py +49 -0
- tools/compliance/incident_response_plan.py +705 -0
- tools/compliance/inheritance_engine.py +693 -0
- tools/compliance/iso27001_assessor.py +92 -0
- tools/compliance/iso42001_assessor.py +114 -0
- tools/compliance/ivv_assessor.py +2314 -0
- tools/compliance/ivv_report_generator.py +1649 -0
- tools/compliance/model_card_generator.py +291 -0
- tools/compliance/mosa_assessor.py +117 -0
- tools/compliance/multi_regime_assessor.py +441 -0
- tools/compliance/narrative_generator.py +1012 -0
- tools/compliance/narrative_quality_gate.py +701 -0
- tools/compliance/narrative_workflow.py +814 -0
- tools/compliance/nist_800_207_assessor.py +191 -0
- tools/compliance/nist_ai_600_1_assessor.py +185 -0
- tools/compliance/nist_ai_rmf_assessor.py +110 -0
- tools/compliance/nist_lookup.py +244 -0
- tools/compliance/omb_m25_21_assessor.py +225 -0
- tools/compliance/omb_m26_04_assessor.py +185 -0
- tools/compliance/oscal_catalog_adapter.py +395 -0
- tools/compliance/oscal_generator.py +2157 -0
- tools/compliance/oscal_tools.py +1182 -0
- tools/compliance/oscal_validator.py +692 -0
- tools/compliance/owasp_agentic_assessor.py +227 -0
- tools/compliance/owasp_asi_assessor.py +197 -0
- tools/compliance/owasp_llm_assessor.py +245 -0
- tools/compliance/pci_dss_assessor.py +80 -0
- tools/compliance/pi_compliance_tracker.py +1447 -0
- tools/compliance/poam_generator.py +388 -0
- tools/compliance/resolve_marking.py +272 -0
- tools/compliance/sbd_assessor.py +2070 -0
- tools/compliance/sbd_report_generator.py +1223 -0
- tools/compliance/sbom_generator.py +993 -0
- tools/compliance/siem_config_generator.py +661 -0
- tools/compliance/slsa_attestation_generator.py +479 -0
- tools/compliance/soc2_assessor.py +77 -0
- tools/compliance/ssp_generator.py +556 -0
- tools/compliance/stig_checker.py +712 -0
- tools/compliance/swft_evidence_bundler.py +326 -0
- tools/compliance/system_card_generator.py +303 -0
- tools/compliance/template_exchange.py +513 -0
- tools/compliance/traceability_matrix.py +1268 -0
- tools/compliance/universal_classification_manager.py +1159 -0
- tools/compliance/xacta/__init__.py +2 -0
- tools/compliance/xacta/xacta_client.py +438 -0
- tools/compliance/xacta/xacta_export.py +546 -0
- tools/compliance/xacta/xacta_sync.py +322 -0
- tools/compliance/xai_assessor.py +231 -0
- tools/core/__init__.py +2 -0
- tools/core/circuit_breaker.py +353 -0
- tools/core/compliance_sidecar.py +344 -0
- tools/core/container.py +110 -0
- tools/core/errors.py +256 -0
- tools/core/feature_flags.py +311 -0
- tools/core/task_dlq.py +350 -0
- tools/dashboard/__init__.py +2 -0
- tools/dashboard/app.py +6288 -0
- tools/dashboard/templates/agent_evolution.html +287 -0
- tools/dashboard/templates/agents/list.html +71 -0
- tools/dashboard/templates/agents.html +132 -0
- tools/dashboard/templates/architecture.html +289 -0
- tools/dashboard/templates/ato_simulator.html +170 -0
- tools/dashboard/templates/audit_engine.html +844 -0
- tools/dashboard/templates/base.html +236 -0
- tools/dashboard/templates/cato_live.html +116 -0
- tools/dashboard/templates/cloudforge.html +195 -0
- tools/dashboard/templates/cloudforge_finops.html +111 -0
- tools/dashboard/templates/cloudforge_hybrid.html +122 -0
- tools/dashboard/templates/cloudforge_metastore.html +234 -0
- tools/dashboard/templates/cloudforge_migration.html +87 -0
- tools/dashboard/templates/cloudforge_runbooks.html +201 -0
- tools/dashboard/templates/cloudforge_siem.html +94 -0
- tools/dashboard/templates/compliance_accel.html +292 -0
- tools/dashboard/templates/crashes.html +122 -0
- tools/dashboard/templates/databridge.html +305 -0
- tools/dashboard/templates/databridge_analytics.html +195 -0
- tools/dashboard/templates/databridge_mapping.html +345 -0
- tools/dashboard/templates/databridge_messaging.html +321 -0
- tools/dashboard/templates/decisions.html +258 -0
- tools/dashboard/templates/devices.html +151 -0
- tools/dashboard/templates/devsecops_maturity.html +278 -0
- tools/dashboard/templates/edge_ai.html +128 -0
- tools/dashboard/templates/firmware.html +120 -0
- tools/dashboard/templates/firmware_sbom.html +193 -0
- tools/dashboard/templates/forge_hub.html +196 -0
- tools/dashboard/templates/forge_studio.html +379 -0
- tools/dashboard/templates/forge_studio_analytics.html +360 -0
- tools/dashboard/templates/forge_studio_builder.html +1637 -0
- tools/dashboard/templates/forge_studio_compliance.html +310 -0
- tools/dashboard/templates/forge_studio_deploy.html +573 -0
- tools/dashboard/templates/forge_studio_enterprise.html +888 -0
- tools/dashboard/templates/forge_studio_marketplace.html +502 -0
- tools/dashboard/templates/forge_studio_workflow.html +696 -0
- tools/dashboard/templates/golden_path.html +175 -0
- tools/dashboard/templates/govcon.html +280 -0
- tools/dashboard/templates/harness.html +148 -0
- tools/dashboard/templates/index.html +207 -0
- tools/dashboard/templates/intelligence.html +336 -0
- tools/dashboard/templates/knowledge/index.html +190 -0
- tools/dashboard/templates/knowledge_graph.html +739 -0
- tools/dashboard/templates/login.html +51 -0
- tools/dashboard/templates/marketplace.html +336 -0
- tools/dashboard/templates/marketplace_admin.html +247 -0
- tools/dashboard/templates/missions.html +403 -0
- tools/dashboard/templates/narratives.html +154 -0
- tools/dashboard/templates/pr_intelligence.html +151 -0
- tools/dashboard/templates/proposals/detail.html +300 -0
- tools/dashboard/templates/proposals/list.html +52 -0
- tools/dashboard/templates/proposals/sam_detail.html +132 -0
- tools/dashboard/templates/proposals/section_detail.html +375 -0
- tools/dashboard/templates/research.html +222 -0
- tools/dashboard/templates/resilience.html +300 -0
- tools/dashboard/templates/scorecard.html +162 -0
- tools/dashboard/templates/simulator.html +131 -0
- tools/dashboard/templates/template_exchange.html +147 -0
- tools/dashboard/templates/thread_heatmap.html +151 -0
- tools/dashboard/templates/threat_model.html +195 -0
- tools/dashboard/templates/vsm.html +141 -0
- tools/dashboard/templates/writeguard.html +277 -0
- tools/databridge/__init__.py +5 -0
- tools/databridge/agent/__init__.py +2 -0
- tools/databridge/agent/daemon.py +227 -0
- tools/databridge/agent/tunnel.py +101 -0
- tools/databridge/agent/ws_relay.py +91 -0
- tools/databridge/analytics.py +167 -0
- tools/databridge/arrow_pipeline.py +327 -0
- tools/databridge/connection_manager.py +424 -0
- tools/databridge/connector.py +331 -0
- tools/databridge/connectors/__init__.py +2 -0
- tools/databridge/connectors/argocd_connector.py +160 -0
- tools/databridge/connectors/avro_connector.py +203 -0
- tools/databridge/connectors/azure_blob.py +63 -0
- tools/databridge/connectors/cdc_connector.py +205 -0
- tools/databridge/connectors/csv_connector.py +172 -0
- tools/databridge/connectors/datadog_connector.py +153 -0
- tools/databridge/connectors/discord_messaging.py +215 -0
- tools/databridge/connectors/dynamics365.py +151 -0
- tools/databridge/connectors/elasticsearch_connector.py +145 -0
- tools/databridge/connectors/email_base.py +114 -0
- tools/databridge/connectors/excel_connector.py +175 -0
- tools/databridge/connectors/fsspec_base.py +300 -0
- tools/databridge/connectors/gcs.py +53 -0
- tools/databridge/connectors/github_connector.py +138 -0
- tools/databridge/connectors/gitlab_connector.py +132 -0
- tools/databridge/connectors/gmail_connector.py +182 -0
- tools/databridge/connectors/hdfs.py +57 -0
- tools/databridge/connectors/health_base.py +401 -0
- tools/databridge/connectors/hubspot.py +124 -0
- tools/databridge/connectors/imap_connector.py +171 -0
- tools/databridge/connectors/jenkins_connector.py +138 -0
- tools/databridge/connectors/jira_connector.py +86 -0
- tools/databridge/connectors/json_connector.py +184 -0
- tools/databridge/connectors/kafka_connector.py +246 -0
- tools/databridge/connectors/kinesis_connector.py +238 -0
- tools/databridge/connectors/local_fs.py +30 -0
- tools/databridge/connectors/matrix.py +197 -0
- tools/databridge/connectors/mattermost_messaging.py +184 -0
- tools/databridge/connectors/messaging_base.py +172 -0
- tools/databridge/connectors/mssql.py +63 -0
- tools/databridge/connectors/mysql.py +57 -0
- tools/databridge/connectors/netsuite.py +170 -0
- tools/databridge/connectors/o365_mail.py +196 -0
- tools/databridge/connectors/oracle.py +65 -0
- tools/databridge/connectors/pagerduty_connector.py +162 -0
- tools/databridge/connectors/parquet_connector.py +131 -0
- tools/databridge/connectors/postgresql.py +58 -0
- tools/databridge/connectors/s3.py +65 -0
- tools/databridge/connectors/saas_base.py +198 -0
- tools/databridge/connectors/salesforce.py +126 -0
- tools/databridge/connectors/sap.py +89 -0
- tools/databridge/connectors/servicenow.py +60 -0
- tools/databridge/connectors/signal_messaging.py +150 -0
- tools/databridge/connectors/slack_messaging.py +203 -0
- tools/databridge/connectors/smtp_connector.py +126 -0
- tools/databridge/connectors/soap_base.py +258 -0
- tools/databridge/connectors/splunk_connector.py +171 -0
- tools/databridge/connectors/sql_base.py +310 -0
- tools/databridge/connectors/sqlite_connector.py +76 -0
- tools/databridge/connectors/teams.py +148 -0
- tools/databridge/connectors/telegram.py +192 -0
- tools/databridge/connectors/whatsapp.py +137 -0
- tools/databridge/data_profiler.py +99 -0
- tools/databridge/forge/__init__.py +6 -0
- tools/databridge/forge/base_selector.py +150 -0
- tools/databridge/forge/code_generator.py +206 -0
- tools/databridge/forge/community_hub.py +539 -0
- tools/databridge/forge/forge_agent.py +306 -0
- tools/databridge/forge/import_handler.py +133 -0
- tools/databridge/forge/integration_tester.py +127 -0
- tools/databridge/forge/marketplace_publisher.py +164 -0
- tools/databridge/forge/promoter.py +159 -0
- tools/databridge/forge/sandbox_manager.py +257 -0
- tools/databridge/forge/spec_parser.py +358 -0
- tools/databridge/forge/static_validator.py +363 -0
- tools/databridge/forge/templates/__init__.py +591 -0
- tools/databridge/format_converter.py +188 -0
- tools/databridge/mapping_engine.py +348 -0
- tools/databridge/messaging/__init__.py +5 -0
- tools/databridge/messaging/agent_bridge.py +254 -0
- tools/databridge/messaging/message_envelope.py +111 -0
- tools/databridge/messaging/message_logger.py +204 -0
- tools/databridge/messaging/messaging_daemon.py +326 -0
- tools/databridge/messaging/oauth2_manager.py +411 -0
- tools/databridge/pii_detector.py +221 -0
- tools/databridge/registry.py +352 -0
- tools/databridge/relay_server.py +105 -0
- tools/databridge/scale/__init__.py +16 -0
- tools/databridge/scale/backpressure.py +134 -0
- tools/databridge/scale/chunked_pipeline.py +169 -0
- tools/databridge/scale/connection_pool.py +293 -0
- tools/databridge/scale/engine.py +492 -0
- tools/databridge/scale/worker_pool.py +140 -0
- tools/databridge/scale/write_batcher.py +250 -0
- tools/databridge/schema_engine.py +324 -0
- tools/databridge/stream_manager.py +225 -0
- tools/databridge/sync_engine.py +411 -0
- tools/databridge/transforms.py +302 -0
- tools/db/__init__.py +1 -0
- tools/db/backup.py +312 -0
- tools/db/backup_manager.py +832 -0
- tools/db/init_icdev_db.py +7753 -0
- tools/db/init_sparkpilot_db.py +431 -0
- tools/db/migrate.py +177 -0
- tools/db/migrate_innovation_audit.py +165 -0
- tools/db/migration_runner.py +548 -0
- tools/db/migrations/001_baseline/meta.json +9 -0
- tools/db/migrations/001_baseline/up.py +67 -0
- tools/db/migrations/002_memory_enhancements/down.sql +8 -0
- tools/db/migrations/002_memory_enhancements/meta.json +9 -0
- tools/db/migrations/002_memory_enhancements/up.py +119 -0
- tools/db/migrations/003_dev_profiles/meta.json +8 -0
- tools/db/migrations/003_dev_profiles/up.py +93 -0
- tools/db/migrations/004_innovation_engine/down.py +19 -0
- tools/db/migrations/004_innovation_engine/up.py +227 -0
- tools/db/migrations/005_phase_37_ai_security/down.py +19 -0
- tools/db/migrations/005_phase_37_ai_security/up.py +257 -0
- tools/db/migrations/006_phase_36_evolution/down.py +21 -0
- tools/db/migrations/006_phase_36_evolution/up.py +323 -0
- tools/db/migrations/007_phase_38_cloud/down.py +14 -0
- tools/db/migrations/007_phase_38_cloud/up.py +110 -0
- tools/db/migrations/008_phase36_37_integration/up.py +55 -0
- tools/db/migrations/__init__.py +2 -0
- tools/db/pg_migrate.py +642 -0
- tools/db/storage.py +1080 -0
- tools/decisions/__init__.py +2 -0
- tools/decisions/dmn_engine.py +695 -0
- tools/devsecops/__init__.py +2 -0
- tools/devsecops/attestation_manager.py +449 -0
- tools/devsecops/network_segmentation_generator.py +604 -0
- tools/devsecops/pdp_config_generator.py +1246 -0
- tools/devsecops/pipeline_security_generator.py +475 -0
- tools/devsecops/policy_generator.py +644 -0
- tools/devsecops/profile_manager.py +374 -0
- tools/devsecops/service_mesh_generator.py +1063 -0
- tools/devsecops/zta_maturity_scorer.py +355 -0
- tools/devsecops/zta_terraform_generator.py +1301 -0
- tools/edge_ai/__init__.py +2 -0
- tools/edge_ai/model_manager.py +200 -0
- tools/embedded/__init__.py +2 -0
- tools/embedded/cmake_generator.py +318 -0
- tools/embedded/crash_analyzer.py +191 -0
- tools/embedded/nl_to_firmware.py +277 -0
- tools/events/__init__.py +1 -0
- tools/events/event_bus.py +199 -0
- tools/finetune/pair_generator.py +832 -0
- tools/fleet/__init__.py +2 -0
- tools/fleet/device_registry.py +148 -0
- tools/fleet/ota_manager.py +153 -0
- tools/forge_studio/__init__.py +13 -0
- tools/forge_studio/analytics/__init__.py +0 -0
- tools/forge_studio/analytics/process_miner.py +383 -0
- tools/forge_studio/audit.py +183 -0
- tools/forge_studio/blueprint/__init__.py +2 -0
- tools/forge_studio/blueprint/build_tracker.py +317 -0
- tools/forge_studio/blueprint/export_engine.py +441 -0
- tools/forge_studio/blueprint/parent_client.py +335 -0
- tools/forge_studio/catalog/__init__.py +2 -0
- tools/forge_studio/catalog/component_registry.py +176 -0
- tools/forge_studio/catalog/schema_validator.py +193 -0
- tools/forge_studio/compliance/__init__.py +1 -0
- tools/forge_studio/compliance/compliance_wiring.py +554 -0
- tools/forge_studio/deploy/__init__.py +1 -0
- tools/forge_studio/deploy/airgap_packager.py +466 -0
- tools/forge_studio/deploy/deploy_engine.py +1792 -0
- tools/forge_studio/deploy/env_manager.py +431 -0
- tools/forge_studio/eject/__init__.py +2 -0
- tools/forge_studio/eject/docker_compose_generator.py +237 -0
- tools/forge_studio/eject/eject_engine.py +230 -0
- tools/forge_studio/eject/expo_scaffolder.py +303 -0
- tools/forge_studio/eject/nextjs_scaffolder.py +338 -0
- tools/forge_studio/enterprise/__init__.py +0 -0
- tools/forge_studio/enterprise/custom_frameworks.py +826 -0
- tools/forge_studio/enterprise/hardening_engine.py +1530 -0
- tools/forge_studio/enterprise/sso_manager.py +718 -0
- tools/forge_studio/enterprise/whitelabel_engine.py +887 -0
- tools/forge_studio/formula/__init__.py +0 -0
- tools/forge_studio/formula/expression_engine.py +562 -0
- tools/forge_studio/formula/formula_registry.py +265 -0
- tools/forge_studio/generator/__init__.py +2 -0
- tools/forge_studio/generator/app_generator.py +584 -0
- tools/forge_studio/generator/complexity_detector.py +368 -0
- tools/forge_studio/generator/prompt_templates.py +104 -0
- tools/forge_studio/generator/spec_builder.py +192 -0
- tools/forge_studio/intake_bridge.py +898 -0
- tools/forge_studio/marketplace/__init__.py +0 -0
- tools/forge_studio/marketplace/component_hub.py +428 -0
- tools/forge_studio/models.py +369 -0
- tools/forge_studio/renderer/__init__.py +2 -0
- tools/forge_studio/renderer/json_render_engine.py +623 -0
- tools/forge_studio/renderer/layout_engine.py +214 -0
- tools/forge_studio/renderer/rn_component_map.py +182 -0
- tools/forge_studio/supabase/__init__.py +2 -0
- tools/forge_studio/supabase/auth_generator.py +283 -0
- tools/forge_studio/supabase/migration_generator.py +93 -0
- tools/forge_studio/supabase/schema_generator.py +281 -0
- tools/forge_studio/tenant_manager.py +387 -0
- tools/forge_studio/workflow/__init__.py +2 -0
- tools/forge_studio/workflow/bpmn_adapter.py +489 -0
- tools/govcon/draft_orchestrator.py +1151 -0
- tools/govcon/engine_enrichment.py +373 -0
- tools/govcon/knowledge_base.py +487 -0
- tools/govcon/knowledge_ingestion.py +510 -0
- tools/govcon/sam_scanner.py +754 -0
- tools/harness/__init__.py +6 -0
- tools/harness/exit_criteria_evaluator.py +231 -0
- tools/harness/maturity_assessor.py +347 -0
- tools/harness/scaffold_harness.py +416 -0
- tools/harness/trace_analyzer.py +281 -0
- tools/infra/__init__.py +1 -0
- tools/infra/ansible_generator.py +867 -0
- tools/infra/dockerfile_generator.py +359 -0
- tools/infra/infra_status.py +384 -0
- tools/infra/ironbank_metadata_generator.py +403 -0
- tools/infra/k8s_generator.py +1000 -0
- tools/infra/pipeline_generator.py +830 -0
- tools/infra/rollback.py +389 -0
- tools/infra/terraform_generator.py +1140 -0
- tools/infra/terraform_generator_azure.py +1252 -0
- tools/infra/terraform_generator_gcp.py +951 -0
- tools/infra/terraform_generator_ibm.py +359 -0
- tools/infra/terraform_generator_oci.py +918 -0
- tools/infra/terraform_generator_onprem.py +318 -0
- tools/knowledge/__init__.py +1 -0
- tools/knowledge/knowledge_ingest.py +281 -0
- tools/knowledge/pattern_detector.py +681 -0
- tools/knowledge/recommendation_engine.py +449 -0
- tools/knowledge/self_heal_analyzer.py +492 -0
- tools/knowledge_graph/__init__.py +2 -0
- tools/knowledge_graph/graph_rag.py +498 -0
- tools/knowledge_graph/ingester.py +406 -0
- tools/knowledge_graph/insight_generator.py +369 -0
- tools/knowledge_graph/text_network.py +832 -0
- tools/llm/__init__.py +72 -0
- tools/llm/anthropic_provider.py +170 -0
- tools/llm/azure_openai_provider.py +338 -0
- tools/llm/bedrock_provider.py +315 -0
- tools/llm/embedding_provider.py +438 -0
- tools/llm/gemini_provider.py +381 -0
- tools/llm/ibm_watsonx_provider.py +231 -0
- tools/llm/oci_genai_provider.py +462 -0
- tools/llm/ollama_provider.py +350 -0
- tools/llm/openai_provider.py +225 -0
- tools/llm/prompt_registry.py +447 -0
- tools/llm/provider.py +355 -0
- tools/llm/provider_sdk.py +175 -0
- tools/llm/router.py +1124 -0
- tools/llm/semantic_cache.py +394 -0
- tools/llm/vertex_ai_provider.py +374 -0
- tools/maintenance/__init__.py +2 -0
- tools/maintenance/dependency_scanner.py +1016 -0
- tools/maintenance/maintenance_auditor.py +804 -0
- tools/maintenance/remediation_engine.py +957 -0
- tools/maintenance/vulnerability_checker.py +978 -0
- tools/manifest.md +1066 -0
- tools/marketplace/asset_installer.py +639 -0
- tools/marketplace/feedback_validator.py +359 -0
- tools/marketplace/license_client.py +458 -0
- tools/marketplace/module_crypto.py +544 -0
- tools/marketplace/module_runtime.py +236 -0
- tools/marketplace/token_store.py +264 -0
- tools/mbse/__init__.py +3 -0
- tools/mbse/des_assessor.py +1173 -0
- tools/mbse/des_report_generator.py +787 -0
- tools/mbse/diagram_extractor.py +792 -0
- tools/mbse/digital_thread.py +1650 -0
- tools/mbse/model_code_generator.py +1115 -0
- tools/mbse/model_control_mapper.py +410 -0
- tools/mbse/pi_model_tracker.py +1079 -0
- tools/mbse/reqif_parser.py +1468 -0
- tools/mbse/sync_engine.py +1789 -0
- tools/mbse/thread_heatmap.py +445 -0
- tools/mbse/xmi_parser.py +1558 -0
- tools/mcp/builder_server.py +64 -0
- tools/mcp/compliance_server.py +64 -0
- tools/mcp/connector_forge_server.py +155 -0
- tools/mcp/core_server.py +64 -0
- tools/mcp/devsecops_server.py +11 -0
- tools/mcp/devsecops_zta_server.py +64 -0
- tools/mcp/knowledge_server.py +64 -0
- tools/mcp/monitor_server.py +64 -0
- tools/mcp/ops_server.py +300 -0
- tools/mcp/requirements_analyst_server.py +64 -0
- tools/mcp/requirements_server.py +11 -0
- tools/mcp/security_server.py +64 -0
- tools/mcp/simulation_server.py +64 -0
- tools/mcp/supply_chain_server.py +64 -0
- tools/mcp/tool_registry.py +299 -0
- tools/memory/__init__.py +2 -0
- tools/memory/auto_capture.py +346 -0
- tools/memory/embed_memory.py +157 -0
- tools/memory/history_compressor.py +334 -0
- tools/memory/hybrid_search.py +235 -0
- tools/memory/maintenance_cron.py +288 -0
- tools/memory/memory_consolidation.py +439 -0
- tools/memory/memory_db.py +132 -0
- tools/memory/memory_read.py +101 -0
- tools/memory/memory_write.py +221 -0
- tools/memory/semantic_search.py +138 -0
- tools/memory/time_decay.py +434 -0
- tools/missions/__init__.py +2 -0
- tools/missions/mission_engine.py +459 -0
- tools/monitor/__init__.py +1 -0
- tools/monitor/alert_correlator.py +486 -0
- tools/monitor/auto_resolver.py +603 -0
- tools/monitor/health_checker.py +507 -0
- tools/monitor/heartbeat_daemon.py +779 -0
- tools/monitor/log_analyzer.py +507 -0
- tools/monitor/metric_collector.py +484 -0
- tools/mosa/__init__.py +10 -0
- tools/mosa/icd_generator.py +358 -0
- tools/mosa/modular_design_analyzer.py +682 -0
- tools/mosa/mosa_code_enforcer.py +348 -0
- tools/mosa/tsp_generator.py +265 -0
- tools/observability/__init__.py +100 -0
- tools/observability/genai_attributes.py +88 -0
- tools/observability/instrumentation.py +140 -0
- tools/observability/mlflow_exporter.py +193 -0
- tools/observability/otel_tracer.py +168 -0
- tools/observability/provenance/__init__.py +3 -0
- tools/observability/provenance/prov_recorder.py +322 -0
- tools/observability/shap/__init__.py +3 -0
- tools/observability/shap/agent_shap.py +274 -0
- tools/observability/sqlite_tracer.py +360 -0
- tools/observability/trace_context.py +205 -0
- tools/observability/tracer.py +230 -0
- tools/orchestration/__init__.py +1 -0
- tools/orchestration/peer_channels.py +254 -0
- tools/orchestration/saga_coordinator.py +390 -0
- tools/project/__init__.py +1 -0
- tools/project/manifest_loader.py +418 -0
- tools/project/project_create.py +350 -0
- tools/project/project_list.py +171 -0
- tools/project/project_scaffold.py +1715 -0
- tools/project/project_status.py +478 -0
- tools/project/session_context_builder.py +752 -0
- tools/project/validate_manifest.py +54 -0
- tools/rag/corrective_rag.py +582 -0
- tools/rag/source_registry.py +482 -0
- tools/requirements/__init__.py +1 -0
- tools/requirements/ai_governance_scorer.py +207 -0
- tools/requirements/boundary_analyzer.py +1281 -0
- tools/requirements/clarification_engine.py +605 -0
- tools/requirements/complexity_scorer.py +369 -0
- tools/requirements/consistency_analyzer.py +789 -0
- tools/requirements/constitution_manager.py +592 -0
- tools/requirements/decomposition_engine.py +764 -0
- tools/requirements/document_extractor.py +1002 -0
- tools/requirements/elicitation_techniques.py +508 -0
- tools/requirements/gap_detector.py +260 -0
- tools/requirements/intake_engine.py +2175 -0
- tools/requirements/prd_generator.py +839 -0
- tools/requirements/prd_validator.py +584 -0
- tools/requirements/readiness_scorer.py +302 -0
- tools/requirements/spec_organizer.py +1015 -0
- tools/requirements/spec_quality_checker.py +1083 -0
- tools/requirements/traceability_builder.py +566 -0
- tools/research/__init__.py +3 -0
- tools/research/academic_scanner.py +130 -0
- tools/research/build_buy_analyzer.py +229 -0
- tools/research/challenge_scorer.py +280 -0
- tools/research/community_scanner.py +174 -0
- tools/research/cross_engine_bridge.py +124 -0
- tools/research/dossier_generator.py +305 -0
- tools/research/landscape_scanner.py +315 -0
- tools/research/regulatory_scanner.py +248 -0
- tools/research/research_manager.py +469 -0
- tools/research/source_scanner.py +150 -0
- tools/research/vertical_loader.py +118 -0
- tools/saas/__init__.py +0 -0
- tools/saas/licensing/__init__.py +0 -0
- tools/saas/licensing/license_validator.py +345 -0
- tools/scaffold/__init__.py +2 -0
- tools/scaffold/golden_path.py +504 -0
- tools/security/__init__.py +1 -0
- tools/security/agent_output_validator.py +330 -0
- tools/security/agent_trust_scorer.py +652 -0
- tools/security/ai_bom_generator.py +718 -0
- tools/security/ai_telemetry_logger.py +469 -0
- tools/security/atlas_red_team.py +541 -0
- tools/security/code_pattern_scanner.py +382 -0
- tools/security/confabulation_detector.py +265 -0
- tools/security/container_scanner.py +489 -0
- tools/security/dependency_auditor.py +942 -0
- tools/security/endpoint_security_scanner.py +626 -0
- tools/security/mcp_tool_authorizer.py +242 -0
- tools/security/output_verifier.py +427 -0
- tools/security/prompt_injection_detector.py +737 -0
- tools/security/sast_runner.py +946 -0
- tools/security/secret_detector.py +376 -0
- tools/security/threat_modeler.py +678 -0
- tools/security/tool_chain_validator.py +357 -0
- tools/security/vuln_scanner.py +536 -0
- tools/simulation/__init__.py +2 -0
- tools/simulation/ato_simulator.py +517 -0
- tools/simulation/coa_generator.py +1539 -0
- tools/simulation/monte_carlo.py +745 -0
- tools/simulation/scenario_manager.py +1060 -0
- tools/simulation/simulation_engine.py +1091 -0
- tools/simulator/__init__.py +2 -0
- tools/simulator/sim_runner.py +272 -0
- tools/supply_chain/__init__.py +2 -0
- tools/supply_chain/cve_triager.py +690 -0
- tools/supply_chain/dependency_graph.py +630 -0
- tools/supply_chain/isa_manager.py +526 -0
- tools/supply_chain/scrm_assessor.py +531 -0
- tools/supply_chain/slsa_verifier.py +473 -0
- tools/testing/__init__.py +2 -0
- tools/testing/acceptance_validator.py +411 -0
- tools/testing/api_surface_extractor.py +749 -0
- tools/testing/claude_dir_validator.py +831 -0
- tools/testing/data_types.py +199 -0
- tools/testing/e2e_runner.py +715 -0
- tools/testing/fuzz_cli.py +306 -0
- tools/testing/health_check.py +483 -0
- tools/testing/platform_check.py +143 -0
- tools/testing/production_audit.py +1836 -0
- tools/testing/production_remediate.py +803 -0
- tools/testing/screenshot_validator.py +538 -0
- tools/testing/smoke_test.py +283 -0
- tools/testing/test_agent_models.py +117 -0
- tools/testing/test_orchestrator.py +957 -0
- tools/testing/utils.py +229 -0
- tools/writeguard/__init__.py +1 -0
- tools/writeguard/main.py +1 -0
- tools/writing/__init__.py +7 -0
- tools/writing/ai_content_detector.py +316 -0
- tools/writing/analysis_engine.py +454 -0
- tools/writing/batch_analyzer.py +276 -0
- tools/writing/coherence_analyzer.py +221 -0
- tools/writing/govcon_bridge.py +509 -0
- tools/writing/grammar_checker.py +270 -0
- tools/writing/plagiarism_detector.py +106 -0
- tools/writing/readability_scorer.py +201 -0
- tools/writing/rewriter.py +96 -0
- tools/writing/signal_registrar.py +167 -0
- tools/writing/snippet_manager.py +276 -0
- tools/writing/style_enforcer.py +220 -0
- tools/writing/style_guide_manager.py +438 -0
- tools/writing/tone_profiler.py +168 -0
|
@@ -0,0 +1,2175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# CUI // SP-CTI
|
|
3
|
+
# Controlled by: Department of Defense
|
|
4
|
+
# CUI Category: CTI
|
|
5
|
+
# Distribution: D
|
|
6
|
+
# POC: SPARKPILOT System Administrator
|
|
7
|
+
"""Conversational requirements intake engine.
|
|
8
|
+
|
|
9
|
+
Creates and manages intake sessions, processes customer turns, extracts
|
|
10
|
+
requirements, detects gaps/ambiguities, and scores readiness.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
# Create new session
|
|
14
|
+
python tools/requirements/intake_engine.py --project-id proj-123 \\
|
|
15
|
+
--customer-name "Jane Smith" --customer-org "PEO IEW&S" --impact-level IL4 --json
|
|
16
|
+
|
|
17
|
+
# Process a customer turn
|
|
18
|
+
python tools/requirements/intake_engine.py --session-id sess-abc \\
|
|
19
|
+
--message "We need a mission planning tool with 200 users" --json
|
|
20
|
+
|
|
21
|
+
# Resume paused session
|
|
22
|
+
python tools/requirements/intake_engine.py --session-id sess-abc --resume --json
|
|
23
|
+
|
|
24
|
+
# Score readiness
|
|
25
|
+
python tools/requirements/intake_engine.py --session-id sess-abc --score-readiness --json
|
|
26
|
+
|
|
27
|
+
# Export requirements
|
|
28
|
+
python tools/requirements/intake_engine.py --session-id sess-abc --export --json
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import argparse
|
|
32
|
+
import json
|
|
33
|
+
import re
|
|
34
|
+
import uuid
|
|
35
|
+
from datetime import datetime, timezone
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from tools.db.storage import get_connection
|
|
38
|
+
DB_PATH = None # Storage layer handles path resolution (D-DB-20)
|
|
39
|
+
|
|
40
|
+
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
41
|
+
|
|
42
|
+
# Graceful import of audit logger
|
|
43
|
+
try:
|
|
44
|
+
from tools.audit.audit_logger import log_event
|
|
45
|
+
_HAS_AUDIT = True
|
|
46
|
+
except ImportError:
|
|
47
|
+
_HAS_AUDIT = False
|
|
48
|
+
def log_event(**kwargs) -> int:
|
|
49
|
+
return -1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _generate_id(prefix="sess"):
|
|
53
|
+
"""Generate a unique ID with prefix."""
|
|
54
|
+
return f"{prefix}-{uuid.uuid4().hex[:12]}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _load_config():
|
|
58
|
+
"""Load RICOAS configuration."""
|
|
59
|
+
config_path = BASE_DIR / "args" / "ricoas_config.yaml"
|
|
60
|
+
if config_path.exists():
|
|
61
|
+
try:
|
|
62
|
+
import yaml
|
|
63
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
64
|
+
return yaml.safe_load(f)
|
|
65
|
+
except ImportError:
|
|
66
|
+
pass
|
|
67
|
+
# Fallback defaults
|
|
68
|
+
return {
|
|
69
|
+
"ricoas": {
|
|
70
|
+
"readiness_threshold": 0.7,
|
|
71
|
+
"readiness_weights": {
|
|
72
|
+
"completeness": 0.25,
|
|
73
|
+
"clarity": 0.25,
|
|
74
|
+
"feasibility": 0.20,
|
|
75
|
+
"compliance": 0.15,
|
|
76
|
+
"testability": 0.15,
|
|
77
|
+
},
|
|
78
|
+
"intake_agent": {
|
|
79
|
+
"max_conversation_turns": 200,
|
|
80
|
+
"auto_readiness_score_interval": 3,
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _load_persona(role, custom_role_description=None):
|
|
87
|
+
"""Load persona definition for a given role from role_personas.yaml.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
role: The role key (e.g. 'developer', 'pm', 'isso').
|
|
91
|
+
custom_role_description: Optional description for custom roles.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict with persona fields (system_prompt, opening_question, etc.)
|
|
95
|
+
or None if persona file is missing.
|
|
96
|
+
"""
|
|
97
|
+
persona_path = BASE_DIR / "args" / "role_personas.yaml"
|
|
98
|
+
if not persona_path.exists():
|
|
99
|
+
return None
|
|
100
|
+
try:
|
|
101
|
+
import yaml
|
|
102
|
+
with open(persona_path, "r", encoding="utf-8") as f:
|
|
103
|
+
data = yaml.safe_load(f) or {}
|
|
104
|
+
except ImportError:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
personas = data.get("personas", {})
|
|
108
|
+
|
|
109
|
+
# Check for built-in persona
|
|
110
|
+
if role in personas:
|
|
111
|
+
return personas[role]
|
|
112
|
+
|
|
113
|
+
# Custom role — synthesize persona from meta template
|
|
114
|
+
if custom_role_description:
|
|
115
|
+
custom_cfg = data.get("custom_role", {})
|
|
116
|
+
meta_prompt = custom_cfg.get("meta_system_prompt", "")
|
|
117
|
+
opening_prompt = custom_cfg.get("opening_prompt", "")
|
|
118
|
+
return {
|
|
119
|
+
"display_name": role.replace("_", " ").title(),
|
|
120
|
+
"system_prompt": meta_prompt.replace("{role_name}", role).replace(
|
|
121
|
+
"{role_description}", custom_role_description
|
|
122
|
+
),
|
|
123
|
+
"opening_question": opening_prompt.replace("{role_name}", role).replace(
|
|
124
|
+
"{role_description}", custom_role_description
|
|
125
|
+
),
|
|
126
|
+
"priority_topics": [],
|
|
127
|
+
"follow_up_patterns": [],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Fallback to developer persona
|
|
131
|
+
return personas.get("developer")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# LLM-powered persona response generation
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
# Conditional LLM imports — LLM may not be available in air-gapped envs
|
|
139
|
+
_HAS_LLM = False
|
|
140
|
+
try:
|
|
141
|
+
from tools.llm import get_router
|
|
142
|
+
from tools.llm.provider import LLMRequest as _LLMRequest
|
|
143
|
+
_HAS_LLM = True
|
|
144
|
+
except ImportError:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _generate_persona_response(session_data, message, signals, conn):
|
|
149
|
+
"""Generate an LLM-powered persona response for the intake conversation.
|
|
150
|
+
|
|
151
|
+
Builds a system prompt from the session's persona, conversation history,
|
|
152
|
+
and current turn signals, then calls the LLM router.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
session_data: dict of the intake_sessions row.
|
|
156
|
+
message: The customer message for this turn.
|
|
157
|
+
signals: dict with keys like extracted_reqs, ambiguities,
|
|
158
|
+
boundary_flags, gap_signals, devsecops_signals,
|
|
159
|
+
zta_signals, mosa_signals, readiness_update.
|
|
160
|
+
conn: Active database connection.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
str with the persona response, or None if LLM is unavailable
|
|
164
|
+
or any error occurs (caller should fall back to deterministic response).
|
|
165
|
+
"""
|
|
166
|
+
if not _HAS_LLM:
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
# Load context from session
|
|
171
|
+
context_raw = session_data.get("context_summary") or "{}"
|
|
172
|
+
try:
|
|
173
|
+
ctx = json.loads(context_raw)
|
|
174
|
+
except (json.JSONDecodeError, TypeError):
|
|
175
|
+
ctx = {}
|
|
176
|
+
|
|
177
|
+
role = ctx.get("role", "developer")
|
|
178
|
+
custom_desc = ctx.get("custom_role_description", "")
|
|
179
|
+
persona = _load_persona(role, custom_desc)
|
|
180
|
+
if not persona:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
# Build conversation history (last 10 turns)
|
|
184
|
+
session_id = session_data.get("id", "")
|
|
185
|
+
history_rows = conn.execute(
|
|
186
|
+
"""SELECT turn_number, role, content
|
|
187
|
+
FROM intake_conversation
|
|
188
|
+
WHERE session_id = ?
|
|
189
|
+
ORDER BY turn_number DESC LIMIT 10""",
|
|
190
|
+
(session_id,),
|
|
191
|
+
).fetchall()
|
|
192
|
+
history_rows = list(reversed(history_rows))
|
|
193
|
+
|
|
194
|
+
conversation_messages = []
|
|
195
|
+
for row in history_rows:
|
|
196
|
+
r = dict(row)
|
|
197
|
+
msg_role = "assistant" if r["role"] == "analyst" else "user"
|
|
198
|
+
if r["role"] == "system":
|
|
199
|
+
continue
|
|
200
|
+
conversation_messages.append({
|
|
201
|
+
"role": msg_role,
|
|
202
|
+
"content": r["content"],
|
|
203
|
+
})
|
|
204
|
+
# Add current customer message
|
|
205
|
+
conversation_messages.append({
|
|
206
|
+
"role": "user",
|
|
207
|
+
"content": message,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
# Build system prompt with persona + session context
|
|
211
|
+
goal = ctx.get("goal", "build")
|
|
212
|
+
selected_fw = ctx.get("selected_frameworks", [])
|
|
213
|
+
req_count = signals.get("total_requirements", 0)
|
|
214
|
+
readiness = signals.get("readiness_update")
|
|
215
|
+
|
|
216
|
+
system_parts = [
|
|
217
|
+
persona.get("system_prompt", ""),
|
|
218
|
+
"",
|
|
219
|
+
"--- Session Context ---",
|
|
220
|
+
f"Goal: {goal}",
|
|
221
|
+
f"Classification: {session_data.get('classification', 'CUI')}",
|
|
222
|
+
f"Impact Level: {session_data.get('impact_level', 'IL5')}",
|
|
223
|
+
]
|
|
224
|
+
if selected_fw:
|
|
225
|
+
system_parts.append(f"Selected Frameworks: {', '.join(selected_fw)}")
|
|
226
|
+
system_parts.append(f"Requirements captured so far: {req_count}")
|
|
227
|
+
|
|
228
|
+
# Inject active elicitation technique (BMAD pattern)
|
|
229
|
+
active_tech_prompt = ctx.get("active_technique_prompt")
|
|
230
|
+
if active_tech_prompt:
|
|
231
|
+
system_parts.append("")
|
|
232
|
+
system_parts.append("--- Active Elicitation Technique ---")
|
|
233
|
+
system_parts.append(active_tech_prompt)
|
|
234
|
+
system_parts.append(
|
|
235
|
+
"IMPORTANT: Frame your response using the active technique above. "
|
|
236
|
+
"Ask questions that align with the technique's approach."
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if readiness:
|
|
240
|
+
system_parts.append(
|
|
241
|
+
f"Readiness score: {readiness.get('overall', 0):.0%}"
|
|
242
|
+
)
|
|
243
|
+
# Show per-dimension scores so the agent targets weak areas
|
|
244
|
+
dims = {
|
|
245
|
+
"completeness": readiness.get("completeness", 0),
|
|
246
|
+
"clarity": readiness.get("clarity", 0),
|
|
247
|
+
"feasibility": readiness.get("feasibility", 0),
|
|
248
|
+
"compliance": readiness.get("compliance", 0),
|
|
249
|
+
"testability": readiness.get("testability", 0),
|
|
250
|
+
}
|
|
251
|
+
dim_strs = [f" {k}: {v:.0%}" for k, v in dims.items()]
|
|
252
|
+
system_parts.append("Readiness by dimension:")
|
|
253
|
+
system_parts.extend(dim_strs)
|
|
254
|
+
# Identify the weakest dimension and hint what to ask
|
|
255
|
+
weakest = min(dims, key=dims.get)
|
|
256
|
+
dim_probes = {
|
|
257
|
+
"completeness": (
|
|
258
|
+
"Completeness is low — ask about requirement types not yet "
|
|
259
|
+
"covered (e.g., performance, security, data, integration, "
|
|
260
|
+
"usability, deployment). Probe for missing user roles, "
|
|
261
|
+
"workflows, or edge cases."
|
|
262
|
+
),
|
|
263
|
+
"clarity": (
|
|
264
|
+
"Clarity is low — some requirements use vague language. "
|
|
265
|
+
"Ask the customer to quantify terms (e.g., 'how many users?', "
|
|
266
|
+
"'what response time?', 'what does success look like?')."
|
|
267
|
+
),
|
|
268
|
+
"feasibility": (
|
|
269
|
+
"Feasibility is low — ask about constraints: available "
|
|
270
|
+
"timeline, team size, technology limitations, existing "
|
|
271
|
+
"systems to integrate with, hosting environment."
|
|
272
|
+
),
|
|
273
|
+
"compliance": (
|
|
274
|
+
"Compliance is low — ask about security requirements: "
|
|
275
|
+
"authentication (CAC/PIV, MFA), encryption (FIPS 140-2), "
|
|
276
|
+
"audit logging, access controls, data handling rules, "
|
|
277
|
+
"or any specific NIST/STIG/FedRAMP controls."
|
|
278
|
+
),
|
|
279
|
+
"testability": (
|
|
280
|
+
"Testability is low — ask the customer to define acceptance "
|
|
281
|
+
"criteria: 'How would you verify this works?', 'What does "
|
|
282
|
+
"a successful outcome look like?', 'What are the pass/fail "
|
|
283
|
+
"conditions?'"
|
|
284
|
+
),
|
|
285
|
+
}
|
|
286
|
+
system_parts.append("")
|
|
287
|
+
system_parts.append(f"PRIORITY: {dim_probes.get(weakest, '')}")
|
|
288
|
+
|
|
289
|
+
# Add conversation coverage analysis
|
|
290
|
+
cov = signals.get("coverage")
|
|
291
|
+
if cov:
|
|
292
|
+
system_parts.append("")
|
|
293
|
+
system_parts.append("--- Conversation Coverage ---")
|
|
294
|
+
system_parts.append(cov["summary"])
|
|
295
|
+
system_parts.append(
|
|
296
|
+
"IMPORTANT: Do NOT ask generic questions. Analyze what the customer "
|
|
297
|
+
"has already told you and ask about a SPECIFIC missing topic from "
|
|
298
|
+
"the list above. Reference what they said to show you were listening."
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Add URL content fetched from customer message
|
|
302
|
+
url_contents = signals.get("url_contents", [])
|
|
303
|
+
if url_contents:
|
|
304
|
+
system_parts.append("")
|
|
305
|
+
system_parts.append("--- URLs Referenced by Customer ---")
|
|
306
|
+
for uc in url_contents:
|
|
307
|
+
system_parts.append(f"URL: {uc['url']}")
|
|
308
|
+
if uc.get("title"):
|
|
309
|
+
system_parts.append(f"Title: {uc['title']}")
|
|
310
|
+
system_parts.append(f"Content: {uc['summary']}")
|
|
311
|
+
system_parts.append("")
|
|
312
|
+
system_parts.append(
|
|
313
|
+
"IMPORTANT: The customer shared URL(s). Review the content above and "
|
|
314
|
+
"reference relevant details in your response. Extract any requirements "
|
|
315
|
+
"or context from the linked content. Show the customer you reviewed "
|
|
316
|
+
"their link."
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Add uploaded document context
|
|
320
|
+
doc_rows = conn.execute(
|
|
321
|
+
"SELECT file_name, document_type, extracted_requirements_count, "
|
|
322
|
+
"extracted_sections FROM intake_documents WHERE session_id = ?",
|
|
323
|
+
(session_id,),
|
|
324
|
+
).fetchall()
|
|
325
|
+
if doc_rows:
|
|
326
|
+
system_parts.append("")
|
|
327
|
+
system_parts.append("--- Uploaded Documents ---")
|
|
328
|
+
for dr in doc_rows:
|
|
329
|
+
d = dict(dr)
|
|
330
|
+
system_parts.append(
|
|
331
|
+
f"Document: {d['file_name']} (type: {d['document_type']}, "
|
|
332
|
+
f"{d['extracted_requirements_count']} requirements extracted)"
|
|
333
|
+
)
|
|
334
|
+
if d.get("extracted_sections"):
|
|
335
|
+
try:
|
|
336
|
+
sections = json.loads(d["extracted_sections"])
|
|
337
|
+
if isinstance(sections, dict):
|
|
338
|
+
if sections.get("description"):
|
|
339
|
+
system_parts.append(
|
|
340
|
+
f" Content: {sections['description'][:300]}"
|
|
341
|
+
)
|
|
342
|
+
if sections.get("category"):
|
|
343
|
+
system_parts.append(
|
|
344
|
+
f" Category: {sections['category']}"
|
|
345
|
+
)
|
|
346
|
+
except (json.JSONDecodeError, TypeError):
|
|
347
|
+
pass
|
|
348
|
+
# Include document-extracted requirements as context
|
|
349
|
+
doc_reqs = conn.execute(
|
|
350
|
+
"SELECT raw_text, requirement_type FROM intake_requirements "
|
|
351
|
+
"WHERE session_id = ? AND source_document IS NOT NULL "
|
|
352
|
+
"ORDER BY created_at LIMIT 20",
|
|
353
|
+
(session_id,),
|
|
354
|
+
).fetchall()
|
|
355
|
+
if doc_reqs:
|
|
356
|
+
system_parts.append("Requirements from documents:")
|
|
357
|
+
for dr in doc_reqs:
|
|
358
|
+
d = dict(dr)
|
|
359
|
+
system_parts.append(
|
|
360
|
+
f" - [{d['requirement_type'].upper()}] {d['raw_text'][:120]}"
|
|
361
|
+
)
|
|
362
|
+
system_parts.append(
|
|
363
|
+
"Reference the uploaded document content when asking follow-up "
|
|
364
|
+
"questions. Use extracted requirements as context to ask deeper, "
|
|
365
|
+
"more specific questions about the customer's needs."
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Add signal summary for this turn
|
|
369
|
+
signal_notes = []
|
|
370
|
+
extracted_reqs = signals.get("extracted_reqs", [])
|
|
371
|
+
if extracted_reqs:
|
|
372
|
+
signal_notes.append(
|
|
373
|
+
f"Extracted {len(extracted_reqs)} requirement(s) this turn."
|
|
374
|
+
)
|
|
375
|
+
ambiguities = signals.get("ambiguities", [])
|
|
376
|
+
if ambiguities:
|
|
377
|
+
terms = [a["phrase"] for a in ambiguities]
|
|
378
|
+
signal_notes.append(
|
|
379
|
+
f"Ambiguous terms detected: {', '.join(terms)}"
|
|
380
|
+
)
|
|
381
|
+
boundary_flags = signals.get("boundary_flags", [])
|
|
382
|
+
if boundary_flags:
|
|
383
|
+
tiers = [f["tier"] for f in boundary_flags]
|
|
384
|
+
signal_notes.append(
|
|
385
|
+
f"ATO boundary flags: {', '.join(tiers)}"
|
|
386
|
+
)
|
|
387
|
+
gap_signals = signals.get("gap_signals", [])
|
|
388
|
+
if gap_signals:
|
|
389
|
+
signal_notes.append(
|
|
390
|
+
f"Gap signals: {'; '.join(gap_signals[:3])}"
|
|
391
|
+
)
|
|
392
|
+
if signal_notes:
|
|
393
|
+
system_parts.append("")
|
|
394
|
+
system_parts.append("--- This Turn ---")
|
|
395
|
+
system_parts.extend(signal_notes)
|
|
396
|
+
|
|
397
|
+
# Structured clarification questions (D159, spec-kit Pattern 4)
|
|
398
|
+
clarifications = signals.get("clarification_signals", [])
|
|
399
|
+
if clarifications:
|
|
400
|
+
system_parts.append("")
|
|
401
|
+
system_parts.append("--- Priority Clarification Questions ---")
|
|
402
|
+
for cq in clarifications[:3]:
|
|
403
|
+
system_parts.append(
|
|
404
|
+
f" [P{cq.get('priority', '?')}] {cq.get('question', '')}"
|
|
405
|
+
)
|
|
406
|
+
system_parts.append(
|
|
407
|
+
"IMPORTANT: Weave ONE of these clarification questions into your "
|
|
408
|
+
"response naturally. Do not ask all at once."
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# Parallel execution opportunities (D161, spec-kit Pattern 7)
|
|
412
|
+
parallel_opps = signals.get("parallel_opportunities", [])
|
|
413
|
+
if parallel_opps:
|
|
414
|
+
system_parts.append("")
|
|
415
|
+
system_parts.append("--- Parallel Execution Opportunities ---")
|
|
416
|
+
system_parts.append(
|
|
417
|
+
f"Detected {len(parallel_opps)} group(s) of independent tasks "
|
|
418
|
+
"that could run concurrently."
|
|
419
|
+
)
|
|
420
|
+
system_parts.append(
|
|
421
|
+
"Mention this when discussing implementation timeline."
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
system_parts.append("")
|
|
425
|
+
system_parts.append(
|
|
426
|
+
"Respond in character. Acknowledge what the customer said, reference "
|
|
427
|
+
"any extracted requirements or issues, and ask a follow-up question "
|
|
428
|
+
"that drives toward completeness. Keep the response concise (2-4 paragraphs)."
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
system_prompt = "\n".join(system_parts)
|
|
432
|
+
|
|
433
|
+
router = get_router()
|
|
434
|
+
request = _LLMRequest(
|
|
435
|
+
messages=conversation_messages,
|
|
436
|
+
system_prompt=system_prompt,
|
|
437
|
+
max_tokens=1024,
|
|
438
|
+
temperature=0.7,
|
|
439
|
+
agent_id="sparkpilot-requirements-analyst",
|
|
440
|
+
project_id=session_data.get("project_id", ""),
|
|
441
|
+
classification=session_data.get("classification", "CUI"),
|
|
442
|
+
)
|
|
443
|
+
response = router.invoke("intake_persona_response", request)
|
|
444
|
+
if response and response.content:
|
|
445
|
+
return response.content.strip()
|
|
446
|
+
return None
|
|
447
|
+
except Exception:
|
|
448
|
+
return None
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# ---------------------------------------------------------------------------
|
|
452
|
+
# Session management
|
|
453
|
+
# ---------------------------------------------------------------------------
|
|
454
|
+
|
|
455
|
+
def create_session(
|
|
456
|
+
project_id: str,
|
|
457
|
+
customer_name: str,
|
|
458
|
+
customer_org: str = None,
|
|
459
|
+
impact_level: str = "IL5",
|
|
460
|
+
classification: str = None,
|
|
461
|
+
created_by: str = "sparkpilot-requirements-analyst",
|
|
462
|
+
db_path=None,
|
|
463
|
+
role: str = "developer",
|
|
464
|
+
goal: str = "build",
|
|
465
|
+
selected_frameworks=None,
|
|
466
|
+
custom_role_description: str = "",
|
|
467
|
+
) -> dict:
|
|
468
|
+
"""Create a new intake session. Returns session data dict.
|
|
469
|
+
|
|
470
|
+
Classification is resolved dynamically (ADR D132):
|
|
471
|
+
- If provided explicitly, use that value.
|
|
472
|
+
- If None, resolve from project metadata (classification + impact_level).
|
|
473
|
+
- Public / IL2 -> "PUBLIC" (no marking required).
|
|
474
|
+
- IL4/IL5 -> "CUI", IL6 -> "SECRET" (backward compat per ADR D54).
|
|
475
|
+
"""
|
|
476
|
+
session_id = _generate_id("sess")
|
|
477
|
+
conn = get_connection(db_path=db_path)
|
|
478
|
+
|
|
479
|
+
# Validate project exists if provided
|
|
480
|
+
if project_id:
|
|
481
|
+
row = conn.execute(
|
|
482
|
+
"SELECT id, classification, impact_level FROM projects WHERE id = ?",
|
|
483
|
+
(project_id,),
|
|
484
|
+
).fetchone()
|
|
485
|
+
if not row:
|
|
486
|
+
conn.close()
|
|
487
|
+
raise ValueError(f"Project '{project_id}' not found in database.")
|
|
488
|
+
|
|
489
|
+
# Resolve classification from project if not provided
|
|
490
|
+
if classification is None:
|
|
491
|
+
proj = dict(row) if hasattr(row, "keys") else {"classification": row[1], "impact_level": row[2]}
|
|
492
|
+
cls_val = (proj.get("classification") or "").upper()
|
|
493
|
+
il_val = (proj.get("impact_level") or "").upper()
|
|
494
|
+
if cls_val == "PUBLIC" or il_val == "IL2":
|
|
495
|
+
classification = "PUBLIC"
|
|
496
|
+
elif cls_val in ("SECRET", "TOP SECRET", "TOP_SECRET") or il_val == "IL6":
|
|
497
|
+
classification = "SECRET"
|
|
498
|
+
else:
|
|
499
|
+
classification = "CUI"
|
|
500
|
+
else:
|
|
501
|
+
if classification is None:
|
|
502
|
+
classification = "CUI"
|
|
503
|
+
|
|
504
|
+
conn.execute(
|
|
505
|
+
"""INSERT INTO intake_sessions
|
|
506
|
+
(id, project_id, customer_name, customer_org, session_status,
|
|
507
|
+
classification, impact_level, created_by)
|
|
508
|
+
VALUES (?, ?, ?, ?, 'active', ?, ?, ?)""",
|
|
509
|
+
(session_id, project_id, customer_name, customer_org,
|
|
510
|
+
classification, impact_level, created_by),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# Store session context (role, goal, frameworks, custom description)
|
|
514
|
+
context = {
|
|
515
|
+
"role": role,
|
|
516
|
+
"goal": goal,
|
|
517
|
+
"selected_frameworks": selected_frameworks or [],
|
|
518
|
+
"custom_role_description": custom_role_description,
|
|
519
|
+
}
|
|
520
|
+
conn.execute("UPDATE intake_sessions SET context_summary = ? WHERE id = ?",
|
|
521
|
+
(json.dumps(context), session_id))
|
|
522
|
+
|
|
523
|
+
# Insert initial system turn
|
|
524
|
+
conn.execute(
|
|
525
|
+
"""INSERT INTO intake_conversation
|
|
526
|
+
(session_id, turn_number, role, content, content_type)
|
|
527
|
+
VALUES (?, 0, 'system', ?, 'text')""",
|
|
528
|
+
(session_id, json.dumps({
|
|
529
|
+
"event": "session_created",
|
|
530
|
+
"project_id": project_id,
|
|
531
|
+
"customer_name": customer_name,
|
|
532
|
+
"impact_level": impact_level,
|
|
533
|
+
})),
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Generate persona-appropriate welcome message
|
|
537
|
+
default_welcome = (
|
|
538
|
+
f"Session created. Welcome, {customer_name}. "
|
|
539
|
+
f"I'm the SPARKPILOT Requirements Analyst. I'll help capture and "
|
|
540
|
+
f"structure your requirements for a {impact_level} system. "
|
|
541
|
+
f"Let's start with the mission context — what problem does "
|
|
542
|
+
f"this system need to solve?"
|
|
543
|
+
)
|
|
544
|
+
welcome_message = default_welcome
|
|
545
|
+
|
|
546
|
+
persona = _load_persona(role, custom_role_description)
|
|
547
|
+
if persona:
|
|
548
|
+
# Try LLM-powered welcome
|
|
549
|
+
llm_welcome = None
|
|
550
|
+
if _HAS_LLM:
|
|
551
|
+
try:
|
|
552
|
+
fw_text = ""
|
|
553
|
+
if selected_frameworks:
|
|
554
|
+
fw_text = f" Compliance frameworks: {', '.join(selected_frameworks)}."
|
|
555
|
+
opening_system = (
|
|
556
|
+
f"{persona.get('system_prompt', '')}\n\n"
|
|
557
|
+
f"You are starting a requirements intake session with "
|
|
558
|
+
f"{customer_name} from {customer_org or 'their organization'}. "
|
|
559
|
+
f"Impact level: {impact_level}. Classification: {classification}. "
|
|
560
|
+
f"Goal: {goal}.{fw_text}\n\n"
|
|
561
|
+
f"Introduce yourself briefly in your role and ask your opening "
|
|
562
|
+
f"question. Keep it to 2-3 sentences."
|
|
563
|
+
)
|
|
564
|
+
router = get_router()
|
|
565
|
+
request = _LLMRequest(
|
|
566
|
+
messages=[{"role": "user", "content": "Begin the intake session."}],
|
|
567
|
+
system_prompt=opening_system,
|
|
568
|
+
max_tokens=512,
|
|
569
|
+
temperature=0.7,
|
|
570
|
+
agent_id="sparkpilot-requirements-analyst",
|
|
571
|
+
project_id=project_id,
|
|
572
|
+
classification=classification or "CUI",
|
|
573
|
+
)
|
|
574
|
+
resp = router.invoke("intake_persona_response", request)
|
|
575
|
+
if resp and resp.content:
|
|
576
|
+
llm_welcome = resp.content.strip()
|
|
577
|
+
except Exception:
|
|
578
|
+
pass
|
|
579
|
+
|
|
580
|
+
if llm_welcome:
|
|
581
|
+
welcome_message = llm_welcome
|
|
582
|
+
elif persona.get("opening_question"):
|
|
583
|
+
welcome_message = persona["opening_question"].strip()
|
|
584
|
+
|
|
585
|
+
# Store welcome as first analyst turn (turn_number=1)
|
|
586
|
+
conn.execute(
|
|
587
|
+
"""INSERT INTO intake_conversation
|
|
588
|
+
(session_id, turn_number, role, content, content_type, classification)
|
|
589
|
+
VALUES (?, 1, 'analyst', ?, 'text', ?)""",
|
|
590
|
+
(session_id, welcome_message, classification or "CUI"),
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
conn.commit()
|
|
594
|
+
conn.close()
|
|
595
|
+
|
|
596
|
+
if _HAS_AUDIT:
|
|
597
|
+
log_event(
|
|
598
|
+
event_type="intake_session_created",
|
|
599
|
+
actor=created_by,
|
|
600
|
+
action=f"Created intake session {session_id} for {customer_name}",
|
|
601
|
+
project_id=project_id,
|
|
602
|
+
details={"session_id": session_id, "impact_level": impact_level},
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
"status": "ok",
|
|
607
|
+
"session_id": session_id,
|
|
608
|
+
"project_id": project_id,
|
|
609
|
+
"customer_name": customer_name,
|
|
610
|
+
"customer_org": customer_org,
|
|
611
|
+
"impact_level": impact_level,
|
|
612
|
+
"session_status": "active",
|
|
613
|
+
"readiness_score": 0.0,
|
|
614
|
+
"message": welcome_message,
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def get_session(session_id: str, db_path=None) -> dict:
|
|
619
|
+
"""Get session status and summary."""
|
|
620
|
+
conn = get_connection(db_path=db_path)
|
|
621
|
+
row = conn.execute(
|
|
622
|
+
"SELECT * FROM intake_sessions WHERE id = ?", (session_id,)
|
|
623
|
+
).fetchone()
|
|
624
|
+
if not row:
|
|
625
|
+
conn.close()
|
|
626
|
+
raise ValueError(f"Session '{session_id}' not found.")
|
|
627
|
+
|
|
628
|
+
session = dict(row)
|
|
629
|
+
|
|
630
|
+
# Get counts
|
|
631
|
+
req_count = conn.execute(
|
|
632
|
+
"SELECT COUNT(*) as cnt FROM intake_requirements WHERE session_id = ?",
|
|
633
|
+
(session_id,),
|
|
634
|
+
).fetchone()["cnt"]
|
|
635
|
+
|
|
636
|
+
turn_count = conn.execute(
|
|
637
|
+
"SELECT COUNT(*) as cnt FROM intake_conversation WHERE session_id = ?",
|
|
638
|
+
(session_id,),
|
|
639
|
+
).fetchone()["cnt"]
|
|
640
|
+
|
|
641
|
+
decomp_count = conn.execute(
|
|
642
|
+
"SELECT COUNT(*) as cnt FROM safe_decomposition WHERE session_id = ?",
|
|
643
|
+
(session_id,),
|
|
644
|
+
).fetchone()["cnt"]
|
|
645
|
+
|
|
646
|
+
doc_count = conn.execute(
|
|
647
|
+
"SELECT COUNT(*) as cnt FROM intake_documents WHERE session_id = ?",
|
|
648
|
+
(session_id,),
|
|
649
|
+
).fetchone()["cnt"]
|
|
650
|
+
|
|
651
|
+
conn.close()
|
|
652
|
+
|
|
653
|
+
session["requirement_count"] = req_count
|
|
654
|
+
session["turn_count"] = turn_count
|
|
655
|
+
session["decomposition_count"] = decomp_count
|
|
656
|
+
session["document_count"] = doc_count
|
|
657
|
+
|
|
658
|
+
return session
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def resume_session(session_id: str, db_path=None) -> dict:
|
|
662
|
+
"""Resume a paused session. Returns context summary and last state."""
|
|
663
|
+
conn = get_connection(db_path=db_path)
|
|
664
|
+
row = conn.execute(
|
|
665
|
+
"SELECT * FROM intake_sessions WHERE id = ?", (session_id,)
|
|
666
|
+
).fetchone()
|
|
667
|
+
if not row:
|
|
668
|
+
conn.close()
|
|
669
|
+
raise ValueError(f"Session '{session_id}' not found.")
|
|
670
|
+
|
|
671
|
+
session = dict(row)
|
|
672
|
+
if session["session_status"] not in ("active", "paused"):
|
|
673
|
+
conn.close()
|
|
674
|
+
raise ValueError(
|
|
675
|
+
f"Session '{session_id}' is {session['session_status']}, cannot resume."
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
# Get last 5 conversation turns for context
|
|
679
|
+
recent_turns = conn.execute(
|
|
680
|
+
"""SELECT turn_number, role, content, content_type
|
|
681
|
+
FROM intake_conversation
|
|
682
|
+
WHERE session_id = ?
|
|
683
|
+
ORDER BY turn_number DESC LIMIT 5""",
|
|
684
|
+
(session_id,),
|
|
685
|
+
).fetchall()
|
|
686
|
+
recent_turns = [dict(t) for t in reversed(recent_turns)]
|
|
687
|
+
|
|
688
|
+
# Get requirement summary
|
|
689
|
+
reqs = conn.execute(
|
|
690
|
+
"""SELECT id, raw_text, requirement_type, priority, status
|
|
691
|
+
FROM intake_requirements
|
|
692
|
+
WHERE session_id = ?
|
|
693
|
+
ORDER BY created_at""",
|
|
694
|
+
(session_id,),
|
|
695
|
+
).fetchall()
|
|
696
|
+
req_summary = [dict(r) for r in reqs]
|
|
697
|
+
|
|
698
|
+
# Update status to active
|
|
699
|
+
conn.execute(
|
|
700
|
+
"UPDATE intake_sessions SET session_status = 'active', updated_at = ? WHERE id = ?",
|
|
701
|
+
(datetime.now(timezone.utc).isoformat(), session_id),
|
|
702
|
+
)
|
|
703
|
+
conn.commit()
|
|
704
|
+
conn.close()
|
|
705
|
+
|
|
706
|
+
if _HAS_AUDIT:
|
|
707
|
+
log_event(
|
|
708
|
+
event_type="intake_session_resumed",
|
|
709
|
+
actor="sparkpilot-requirements-analyst",
|
|
710
|
+
action=f"Resumed intake session {session_id}",
|
|
711
|
+
project_id=session.get("project_id"),
|
|
712
|
+
details={"session_id": session_id},
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
return {
|
|
716
|
+
"status": "ok",
|
|
717
|
+
"session_id": session_id,
|
|
718
|
+
"session_status": "active",
|
|
719
|
+
"readiness_score": session.get("readiness_score", 0.0),
|
|
720
|
+
"context_summary": session.get("context_summary", ""),
|
|
721
|
+
"requirement_count": len(req_summary),
|
|
722
|
+
"recent_turns": recent_turns,
|
|
723
|
+
"requirements": req_summary,
|
|
724
|
+
"message": f"Session resumed. You have {len(req_summary)} requirements captured "
|
|
725
|
+
f"with readiness score {session.get('readiness_score', 0.0):.1%}. "
|
|
726
|
+
f"Where would you like to continue?",
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def pause_session(session_id: str, db_path=None) -> dict:
|
|
731
|
+
"""Pause a session for later resumption."""
|
|
732
|
+
conn = get_connection(db_path=db_path)
|
|
733
|
+
conn.execute(
|
|
734
|
+
"UPDATE intake_sessions SET session_status = 'paused', updated_at = ? WHERE id = ?",
|
|
735
|
+
(datetime.now(timezone.utc).isoformat(), session_id),
|
|
736
|
+
)
|
|
737
|
+
conn.commit()
|
|
738
|
+
conn.close()
|
|
739
|
+
return {"status": "ok", "session_id": session_id, "session_status": "paused"}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
# ---------------------------------------------------------------------------
|
|
743
|
+
# Conversation processing
|
|
744
|
+
# ---------------------------------------------------------------------------
|
|
745
|
+
|
|
746
|
+
def process_turn(
|
|
747
|
+
session_id: str,
|
|
748
|
+
customer_message: str,
|
|
749
|
+
db_path=None,
|
|
750
|
+
) -> dict:
|
|
751
|
+
"""Process a customer message turn. Extracts requirements, detects gaps,
|
|
752
|
+
and generates analyst response.
|
|
753
|
+
|
|
754
|
+
This is the core function that the agent chat calls on each turn.
|
|
755
|
+
It stores the customer message, analyzes it for requirements and issues,
|
|
756
|
+
and returns a structured response.
|
|
757
|
+
"""
|
|
758
|
+
conn = get_connection(db_path=db_path)
|
|
759
|
+
|
|
760
|
+
# Verify session exists and is active
|
|
761
|
+
session = conn.execute(
|
|
762
|
+
"SELECT * FROM intake_sessions WHERE id = ?", (session_id,)
|
|
763
|
+
).fetchone()
|
|
764
|
+
if not session:
|
|
765
|
+
conn.close()
|
|
766
|
+
raise ValueError(f"Session '{session_id}' not found.")
|
|
767
|
+
if dict(session)["session_status"] != "active":
|
|
768
|
+
conn.close()
|
|
769
|
+
raise ValueError(f"Session is {dict(session)['session_status']}, not active.")
|
|
770
|
+
|
|
771
|
+
session_data = dict(session)
|
|
772
|
+
|
|
773
|
+
# Get current turn number
|
|
774
|
+
last_turn = conn.execute(
|
|
775
|
+
"SELECT MAX(turn_number) as max_turn FROM intake_conversation WHERE session_id = ?",
|
|
776
|
+
(session_id,),
|
|
777
|
+
).fetchone()
|
|
778
|
+
turn_number = (last_turn["max_turn"] or 0) + 1
|
|
779
|
+
|
|
780
|
+
# Store customer turn
|
|
781
|
+
conn.execute(
|
|
782
|
+
"""INSERT INTO intake_conversation
|
|
783
|
+
(session_id, turn_number, role, content, content_type, classification)
|
|
784
|
+
VALUES (?, ?, 'customer', ?, 'text', ?)""",
|
|
785
|
+
(session_id, turn_number, customer_message, session_data.get("classification", "CUI")),
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# --- Requirement extraction (deterministic keyword analysis) ---
|
|
789
|
+
extracted_reqs = _extract_requirements_from_text(
|
|
790
|
+
customer_message, session_id, turn_number, conn
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
# --- URL detection and content fetching ---
|
|
794
|
+
url_contents = _extract_and_fetch_urls(customer_message)
|
|
795
|
+
|
|
796
|
+
# --- Ambiguity detection (with dedup across turns) ---
|
|
797
|
+
raw_ambiguities = _detect_ambiguities_in_text(customer_message)
|
|
798
|
+
|
|
799
|
+
# Load previously flagged terms from context so we don't re-flag them
|
|
800
|
+
context = {}
|
|
801
|
+
try:
|
|
802
|
+
context = json.loads(session_data.get("context_summary") or "{}")
|
|
803
|
+
except (ValueError, TypeError):
|
|
804
|
+
pass
|
|
805
|
+
flagged_terms = set(context.get("flagged_ambiguities", []))
|
|
806
|
+
ambiguities = [a for a in raw_ambiguities if a["phrase"].lower() not in flagged_terms]
|
|
807
|
+
|
|
808
|
+
# Record newly flagged terms so future turns skip them
|
|
809
|
+
if ambiguities:
|
|
810
|
+
for a in ambiguities:
|
|
811
|
+
flagged_terms.add(a["phrase"].lower())
|
|
812
|
+
context["flagged_ambiguities"] = sorted(flagged_terms)
|
|
813
|
+
conn.execute(
|
|
814
|
+
"UPDATE intake_sessions SET context_summary = ? WHERE id = ?",
|
|
815
|
+
(json.dumps(context), session_id),
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
# --- Gap signals ---
|
|
819
|
+
gap_signals = _detect_gap_signals(customer_message, session_id, conn)
|
|
820
|
+
|
|
821
|
+
# --- Boundary flags ---
|
|
822
|
+
boundary_flags = _detect_boundary_signals(customer_message, session_data)
|
|
823
|
+
|
|
824
|
+
# --- DevSecOps / ZTA signals (Phase 24) ---
|
|
825
|
+
devsecops_signals = _detect_devsecops_signals(customer_message)
|
|
826
|
+
zta_signals = _detect_zta_signals(customer_message)
|
|
827
|
+
|
|
828
|
+
# --- MOSA signals (Phase 26, D125) ---
|
|
829
|
+
mosa_signals = _detect_mosa_signals(customer_message, session_data)
|
|
830
|
+
|
|
831
|
+
# --- Dev profile signals (Phase 34, D184-D188) ---
|
|
832
|
+
dev_profile_signals = _detect_dev_profile_signals(customer_message, session_data)
|
|
833
|
+
|
|
834
|
+
# --- AI governance signals (Phase 50, D322) ---
|
|
835
|
+
ai_governance_signals = _detect_ai_governance_signals(customer_message, session_data)
|
|
836
|
+
|
|
837
|
+
# --- Structured clarification (D159, spec-kit Pattern 4) ---
|
|
838
|
+
clarification_signals = []
|
|
839
|
+
try:
|
|
840
|
+
from tools.requirements.clarification_engine import analyze_requirements_clarity
|
|
841
|
+
db_path_resolved = db_path or DB_PATH
|
|
842
|
+
clarity_result = analyze_requirements_clarity(
|
|
843
|
+
session_id, max_questions=3, db_path=db_path_resolved,
|
|
844
|
+
)
|
|
845
|
+
clarification_signals = clarity_result.get("questions", [])
|
|
846
|
+
except (ImportError, Exception):
|
|
847
|
+
pass
|
|
848
|
+
|
|
849
|
+
# --- Detect parallel opportunities (D161, spec-kit Pattern 7) ---
|
|
850
|
+
parallel_opportunities = []
|
|
851
|
+
try:
|
|
852
|
+
from tools.requirements.decomposition_engine import detect_parallel_groups
|
|
853
|
+
db_path_resolved = db_path or DB_PATH
|
|
854
|
+
parallel_opportunities = detect_parallel_groups(session_id, db_path=db_path_resolved)
|
|
855
|
+
except (ImportError, Exception):
|
|
856
|
+
pass
|
|
857
|
+
|
|
858
|
+
# --- Update session counters ---
|
|
859
|
+
req_count = conn.execute(
|
|
860
|
+
"SELECT COUNT(*) as cnt FROM intake_requirements WHERE session_id = ?",
|
|
861
|
+
(session_id,),
|
|
862
|
+
).fetchone()["cnt"]
|
|
863
|
+
|
|
864
|
+
conn.execute(
|
|
865
|
+
"""UPDATE intake_sessions
|
|
866
|
+
SET total_requirements = ?,
|
|
867
|
+
ambiguity_count = ambiguity_count + ?,
|
|
868
|
+
updated_at = ?
|
|
869
|
+
WHERE id = ?""",
|
|
870
|
+
(req_count, len(ambiguities), datetime.now(timezone.utc).isoformat(), session_id),
|
|
871
|
+
)
|
|
872
|
+
|
|
873
|
+
# --- BDD preview generation + store as acceptance criteria ---
|
|
874
|
+
# Must run BEFORE readiness so testability score reflects BDD criteria
|
|
875
|
+
bdd_previews = []
|
|
876
|
+
try:
|
|
877
|
+
from tools.requirements.decomposition_engine import generate_bdd_criteria
|
|
878
|
+
for req in extracted_reqs:
|
|
879
|
+
gherkin = generate_bdd_criteria(req["raw_text"], req["requirement_type"])
|
|
880
|
+
bdd_previews.append({"requirement": req["raw_text"][:80], "gherkin": gherkin})
|
|
881
|
+
# Store BDD as acceptance criteria so testability score reflects it
|
|
882
|
+
if gherkin and req.get("id"):
|
|
883
|
+
conn.execute(
|
|
884
|
+
"UPDATE intake_requirements SET acceptance_criteria = ? WHERE id = ?",
|
|
885
|
+
(gherkin, req["id"]),
|
|
886
|
+
)
|
|
887
|
+
except ImportError:
|
|
888
|
+
pass
|
|
889
|
+
|
|
890
|
+
# Flush BDD + requirement writes so the sidebar readiness poller
|
|
891
|
+
# (which opens its own connection) can see them immediately.
|
|
892
|
+
conn.commit()
|
|
893
|
+
|
|
894
|
+
# --- Conversation coverage analysis (what topics are covered vs missing) ---
|
|
895
|
+
coverage = _analyze_conversation_coverage(session_id, conn)
|
|
896
|
+
|
|
897
|
+
# --- Readiness update (computed every turn, after BDD storage) ---
|
|
898
|
+
readiness_update = _quick_readiness_estimate(session_id, conn)
|
|
899
|
+
|
|
900
|
+
# --- Try LLM-powered persona response ---
|
|
901
|
+
analyst_turn = turn_number + 1
|
|
902
|
+
persona_signals = {
|
|
903
|
+
"extracted_reqs": extracted_reqs,
|
|
904
|
+
"ambiguities": ambiguities,
|
|
905
|
+
"boundary_flags": boundary_flags,
|
|
906
|
+
"gap_signals": gap_signals,
|
|
907
|
+
"devsecops_signals": devsecops_signals,
|
|
908
|
+
"zta_signals": zta_signals,
|
|
909
|
+
"mosa_signals": mosa_signals,
|
|
910
|
+
"ai_governance_signals": ai_governance_signals,
|
|
911
|
+
"readiness_update": readiness_update,
|
|
912
|
+
"total_requirements": req_count,
|
|
913
|
+
"coverage": coverage,
|
|
914
|
+
"url_contents": url_contents,
|
|
915
|
+
"clarification_signals": clarification_signals,
|
|
916
|
+
"parallel_opportunities": parallel_opportunities,
|
|
917
|
+
}
|
|
918
|
+
persona_response = _generate_persona_response(
|
|
919
|
+
session_data, customer_message, persona_signals, conn
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
if persona_response is not None:
|
|
923
|
+
analyst_response = persona_response
|
|
924
|
+
else:
|
|
925
|
+
# --- Fallback: deterministic analyst response ---
|
|
926
|
+
response_parts = []
|
|
927
|
+
|
|
928
|
+
if extracted_reqs:
|
|
929
|
+
response_parts.append(
|
|
930
|
+
f"I captured {len(extracted_reqs)} requirement(s) from what you described."
|
|
931
|
+
)
|
|
932
|
+
for req in extracted_reqs:
|
|
933
|
+
response_parts.append(
|
|
934
|
+
f" - [{req['requirement_type'].upper()}] {req['raw_text'][:100]}"
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
if url_contents:
|
|
938
|
+
response_parts.append("\nI reviewed the link(s) you shared:")
|
|
939
|
+
for uc in url_contents:
|
|
940
|
+
title_part = f" ({uc['title']})" if uc.get("title") else ""
|
|
941
|
+
response_parts.append(f" - {uc['url']}{title_part}")
|
|
942
|
+
if uc.get("summary") and not uc["summary"].startswith("("):
|
|
943
|
+
# Show a brief excerpt of the fetched content
|
|
944
|
+
summary_short = uc["summary"][:300]
|
|
945
|
+
response_parts.append(f" Content: {summary_short}")
|
|
946
|
+
|
|
947
|
+
if ambiguities:
|
|
948
|
+
response_parts.append(
|
|
949
|
+
f"\nI noticed {len(ambiguities)} term(s) that need clarification:"
|
|
950
|
+
)
|
|
951
|
+
for amb in ambiguities:
|
|
952
|
+
response_parts.append(f" - '{amb['phrase']}': {amb['clarification']}")
|
|
953
|
+
|
|
954
|
+
if boundary_flags:
|
|
955
|
+
response_parts.append("\nATO Boundary flags:")
|
|
956
|
+
for flag in boundary_flags:
|
|
957
|
+
response_parts.append(f" - [{flag['tier']}] {flag['description']}")
|
|
958
|
+
|
|
959
|
+
if gap_signals:
|
|
960
|
+
response_parts.append("\nPotential gaps detected:")
|
|
961
|
+
for gap in gap_signals:
|
|
962
|
+
response_parts.append(f" - {gap}")
|
|
963
|
+
|
|
964
|
+
if devsecops_signals.get("detected_stages"):
|
|
965
|
+
stages = devsecops_signals["detected_stages"]
|
|
966
|
+
maturity = devsecops_signals.get("maturity_estimate", "unknown")
|
|
967
|
+
response_parts.append(
|
|
968
|
+
f"\nDevSecOps signals detected: {', '.join(stages)} "
|
|
969
|
+
f"(estimated maturity: {maturity})"
|
|
970
|
+
)
|
|
971
|
+
elif devsecops_signals.get("greenfield"):
|
|
972
|
+
response_parts.append(
|
|
973
|
+
"\nNo existing DevSecOps tooling detected — "
|
|
974
|
+
"SPARKPILOT will configure pipeline security stages based on impact level."
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
if zta_signals.get("zta_detected"):
|
|
978
|
+
pillars = zta_signals.get("detected_pillars", [])
|
|
979
|
+
response_parts.append(
|
|
980
|
+
"\nZero Trust Architecture requirement detected"
|
|
981
|
+
+ (f" (pillars: {', '.join(pillars)})" if pillars else "")
|
|
982
|
+
+ ". NIST SP 800-207 framework will be included in compliance assessment."
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
if mosa_signals.get("mosa_detected"):
|
|
986
|
+
mosa_pillars = mosa_signals.get("detected_pillars", [])
|
|
987
|
+
if mosa_signals.get("dod_ic_detected"):
|
|
988
|
+
response_parts.append(
|
|
989
|
+
"\nDoD/IC customer detected — MOSA (Modular Open Systems Approach) "
|
|
990
|
+
"is required per 10 U.S.C. §4401. SPARKPILOT will enforce modular architecture, "
|
|
991
|
+
"open standards, and interface control documentation."
|
|
992
|
+
)
|
|
993
|
+
else:
|
|
994
|
+
response_parts.append(
|
|
995
|
+
"\nMOSA (Modular Open Systems Approach) signals detected. "
|
|
996
|
+
"MOSA framework will be included in compliance assessment."
|
|
997
|
+
)
|
|
998
|
+
if mosa_pillars:
|
|
999
|
+
response_parts.append(
|
|
1000
|
+
f" MOSA pillars identified: {', '.join(p.replace('_', ' ') for p in mosa_pillars)}"
|
|
1001
|
+
)
|
|
1002
|
+
# Probe for missing MOSA pillars
|
|
1003
|
+
all_pillars = {"modular_architecture", "open_standards", "open_interfaces",
|
|
1004
|
+
"data_rights", "competitive_sourcing", "continuous_assessment"}
|
|
1005
|
+
missing = all_pillars - set(mosa_pillars)
|
|
1006
|
+
if missing and len(mosa_pillars) < 4:
|
|
1007
|
+
probes = {
|
|
1008
|
+
"open_interfaces": "Do you have existing Interface Control Documents (ICDs) or API specifications?",
|
|
1009
|
+
"data_rights": "What are the government data rights requirements? (GPR, unlimited rights, etc.)",
|
|
1010
|
+
"competitive_sourcing": "Is multi-vendor replaceability a requirement?",
|
|
1011
|
+
"open_standards": "Which standard protocols/data formats will be used? (REST/OpenAPI, gRPC, JSON, Protobuf)",
|
|
1012
|
+
"modular_architecture": "Is the system designed with modular, loosely-coupled components?",
|
|
1013
|
+
"continuous_assessment": "Will there be ongoing architecture reviews and modularity metrics?",
|
|
1014
|
+
}
|
|
1015
|
+
probe_q = [probes[p] for p in sorted(missing) if p in probes][:2]
|
|
1016
|
+
if probe_q:
|
|
1017
|
+
response_parts.append("\nTo complete MOSA assessment, please clarify:")
|
|
1018
|
+
for q in probe_q:
|
|
1019
|
+
response_parts.append(f" - {q}")
|
|
1020
|
+
|
|
1021
|
+
if ai_governance_signals.get("ai_governance_detected"):
|
|
1022
|
+
gov_pillars = ai_governance_signals.get("detected_pillars", [])
|
|
1023
|
+
if ai_governance_signals.get("federal_agency_detected"):
|
|
1024
|
+
response_parts.append(
|
|
1025
|
+
"\nFederal agency detected — AI governance requirements apply per OMB M-25-21. "
|
|
1026
|
+
"SPARKPILOT will track AI inventory, model documentation, human oversight, "
|
|
1027
|
+
"impact assessments, transparency, and accountability."
|
|
1028
|
+
)
|
|
1029
|
+
else:
|
|
1030
|
+
response_parts.append(
|
|
1031
|
+
"\nAI/ML system usage detected. AI governance framework will be "
|
|
1032
|
+
"included in compliance assessment."
|
|
1033
|
+
)
|
|
1034
|
+
if gov_pillars:
|
|
1035
|
+
response_parts.append(
|
|
1036
|
+
f" AI governance pillars identified: {', '.join(p.replace('_', ' ') for p in gov_pillars)}"
|
|
1037
|
+
)
|
|
1038
|
+
# Probe for missing governance pillars
|
|
1039
|
+
all_gov_pillars = {"ai_inventory", "model_documentation", "human_oversight",
|
|
1040
|
+
"impact_assessment", "transparency", "accountability"}
|
|
1041
|
+
missing_gov = all_gov_pillars - set(gov_pillars)
|
|
1042
|
+
if missing_gov and len(gov_pillars) < 4:
|
|
1043
|
+
gov_probes = {
|
|
1044
|
+
"ai_inventory": "Does this system use AI/ML models? If so, what types (classification, NLP, recommendation, generation)?",
|
|
1045
|
+
"model_documentation": "Are there existing model cards or documentation for the AI models used?",
|
|
1046
|
+
"human_oversight": "What human oversight is in place for AI decisions? Is there an appeal process?",
|
|
1047
|
+
"impact_assessment": "Has an algorithmic impact assessment been conducted? Does the AI make rights-impacting decisions?",
|
|
1048
|
+
"transparency": "Are users notified when AI is making or supporting decisions?",
|
|
1049
|
+
"accountability": "Is there a designated Chief AI Officer (CAIO) or responsible official?",
|
|
1050
|
+
}
|
|
1051
|
+
probe_q = [gov_probes[p] for p in sorted(missing_gov) if p in gov_probes][:2]
|
|
1052
|
+
if probe_q:
|
|
1053
|
+
response_parts.append("\nTo complete AI governance assessment, please clarify:")
|
|
1054
|
+
for q in probe_q:
|
|
1055
|
+
response_parts.append(f" - {q}")
|
|
1056
|
+
|
|
1057
|
+
# Add readiness update and targeted follow-up question
|
|
1058
|
+
if readiness_update:
|
|
1059
|
+
response_parts.append(
|
|
1060
|
+
f"\nReadiness: {readiness_update['overall']:.0%} "
|
|
1061
|
+
f"(completeness={readiness_update['completeness']:.0%}, "
|
|
1062
|
+
f"clarity={readiness_update['clarity']:.0%}, "
|
|
1063
|
+
f"feasibility={readiness_update['feasibility']:.0%}, "
|
|
1064
|
+
f"compliance={readiness_update['compliance']:.0%}, "
|
|
1065
|
+
f"testability={readiness_update['testability']:.0%})"
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
# Ask a targeted question to improve the weakest dimension
|
|
1069
|
+
dims = {
|
|
1070
|
+
"completeness": readiness_update.get("completeness", 0),
|
|
1071
|
+
"clarity": readiness_update.get("clarity", 0),
|
|
1072
|
+
"feasibility": readiness_update.get("feasibility", 0),
|
|
1073
|
+
"compliance": readiness_update.get("compliance", 0),
|
|
1074
|
+
"testability": readiness_update.get("testability", 0),
|
|
1075
|
+
}
|
|
1076
|
+
weakest = min(dims, key=dims.get)
|
|
1077
|
+
|
|
1078
|
+
# Use coverage-based targeted questions for completeness
|
|
1079
|
+
# instead of generic "describe user roles, workflows..."
|
|
1080
|
+
cov = persona_signals.get("coverage", {})
|
|
1081
|
+
missing_qs = cov.get("missing_questions", [])
|
|
1082
|
+
|
|
1083
|
+
followup_questions = {
|
|
1084
|
+
"completeness": (
|
|
1085
|
+
missing_qs[0] if missing_qs else
|
|
1086
|
+
"What other capabilities or workflows should this system support?"
|
|
1087
|
+
),
|
|
1088
|
+
"clarity": (
|
|
1089
|
+
"Could you quantify some of the requirements? "
|
|
1090
|
+
"For example, expected number of users, response times, or data volumes."
|
|
1091
|
+
),
|
|
1092
|
+
"feasibility": (
|
|
1093
|
+
"What's the target timeline, team size, "
|
|
1094
|
+
"and hosting environment? Any technology constraints?"
|
|
1095
|
+
),
|
|
1096
|
+
"compliance": (
|
|
1097
|
+
"What security requirements apply? "
|
|
1098
|
+
"For example: authentication method (CAC/PIV, MFA), encryption "
|
|
1099
|
+
"standards, audit logging, or access control policies."
|
|
1100
|
+
),
|
|
1101
|
+
"testability": (
|
|
1102
|
+
"How would you verify each requirement works? "
|
|
1103
|
+
"What are the pass/fail conditions or acceptance criteria?"
|
|
1104
|
+
),
|
|
1105
|
+
}
|
|
1106
|
+
if dims[weakest] < 0.7:
|
|
1107
|
+
response_parts.append(f"\n{followup_questions[weakest]}")
|
|
1108
|
+
|
|
1109
|
+
# Structured clarification from Impact × Uncertainty matrix (D159)
|
|
1110
|
+
if clarification_signals:
|
|
1111
|
+
top_q = clarification_signals[0]
|
|
1112
|
+
question_text = top_q.get("question", "")
|
|
1113
|
+
if question_text:
|
|
1114
|
+
response_parts.append(f"\nTo help me clarify: {question_text}")
|
|
1115
|
+
|
|
1116
|
+
# Parallel execution opportunities (D161)
|
|
1117
|
+
if parallel_opportunities:
|
|
1118
|
+
response_parts.append(
|
|
1119
|
+
f"\nNote: I've identified {len(parallel_opportunities)} group(s) of "
|
|
1120
|
+
"independent tasks that could run in parallel to speed up delivery."
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
analyst_response = "\n".join(response_parts) if response_parts else (
|
|
1124
|
+
"Thank you. Could you tell me more about the specific capabilities "
|
|
1125
|
+
"you need? For example, what are the key user workflows?"
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
# Store analyst turn
|
|
1129
|
+
conn.execute(
|
|
1130
|
+
"""INSERT INTO intake_conversation
|
|
1131
|
+
(session_id, turn_number, role, content, content_type,
|
|
1132
|
+
extracted_requirements, metadata, classification)
|
|
1133
|
+
VALUES (?, ?, 'analyst', ?, 'text', ?, ?, ?)""",
|
|
1134
|
+
(
|
|
1135
|
+
session_id,
|
|
1136
|
+
analyst_turn,
|
|
1137
|
+
analyst_response,
|
|
1138
|
+
json.dumps([r["id"] for r in extracted_reqs]) if extracted_reqs else None,
|
|
1139
|
+
json.dumps({
|
|
1140
|
+
"ambiguities_found": len(ambiguities),
|
|
1141
|
+
"requirements_extracted": len(extracted_reqs),
|
|
1142
|
+
"boundary_flags": len(boundary_flags),
|
|
1143
|
+
"devsecops_stages_detected": devsecops_signals.get("detected_stages", []),
|
|
1144
|
+
"devsecops_maturity_estimate": devsecops_signals.get("maturity_estimate"),
|
|
1145
|
+
"zta_detected": zta_signals.get("zta_detected", False),
|
|
1146
|
+
"zta_pillars_detected": zta_signals.get("detected_pillars", []),
|
|
1147
|
+
"mosa_detected": mosa_signals.get("mosa_detected", False),
|
|
1148
|
+
"mosa_dod_ic_detected": mosa_signals.get("dod_ic_detected", False),
|
|
1149
|
+
"mosa_pillars_detected": mosa_signals.get("detected_pillars", []),
|
|
1150
|
+
"ai_governance_detected": ai_governance_signals.get("ai_governance_detected", False),
|
|
1151
|
+
"ai_governance_pillars_detected": ai_governance_signals.get("detected_pillars", []),
|
|
1152
|
+
"ai_governance_federal_agency": ai_governance_signals.get("federal_agency_detected", False),
|
|
1153
|
+
"clarification_questions": len(clarification_signals),
|
|
1154
|
+
"parallel_groups": len(parallel_opportunities),
|
|
1155
|
+
}),
|
|
1156
|
+
session_data.get("classification", "CUI"),
|
|
1157
|
+
),
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
conn.commit()
|
|
1161
|
+
conn.close()
|
|
1162
|
+
|
|
1163
|
+
# Audit log
|
|
1164
|
+
if _HAS_AUDIT and extracted_reqs:
|
|
1165
|
+
log_event(
|
|
1166
|
+
event_type="requirement_captured",
|
|
1167
|
+
actor="sparkpilot-requirements-analyst",
|
|
1168
|
+
action=f"Extracted {len(extracted_reqs)} requirement(s) from turn {turn_number}",
|
|
1169
|
+
project_id=session_data.get("project_id"),
|
|
1170
|
+
details={
|
|
1171
|
+
"session_id": session_id,
|
|
1172
|
+
"turn": turn_number,
|
|
1173
|
+
"count": len(extracted_reqs),
|
|
1174
|
+
},
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
return {
|
|
1178
|
+
"status": "ok",
|
|
1179
|
+
"session_id": session_id,
|
|
1180
|
+
"turn_number": analyst_turn,
|
|
1181
|
+
"analyst_response": analyst_response,
|
|
1182
|
+
"extracted_requirements": extracted_reqs,
|
|
1183
|
+
"ambiguities": ambiguities,
|
|
1184
|
+
"boundary_flags": boundary_flags,
|
|
1185
|
+
"gap_signals": gap_signals,
|
|
1186
|
+
"readiness_update": readiness_update,
|
|
1187
|
+
"total_requirements": req_count + len(extracted_reqs),
|
|
1188
|
+
"bdd_previews": bdd_previews,
|
|
1189
|
+
"url_contents": url_contents,
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
|
|
1193
|
+
# ---------------------------------------------------------------------------
|
|
1194
|
+
# Requirement extraction helpers
|
|
1195
|
+
# ---------------------------------------------------------------------------
|
|
1196
|
+
|
|
1197
|
+
# Requirement type detection keywords
|
|
1198
|
+
_REQ_TYPE_KEYWORDS = {
|
|
1199
|
+
"security": [
|
|
1200
|
+
"authenticate", "authorize", "encrypt", "CAC", "PIV", "MFA",
|
|
1201
|
+
"FIPS", "STIG", "access control", "audit log", "credential",
|
|
1202
|
+
"certificate", "PKI", "RBAC", "permission", "classification",
|
|
1203
|
+
],
|
|
1204
|
+
"performance": [
|
|
1205
|
+
"response time", "latency", "throughput", "concurrent",
|
|
1206
|
+
"availability", "uptime", "SLA", "load", "capacity",
|
|
1207
|
+
],
|
|
1208
|
+
"interface": [
|
|
1209
|
+
"integrate", "interface", "API", "REST", "SOAP", "feed",
|
|
1210
|
+
"import", "export", "connect", "external system", "third-party",
|
|
1211
|
+
],
|
|
1212
|
+
"data": [
|
|
1213
|
+
"database", "data store", "retention", "backup", "archive",
|
|
1214
|
+
"migrate data", "data format", "schema", "CUI data",
|
|
1215
|
+
],
|
|
1216
|
+
"compliance": [
|
|
1217
|
+
"NIST", "FedRAMP", "CMMC", "ATO", "SSP", "POAM",
|
|
1218
|
+
"STIG", "RMF", "accreditation", "authorization boundary",
|
|
1219
|
+
],
|
|
1220
|
+
"non_functional": [
|
|
1221
|
+
"scalab", "reliab", "maintainab", "portab", "usab",
|
|
1222
|
+
"accessibility", "WCAG", "Section 508", "i18n", "l10n",
|
|
1223
|
+
],
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
# Priority detection
|
|
1227
|
+
_PRIORITY_KEYWORDS = {
|
|
1228
|
+
"critical": ["must", "shall", "critical", "mandatory", "required", "essential"],
|
|
1229
|
+
"high": ["should", "important", "needed", "key", "primary"],
|
|
1230
|
+
"medium": ["could", "nice to have", "desirable", "want"],
|
|
1231
|
+
"low": ["may", "optional", "future", "nice", "wish"],
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
def _extract_requirements_from_text(text, session_id, turn_number, conn):
|
|
1236
|
+
"""Extract structured requirements from customer text using keyword analysis."""
|
|
1237
|
+
extracted = []
|
|
1238
|
+
# Split on sentence boundaries
|
|
1239
|
+
sentences = [s.strip() for s in text.replace("\n", ". ").split(".") if s.strip() and len(s.strip()) > 10]
|
|
1240
|
+
|
|
1241
|
+
for sentence in sentences:
|
|
1242
|
+
lower = sentence.lower()
|
|
1243
|
+
|
|
1244
|
+
# Check if this sentence contains requirement-like language
|
|
1245
|
+
has_req_signal = any(
|
|
1246
|
+
kw in lower
|
|
1247
|
+
for kw in ["need", "want", "must", "shall", "should", "require",
|
|
1248
|
+
"able to", "capability", "feature", "support", "provide",
|
|
1249
|
+
"enable", "allow", "system will", "system shall"]
|
|
1250
|
+
)
|
|
1251
|
+
if not has_req_signal:
|
|
1252
|
+
continue
|
|
1253
|
+
|
|
1254
|
+
# Detect type
|
|
1255
|
+
req_type = "functional" # default
|
|
1256
|
+
for rtype, keywords in _REQ_TYPE_KEYWORDS.items():
|
|
1257
|
+
if any(kw.lower() in lower for kw in keywords):
|
|
1258
|
+
req_type = rtype
|
|
1259
|
+
break
|
|
1260
|
+
|
|
1261
|
+
# Detect priority
|
|
1262
|
+
priority = "medium" # default
|
|
1263
|
+
for prio, keywords in _PRIORITY_KEYWORDS.items():
|
|
1264
|
+
if any(kw in lower for kw in keywords):
|
|
1265
|
+
priority = prio
|
|
1266
|
+
break
|
|
1267
|
+
|
|
1268
|
+
# Create requirement record — classification inherited from session
|
|
1269
|
+
req_id = _generate_id("req")
|
|
1270
|
+
sess_row = conn.execute(
|
|
1271
|
+
"SELECT classification FROM intake_sessions WHERE id = ?",
|
|
1272
|
+
(session_id,),
|
|
1273
|
+
).fetchone()
|
|
1274
|
+
req_classification = sess_row[0] if sess_row else "CUI"
|
|
1275
|
+
conn.execute(
|
|
1276
|
+
"""INSERT INTO intake_requirements
|
|
1277
|
+
(id, session_id, source_turn, raw_text, requirement_type,
|
|
1278
|
+
priority, status, classification)
|
|
1279
|
+
VALUES (?, ?, ?, ?, ?, ?, 'draft', ?)""",
|
|
1280
|
+
(req_id, session_id, turn_number, sentence.strip(), req_type,
|
|
1281
|
+
priority, req_classification),
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
extracted.append({
|
|
1285
|
+
"id": req_id,
|
|
1286
|
+
"raw_text": sentence.strip(),
|
|
1287
|
+
"requirement_type": req_type,
|
|
1288
|
+
"priority": priority,
|
|
1289
|
+
})
|
|
1290
|
+
|
|
1291
|
+
return extracted
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
def _analyze_conversation_coverage(session_id, conn):
|
|
1295
|
+
"""Analyze conversation history to identify covered topics and specific gaps.
|
|
1296
|
+
|
|
1297
|
+
Returns dict with 'covered' (set of topic keys), 'missing' (list of
|
|
1298
|
+
specific gap questions), and 'summary' (string for LLM context).
|
|
1299
|
+
"""
|
|
1300
|
+
# Gather all customer messages
|
|
1301
|
+
rows = conn.execute(
|
|
1302
|
+
"SELECT content FROM intake_conversation "
|
|
1303
|
+
"WHERE session_id = ? AND role = 'customer' ORDER BY turn_number",
|
|
1304
|
+
(session_id,),
|
|
1305
|
+
).fetchall()
|
|
1306
|
+
all_text = " ".join(dict(r)["content"] for r in rows).lower()
|
|
1307
|
+
|
|
1308
|
+
# Also include requirements extracted from uploaded documents
|
|
1309
|
+
doc_reqs = conn.execute(
|
|
1310
|
+
"SELECT raw_text FROM intake_requirements "
|
|
1311
|
+
"WHERE session_id = ? AND source_document IS NOT NULL",
|
|
1312
|
+
(session_id,),
|
|
1313
|
+
).fetchall()
|
|
1314
|
+
if doc_reqs:
|
|
1315
|
+
doc_text = " ".join(dict(r)["raw_text"] for r in doc_reqs).lower()
|
|
1316
|
+
all_text += " " + doc_text
|
|
1317
|
+
|
|
1318
|
+
# Topic detection with specific follow-up questions
|
|
1319
|
+
topics = {
|
|
1320
|
+
"users_roles": {
|
|
1321
|
+
"keywords": ["user", "role", "agent", "admin", "operator", "analyst",
|
|
1322
|
+
"viewer", "customer", "personnel", "staff"],
|
|
1323
|
+
"covered_question": None,
|
|
1324
|
+
"gap_question": "Who will use this system? What are the distinct user roles and their permissions?",
|
|
1325
|
+
},
|
|
1326
|
+
"workflow": {
|
|
1327
|
+
"keywords": ["workflow", "process", "step", "flow", "sequence",
|
|
1328
|
+
"procedure", "pipeline", "task"],
|
|
1329
|
+
"covered_question": None,
|
|
1330
|
+
"gap_question": "What's the primary user workflow from start to finish?",
|
|
1331
|
+
},
|
|
1332
|
+
"data_model": {
|
|
1333
|
+
"keywords": ["data", "database", "record", "field", "store", "table",
|
|
1334
|
+
"schema", "entity", "model", "input", "output"],
|
|
1335
|
+
"covered_question": None,
|
|
1336
|
+
"gap_question": "What data does the system manage? What are the key entities and their relationships?",
|
|
1337
|
+
},
|
|
1338
|
+
"integration": {
|
|
1339
|
+
"keywords": ["integrate", "api", "rest", "connect", "external",
|
|
1340
|
+
"third-party", "system", "mcp", "soap", "feed"],
|
|
1341
|
+
"covered_question": None,
|
|
1342
|
+
"gap_question": "What external systems does this integrate with? What protocols (REST, MCP, file)?",
|
|
1343
|
+
},
|
|
1344
|
+
"performance": {
|
|
1345
|
+
"keywords": ["performance", "sla", "uptime", "latency", "response time",
|
|
1346
|
+
"concurrent", "throughput", "availability", "99"],
|
|
1347
|
+
"covered_question": None,
|
|
1348
|
+
"gap_question": "What are the performance requirements? (SLA, response times, concurrent users)",
|
|
1349
|
+
},
|
|
1350
|
+
"security_auth": {
|
|
1351
|
+
"keywords": ["security", "auth", "login", "cac", "piv", "mfa",
|
|
1352
|
+
"encrypt", "fips", "access control", "rbac", "permission"],
|
|
1353
|
+
"covered_question": None,
|
|
1354
|
+
"gap_question": "How do users authenticate? (CAC/PIV, MFA, username/password) What access controls are needed?",
|
|
1355
|
+
},
|
|
1356
|
+
"error_handling": {
|
|
1357
|
+
"keywords": ["error", "fail", "exception", "retry", "fallback",
|
|
1358
|
+
"validation", "invalid", "reject", "deny"],
|
|
1359
|
+
"covered_question": None,
|
|
1360
|
+
"gap_question": "What happens when something goes wrong? (validation failures, system errors, invalid inputs)",
|
|
1361
|
+
},
|
|
1362
|
+
"reporting": {
|
|
1363
|
+
"keywords": ["report", "dashboard", "metric", "analytics", "audit",
|
|
1364
|
+
"log", "history", "export", "csv", "pdf"],
|
|
1365
|
+
"covered_question": None,
|
|
1366
|
+
"gap_question": "Does the system need reporting, audit trails, or dashboards? What metrics matter?",
|
|
1367
|
+
},
|
|
1368
|
+
"deployment": {
|
|
1369
|
+
"keywords": ["deploy", "host", "cloud", "aws", "govcloud", "on-prem",
|
|
1370
|
+
"environment", "staging", "production", "docker", "k8s"],
|
|
1371
|
+
"covered_question": None,
|
|
1372
|
+
"gap_question": "Where will this be deployed? (AWS GovCloud, on-prem, hybrid) What environments are needed?",
|
|
1373
|
+
},
|
|
1374
|
+
"ui_ux": {
|
|
1375
|
+
"keywords": ["ui", "ux", "interface", "screen", "page", "form",
|
|
1376
|
+
"button", "design", "mobile", "responsive", "intuitive"],
|
|
1377
|
+
"covered_question": None,
|
|
1378
|
+
"gap_question": "What should the user interface look like? (web app, mobile, desktop) Any specific UX requirements?",
|
|
1379
|
+
},
|
|
1380
|
+
"ai_governance": {
|
|
1381
|
+
"keywords": ["ai system", "machine learning", "ml model", "deep learning",
|
|
1382
|
+
"neural network", "nlp", "computer vision", "recommendation engine",
|
|
1383
|
+
"predictive model", "automated decision", "algorithmic", "chatbot",
|
|
1384
|
+
"generative ai", "llm", "foundation model", "model card",
|
|
1385
|
+
"human oversight", "impact assessment", "ai governance",
|
|
1386
|
+
"responsible ai", "caio", "chief ai officer"],
|
|
1387
|
+
"covered_question": None,
|
|
1388
|
+
"gap_question": "Does this system use AI/ML? If so, what governance is needed (model documentation, human oversight, impact assessments)?",
|
|
1389
|
+
},
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
covered = set()
|
|
1393
|
+
missing_questions = []
|
|
1394
|
+
for topic_key, topic in topics.items():
|
|
1395
|
+
if any(kw in all_text for kw in topic["keywords"]):
|
|
1396
|
+
covered.add(topic_key)
|
|
1397
|
+
else:
|
|
1398
|
+
missing_questions.append(topic["gap_question"])
|
|
1399
|
+
|
|
1400
|
+
# Build summary for LLM context
|
|
1401
|
+
summary_parts = [f"Topics covered ({len(covered)}/{len(topics)}): {', '.join(sorted(covered)) or 'none'}"]
|
|
1402
|
+
if missing_questions:
|
|
1403
|
+
summary_parts.append(f"Topics NOT covered: {', '.join(sorted(set(topics.keys()) - covered))}")
|
|
1404
|
+
summary_parts.append("Ask about ONE of these specific gaps (pick the most important):")
|
|
1405
|
+
for q in missing_questions[:3]:
|
|
1406
|
+
summary_parts.append(f" - {q}")
|
|
1407
|
+
|
|
1408
|
+
return {
|
|
1409
|
+
"covered": covered,
|
|
1410
|
+
"total_topics": len(topics),
|
|
1411
|
+
"missing_questions": missing_questions,
|
|
1412
|
+
"summary": "\n".join(summary_parts),
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
def _extract_and_fetch_urls(text):
|
|
1417
|
+
"""Detect URLs in customer message and fetch page content/summaries.
|
|
1418
|
+
|
|
1419
|
+
Returns a list of dicts: [{"url": "...", "title": "...", "summary": "..."}]
|
|
1420
|
+
Only fetches HTTP/HTTPS URLs. Best-effort — failures return a note.
|
|
1421
|
+
"""
|
|
1422
|
+
url_pattern = re.compile(
|
|
1423
|
+
r'https?://[^\s<>\"\')]+', re.IGNORECASE
|
|
1424
|
+
)
|
|
1425
|
+
urls = url_pattern.findall(text)
|
|
1426
|
+
if not urls:
|
|
1427
|
+
return []
|
|
1428
|
+
|
|
1429
|
+
results = []
|
|
1430
|
+
try:
|
|
1431
|
+
import requests as _requests
|
|
1432
|
+
except ImportError:
|
|
1433
|
+
for u in urls:
|
|
1434
|
+
results.append({"url": u, "title": "", "summary": "(requests library not available)"})
|
|
1435
|
+
return results
|
|
1436
|
+
|
|
1437
|
+
for url in urls[:3]: # limit to 3 URLs per message
|
|
1438
|
+
try:
|
|
1439
|
+
resp = _requests.get(url, timeout=10, headers={
|
|
1440
|
+
"User-Agent": "SPARKPILOT-Intake-Agent/1.0",
|
|
1441
|
+
"Accept": "text/html,application/json,text/plain",
|
|
1442
|
+
})
|
|
1443
|
+
resp.raise_for_status()
|
|
1444
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
1445
|
+
|
|
1446
|
+
if "application/json" in content_type:
|
|
1447
|
+
# JSON API — summarize top-level keys
|
|
1448
|
+
try:
|
|
1449
|
+
data = resp.json()
|
|
1450
|
+
if isinstance(data, dict):
|
|
1451
|
+
summary = f"JSON with keys: {', '.join(list(data.keys())[:15])}"
|
|
1452
|
+
title = data.get("name", data.get("full_name", data.get("title", "")))
|
|
1453
|
+
desc = data.get("description", "")
|
|
1454
|
+
if desc:
|
|
1455
|
+
summary += f". Description: {desc}"
|
|
1456
|
+
elif isinstance(data, list):
|
|
1457
|
+
summary = f"JSON array with {len(data)} items"
|
|
1458
|
+
title = ""
|
|
1459
|
+
else:
|
|
1460
|
+
summary = str(data)[:300]
|
|
1461
|
+
title = ""
|
|
1462
|
+
except (ValueError, TypeError):
|
|
1463
|
+
summary = resp.text[:500]
|
|
1464
|
+
title = ""
|
|
1465
|
+
else:
|
|
1466
|
+
# HTML — extract title and meta description / first text
|
|
1467
|
+
body = resp.text[:50000]
|
|
1468
|
+
title_match = re.search(r'<title[^>]*>([^<]+)</title>', body, re.I)
|
|
1469
|
+
title = title_match.group(1).strip() if title_match else ""
|
|
1470
|
+
|
|
1471
|
+
# Try meta description
|
|
1472
|
+
meta_match = re.search(
|
|
1473
|
+
r'<meta\s+[^>]*name=["\']description["\']\s+content=["\']([^"\']+)',
|
|
1474
|
+
body, re.I
|
|
1475
|
+
)
|
|
1476
|
+
if not meta_match:
|
|
1477
|
+
meta_match = re.search(
|
|
1478
|
+
r'<meta\s+[^>]*content=["\']([^"\']+)["\'][^>]*name=["\']description',
|
|
1479
|
+
body, re.I
|
|
1480
|
+
)
|
|
1481
|
+
desc = meta_match.group(1).strip() if meta_match else ""
|
|
1482
|
+
|
|
1483
|
+
# Try README or about-section text for GitHub-like pages
|
|
1484
|
+
readme_match = re.search(
|
|
1485
|
+
r'<article[^>]*class="[^"]*markdown-body[^"]*"[^>]*>(.*?)</article>',
|
|
1486
|
+
body, re.I | re.S
|
|
1487
|
+
)
|
|
1488
|
+
readme_text = ""
|
|
1489
|
+
if readme_match:
|
|
1490
|
+
# Strip HTML tags for plain text
|
|
1491
|
+
raw = re.sub(r'<[^>]+>', ' ', readme_match.group(1))
|
|
1492
|
+
raw = re.sub(r'\s+', ' ', raw).strip()
|
|
1493
|
+
readme_text = raw[:800]
|
|
1494
|
+
|
|
1495
|
+
if readme_text:
|
|
1496
|
+
summary = f"{desc}. README: {readme_text}" if desc else f"README: {readme_text}"
|
|
1497
|
+
elif desc:
|
|
1498
|
+
summary = desc
|
|
1499
|
+
else:
|
|
1500
|
+
# Fallback: strip tags from first visible text
|
|
1501
|
+
stripped = re.sub(r'<script[^>]*>.*?</script>', '', body, flags=re.S | re.I)
|
|
1502
|
+
stripped = re.sub(r'<style[^>]*>.*?</style>', '', stripped, flags=re.S | re.I)
|
|
1503
|
+
stripped = re.sub(r'<[^>]+>', ' ', stripped)
|
|
1504
|
+
stripped = re.sub(r'\s+', ' ', stripped).strip()
|
|
1505
|
+
summary = stripped[:500] if stripped else "(no readable content)"
|
|
1506
|
+
|
|
1507
|
+
results.append({"url": url, "title": title, "summary": summary[:1000]})
|
|
1508
|
+
|
|
1509
|
+
except Exception as exc:
|
|
1510
|
+
results.append({"url": url, "title": "", "summary": f"(could not fetch: {exc})"})
|
|
1511
|
+
|
|
1512
|
+
return results
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
def _detect_ambiguities_in_text(text):
|
|
1516
|
+
"""Detect ambiguous terms using pattern matching."""
|
|
1517
|
+
ambiguities = []
|
|
1518
|
+
lower = text.lower()
|
|
1519
|
+
|
|
1520
|
+
# Load patterns from context file if available
|
|
1521
|
+
patterns_path = BASE_DIR / "context" / "requirements" / "ambiguity_patterns.json"
|
|
1522
|
+
if patterns_path.exists():
|
|
1523
|
+
with open(patterns_path, "r", encoding="utf-8") as f:
|
|
1524
|
+
data = json.load(f)
|
|
1525
|
+
patterns = data.get("ambiguity_patterns", [])
|
|
1526
|
+
else:
|
|
1527
|
+
# Fallback minimal patterns
|
|
1528
|
+
patterns = [
|
|
1529
|
+
{"phrase": "as needed", "severity": "high",
|
|
1530
|
+
"clarification": "Define specific conditions that trigger this action."},
|
|
1531
|
+
{"phrase": "appropriate", "severity": "high",
|
|
1532
|
+
"clarification": "Define measurable criteria."},
|
|
1533
|
+
{"phrase": "timely", "severity": "high",
|
|
1534
|
+
"clarification": "Specify an exact time threshold."},
|
|
1535
|
+
{"phrase": "user-friendly", "severity": "medium",
|
|
1536
|
+
"clarification": "Define specific usability criteria."},
|
|
1537
|
+
{"phrase": "fast", "severity": "high",
|
|
1538
|
+
"clarification": "Specify a measurable target."},
|
|
1539
|
+
{"phrase": "secure", "severity": "critical",
|
|
1540
|
+
"clarification": "Specify security requirements: FIPS, STIG, controls."},
|
|
1541
|
+
{"phrase": "scalable", "severity": "medium",
|
|
1542
|
+
"clarification": "Define target scale: users, data volume."},
|
|
1543
|
+
]
|
|
1544
|
+
|
|
1545
|
+
for pattern in patterns:
|
|
1546
|
+
if pattern["phrase"].lower() in lower:
|
|
1547
|
+
ambiguities.append({
|
|
1548
|
+
"phrase": pattern["phrase"],
|
|
1549
|
+
"severity": pattern.get("severity", "medium"),
|
|
1550
|
+
"clarification": pattern.get("clarification", "Please clarify this term."),
|
|
1551
|
+
})
|
|
1552
|
+
|
|
1553
|
+
return ambiguities
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
def _detect_gap_signals(text, session_id, conn):
|
|
1557
|
+
"""Detect signals that may indicate requirement gaps."""
|
|
1558
|
+
signals = []
|
|
1559
|
+
lower = text.lower()
|
|
1560
|
+
|
|
1561
|
+
# Check for external system mentions without interface detail
|
|
1562
|
+
interface_terms = ["integrate", "connect", "interface", "external", "feed", "third-party"]
|
|
1563
|
+
protocol_terms = ["rest", "api", "soap", "message queue", "file", "isa", "mou"]
|
|
1564
|
+
if any(t in lower for t in interface_terms) and not any(t in lower for t in protocol_terms):
|
|
1565
|
+
signals.append(
|
|
1566
|
+
"External system mentioned without interface protocol — "
|
|
1567
|
+
"ask about REST/SOAP/MQ and ISA/MOU requirements"
|
|
1568
|
+
)
|
|
1569
|
+
|
|
1570
|
+
# Check for security without specifics
|
|
1571
|
+
if any(t in lower for t in ["secure", "security", "protect"]) and not any(
|
|
1572
|
+
t in lower for t in ["fips", "stig", "nist", "cac", "piv", "encrypt", "mfa"]
|
|
1573
|
+
):
|
|
1574
|
+
signals.append(
|
|
1575
|
+
"Security mentioned without specifics — "
|
|
1576
|
+
"ask about FIPS encryption, CAC/PIV auth, STIG compliance"
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
# Check for data mentions without classification
|
|
1580
|
+
if any(t in lower for t in ["data", "information", "records"]) and not any(
|
|
1581
|
+
t in lower for t in ["cui", "classified", "unclassified", "fouo", "secret"]
|
|
1582
|
+
):
|
|
1583
|
+
signals.append(
|
|
1584
|
+
"Data mentioned without classification — "
|
|
1585
|
+
"ask about CUI categories and data handling requirements"
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
return signals
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
def _detect_boundary_signals(text, session_data):
|
|
1592
|
+
"""Detect potential ATO boundary impact signals."""
|
|
1593
|
+
flags = []
|
|
1594
|
+
lower = text.lower()
|
|
1595
|
+
impact_level = session_data.get("impact_level", "IL5")
|
|
1596
|
+
|
|
1597
|
+
# Classification upgrade signals
|
|
1598
|
+
if impact_level in ("IL4", "IL5") and any(
|
|
1599
|
+
t in lower for t in ["secret", "ts/sci", "top secret", "classified"]
|
|
1600
|
+
):
|
|
1601
|
+
flags.append({
|
|
1602
|
+
"tier": "RED",
|
|
1603
|
+
"description": f"Classification upgrade detected — current system is {impact_level} "
|
|
1604
|
+
f"but SECRET/TS data mentioned. This would invalidate the current ATO.",
|
|
1605
|
+
})
|
|
1606
|
+
|
|
1607
|
+
# New external interface
|
|
1608
|
+
if any(t in lower for t in ["new system", "new interface", "new connection", "new integration"]):
|
|
1609
|
+
flags.append({
|
|
1610
|
+
"tier": "ORANGE",
|
|
1611
|
+
"description": "New external interface — requires ISA/MOU and SSP Section 9 update.",
|
|
1612
|
+
})
|
|
1613
|
+
|
|
1614
|
+
# BYOD/mobile
|
|
1615
|
+
if any(t in lower for t in ["mobile", "byod", "personal device", "phone", "tablet"]):
|
|
1616
|
+
flags.append({
|
|
1617
|
+
"tier": "ORANGE",
|
|
1618
|
+
"description": "Mobile/BYOD access — requires AC-19, MDM solution, SSP boundary update.",
|
|
1619
|
+
})
|
|
1620
|
+
|
|
1621
|
+
# Cloud service change
|
|
1622
|
+
if any(t in lower for t in ["aws commercial", "azure", "gcp", "public cloud"]):
|
|
1623
|
+
flags.append({
|
|
1624
|
+
"tier": "ORANGE",
|
|
1625
|
+
"description": "Non-GovCloud service mentioned — current boundary is AWS GovCloud only.",
|
|
1626
|
+
})
|
|
1627
|
+
|
|
1628
|
+
return flags
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
def _detect_devsecops_signals(text):
|
|
1632
|
+
"""Detect DevSecOps maturity signals from customer text (Phase 24).
|
|
1633
|
+
|
|
1634
|
+
Uses keyword matching from args/devsecops_config.yaml to identify existing
|
|
1635
|
+
security tooling and estimate maturity level.
|
|
1636
|
+
"""
|
|
1637
|
+
try:
|
|
1638
|
+
from tools.devsecops.profile_manager import detect_maturity_from_text
|
|
1639
|
+
return detect_maturity_from_text(text)
|
|
1640
|
+
except (ImportError, Exception):
|
|
1641
|
+
# Fallback: inline minimal detection
|
|
1642
|
+
lower = text.lower()
|
|
1643
|
+
detected = []
|
|
1644
|
+
keyword_map = {
|
|
1645
|
+
"sast": ["static analysis", "code scanning", "bandit", "sonarqube", "fortify"],
|
|
1646
|
+
"sca": ["dependency scan", "pip-audit", "snyk", "npm audit"],
|
|
1647
|
+
"secret_detection": ["secret scanning", "gitleaks", "detect-secrets"],
|
|
1648
|
+
"container_scan": ["container scanning", "trivy", "grype", "image scanning"],
|
|
1649
|
+
"policy_as_code": ["policy as code", "opa", "gatekeeper", "kyverno"],
|
|
1650
|
+
"image_signing": ["image signing", "cosign", "sigstore"],
|
|
1651
|
+
}
|
|
1652
|
+
for stage, keywords in keyword_map.items():
|
|
1653
|
+
if any(kw in lower for kw in keywords):
|
|
1654
|
+
detected.append(stage)
|
|
1655
|
+
greenfield = any(s in lower for s in [
|
|
1656
|
+
"no security scanning", "greenfield", "starting from scratch",
|
|
1657
|
+
])
|
|
1658
|
+
return {
|
|
1659
|
+
"detected_stages": sorted(set(detected)),
|
|
1660
|
+
"maturity_estimate": "level_1_initial" if greenfield else (
|
|
1661
|
+
"level_3_defined" if len(detected) >= 4 else
|
|
1662
|
+
"level_2_managed" if len(detected) >= 2 else
|
|
1663
|
+
"level_1_initial"
|
|
1664
|
+
),
|
|
1665
|
+
"zta_detected": False,
|
|
1666
|
+
"greenfield": greenfield,
|
|
1667
|
+
"stage_count": len(detected),
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
|
|
1671
|
+
def _detect_zta_signals(text):
|
|
1672
|
+
"""Detect Zero Trust Architecture signals from customer text (Phase 24-25).
|
|
1673
|
+
|
|
1674
|
+
Identifies ZTA-relevant keywords and maps them to ZTA pillars.
|
|
1675
|
+
"""
|
|
1676
|
+
lower = text.lower()
|
|
1677
|
+
zta_detected = False
|
|
1678
|
+
detected_pillars = []
|
|
1679
|
+
|
|
1680
|
+
# General ZTA indicators
|
|
1681
|
+
general_keywords = [
|
|
1682
|
+
"zero trust", "nist 800-207", "never trust always verify",
|
|
1683
|
+
"zero trust architecture",
|
|
1684
|
+
]
|
|
1685
|
+
if any(kw in lower for kw in general_keywords):
|
|
1686
|
+
zta_detected = True
|
|
1687
|
+
|
|
1688
|
+
# Pillar-specific keywords
|
|
1689
|
+
pillar_keywords = {
|
|
1690
|
+
"user_identity": ["mfa", "multi-factor", "cac", "piv", "identity provider",
|
|
1691
|
+
"sso", "single sign-on", "continuous auth", "icam"],
|
|
1692
|
+
"device": ["device posture", "mdm", "endpoint detection", "device trust",
|
|
1693
|
+
"device compliance", "edr"],
|
|
1694
|
+
"network": ["micro-segmentation", "microsegmentation", "mtls", "mutual tls",
|
|
1695
|
+
"service mesh", "istio", "linkerd", "network policy",
|
|
1696
|
+
"software-defined perimeter", "ztna"],
|
|
1697
|
+
"application_workload": ["workload identity", "container hardening",
|
|
1698
|
+
"admission control", "signed images"],
|
|
1699
|
+
"data": ["data classification", "encryption at rest", "dlp",
|
|
1700
|
+
"data loss prevention", "tokenization"],
|
|
1701
|
+
"visibility_analytics": ["siem", "continuous monitoring", "anomaly detection",
|
|
1702
|
+
"threat intelligence", "security analytics"],
|
|
1703
|
+
"automation_orchestration": ["soar", "auto-remediation", "security orchestration",
|
|
1704
|
+
"automated response", "self-healing"],
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
for pillar, keywords in pillar_keywords.items():
|
|
1708
|
+
if any(kw in lower for kw in keywords):
|
|
1709
|
+
detected_pillars.append(pillar)
|
|
1710
|
+
zta_detected = True
|
|
1711
|
+
|
|
1712
|
+
return {
|
|
1713
|
+
"zta_detected": zta_detected,
|
|
1714
|
+
"detected_pillars": sorted(detected_pillars),
|
|
1715
|
+
"pillar_count": len(detected_pillars),
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
def _detect_mosa_signals(text, session_data=None):
|
|
1720
|
+
"""Detect MOSA (Modular Open Systems Approach) signals (Phase 26, D125).
|
|
1721
|
+
|
|
1722
|
+
Auto-triggers for DoD/IC customers per 10 U.S.C. §4401. Also detects
|
|
1723
|
+
MOSA pillar keywords for targeted follow-up questions.
|
|
1724
|
+
"""
|
|
1725
|
+
lower = text.lower()
|
|
1726
|
+
mosa_detected = False
|
|
1727
|
+
detected_pillars = []
|
|
1728
|
+
dod_ic_detected = False
|
|
1729
|
+
|
|
1730
|
+
# DoD/IC customer keywords — auto-trigger MOSA (D125)
|
|
1731
|
+
dod_ic_keywords = [
|
|
1732
|
+
"department of defense", "dod", "air force", "army", "navy",
|
|
1733
|
+
"marine corps", "space force", "intelligence community",
|
|
1734
|
+
"combatant command", "acquisition program", "mdap", "acat",
|
|
1735
|
+
"program of record", "warfighter", "nsa", "dia", "nro", "nga",
|
|
1736
|
+
"military", "defense information systems",
|
|
1737
|
+
]
|
|
1738
|
+
if any(kw in lower for kw in dod_ic_keywords):
|
|
1739
|
+
dod_ic_detected = True
|
|
1740
|
+
mosa_detected = True
|
|
1741
|
+
|
|
1742
|
+
# Also check session customer_org for DoD/IC indicators
|
|
1743
|
+
if session_data:
|
|
1744
|
+
org = (session_data.get("customer_org") or "").lower()
|
|
1745
|
+
il = (session_data.get("impact_level") or "").upper()
|
|
1746
|
+
if any(kw in org for kw in ["dod", "defense", "military", "ic", "intelligence"]):
|
|
1747
|
+
dod_ic_detected = True
|
|
1748
|
+
mosa_detected = True
|
|
1749
|
+
if il in ("IL4", "IL5", "IL6"):
|
|
1750
|
+
mosa_detected = True
|
|
1751
|
+
|
|
1752
|
+
# MOSA pillar keywords (from mosa_config.yaml intake_detection)
|
|
1753
|
+
pillar_keywords = {
|
|
1754
|
+
"modular_architecture": ["modular", "loosely coupled", "microservice",
|
|
1755
|
+
"component-based", "plugin", "module boundary",
|
|
1756
|
+
"encapsulation"],
|
|
1757
|
+
"open_standards": ["openapi", "rest api", "grpc", "protobuf",
|
|
1758
|
+
"standard protocol", "open standard", "json schema"],
|
|
1759
|
+
"open_interfaces": ["interface control", "icd", "api versioning",
|
|
1760
|
+
"backward compatible", "interface specification",
|
|
1761
|
+
"integration spec"],
|
|
1762
|
+
"data_rights": ["data rights", "government purpose", "license tracking",
|
|
1763
|
+
"source escrow", "intellectual property", "gpr",
|
|
1764
|
+
"unlimited rights"],
|
|
1765
|
+
"competitive_sourcing": ["vendor lock-in", "vendor neutral", "competitive",
|
|
1766
|
+
"replaceability", "build vs buy", "multi-vendor",
|
|
1767
|
+
"plug-and-play"],
|
|
1768
|
+
"continuous_assessment": ["architecture review", "modularity metrics",
|
|
1769
|
+
"design review", "architecture evolution",
|
|
1770
|
+
"technology refresh"],
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
for pillar, keywords in pillar_keywords.items():
|
|
1774
|
+
if any(kw in lower for kw in keywords):
|
|
1775
|
+
detected_pillars.append(pillar)
|
|
1776
|
+
mosa_detected = True
|
|
1777
|
+
|
|
1778
|
+
return {
|
|
1779
|
+
"mosa_detected": mosa_detected,
|
|
1780
|
+
"dod_ic_detected": dod_ic_detected,
|
|
1781
|
+
"detected_pillars": sorted(detected_pillars),
|
|
1782
|
+
"pillar_count": len(detected_pillars),
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
def _detect_dev_profile_signals(text, session_data=None):
|
|
1787
|
+
"""Detect development profile signals from customer text (Phase 34, D184-D188).
|
|
1788
|
+
|
|
1789
|
+
Identifies coding standards, tooling preferences, and development methodology
|
|
1790
|
+
signals to recommend or auto-apply a development profile template.
|
|
1791
|
+
"""
|
|
1792
|
+
try:
|
|
1793
|
+
from tools.builder.profile_detector import detect_from_text
|
|
1794
|
+
raw = detect_from_text(text)
|
|
1795
|
+
# Normalize to expected shape
|
|
1796
|
+
signals = raw.get("detected_signals", {})
|
|
1797
|
+
return {
|
|
1798
|
+
"profile_detected": raw.get("signal_count", 0) > 0,
|
|
1799
|
+
"detected_dimensions": sorted(signals.keys()),
|
|
1800
|
+
"dimension_count": raw.get("signal_count", 0),
|
|
1801
|
+
"suggested_templates": [],
|
|
1802
|
+
"raw_signals": signals,
|
|
1803
|
+
}
|
|
1804
|
+
except (ImportError, Exception):
|
|
1805
|
+
pass
|
|
1806
|
+
|
|
1807
|
+
# Fallback: inline minimal keyword detection
|
|
1808
|
+
lower = text.lower()
|
|
1809
|
+
detected_dimensions = []
|
|
1810
|
+
|
|
1811
|
+
dimension_keywords = {
|
|
1812
|
+
"language": ["python", "java", "go", "golang", "rust", "typescript", "c#",
|
|
1813
|
+
"csharp", ".net", "flask", "fastapi", "spring boot", "express"],
|
|
1814
|
+
"style": ["snake_case", "camelcase", "camel case", "naming convention",
|
|
1815
|
+
"code style", "indent", "line length", "prettier", "black",
|
|
1816
|
+
"eslint", "ruff", "gofmt", "formatting", "linter"],
|
|
1817
|
+
"testing": ["tdd", "bdd", "test driven", "test coverage", "unit test",
|
|
1818
|
+
"e2e test", "cucumber", "behave", "jest", "pytest"],
|
|
1819
|
+
"architecture": ["microservice", "monolith", "api gateway", "rest",
|
|
1820
|
+
"graphql", "event driven", "hexagonal", "layered"],
|
|
1821
|
+
"security": ["fips", "encryption", "secret management", "sast",
|
|
1822
|
+
"container hardening", "stig", "vulnerability"],
|
|
1823
|
+
"operations": ["kubernetes", "k8s", "docker", "docker compose",
|
|
1824
|
+
"gitlab ci", "github actions", "jenkins", "air-gapped"],
|
|
1825
|
+
"git": ["trunk-based", "gitflow", "github flow", "squash merge",
|
|
1826
|
+
"conventional commits", "branch naming"],
|
|
1827
|
+
"ai": ["bedrock", "openai", "ollama", "byok", "token budget",
|
|
1828
|
+
"llm", "ai model", "code generation model"],
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
for dim, keywords in dimension_keywords.items():
|
|
1832
|
+
if any(kw in lower for kw in keywords):
|
|
1833
|
+
detected_dimensions.append(dim)
|
|
1834
|
+
|
|
1835
|
+
# Check for template-matching signals
|
|
1836
|
+
template_signals = {
|
|
1837
|
+
"dod_baseline": ["dod", "department of defense", "il4", "il5", "il6",
|
|
1838
|
+
"cmmc", "stig"],
|
|
1839
|
+
"fedramp_baseline": ["fedramp", "fed ramp", "jab", "3pao"],
|
|
1840
|
+
"healthcare_baseline": ["hipaa", "hitrust", "phi", "health"],
|
|
1841
|
+
"financial_baseline": ["pci dss", "pci", "sox", "financial"],
|
|
1842
|
+
"law_enforcement_baseline": ["cjis", "law enforcement", "fbi"],
|
|
1843
|
+
"startup": ["startup", "mvp", "lean", "fast iteration"],
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
suggested_templates = []
|
|
1847
|
+
for template, keywords in template_signals.items():
|
|
1848
|
+
if any(kw in lower for kw in keywords):
|
|
1849
|
+
suggested_templates.append(template)
|
|
1850
|
+
|
|
1851
|
+
return {
|
|
1852
|
+
"profile_detected": len(detected_dimensions) > 0,
|
|
1853
|
+
"detected_dimensions": sorted(detected_dimensions),
|
|
1854
|
+
"dimension_count": len(detected_dimensions),
|
|
1855
|
+
"suggested_templates": suggested_templates,
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
def _detect_ai_governance_signals(text, session_data=None):
|
|
1860
|
+
"""Detect AI governance signals from customer text (D322).
|
|
1861
|
+
|
|
1862
|
+
Auto-triggers for federal agencies per OMB M-25-21 and any AI/ML mention.
|
|
1863
|
+
Detects 6 governance pillar keywords for targeted follow-up questions.
|
|
1864
|
+
"""
|
|
1865
|
+
lower = text.lower()
|
|
1866
|
+
ai_governance_detected = False
|
|
1867
|
+
detected_pillars = []
|
|
1868
|
+
federal_agency_detected = False
|
|
1869
|
+
|
|
1870
|
+
# AI/ML mention keywords — auto-trigger governance (D322)
|
|
1871
|
+
ai_ml_keywords = [
|
|
1872
|
+
"ai system", "machine learning", "ml model", "deep learning",
|
|
1873
|
+
"neural network", "natural language processing", "nlp",
|
|
1874
|
+
"computer vision", "recommendation engine", "predictive model",
|
|
1875
|
+
"automated decision", "algorithmic", "chatbot", "virtual assistant",
|
|
1876
|
+
"generative ai", "large language model", "llm", "foundation model",
|
|
1877
|
+
]
|
|
1878
|
+
if any(kw in lower for kw in ai_ml_keywords):
|
|
1879
|
+
ai_governance_detected = True
|
|
1880
|
+
|
|
1881
|
+
# Federal agency keywords — auto-trigger per OMB M-25-21
|
|
1882
|
+
federal_keywords = [
|
|
1883
|
+
"federal agency", "omb", "executive order", "federal government",
|
|
1884
|
+
"government agency", "gsa", "irs", "fda", "epa", "usda",
|
|
1885
|
+
"hhs", "dhs", "dot", "hud", "ed.gov", "va ", "opm",
|
|
1886
|
+
]
|
|
1887
|
+
if any(kw in lower for kw in federal_keywords):
|
|
1888
|
+
federal_agency_detected = True
|
|
1889
|
+
ai_governance_detected = True
|
|
1890
|
+
|
|
1891
|
+
# Also check session customer_org for federal indicators
|
|
1892
|
+
if session_data:
|
|
1893
|
+
org = (session_data.get("customer_org") or "").lower()
|
|
1894
|
+
if any(kw in org for kw in ["federal", "agency", "government", "gsa",
|
|
1895
|
+
"omb", "dod", "defense", "military"]):
|
|
1896
|
+
federal_agency_detected = True
|
|
1897
|
+
ai_governance_detected = True
|
|
1898
|
+
|
|
1899
|
+
# Governance pillar keywords (from ai_governance_config.yaml)
|
|
1900
|
+
pillar_keywords = {
|
|
1901
|
+
"ai_inventory": [
|
|
1902
|
+
"ai system", "machine learning", "ml model", "deep learning",
|
|
1903
|
+
"neural network", "nlp", "computer vision", "recommendation engine",
|
|
1904
|
+
"predictive model", "automated decision", "algorithmic", "chatbot",
|
|
1905
|
+
"generative ai", "llm", "foundation model",
|
|
1906
|
+
],
|
|
1907
|
+
"model_documentation": [
|
|
1908
|
+
"model card", "model documentation", "training data",
|
|
1909
|
+
"model performance", "model accuracy", "model bias",
|
|
1910
|
+
"model validation", "model versioning",
|
|
1911
|
+
],
|
|
1912
|
+
"human_oversight": [
|
|
1913
|
+
"human oversight", "human in the loop", "human on the loop",
|
|
1914
|
+
"manual review", "human approval", "override capability",
|
|
1915
|
+
"escalation", "appeal process",
|
|
1916
|
+
],
|
|
1917
|
+
"impact_assessment": [
|
|
1918
|
+
"impact assessment", "rights impacting", "safety critical",
|
|
1919
|
+
"high risk ai", "algorithmic impact", "disparate impact",
|
|
1920
|
+
"bias assessment", "fairness",
|
|
1921
|
+
],
|
|
1922
|
+
"transparency": [
|
|
1923
|
+
"transparency", "explainability", "interpretability",
|
|
1924
|
+
"notice", "disclosure", "ai disclosure",
|
|
1925
|
+
],
|
|
1926
|
+
"accountability": [
|
|
1927
|
+
"accountability", "responsible ai", "caio",
|
|
1928
|
+
"chief ai officer", "ai governance", "ethics review",
|
|
1929
|
+
"incident response",
|
|
1930
|
+
],
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
for pillar, keywords in pillar_keywords.items():
|
|
1934
|
+
if any(kw in lower for kw in keywords):
|
|
1935
|
+
detected_pillars.append(pillar)
|
|
1936
|
+
ai_governance_detected = True
|
|
1937
|
+
|
|
1938
|
+
return {
|
|
1939
|
+
"ai_governance_detected": ai_governance_detected,
|
|
1940
|
+
"federal_agency_detected": federal_agency_detected,
|
|
1941
|
+
"detected_pillars": sorted(detected_pillars),
|
|
1942
|
+
"pillar_count": len(detected_pillars),
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
|
|
1946
|
+
def _quick_readiness_estimate(session_id, conn):
|
|
1947
|
+
"""Quick readiness estimate based on requirement counts and quality."""
|
|
1948
|
+
reqs = conn.execute(
|
|
1949
|
+
"SELECT * FROM intake_requirements WHERE session_id = ?",
|
|
1950
|
+
(session_id,),
|
|
1951
|
+
).fetchall()
|
|
1952
|
+
|
|
1953
|
+
total = len(reqs)
|
|
1954
|
+
if total == 0:
|
|
1955
|
+
return {"overall": 0.0, "completeness": 0.0, "clarity": 1.0,
|
|
1956
|
+
"feasibility": 0.5, "compliance": 0.0, "testability": 0.0}
|
|
1957
|
+
|
|
1958
|
+
# Completeness: check if we have multiple types
|
|
1959
|
+
types = set(dict(r)["requirement_type"] for r in reqs)
|
|
1960
|
+
type_coverage = len(types) / 6.0 # 6 major types
|
|
1961
|
+
completeness = min(1.0, type_coverage * (min(total, 20) / 20.0))
|
|
1962
|
+
|
|
1963
|
+
# Clarity: based on unresolved ambiguities vs total requirements
|
|
1964
|
+
# Resolved = flagged but user has continued the conversation (addressed it)
|
|
1965
|
+
sess_row = conn.execute(
|
|
1966
|
+
"SELECT ambiguity_count, context_summary FROM intake_sessions WHERE id = ?",
|
|
1967
|
+
(session_id,),
|
|
1968
|
+
).fetchone()
|
|
1969
|
+
sess_dict = dict(sess_row) if sess_row else {}
|
|
1970
|
+
amb_count = sess_dict.get("ambiguity_count", 0)
|
|
1971
|
+
ctx = {}
|
|
1972
|
+
try:
|
|
1973
|
+
ctx = json.loads(sess_dict.get("context_summary") or "{}")
|
|
1974
|
+
except (ValueError, TypeError):
|
|
1975
|
+
pass
|
|
1976
|
+
flagged = ctx.get("flagged_ambiguities", [])
|
|
1977
|
+
# Count user turns after ambiguities were first flagged as clarification
|
|
1978
|
+
turn_count = conn.execute(
|
|
1979
|
+
"SELECT COUNT(*) as cnt FROM intake_conversation "
|
|
1980
|
+
"WHERE session_id = ? AND role = 'customer'",
|
|
1981
|
+
(session_id,),
|
|
1982
|
+
).fetchone()["cnt"]
|
|
1983
|
+
# Each user turn after the first resolves ambiguity somewhat
|
|
1984
|
+
resolved_credit = min(len(flagged), max(0, turn_count - 1)) if flagged else 0
|
|
1985
|
+
unresolved = max(0, len(flagged) - resolved_credit)
|
|
1986
|
+
# Clarity starts at 50% (baseline for having requirements), penalized by
|
|
1987
|
+
# unresolved ambiguities, boosted by conversation depth
|
|
1988
|
+
clarity_base = 0.50
|
|
1989
|
+
penalty = min(0.40, unresolved * 0.15)
|
|
1990
|
+
depth_bonus = min(0.50, turn_count * 0.05) # each turn adds 5%, up to 50%
|
|
1991
|
+
clarity = min(1.0, max(0.0, clarity_base - penalty + depth_bonus))
|
|
1992
|
+
|
|
1993
|
+
# Feasibility: assume 0.5 without architect review
|
|
1994
|
+
feasibility = 0.5
|
|
1995
|
+
|
|
1996
|
+
# Compliance: check selected frameworks + security-type requirements
|
|
1997
|
+
context = {}
|
|
1998
|
+
try:
|
|
1999
|
+
ctx_row = conn.execute(
|
|
2000
|
+
"SELECT context_summary FROM intake_sessions WHERE id = ?",
|
|
2001
|
+
(session_id,),
|
|
2002
|
+
).fetchone()
|
|
2003
|
+
if ctx_row:
|
|
2004
|
+
context = json.loads(dict(ctx_row).get("context_summary") or "{}")
|
|
2005
|
+
except (ValueError, TypeError):
|
|
2006
|
+
pass
|
|
2007
|
+
selected_fw = context.get("selected_frameworks", [])
|
|
2008
|
+
sec_reqs = sum(1 for r in reqs if dict(r)["requirement_type"] in ("security", "compliance"))
|
|
2009
|
+
|
|
2010
|
+
if selected_fw:
|
|
2011
|
+
# Selecting frameworks IS the compliance declaration — full credit.
|
|
2012
|
+
compliance = 1.0
|
|
2013
|
+
else:
|
|
2014
|
+
compliance = min(1.0, sec_reqs / max(3, 1))
|
|
2015
|
+
|
|
2016
|
+
# Testability: check for acceptance criteria (BDD/Gherkin stored during turn)
|
|
2017
|
+
with_criteria = 0
|
|
2018
|
+
for r in reqs:
|
|
2019
|
+
rd = dict(r)
|
|
2020
|
+
ac = rd.get("acceptance_criteria") or ""
|
|
2021
|
+
if ac.strip():
|
|
2022
|
+
with_criteria += 1
|
|
2023
|
+
testability = with_criteria / max(total, 1)
|
|
2024
|
+
|
|
2025
|
+
config = _load_config()
|
|
2026
|
+
weights = config.get("ricoas", {}).get("readiness_weights", {
|
|
2027
|
+
"completeness": 0.25, "clarity": 0.25, "feasibility": 0.20,
|
|
2028
|
+
"compliance": 0.15, "testability": 0.15,
|
|
2029
|
+
})
|
|
2030
|
+
|
|
2031
|
+
overall = (
|
|
2032
|
+
completeness * weights.get("completeness", 0.25) +
|
|
2033
|
+
clarity * weights.get("clarity", 0.25) +
|
|
2034
|
+
feasibility * weights.get("feasibility", 0.20) +
|
|
2035
|
+
compliance * weights.get("compliance", 0.15) +
|
|
2036
|
+
testability * weights.get("testability", 0.15)
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
return {
|
|
2040
|
+
"overall": round(overall, 3),
|
|
2041
|
+
"completeness": round(completeness, 3),
|
|
2042
|
+
"clarity": round(clarity, 3),
|
|
2043
|
+
"feasibility": round(feasibility, 3),
|
|
2044
|
+
"compliance": round(compliance, 3),
|
|
2045
|
+
"testability": round(testability, 3),
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
# ---------------------------------------------------------------------------
|
|
2050
|
+
# Export
|
|
2051
|
+
# ---------------------------------------------------------------------------
|
|
2052
|
+
|
|
2053
|
+
def export_requirements(session_id: str, db_path=None) -> dict:
|
|
2054
|
+
"""Export all requirements from a session as structured JSON."""
|
|
2055
|
+
conn = get_connection(db_path=db_path)
|
|
2056
|
+
reqs = conn.execute(
|
|
2057
|
+
"SELECT * FROM intake_requirements WHERE session_id = ? ORDER BY created_at",
|
|
2058
|
+
(session_id,),
|
|
2059
|
+
).fetchall()
|
|
2060
|
+
|
|
2061
|
+
session = conn.execute(
|
|
2062
|
+
"SELECT * FROM intake_sessions WHERE id = ?", (session_id,)
|
|
2063
|
+
).fetchone()
|
|
2064
|
+
conn.close()
|
|
2065
|
+
|
|
2066
|
+
if not session:
|
|
2067
|
+
raise ValueError(f"Session '{session_id}' not found.")
|
|
2068
|
+
|
|
2069
|
+
return {
|
|
2070
|
+
"status": "ok",
|
|
2071
|
+
"session_id": session_id,
|
|
2072
|
+
"project_id": dict(session).get("project_id"),
|
|
2073
|
+
"customer_name": dict(session).get("customer_name"),
|
|
2074
|
+
"impact_level": dict(session).get("impact_level"),
|
|
2075
|
+
"readiness_score": dict(session).get("readiness_score", 0.0),
|
|
2076
|
+
"total_requirements": len(reqs),
|
|
2077
|
+
"requirements": [dict(r) for r in reqs],
|
|
2078
|
+
"exported_at": datetime.now(timezone.utc).isoformat(),
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
|
|
2082
|
+
# ---------------------------------------------------------------------------
|
|
2083
|
+
# CLI
|
|
2084
|
+
# ---------------------------------------------------------------------------
|
|
2085
|
+
|
|
2086
|
+
def main():
|
|
2087
|
+
parser = argparse.ArgumentParser(
|
|
2088
|
+
description="SPARKPILOT Requirements Intake Engine"
|
|
2089
|
+
)
|
|
2090
|
+
parser.add_argument("--project-id", help="SPARKPILOT project ID")
|
|
2091
|
+
parser.add_argument("--session-id", help="Existing session ID")
|
|
2092
|
+
parser.add_argument("--customer-name", help="Customer name (for new session)")
|
|
2093
|
+
parser.add_argument("--customer-org", help="Customer organization")
|
|
2094
|
+
parser.add_argument(
|
|
2095
|
+
"--impact-level",
|
|
2096
|
+
choices=["IL2", "IL4", "IL5", "IL6"],
|
|
2097
|
+
default="IL5",
|
|
2098
|
+
help="Classification impact level",
|
|
2099
|
+
)
|
|
2100
|
+
parser.add_argument(
|
|
2101
|
+
"--classification",
|
|
2102
|
+
choices=["CUI", "FOUO", "Public", "SECRET", "TOP_SECRET", "NONE"],
|
|
2103
|
+
default=None,
|
|
2104
|
+
help="Data classification override (default: resolved from project)",
|
|
2105
|
+
)
|
|
2106
|
+
parser.add_argument("--message", help="Customer message (single turn)")
|
|
2107
|
+
parser.add_argument("--resume", action="store_true", help="Resume paused session")
|
|
2108
|
+
parser.add_argument("--pause", action="store_true", help="Pause active session")
|
|
2109
|
+
parser.add_argument("--score-readiness", action="store_true", help="Score readiness")
|
|
2110
|
+
parser.add_argument("--export", action="store_true", help="Export requirements")
|
|
2111
|
+
parser.add_argument("--status", action="store_true", help="Get session status")
|
|
2112
|
+
parser.add_argument("--json", action="store_true", help="JSON output")
|
|
2113
|
+
args = parser.parse_args()
|
|
2114
|
+
|
|
2115
|
+
try:
|
|
2116
|
+
result = None
|
|
2117
|
+
|
|
2118
|
+
if args.session_id and args.resume:
|
|
2119
|
+
result = resume_session(args.session_id)
|
|
2120
|
+
|
|
2121
|
+
elif args.session_id and args.pause:
|
|
2122
|
+
result = pause_session(args.session_id)
|
|
2123
|
+
|
|
2124
|
+
elif args.session_id and args.message:
|
|
2125
|
+
result = process_turn(args.session_id, args.message)
|
|
2126
|
+
|
|
2127
|
+
elif args.session_id and args.score_readiness:
|
|
2128
|
+
conn = get_connection()
|
|
2129
|
+
readiness = _quick_readiness_estimate(args.session_id, conn)
|
|
2130
|
+
conn.close()
|
|
2131
|
+
result = {"status": "ok", "session_id": args.session_id, "readiness": readiness}
|
|
2132
|
+
|
|
2133
|
+
elif args.session_id and args.export:
|
|
2134
|
+
result = export_requirements(args.session_id)
|
|
2135
|
+
|
|
2136
|
+
elif args.session_id and args.status:
|
|
2137
|
+
result = get_session(args.session_id)
|
|
2138
|
+
|
|
2139
|
+
elif args.customer_name and args.project_id:
|
|
2140
|
+
# Resolve classification: NONE -> "PUBLIC", None -> resolve from project
|
|
2141
|
+
cls_override = args.classification
|
|
2142
|
+
if cls_override == "NONE":
|
|
2143
|
+
cls_override = "PUBLIC"
|
|
2144
|
+
result = create_session(
|
|
2145
|
+
project_id=args.project_id,
|
|
2146
|
+
customer_name=args.customer_name,
|
|
2147
|
+
customer_org=args.customer_org,
|
|
2148
|
+
impact_level=args.impact_level,
|
|
2149
|
+
classification=cls_override,
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2152
|
+
else:
|
|
2153
|
+
parser.print_help()
|
|
2154
|
+
return
|
|
2155
|
+
|
|
2156
|
+
if args.json:
|
|
2157
|
+
print(json.dumps(result, indent=2, default=str))
|
|
2158
|
+
else:
|
|
2159
|
+
if "message" in result:
|
|
2160
|
+
print(result["message"])
|
|
2161
|
+
elif "analyst_response" in result:
|
|
2162
|
+
print(result["analyst_response"])
|
|
2163
|
+
else:
|
|
2164
|
+
print(json.dumps(result, indent=2, default=str))
|
|
2165
|
+
|
|
2166
|
+
except (ValueError, FileNotFoundError) as e:
|
|
2167
|
+
if args.json:
|
|
2168
|
+
print(json.dumps({"error": str(e)}, indent=2))
|
|
2169
|
+
else:
|
|
2170
|
+
print(f"Error: {e}")
|
|
2171
|
+
raise SystemExit(1)
|
|
2172
|
+
|
|
2173
|
+
|
|
2174
|
+
if __name__ == "__main__":
|
|
2175
|
+
main()
|