From c6388d89f275ce091b55bb8e0b45a9eafa943dfb Mon Sep 17 00:00:00 2001 From: Nik Richers Date: Tue, 19 May 2026 18:26:51 -0700 Subject: [PATCH 1/5] Add chatbot product-to-docs map for Valerie RAG (sc-16170) Generate a product-aligned mini sitemap from frontend routes and help links, wire it into LLM render output, include the docs IA hub page in the corpus, and verify the artifact in CI. --- .github/workflows/publish-llm-markdown.yaml | 7 + .github/workflows/validate-docs-site.yaml | 23 + AGENTS.md | 8 + site/Makefile | 7 +- site/llm/_quarto.yml | 3 +- site/llm/chatbot-product-map.md | 582 ++++++++++++++++++ site/llm/render.sh | 11 +- site/scripts/generate_chatbot_product_map.py | 576 +++++++++++++++++ .../test_generate_chatbot_product_map.py | 51 ++ 9 files changed, 1265 insertions(+), 3 deletions(-) create mode 100644 site/llm/chatbot-product-map.md create mode 100644 site/scripts/generate_chatbot_product_map.py create mode 100644 site/scripts/test_generate_chatbot_product_map.py diff --git a/.github/workflows/publish-llm-markdown.yaml b/.github/workflows/publish-llm-markdown.yaml index 0610dfcaa6..aed2101924 100644 --- a/.github/workflows/publish-llm-markdown.yaml +++ b/.github/workflows/publish-llm-markdown.yaml @@ -34,6 +34,13 @@ jobs: path: site/_source/validmind-library token: ${{ secrets.DOCS_CI_RO_PAT }} + - name: Check out frontend repository (chatbot product map) + uses: actions/checkout@v4 + with: + repository: validmind/frontend + path: frontend + token: ${{ secrets.DOCS_CI_RO_PAT }} + - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 with: diff --git a/.github/workflows/validate-docs-site.yaml b/.github/workflows/validate-docs-site.yaml index 1116bfae2a..f436e33135 100644 --- a/.github/workflows/validate-docs-site.yaml +++ b/.github/workflows/validate-docs-site.yaml @@ -35,6 +35,13 @@ jobs: path: site/_source/validmind-library token: ${{ secrets.DOCS_CI_RO_PAT }} + - name: Check out frontend repository (chatbot product map) + uses: actions/checkout@v4 + with: + repository: validmind/frontend + path: frontend + token: ${{ secrets.DOCS_CI_RO_PAT }} + - name: Check out installation repository uses: actions/checkout@v4 with: @@ -166,10 +173,26 @@ jobs: sudo apt-get update sudo apt-get install -y pandoc + - name: Verify chatbot product map is up to date + run: | + python3 site/scripts/generate_chatbot_product_map.py + git diff --exit-code site/llm/chatbot-product-map.md + + - name: Test chatbot product map generator + run: python3 -m unittest site/scripts/test_generate_chatbot_product_map.py -v + - name: Validate LLM markdown render run: bash llm/render.sh && bash llm/clean.sh working-directory: site + - name: Verify LLM corpus includes product map and docs IA hub + run: | + test -f site/llm/_llm-output/chatbot-product-map.md + test -f site/llm/_llm-output/AGENTS.md + test -f site/llm/_llm-output/about/contributing/using-the-documentation.md + test ! -f site/llm/_llm-output/about/contributing/validmind-community.md + test ! -d site/llm/_llm-output/about/contributing/style-guide + # Release headroom and shrink before final lightweight steps & post-job - name: Release reserve & shrink if: always() diff --git a/AGENTS.md b/AGENTS.md index b1c662e0e7..245e21e390 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,14 @@ If you are an AI agent embedded in ValidMind, your capabilities are documented h This page describes what the assistant can and cannot do, including context-aware features and current limitations. +## Product UI mapping + +The in-app assistant (Valerie) also ingests **`chatbot-product-map.md`** in the LLM corpus. That file maps **platform routes** (for example `/settings/workflows`, `/model-inventory`, `/dashboard`) to documentation URLs and section hints. + +Use it when the user’s question is tied to where they are in the product — especially **Settings**, where the UI groups features differently than the documentation sidebars (Configuration, Workflows, Inventory, and so on). + +For documentation organized by topic, continue to use **Using the documentation** (above) and the section table in this file. + ## File format Documentation is written in Quarto Markdown (`.qmd`). Key conventions: diff --git a/site/Makefile b/site/Makefile index df2a4dbe37..bc8ab3e52c 100644 --- a/site/Makefile +++ b/site/Makefile @@ -14,7 +14,7 @@ SRC_ROOT := _source SRC_DIR := $(SRC_ROOT)/validmind-library # Define .PHONY target for help section -.PHONY: help add-copyright clean clone copy-installation copy-release-notes delete-demo-branch deploy-demo-branch deploy-prod deploy-staging docker-build docker-serve docker-site docker-site-lite docs-site execute generate-sitemap get-api-json get-source kind-serve kind-stop kind-restart kind-logs notebooks python-docs release-notes render-llm template-schema-docs test-descriptions verify-copyright yearly-releases +.PHONY: help add-copyright clean clone copy-installation copy-release-notes delete-demo-branch deploy-demo-branch deploy-prod deploy-staging docker-build docker-serve docker-site docker-site-lite docs-site execute generate-chatbot-product-map generate-sitemap get-api-json get-source kind-serve kind-stop kind-restart kind-logs notebooks python-docs release-notes render-llm template-schema-docs test-descriptions verify-copyright yearly-releases # Help section help: @@ -39,6 +39,7 @@ help: @echo " docker-site Get source, render site with Docker profile, execute notebooks" @echo " docker-site-lite Get source and render site with Docker profile (skips notebook execution)" @echo " docs-site Get all source files and render the production docs site with Quarto" + @echo " generate-chatbot-product-map Generate product-to-docs map for the in-app assistant" @echo " generate-sitemap Generate a sitemap for the static HTML site" @echo " execute Execute a Jupyter Notebook or notebook directory" @echo " get-api-json Download Swagger JSON specs from ValidMind APIs into reference/" @@ -450,6 +451,10 @@ yearly-releases: git status | grep -v 'release-scripts/' quarto preview +# Generate product-to-documentation map for chatbot RAG (requires frontend checkout) +generate-chatbot-product-map: + @python3 scripts/generate_chatbot_product_map.py + # Render site to GFM markdown for LLM ingestion render-llm: @echo "\nRendering site to GFM markdown for LLM ingestion ..." diff --git a/site/llm/_quarto.yml b/site/llm/_quarto.yml index fecfcafdef..b9322acf09 100644 --- a/site/llm/_quarto.yml +++ b/site/llm/_quarto.yml @@ -12,7 +12,8 @@ project: - "../**/*.qmd" - "!../notebooks/" - "!../404.qmd" - - "!../about/contributing/" + - "!../about/contributing/validmind-community.qmd" + - "!../about/contributing/style-guide/" - "!../about/deployment/" - "!../about/fine-print/" - "!../_site/" diff --git a/site/llm/chatbot-product-map.md b/site/llm/chatbot-product-map.md new file mode 100644 index 0000000000..d8f5ad2a3c --- /dev/null +++ b/site/llm/chatbot-product-map.md @@ -0,0 +1,582 @@ +# ValidMind product-to-documentation map + +> Auto-generated. Maps in-product routes to documentation URLs and key sections. +> For how documentation is organized by topic, see `AGENTS.md` and +> [Using the documentation](/about/contributing/using-the-documentation.html). + +## Settings + +### Main navigation + +#### `/settings` — Settings + +- *No direct help link; content may be covered under scattered guide sections.* + +### Governance + +#### `/settings/attestation-templates` — Attestation Templates + +**Docs (related):** + +- `/guide/attestation/submit-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations +- `/guide/attestation/review-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/manage-attestations.html` + - Sections: Prerequisites; Add attestation templates; Test attestation schedules; Edit attestation periods; Cancel attestation periods; View attestations dashboard; Progress; Responses +- `/guide/attestation/approve-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates + +- *No direct help link in frontend; related docs inferred from keywords.* + +### Main navigation + +#### `/settings/attestations` — /settings/attestations + +**Docs (primary):** + +- `/guide/attestation/manage-attestations.html` + - Sections: Prerequisites; Add attestation templates; Test attestation schedules; Edit attestation periods; Cancel attestation periods; View attestations dashboard; Progress; Responses +- `/guide/shared/work-with-filters.html` + +**Docs (related):** + +- `/guide/attestation/submit-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations +- `/guide/attestation/review-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/approve-attestations.html` + - Sections: Prerequisites; Steps + +### Organization + +#### `/settings/authentication` — Authentication + +**Docs (primary):** + +- `/installation/security/configure-single-sign-on-sso.html` + - Sections: What is SSO?; Prerequisites; Step 1: Set up Microsoft Entra for SSO; Step 2: Contact ValidMind to enable SSO + +**Docs (related):** + +- `/guide/configuration/managing-your-organization.html` + - Sections: Prerequisites; Switch between organizations; Change names of organizations; Manage document defaults; Tracked changes; Numbered table and figure captions; Organization setup + +### Documents + +#### `/settings/block-library` — Block Library + +**Docs (primary):** + +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +### Main navigation + +#### `/settings/custom-fields` — /settings/custom-fields + +**Docs (primary):** + +- `/guide/model-inventory/manage-model-inventory-fields.html` + - Note: no matching `.qmd` source found + +### Documents + +#### `/settings/document-types` — Document Types + +**Docs (primary):** + +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks + +### Organization + +#### `/settings/email-notifications` — Email Notifications + +**Docs (primary):** + +- `/guide/configuration/manage-platform-notifications.html` (section: #customize-email-notifications) + - Sections: Prerequisites; View platform notifications; Review updates; Mark updates as read; Dismiss updates; Customize email notifications + +**Docs (related):** + +- `/guide/configuration/managing-your-organization.html` + - Sections: Prerequisites; Switch between organizations; Change names of organizations; Manage document defaults; Tracked changes; Numbered table and figure captions; Organization setup + +### Artifacts + +#### `/settings/finding-custom-fields` — Artifact Fields + +**Docs (primary):** + +- `/guide/model-validation/manage-model-finding-fields.html` + - Note: no matching `.qmd` source found + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +#### `/settings/finding-severities` — Artifact Severities + +**Docs (primary):** + +- `/guide/model-validation/manage-artifact-severities.html` + - Note: no matching `.qmd` source found + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +#### `/settings/finding-types` — Artifact Types + +**Docs (primary):** + +- `/guide/model-validation/manage-artifact-types.html` + - Note: no matching `.qmd` source found + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +### Users & Access + +#### `/settings/groups` — Groups + +**Docs (primary):** + +- `/guide/configuration/manage-groups.html` + - Sections: Prerequisites; View group details; Add new groups; Remove groups; Add or remove group members + +**Docs (related):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management +- `/guide/configuration/manage-users.html` + - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles + +### Main navigation + +#### `/settings/index.tsx` — /settings/index.tsx + +**Docs (primary):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management + +### Integrations + +#### `/settings/integrations/connections` — Connections + +**Docs (related):** + +- `/faq/faq-integrations.html` + - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more +- `/guide/configuration/set-up-your-organization.html` + - Sections: Prerequisites; Manage business units; Manage use cases; Add use cases and use case groups; Remove use cases and use case groups; Manage risk areas; What's next +- `/guide/configuration/manage-record-stakeholder-types.html` + - Sections: Prerequisites; Add record stakeholder types; Edit record stakeholder types; Assign stakeholder types to record types; Configure stakeholder types on registration; Manage stakeholder type permissions; Manage stakeholder types on records +- `/guide/configuration/personalizing-validmind.html` +- `/guide/configuration/configure-google-private-service-connect.html` + - Sections: Prerequisites; VPC service information; Configure your Google Cloud Platform project; Request access from }; Prepare your network for connection; Create an endpoint to connect to }; Steps; Create an endpoint to connect to the } authentication service +- `/guide/configuration/manage-roles.html` + - Sections: Prerequisites; Add or update roles; Two special default roles provided by } have unique characteristics.; Manage role permissions; Manage role users; Rename existing roles + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/settings/integrations/data-exports` — Analytics Exports + +**Docs (related):** + +- `/faq/faq-integrations.html` + - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more +- `/faq/faq-reporting.html` + - Sections: What analytic features are offered by }?; Learn more +- `/guide/reporting/export-inventory.html` + - Sections: Prerequisites; Export lists of models +- `/guide/reporting/working-with-analytics.html` + - Sections: Prerequisites; Default report pages; Arrange report widgets; Work with analytics +- `/guide/reporting/view-report-data.html` + - Sections: Prerequisites; View report data +- `/guide/reporting/generating-exports.html` + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/settings/integrations/secrets` — Secrets + +**Docs (related):** + +- `/faq/faq-integrations.html` + - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more +- `/guide/configuration/set-up-your-organization.html` + - Sections: Prerequisites; Manage business units; Manage use cases; Add use cases and use case groups; Remove use cases and use case groups; Manage risk areas; What's next +- `/guide/configuration/manage-record-stakeholder-types.html` + - Sections: Prerequisites; Add record stakeholder types; Edit record stakeholder types; Assign stakeholder types to record types; Configure stakeholder types on registration; Manage stakeholder type permissions; Manage stakeholder types on records +- `/guide/configuration/personalizing-validmind.html` +- `/guide/configuration/configure-google-private-service-connect.html` + - Sections: Prerequisites; VPC service information; Configure your Google Cloud Platform project; Request access from }; Prepare your network for connection; Create an endpoint to connect to }; Steps; Create an endpoint to connect to the } authentication service +- `/guide/configuration/manage-roles.html` + - Sections: Prerequisites; Add or update roles; Two special default roles provided by } have unique characteristics.; Manage role permissions; Manage role users; Rename existing roles + +- *No direct help link in frontend; related docs inferred from keywords.* + +### Users & Access + +#### `/settings/invitation` — Invite New Users + +**Docs (primary):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management +- `/guide/configuration/manage-users.html` (section: #manage-user-invitations) + - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles + +### Organization + +#### `/settings/organization` — Organization + +**Docs (primary):** + +- `/guide/configuration/managing-your-organization.html` + - Sections: Prerequisites; Switch between organizations; Change names of organizations; Manage document defaults; Tracked changes; Numbered table and figure captions; Organization setup + +### Users & Access + +#### `/settings/permissions` — Permissions + +**Docs (primary):** + +- `/guide/configuration/manage-permissions.html` + - Sections: Prerequisites; Steps + +**Docs (related):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management +- `/guide/configuration/manage-users.html` + - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles + +### Main navigation + +#### `/settings/primary-record-type-stages` — /settings/primary-record-type-stages + +**Docs (primary):** + +- `/guide/workflows/manage-model-stages.html` + - Note: no matching `.qmd` source found + +### Your Account + +#### `/settings/profile` — Profile + +**Docs (primary):** + +- `/guide/configuration/view-your-profile.html` + - Note: no matching `.qmd` source found + +**Docs (related):** + +- `/guide/configuration/personalizing-validmind.html` +- `/guide/configuration/manage-your-profile.html` + - Sections: Prerequisites; Access your profile; Onboarding; User Interface Preferences; Terms; Localization; Access Keys; To revoke and regenerate keys, click **Revoke & Regenerate Keys**. + +### Main navigation + +#### `/settings/regulation-policy` — /settings/regulation-policy + +**Docs (primary):** + +- `/guide/templates/customize-virtual-document-validator.html` (section: #add-or-edit-assessment-questions) + - Note: no matching `.qmd` source found + +### Governance + +#### `/settings/regulations` — Regulations & Policies + +- *No direct help link; content may be covered under scattered guide sections.* + +#### `/settings/risk-areas` — Risk Areas & Validation Guidelines + +**Docs (primary):** + +- `/guide/model-validation/manage-validation-guidelines.html` + - Note: no matching `.qmd` source found + +### Users & Access + +#### `/settings/roles` — Roles + +**Docs (primary):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management +- `/guide/configuration/manage-roles.html` + - Sections: Prerequisites; Add or update roles; Two special default roles provided by } have unique characteristics.; Manage role permissions; Manage role users; Rename existing roles + +**Docs (related):** + +- `/guide/configuration/manage-users.html` + - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles + +### Main navigation + +#### `/settings/stakeholders` — /settings/stakeholders + +**Docs (primary):** + +- `/guide/configuration/manage-model-stakeholder-types.html` + - Note: no matching `.qmd` source found + +#### `/settings/statuses` — /settings/statuses + +**Docs (primary):** + +- `/guide/workflows/manage-model-stages.html` + - Note: no matching `.qmd` source found + +### Documents + +#### `/settings/templates` — Templates + +**Docs (primary):** + +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +### Your Account + +#### `/settings/theme-customization` — Theme Customization + +- *No direct help link; content may be covered under scattered guide sections.* + +### Users & Access + +#### `/settings/user-directory` — User Directory + +**Docs (primary):** + +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management +- `/guide/configuration/manage-users.html` + - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles + +### Governance + +#### `/settings/workflow-states` — Workflow States + +**Docs (primary):** + +- `/guide/workflows/workflow-states.html` + - Sections: Prerequisites; Manage workflow states + +**Docs (related):** + +- `/faq/faq-workflows.html` + - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more +- `/guide/workflows/setting-up-workflows.html` + - Sections: View, sort, and filter workflows; Sort workflows; Filter workflows; How do I create effective filters?; Set up workflows; What's next +- `/guide/workflows/workflow-configuration-examples.html` + - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples +- `/guide/workflows/manage-workflow-tasks.html` + - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows +- `/guide/workflows/transition-workflows.html` + - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/workflows/conditional-step-requirements.html` + - Sections: Prerequisites; Configure conditional requirements + +#### `/settings/workflows` — Workflows + +**Docs (primary):** + +- `/guide/workflows/setting-up-workflows.html` + - Sections: View, sort, and filter workflows; Sort workflows; Filter workflows; How do I create effective filters?; Set up workflows; What's next + +**Docs (related):** + +- `/faq/faq-workflows.html` + - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more +- `/guide/workflows/workflow-configuration-examples.html` + - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples +- `/guide/workflows/manage-workflow-tasks.html` + - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows +- `/guide/workflows/transition-workflows.html` + - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/workflows/conditional-step-requirements.html` + - Sections: Prerequisites; Configure conditional requirements +- `/guide/workflows/introduction-to-workflows.html` + - Sections: Workflow elements; What's next + +## Main application + +#### `/analytics` — sidebar.analytics + +**Docs (related):** + +- `/faq/faq-reporting.html` + - Sections: What analytic features are offered by }?; Learn more +- `/guide/reporting/export-inventory.html` + - Sections: Prerequisites; Export lists of models +- `/guide/reporting/working-with-analytics.html` + - Sections: Prerequisites; Default report pages; Arrange report widgets; Work with analytics +- `/guide/reporting/view-report-data.html` + - Sections: Prerequisites; View report data +- `/guide/reporting/generating-exports.html` +- `/guide/reporting/manage-custom-reports.html` + - Sections: Visualization configuration options; Prerequisites; Add custom analytics; Edit custom analytics; Remove custom analytics + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/artifacts` — Artifacts + +**Docs (related):** + +- `/guide/templates/manage-document-templates.html` + - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-document-types.html` + - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next +- `/guide/templates/working-with-documents.html` + - Sections: What's next +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/attestations` — Attestations + +**Docs (related):** + +- `/guide/attestation/submit-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations +- `/guide/attestation/review-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/manage-attestations.html` + - Sections: Prerequisites; Add attestation templates; Test attestation schedules; Edit attestation periods; Cancel attestation periods; View attestations dashboard; Progress; Responses +- `/guide/attestation/approve-attestations.html` + - Sections: Prerequisites; Steps + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/dashboard` — Dashboard + +**Docs (related):** + +- `/guide/configuration/customize-your-dashboard.html` + - Sections: Prerequisites; Manage dashboards; Add dashboards; Edit or remove dashboards; Manage widgets; Arrange widgets; Add widgets; Remove widgets + +- *No direct help link in frontend; related docs inferred from keywords.* + +#### `/validation-issues` — Validation Issues + +- *No direct help link; content may be covered under scattered guide sections.* + +#### `/workflows` — Workflows + +**Docs (related):** + +- `/faq/faq-workflows.html` + - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more +- `/guide/workflows/setting-up-workflows.html` + - Sections: View, sort, and filter workflows; Sort workflows; Filter workflows; How do I create effective filters?; Set up workflows; What's next +- `/guide/workflows/workflow-configuration-examples.html` + - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples +- `/guide/workflows/manage-workflow-tasks.html` + - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows +- `/guide/workflows/transition-workflows.html` + - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/workflows/conditional-step-requirements.html` + - Sections: Prerequisites; Configure conditional requirements + +- *No direct help link in frontend; related docs inferred from keywords.* + +## Documentation index (human-oriented) + +See `AGENTS.md` and `about/contributing/using-the-documentation.md` in the LLM corpus for guides organized by feature area (Configuration, Workflows, Inventory, etc.). diff --git a/site/llm/render.sh b/site/llm/render.sh index 6c05ae0d99..e4ef0c4240 100755 --- a/site/llm/render.sh +++ b/site/llm/render.sh @@ -30,7 +30,8 @@ project: - "**/*.qmd" - "!notebooks/" - "!404.qmd" - - "!about/contributing/" + - "!about/contributing/validmind-community.qmd" + - "!about/contributing/style-guide/" - "!about/deployment/" - "!about/fine-print/" - "!llm/" @@ -53,10 +54,18 @@ quarto render --to gfm # AGENTS.md lives at the repo root so IDE/agent tooling finds it there, but it # must also reach the LLM output so the docs chatbot can ingest it. +echo "" +echo "=== Generating chatbot product map ===" +python3 scripts/generate_chatbot_product_map.py + echo "" echo "=== Copying AGENTS.md from repo root into LLM output ===" cp ../AGENTS.md llm/_llm-output/AGENTS.md +echo "" +echo "=== Copying chatbot product map into LLM output ===" +cp llm/chatbot-product-map.md llm/_llm-output/chatbot-product-map.md + echo "" echo "=== Post-processing markdown files ===" bash llm/clean.sh diff --git a/site/scripts/generate_chatbot_product_map.py b/site/scripts/generate_chatbot_product_map.py new file mode 100644 index 0000000000..e8c299e622 --- /dev/null +++ b/site/scripts/generate_chatbot_product_map.py @@ -0,0 +1,576 @@ +#!/usr/bin/env python3 +# Copyright © 2023-2026 ValidMind Inc. All rights reserved. +# Refer to the LICENSE file in the root of this repository for details. +# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial +""" +Generate a product-to-documentation map for the in-app chatbot (LanceDB / RAG). + +Correlates frontend routes and help links with documentation pages and headings. + +Usage (from documentation repo root): + python site/scripts/generate_chatbot_product_map.py + python site/scripts/generate_chatbot_product_map.py --frontend-root ../frontend +""" + +from __future__ import annotations + +import argparse +import json +import re +from dataclasses import dataclass, field +from pathlib import Path + +try: + import yaml +except ImportError: # pragma: no cover + yaml = None # type: ignore + + +DOCS_URL_PATTERN = re.compile( + r"(?:VALIDMIND_DOCS_URL|docs\.validmind\.ai)" + r'[^`"\']*?' + r'(/(?:guide|get-started|developer|faq|support|training|about|reference)[^`"\')\s#]+)' + r"(?:#([a-zA-Z0-9_-]+))?" +) + +HELP_LINK_PATTERN = re.compile( + r"helpLink=\{?`(?:\$\{CONFIG\.VALIDMIND_DOCS_URL\}|https://docs\.validmind\.ai)" + r'(/[^`"\')\s#]+)(?:#([a-zA-Z0-9_-]+))?`?\}?' +) + +DOCUMENTATION_LINK_PATTERN = re.compile( + r'documentationLink=\{?`(?:\$\{CONFIG\.VALIDMIND_DOCS_URL\}|https://docs\.validmind\.ai)' + r'(/[^`"\')\s#]+)(?:#([a-zA-Z0-9_-]+))?`?\}?' +) + +LINK_PROP_PATTERN = re.compile( + r'link=\{?`(?:\$\{CONFIG\.VALIDMIND_DOCS_URL\}|https://docs\.validmind\.ai)' + r'(/[^`"\')\s#]+)(?:#([a-zA-Z0-9_-]+))?`?\}?' +) + +SETTING_GROUP_TITLE_PATTERN = re.compile( + r']*\btitle="([^"]+)"', +) + +SETTING_LINK_PATTERN = re.compile( + r']*\btitle="([^"]+)"[^>]*\bpath="([^"]+)"', +) + +ATTR_PATTERN = re.compile(r'(\w+)=["{`]([^"`}]+)["`}]') + +HEADING_PATTERN = re.compile(r"^(#{2,3})\s+(.+)$", re.MULTILINE) + +SIDEBAR_PATH_PATTERN = re.compile( + r"(?:path|documentationLink):\s*['\"](/[^'\"]+)['\"]" +) +SIDEBAR_LABEL_PATTERN = re.compile( + r"label:\s*(?:copy\([^)]+\)|['\"]([^'\"]+)['\"])" +) + +# Map settings link titles (lowercase) to likely guide doc path segments for related docs. +# Frontend help URLs that do not match published doc paths. +DOC_PATH_ALIASES: dict[str, str] = { + "/guide/model-workflows/setting-up-model-workflows.html": ( + "/guide/workflows/setting-up-workflows.html" + ), +} + +RELATED_DOC_PREFIXES = ( + "/guide/", + "/get-started/", + "/support/", + "/faq/faq-", + "/about/contributing/", +) + +RELATED_DOC_KEYWORDS: dict[str, list[str]] = { + "workflows": ["workflows", "model-workflows"], + "workflow": ["workflows", "model-workflows"], + "roles": ["configuration/manage-roles", "configuration/managing-users"], + "permissions": ["configuration/manage-permissions", "configuration/managing-users"], + "groups": ["configuration/manage-groups", "configuration/managing-users"], + "users": ["configuration/managing-users", "configuration/manage-users"], + "invitation": ["configuration/managing-users", "configuration/manage-users"], + "integrations": ["integrations", "configuration"], + "templates": ["templates", "model-documentation"], + "document": ["templates", "model-documentation"], + "inventory": ["inventory", "model-inventory"], + "finding": ["model-validation", "findings"], + "artifact": ["model-validation", "templates"], + "attestation": ["attestation"], + "regulation": ["templates/customize-virtual-document-validator", "model-validation"], + "risk": ["model-validation/manage-validation-guidelines"], + "authentication": ["configuration/managing-your-organization"], + "organization": ["configuration/managing-your-organization"], + "profile": ["configuration/manage-your-profile", "configuration/personalizing-validmind"], + "analytics": ["reporting", "monitoring"], + "dashboard": ["configuration/customize-your-dashboard"], +} + + +@dataclass +class DocRef: + path: str # URL path like /guide/foo/bar.html + anchor: str | None = None + + @property + def key(self) -> str: + return f"{self.path}#{self.anchor}" if self.anchor else self.path + + +@dataclass +class ProductRoute: + path: str + label: str + group: str | None = None + primary_docs: list[DocRef] = field(default_factory=list) + related_docs: list[DocRef] = field(default_factory=list) + notes: list[str] = field(default_factory=list) + + +def find_repo_root() -> Path: + current = Path(__file__).resolve() + for parent in current.parents: + if (parent / ".git").is_dir(): + return parent + return current.parent.parent.parent + + +def html_path_to_qmd(site_dir: Path, doc_path: str) -> Path | None: + """Map /guide/foo/bar.html -> site/guide/foo/bar.qmd""" + path = doc_path.strip() + if not path.startswith("/"): + path = "/" + path + if path.endswith(".html"): + path = path[:-5] + rel = path.lstrip("/") + ".qmd" + candidate = site_dir / rel + return candidate if candidate.is_file() else None + + +def extract_headings(qmd_path: Path, max_level: int = 3) -> list[str]: + text = qmd_path.read_text(encoding="utf-8") + headings: list[str] = [] + for match in HEADING_PATTERN.finditer(text): + level = len(match.group(1)) + if level > max_level: + continue + title = match.group(2).strip() + title = re.sub(r"\{[^}]*\}", "", title) + title = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", title) + title = re.sub(r"\s+", " ", title).strip() + if title: + headings.append(title) + return headings[:12] + + +def parse_doc_refs_from_text(text: str) -> list[DocRef]: + refs: list[DocRef] = [] + seen: set[str] = set() + + def add(path: str, anchor: str | None) -> None: + if not path.endswith(".html"): + path = path.rstrip("/") + ".html" + path = resolve_doc_path(path) + ref = DocRef(path=path, anchor=anchor) + if ref.key not in seen: + seen.add(ref.key) + refs.append(ref) + + for pattern in (HELP_LINK_PATTERN, DOCUMENTATION_LINK_PATTERN, LINK_PROP_PATTERN): + for m in pattern.finditer(text): + add(m.group(1), m.group(2)) + + for m in DOCS_URL_PATTERN.finditer(text): + add(m.group(1), m.group(2)) + + return refs + + +def resolve_doc_path(path: str) -> str: + return DOC_PATH_ALIASES.get(path, path) + + +def parse_settings_index(frontend_root: Path) -> list[ProductRoute]: + settings_file = frontend_root / "src/pages/Settings/index.tsx" + if not settings_file.is_file(): + return [] + + content = settings_file.read_text(encoding="utf-8") + routes: list[ProductRoute] = [] + seen_paths: set[str] = set() + + group_positions = [ + (m.start(), m.group(1)) + for m in SETTING_GROUP_TITLE_PATTERN.finditer(content) + ] + + def group_for_position(pos: int) -> str | None: + title = None + for gpos, gtitle in group_positions: + if gpos <= pos: + title = gtitle + else: + break + return title + + for link_match in SETTING_LINK_PATTERN.finditer(content): + title = link_match.group(1).strip() + path = link_match.group(2).strip() + if not path.startswith("/settings") or path in seen_paths: + continue + seen_paths.add(path) + pos = link_match.start() + # Group-level helpLink appears before SettingLinks in the same group. + window_start = max(0, pos - 1200) + window = content[window_start:pos] + group_help = parse_doc_refs_from_text(window) + route = ProductRoute( + path=path, + label=title, + group=group_for_position(pos), + ) + if group_help: + route.primary_docs.extend(group_help) + routes.append(route) + + return routes + + +def file_to_route_hint(file_path: Path) -> str | None: + """Infer product route from frontend page index files only.""" + if file_path.name != "index.tsx": + return None + parts = file_path.parts + if "pages" not in parts or "components" in parts: + return None + idx = parts.index("pages") + rest = parts[idx + 1 :] + if not rest: + return None + if rest[0] == "Settings": + if len(rest) == 1: + return "/settings" + # Settings/Workflows/index.tsx -> /settings/workflows + slug = rest[1].replace("_", "-") + # CamelCase to kebab + slug = re.sub(r"(? 1: + return None + if len(rest) > 1: + return None + return f"/{kebab}" + + +def scan_frontend_doc_links(frontend_root: Path) -> dict[str, list[DocRef]]: + """Map approximate product route -> doc refs from source files.""" + by_route: dict[str, list[DocRef]] = {} + src = frontend_root / "src" + if not src.is_dir(): + return by_route + + for path in list(src.rglob("*.tsx")) + list(src.rglob("*.ts")): + if "node_modules" in path.parts or ".test." in path.name: + continue + if path.name != "index.tsx" and "Settings" not in path.parts: + continue + text = path.read_text(encoding="utf-8", errors="ignore") + refs = parse_doc_refs_from_text(text) + if not refs: + continue + route_hint = file_to_route_hint(path) + if route_hint: + existing = by_route.setdefault(route_hint, []) + seen = {r.key for r in existing} + for ref in refs: + if ref.key not in seen: + seen.add(ref.key) + existing.append(ref) + return by_route + + +def parse_sidebar_nav(frontend_root: Path) -> list[ProductRoute]: + sidebar_file = frontend_root / "src/components/Sidebar/index.tsx" + if not sidebar_file.is_file(): + return [] + content = sidebar_file.read_text(encoding="utf-8") + routes: list[ProductRoute] = [] + # Match menu item objects with path and label + blocks = re.split(r"\{\s*key:\s*['\"]", content) + for block in blocks[1:]: + path_m = re.search(r"path:\s*['\"]([^'\"]+)['\"]", block) + if not path_m: + continue + path = path_m.group(1) + if not path or path == "": + continue + label_m = re.search(r"label:\s*(?:copy\(['\"]([^'\"]+)['\"]\)|['\"]([^'\"]+)['\"])", block) + label = (label_m.group(1) or label_m.group(2) or path) if label_m else path + doc_m = DOCUMENTATION_LINK_PATTERN.search(block) + route = ProductRoute(path=path, label=label, group="Main navigation") + if doc_m: + route.primary_docs.append(DocRef(path=doc_m.group(1), anchor=doc_m.group(2))) + routes.append(route) + return routes + + +def collect_all_doc_qmd_paths(site_dir: Path) -> list[str]: + """Return URL-style paths for all guide-related qmd files.""" + paths: list[str] = [] + for qmd in site_dir.rglob("*.qmd"): + rel = qmd.relative_to(site_dir).as_posix() + if rel.startswith(("internal/", "tests/", "notebooks/", "llm/")): + continue + url = "/" + rel[:-4] + ".html" + paths.append(url) + return paths + + +def is_user_facing_doc(path: str) -> bool: + if "/_source/" in path: + return False + if any(part.startswith("_") for part in path.split("/") if part): + return False + return path.startswith(RELATED_DOC_PREFIXES) + + +def suggest_related_docs(route: ProductRoute, all_doc_paths: list[str]) -> list[DocRef]: + """Suggest related documentation based on route/title keywords.""" + haystack = f"{route.path} {route.label} {route.group or ''}".lower() + segments: set[str] = set() + for keyword, doc_segments in RELATED_DOC_KEYWORDS.items(): + if keyword in haystack: + segments.update(doc_segments) + + primary_paths = {d.path for d in route.primary_docs} + related: list[DocRef] = [] + for doc_path in all_doc_paths: + if doc_path in primary_paths or not is_user_facing_doc(doc_path): + continue + inner = doc_path.lower() + if any(seg in inner for seg in segments): + related.append(DocRef(path=doc_path)) + return related[:6] + + +def merge_routes( + settings: list[ProductRoute], + nav: list[ProductRoute], + file_links: dict[str, list[DocRef]], +) -> dict[str, ProductRoute]: + by_path: dict[str, ProductRoute] = {} + + def get_or_add(route: ProductRoute) -> ProductRoute: + if route.path not in by_path: + by_path[route.path] = route + else: + existing = by_path[route.path] + if route.label and existing.label == route.path: + existing.label = route.label + if route.group and not existing.group: + existing.group = route.group + return by_path[route.path] + + settings_paths: set[str] = set() + for route in settings: + merged = get_or_add(route) + settings_paths.add(route.path) + for route in nav: + get_or_add(route) + + for path, refs in file_links.items(): + if path in settings_paths: + route = by_path[path] + seen = {d.key for d in route.primary_docs} + for ref in refs: + if ref.key not in seen: + seen.add(ref.key) + route.primary_docs.append(ref) + continue + if path not in by_path: + by_path[path] = ProductRoute(path=path, label=path, group="Main navigation") + route = by_path[path] + seen = {d.key for d in route.primary_docs} + for ref in refs: + if ref.key not in seen: + seen.add(ref.key) + route.primary_docs.append(ref) + + return by_path + + +def format_doc_line( + ref: DocRef, site_dir: Path, llm_output_dir: Path | None +) -> str: + qmd = html_path_to_qmd(site_dir, ref.path) + anchor_suffix = f" (section: #{ref.anchor})" if ref.anchor else "" + line = f"- `{ref.path}`{anchor_suffix}" + if qmd: + headings = extract_headings(qmd) + if headings: + line += f"\n - Sections: {'; '.join(headings[:8])}" + if llm_output_dir: + rel_md = qmd.relative_to(site_dir).with_suffix(".md").as_posix() + md_file = llm_output_dir / rel_md + if not md_file.is_file(): + line += "\n - Note: not yet in `_llm-output` (run `make render-llm`)" + else: + line += "\n - Note: no matching `.qmd` source found" + return line + + +def render_markdown( + routes: dict[str, ProductRoute], + site_dir: Path, + all_doc_paths: list[str], + llm_output_dir: Path | None, +) -> str: + lines = [ + "# ValidMind product-to-documentation map", + "", + "> Auto-generated. Maps in-product routes to documentation URLs and key sections.", + "> For how documentation is organized by topic, see `AGENTS.md` and", + "> [Using the documentation](/about/contributing/using-the-documentation.html).", + "", + ] + + settings_routes = sorted( + (r for r in routes.values() if r.path.startswith("/settings")), + key=lambda r: r.path, + ) + other_routes = sorted( + (r for r in routes.values() if not r.path.startswith("/settings")), + key=lambda r: r.path, + ) + + def render_section(title: str, section_routes: list[ProductRoute]) -> None: + if not section_routes: + return + lines.append(f"## {title}") + lines.append("") + current_group: str | None = None + for route in section_routes: + if title == "Settings" and route.group and route.group != current_group: + current_group = route.group + lines.append(f"### {current_group}") + lines.append("") + lines.append(f"#### `{route.path}` — {route.label}") + lines.append("") + if not route.primary_docs: + related = suggest_related_docs(route, all_doc_paths) + if related: + route.related_docs = related + route.notes.append( + "No direct help link in frontend; related docs inferred from keywords." + ) + else: + route.notes.append( + "No direct help link; content may be covered under scattered guide sections." + ) + if route.primary_docs: + lines.append("**Docs (primary):**") + lines.append("") + for ref in route.primary_docs: + lines.append(format_doc_line(ref, site_dir, llm_output_dir)) + lines.append("") + related = route.related_docs or suggest_related_docs(route, all_doc_paths) + # Exclude primary from related + primary_keys = {d.path for d in route.primary_docs} + related = [r for r in related if r.path not in primary_keys] + if related: + lines.append("**Docs (related):**") + lines.append("") + for ref in related[:6]: + lines.append(format_doc_line(ref, site_dir, llm_output_dir)) + lines.append("") + for note in route.notes: + lines.append(f"- *{note}*") + if route.notes: + lines.append("") + + render_section("Settings", settings_routes) + render_section("Main application", other_routes) + + lines.append("## Documentation index (human-oriented)") + lines.append("") + lines.append( + "See `AGENTS.md` and `about/contributing/using-the-documentation.md` in the " + "LLM corpus for guides organized by feature area (Configuration, Workflows, " + "Inventory, etc.)." + ) + lines.append("") + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Generate chatbot product-to-docs map") + parser.add_argument( + "--frontend-root", + type=Path, + default=None, + help="Path to validmind/frontend (default: /../frontend)", + ) + parser.add_argument( + "--site-dir", + type=Path, + default=None, + help="Path to documentation site/ (default: /site)", + ) + parser.add_argument( + "--output", + type=Path, + default=None, + help="Output markdown path (default: site/llm/chatbot-product-map.md)", + ) + parser.add_argument( + "--json-output", + type=Path, + default=None, + help="Optional JSON output path", + ) + args = parser.parse_args() + + repo_root = find_repo_root() + site_dir = (args.site_dir or repo_root / "site").resolve() + frontend_root = (args.frontend_root or repo_root / "frontend").resolve() + if not frontend_root.is_dir(): + frontend_root = (repo_root.parent / "frontend").resolve() + output_path = (args.output or site_dir / "llm/chatbot-product-map.md").resolve() + llm_output = site_dir / "llm/_llm-output" + + if not frontend_root.is_dir(): + raise SystemExit(f"Frontend root not found: {frontend_root}") + + settings = parse_settings_index(frontend_root) + nav = parse_sidebar_nav(frontend_root) + file_links = scan_frontend_doc_links(frontend_root) + routes = merge_routes(settings, nav, file_links) + all_doc_paths = collect_all_doc_qmd_paths(site_dir) + + md = render_markdown(routes, site_dir, all_doc_paths, llm_output if llm_output.is_dir() else None) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(md, encoding="utf-8") + print(f"Wrote {output_path} ({len(routes)} routes)") + + if args.json_output: + payload = { + path: { + "label": r.label, + "group": r.group, + "primary_docs": [{"path": d.path, "anchor": d.anchor} for d in r.primary_docs], + "related_docs": [{"path": d.path, "anchor": d.anchor} for d in r.related_docs], + "notes": r.notes, + } + for path, r in sorted(routes.items()) + } + args.json_output.write_text(json.dumps(payload, indent=2), encoding="utf-8") + print(f"Wrote {args.json_output}") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/site/scripts/test_generate_chatbot_product_map.py b/site/scripts/test_generate_chatbot_product_map.py new file mode 100644 index 0000000000..546ec3e2cf --- /dev/null +++ b/site/scripts/test_generate_chatbot_product_map.py @@ -0,0 +1,51 @@ +# Copyright © 2023-2026 ValidMind Inc. All rights reserved. +# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial + +"""Unit tests for generate_chatbot_product_map.py""" + +import unittest +from pathlib import Path + +import generate_chatbot_product_map as gen + + +class TestGenerateChatbotProductMap(unittest.TestCase): + def test_resolve_doc_path_alias(self) -> None: + self.assertEqual( + gen.resolve_doc_path( + "/guide/model-workflows/setting-up-model-workflows.html" + ), + "/guide/workflows/setting-up-workflows.html", + ) + + def test_parse_doc_refs_from_help_link(self) -> None: + text = ( + "helpLink={`${CONFIG.VALIDMIND_DOCS_URL}" + "/guide/configuration/managing-users.html`}" + ) + refs = gen.parse_doc_refs_from_text(text) + self.assertEqual(len(refs), 1) + self.assertEqual(refs[0].path, "/guide/configuration/managing-users.html") + + def test_html_path_to_qmd(self) -> None: + site = Path(__file__).resolve().parents[1] + qmd = gen.html_path_to_qmd(site, "/guide/workflows/setting-up-workflows.html") + self.assertIsNotNone(qmd) + self.assertEqual(qmd.name, "setting-up-workflows.qmd") + + def test_extract_headings(self) -> None: + qmd = ( + Path(__file__).resolve().parents[1] + / "guide/workflows/setting-up-workflows.qmd" + ) + headings = gen.extract_headings(qmd) + self.assertTrue(any("workflows" in h.lower() for h in headings)) + + def test_is_user_facing_doc(self) -> None: + self.assertTrue(gen.is_user_facing_doc("/guide/workflows/manage-workflow-tasks.html")) + self.assertFalse(gen.is_user_facing_doc("/_source/release-notes/foo.html")) + self.assertFalse(gen.is_user_facing_doc("/guide/workflows/_partial.html")) + + +if __name__ == "__main__": + unittest.main() From 6aba4152ed1e32d93770dc28453b4df65c0f64eb Mon Sep 17 00:00:00 2001 From: Nik Richers Date: Tue, 19 May 2026 18:56:02 -0700 Subject: [PATCH 2/5] Vendor frontend snapshot for chatbot product map (no CI frontend checkout) Store extracted routes and help links in site/llm/chatbot-product-map-frontend-snapshot.json so CI builds the map without validmind/frontend access. Refresh locally with make -C site refresh-chatbot-product-map when product UI links change. --- .github/workflows/publish-llm-markdown.yaml | 7 - .github/workflows/validate-docs-site.yaml | 9 +- AGENTS.md | 4 +- site/Makefile | 11 +- ...chatbot-product-map-frontend-snapshot.json | 366 ++++++++++++++++++ site/scripts/generate_chatbot_product_map.py | 130 ++++++- .../test_generate_chatbot_product_map.py | 31 ++ 7 files changed, 525 insertions(+), 33 deletions(-) create mode 100644 site/llm/chatbot-product-map-frontend-snapshot.json diff --git a/.github/workflows/publish-llm-markdown.yaml b/.github/workflows/publish-llm-markdown.yaml index aed2101924..0610dfcaa6 100644 --- a/.github/workflows/publish-llm-markdown.yaml +++ b/.github/workflows/publish-llm-markdown.yaml @@ -34,13 +34,6 @@ jobs: path: site/_source/validmind-library token: ${{ secrets.DOCS_CI_RO_PAT }} - - name: Check out frontend repository (chatbot product map) - uses: actions/checkout@v4 - with: - repository: validmind/frontend - path: frontend - token: ${{ secrets.DOCS_CI_RO_PAT }} - - name: Set up Quarto uses: quarto-dev/quarto-actions/setup@v2 with: diff --git a/.github/workflows/validate-docs-site.yaml b/.github/workflows/validate-docs-site.yaml index f436e33135..d9f6595898 100644 --- a/.github/workflows/validate-docs-site.yaml +++ b/.github/workflows/validate-docs-site.yaml @@ -35,13 +35,6 @@ jobs: path: site/_source/validmind-library token: ${{ secrets.DOCS_CI_RO_PAT }} - - name: Check out frontend repository (chatbot product map) - uses: actions/checkout@v4 - with: - repository: validmind/frontend - path: frontend - token: ${{ secrets.DOCS_CI_RO_PAT }} - - name: Check out installation repository uses: actions/checkout@v4 with: @@ -176,7 +169,7 @@ jobs: - name: Verify chatbot product map is up to date run: | python3 site/scripts/generate_chatbot_product_map.py - git diff --exit-code site/llm/chatbot-product-map.md + git diff --exit-code site/llm/chatbot-product-map.md site/llm/chatbot-product-map-frontend-snapshot.json - name: Test chatbot product map generator run: python3 -m unittest site/scripts/test_generate_chatbot_product_map.py -v diff --git a/AGENTS.md b/AGENTS.md index 245e21e390..271e8e1c83 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,9 @@ This page describes what the assistant can and cannot do, including context-awar The in-app assistant (Valerie) also ingests **`chatbot-product-map.md`** in the LLM corpus. That file maps **platform routes** (for example `/settings/workflows`, `/model-inventory`, `/dashboard`) to documentation URLs and section hints. -Use it when the user’s question is tied to where they are in the product — especially **Settings**, where the UI groups features differently than the documentation sidebars (Configuration, Workflows, Inventory, and so on). +Route and help-link data from the product UI is vendored as **`site/llm/chatbot-product-map-frontend-snapshot.json`**. Regenerate it with `make -C site refresh-chatbot-product-map` when frontend routes or `helpLink` values change (requires a local `validmind/frontend` checkout). + +Use the map when the user’s question is tied to where they are in the product — especially **Settings**, where the UI groups features differently than the documentation sidebars (Configuration, Workflows, Inventory, and so on). For documentation organized by topic, continue to use **Using the documentation** (above) and the section table in this file. diff --git a/site/Makefile b/site/Makefile index bc8ab3e52c..555bbe928b 100644 --- a/site/Makefile +++ b/site/Makefile @@ -14,7 +14,7 @@ SRC_ROOT := _source SRC_DIR := $(SRC_ROOT)/validmind-library # Define .PHONY target for help section -.PHONY: help add-copyright clean clone copy-installation copy-release-notes delete-demo-branch deploy-demo-branch deploy-prod deploy-staging docker-build docker-serve docker-site docker-site-lite docs-site execute generate-chatbot-product-map generate-sitemap get-api-json get-source kind-serve kind-stop kind-restart kind-logs notebooks python-docs release-notes render-llm template-schema-docs test-descriptions verify-copyright yearly-releases +.PHONY: help add-copyright clean clone copy-installation copy-release-notes delete-demo-branch deploy-demo-branch deploy-prod deploy-staging docker-build docker-serve docker-site docker-site-lite docs-site execute generate-chatbot-product-map refresh-chatbot-product-map generate-sitemap get-api-json get-source kind-serve kind-stop kind-restart kind-logs notebooks python-docs release-notes render-llm template-schema-docs test-descriptions verify-copyright yearly-releases # Help section help: @@ -39,7 +39,8 @@ help: @echo " docker-site Get source, render site with Docker profile, execute notebooks" @echo " docker-site-lite Get source and render site with Docker profile (skips notebook execution)" @echo " docs-site Get all source files and render the production docs site with Quarto" - @echo " generate-chatbot-product-map Generate product-to-docs map for the in-app assistant" + @echo " generate-chatbot-product-map Generate product-to-docs map (from committed frontend snapshot)" + @echo " refresh-chatbot-product-map Re-extract frontend snapshot + map (requires ../frontend)" @echo " generate-sitemap Generate a sitemap for the static HTML site" @echo " execute Execute a Jupyter Notebook or notebook directory" @echo " get-api-json Download Swagger JSON specs from ValidMind APIs into reference/" @@ -451,10 +452,14 @@ yearly-releases: git status | grep -v 'release-scripts/' quarto preview -# Generate product-to-documentation map for chatbot RAG (requires frontend checkout) +# Generate product-to-documentation map for chatbot RAG (uses committed frontend snapshot) generate-chatbot-product-map: @python3 scripts/generate_chatbot_product_map.py +# Refresh vendored frontend snapshot from a local validmind/frontend checkout +refresh-chatbot-product-map: + @python3 scripts/generate_chatbot_product_map.py --from-frontend + # Render site to GFM markdown for LLM ingestion render-llm: @echo "\nRendering site to GFM markdown for LLM ingestion ..." diff --git a/site/llm/chatbot-product-map-frontend-snapshot.json b/site/llm/chatbot-product-map-frontend-snapshot.json new file mode 100644 index 0000000000..ff9d17751b --- /dev/null +++ b/site/llm/chatbot-product-map-frontend-snapshot.json @@ -0,0 +1,366 @@ +{ + "version": 1, + "generated_at": "2026-05-20T01:55:33.719811+00:00", + "frontend_root": "/Users/nrichers/GitHub/validmind/frontend", + "settings": [ + { + "path": "/settings/profile", + "label": "Profile", + "group": "Your Account", + "primary_docs": [] + }, + { + "path": "/settings/theme-customization", + "label": "Theme Customization", + "group": "Your Account", + "primary_docs": [] + }, + { + "path": "/settings/organization", + "label": "Organization", + "group": "Organization", + "primary_docs": [] + }, + { + "path": "/settings/authentication", + "label": "Authentication", + "group": "Organization", + "primary_docs": [] + }, + { + "path": "/settings/email-notifications", + "label": "Email Notifications", + "group": "Organization", + "primary_docs": [] + }, + { + "path": "/settings/user-directory", + "label": "User Directory", + "group": "Users & Access", + "primary_docs": [ + { + "path": "/guide/configuration/managing-users.html", + "anchor": null + } + ] + }, + { + "path": "/settings/invitation", + "label": "Invite New Users", + "group": "Users & Access", + "primary_docs": [ + { + "path": "/guide/configuration/managing-users.html", + "anchor": null + } + ] + }, + { + "path": "/settings/roles", + "label": "Roles", + "group": "Users & Access", + "primary_docs": [ + { + "path": "/guide/configuration/managing-users.html", + "anchor": null + } + ] + }, + { + "path": "/settings/permissions", + "label": "Permissions", + "group": "Users & Access", + "primary_docs": [] + }, + { + "path": "/settings/groups", + "label": "Groups", + "group": "Users & Access", + "primary_docs": [] + }, + { + "path": "/settings/integrations/connections", + "label": "Connections", + "group": "Integrations", + "primary_docs": [] + }, + { + "path": "/settings/integrations/secrets", + "label": "Secrets", + "group": "Integrations", + "primary_docs": [] + }, + { + "path": "/settings/integrations/data-exports", + "label": "Analytics Exports", + "group": "Integrations", + "primary_docs": [] + }, + { + "path": "/settings/finding-types", + "label": "Artifact Types", + "group": "Artifacts", + "primary_docs": [] + }, + { + "path": "/settings/finding-severities", + "label": "Artifact Severities", + "group": "Artifacts", + "primary_docs": [] + }, + { + "path": "/settings/finding-custom-fields", + "label": "Artifact Fields", + "group": "Artifacts", + "primary_docs": [] + }, + { + "path": "/settings/regulations", + "label": "Regulations & Policies", + "group": "Governance", + "primary_docs": [] + }, + { + "path": "/settings/risk-areas", + "label": "Risk Areas & Validation Guidelines", + "group": "Governance", + "primary_docs": [] + }, + { + "path": "/settings/workflows", + "label": "Workflows", + "group": "Governance", + "primary_docs": [] + }, + { + "path": "/settings/workflow-states", + "label": "Workflow States", + "group": "Governance", + "primary_docs": [] + }, + { + "path": "/settings/attestation-templates", + "label": "Attestation Templates", + "group": "Governance", + "primary_docs": [] + }, + { + "path": "/settings/document-types", + "label": "Document Types", + "group": "Documents", + "primary_docs": [] + }, + { + "path": "/settings/templates", + "label": "Templates", + "group": "Documents", + "primary_docs": [] + }, + { + "path": "/settings/block-library", + "label": "Block Library", + "group": "Documents", + "primary_docs": [] + } + ], + "nav": [ + { + "path": "/dashboard", + "label": "Dashboard", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/validation-issues", + "label": "Validation Issues", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/attestations", + "label": "Attestations", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/artifacts", + "label": "Artifacts", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/workflows", + "label": "Workflows", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/analytics", + "label": "sidebar.analytics", + "group": "Main navigation", + "primary_docs": [] + }, + { + "path": "/settings", + "label": "Settings", + "group": "Main navigation", + "primary_docs": [] + } + ], + "file_links": { + "/settings/attestations": [ + { + "path": "/guide/attestation/manage-attestations.html", + "anchor": null + }, + { + "path": "/guide/shared/work-with-filters.html", + "anchor": null + } + ], + "/settings/authentication": [ + { + "path": "/installation/security/configure-single-sign-on-sso.html", + "anchor": null + } + ], + "/settings/block-library": [ + { + "path": "/guide/templates/manage-text-block-library.html", + "anchor": null + } + ], + "/settings/custom-fields": [ + { + "path": "/guide/model-inventory/manage-model-inventory-fields.html", + "anchor": null + } + ], + "/settings/document-types": [ + { + "path": "/guide/templates/manage-document-types.html", + "anchor": null + } + ], + "/settings/email-notifications": [ + { + "path": "/guide/configuration/manage-platform-notifications.html", + "anchor": "customize-email-notifications" + } + ], + "/settings/finding-custom-fields": [ + { + "path": "/guide/model-validation/manage-model-finding-fields.html", + "anchor": null + } + ], + "/settings/finding-severities": [ + { + "path": "/guide/model-validation/manage-artifact-severities.html", + "anchor": null + } + ], + "/settings/finding-types": [ + { + "path": "/guide/model-validation/manage-artifact-types.html", + "anchor": null + } + ], + "/settings/groups": [ + { + "path": "/guide/configuration/manage-groups.html", + "anchor": null + } + ], + "/settings/index.tsx": [ + { + "path": "/guide/configuration/managing-users.html", + "anchor": null + } + ], + "/settings/invitation": [ + { + "path": "/guide/configuration/manage-users.html", + "anchor": "manage-user-invitations" + } + ], + "/settings/organization": [ + { + "path": "/guide/configuration/managing-your-organization.html", + "anchor": null + } + ], + "/settings/permissions": [ + { + "path": "/guide/configuration/manage-permissions.html", + "anchor": null + } + ], + "/settings/primary-record-type-stages": [ + { + "path": "/guide/workflows/manage-model-stages.html", + "anchor": null + } + ], + "/settings/profile": [ + { + "path": "/guide/configuration/view-your-profile.html", + "anchor": null + } + ], + "/settings/regulation-policy": [ + { + "path": "/guide/templates/customize-virtual-document-validator.html", + "anchor": "add-or-edit-assessment-questions" + } + ], + "/settings/risk-areas": [ + { + "path": "/guide/model-validation/manage-validation-guidelines.html", + "anchor": null + } + ], + "/settings/roles": [ + { + "path": "/guide/configuration/manage-roles.html", + "anchor": null + } + ], + "/settings/stakeholders": [ + { + "path": "/guide/configuration/manage-model-stakeholder-types.html", + "anchor": null + } + ], + "/settings/statuses": [ + { + "path": "/guide/workflows/manage-model-stages.html", + "anchor": null + } + ], + "/settings/templates": [ + { + "path": "/guide/templates/customize-document-templates.html", + "anchor": null + } + ], + "/settings/user-directory": [ + { + "path": "/guide/configuration/manage-users.html", + "anchor": null + } + ], + "/settings/workflow-states": [ + { + "path": "/guide/workflows/workflow-states.html", + "anchor": null + } + ], + "/settings/workflows": [ + { + "path": "/guide/workflows/setting-up-workflows.html", + "anchor": null + } + ] + } +} diff --git a/site/scripts/generate_chatbot_product_map.py b/site/scripts/generate_chatbot_product_map.py index e8c299e622..46d88a37f0 100644 --- a/site/scripts/generate_chatbot_product_map.py +++ b/site/scripts/generate_chatbot_product_map.py @@ -8,8 +8,12 @@ Correlates frontend routes and help links with documentation pages and headings. Usage (from documentation repo root): + # CI / default: build map from committed frontend snapshot (no frontend checkout) python site/scripts/generate_chatbot_product_map.py - python site/scripts/generate_chatbot_product_map.py --frontend-root ../frontend + + # Refresh snapshot + map when frontend sources change (local frontend checkout) + python site/scripts/generate_chatbot_product_map.py --from-frontend + python site/scripts/generate_chatbot_product_map.py --from-frontend --frontend-root ../frontend """ from __future__ import annotations @@ -18,6 +22,7 @@ import json import re from dataclasses import dataclass, field +from datetime import datetime, timezone from pathlib import Path try: @@ -356,6 +361,84 @@ def suggest_related_docs(route: ProductRoute, all_doc_paths: list[str]) -> list[ return related[:6] +DEFAULT_SNAPSHOT_NAME = "chatbot-product-map-frontend-snapshot.json" + + +def doc_ref_to_dict(ref: DocRef) -> dict[str, str | None]: + return {"path": ref.path, "anchor": ref.anchor} + + +def doc_ref_from_dict(data: dict[str, str | None]) -> DocRef: + return DocRef(path=data["path"], anchor=data.get("anchor")) + + +def route_to_dict(route: ProductRoute) -> dict: + return { + "path": route.path, + "label": route.label, + "group": route.group, + "primary_docs": [doc_ref_to_dict(d) for d in route.primary_docs], + } + + +def route_from_dict(data: dict) -> ProductRoute: + return ProductRoute( + path=data["path"], + label=data["label"], + group=data.get("group"), + primary_docs=[doc_ref_from_dict(d) for d in data.get("primary_docs", [])], + ) + + +def extract_frontend_snapshot(frontend_root: Path) -> dict: + """Extract route/help-link data from frontend for vendoring in the docs repo.""" + settings = parse_settings_index(frontend_root) + nav = parse_sidebar_nav(frontend_root) + file_links = scan_frontend_doc_links(frontend_root) + return { + "version": 1, + "generated_at": datetime.now(timezone.utc).isoformat(), + "settings": [route_to_dict(r) for r in settings], + "nav": [route_to_dict(r) for r in nav], + "file_links": { + path: [doc_ref_to_dict(d) for d in refs] + for path, refs in sorted(file_links.items()) + }, + } + + +def load_frontend_snapshot(snapshot_path: Path) -> tuple[list[ProductRoute], list[ProductRoute], dict[str, list[DocRef]]]: + data = json.loads(snapshot_path.read_text(encoding="utf-8")) + settings = [route_from_dict(r) for r in data.get("settings", [])] + nav = [route_from_dict(r) for r in data.get("nav", [])] + file_links = { + path: [doc_ref_from_dict(d) for d in refs] + for path, refs in data.get("file_links", {}).items() + } + return settings, nav, file_links + + +def write_frontend_snapshot(snapshot_path: Path, payload: dict) -> None: + snapshot_path.parent.mkdir(parents=True, exist_ok=True) + snapshot_path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + + +def resolve_frontend_root(repo_root: Path, explicit: Path | None) -> Path: + if explicit is not None: + candidate = explicit.resolve() + if candidate.is_dir(): + return candidate + raise SystemExit(f"Frontend root not found: {candidate}") + + for candidate in (repo_root / "frontend", repo_root.parent / "frontend"): + if candidate.is_dir(): + return candidate.resolve() + raise SystemExit( + "Frontend root not found. Use --from-frontend with a local checkout, or rely on " + f"the committed snapshot at site/llm/{DEFAULT_SNAPSHOT_NAME}." + ) + + def merge_routes( settings: list[ProductRoute], nav: list[ProductRoute], @@ -507,11 +590,22 @@ def render_section(title: str, section_routes: list[ProductRoute]) -> None: def main() -> int: parser = argparse.ArgumentParser(description="Generate chatbot product-to-docs map") + parser.add_argument( + "--from-frontend", + action="store_true", + help="Re-extract route/help-link data from frontend and update the committed snapshot", + ) parser.add_argument( "--frontend-root", type=Path, default=None, - help="Path to validmind/frontend (default: /../frontend)", + help="Path to validmind/frontend (only with --from-frontend)", + ) + parser.add_argument( + "--snapshot", + type=Path, + default=None, + help=f"Frontend snapshot JSON (default: site/llm/{DEFAULT_SNAPSHOT_NAME})", ) parser.add_argument( "--site-dir", @@ -529,24 +623,32 @@ def main() -> int: "--json-output", type=Path, default=None, - help="Optional JSON output path", + help="Optional JSON output of merged routes (debug/tooling)", ) args = parser.parse_args() repo_root = find_repo_root() site_dir = (args.site_dir or repo_root / "site").resolve() - frontend_root = (args.frontend_root or repo_root / "frontend").resolve() - if not frontend_root.is_dir(): - frontend_root = (repo_root.parent / "frontend").resolve() + snapshot_path = ( + args.snapshot or site_dir / "llm" / DEFAULT_SNAPSHOT_NAME + ).resolve() output_path = (args.output or site_dir / "llm/chatbot-product-map.md").resolve() llm_output = site_dir / "llm/_llm-output" - if not frontend_root.is_dir(): - raise SystemExit(f"Frontend root not found: {frontend_root}") + if args.from_frontend: + frontend_root = resolve_frontend_root(repo_root, args.frontend_root) + payload = extract_frontend_snapshot(frontend_root) + write_frontend_snapshot(snapshot_path, payload) + print(f"Wrote {snapshot_path}") + settings, nav, file_links = load_frontend_snapshot(snapshot_path) + elif snapshot_path.is_file(): + settings, nav, file_links = load_frontend_snapshot(snapshot_path) + else: + raise SystemExit( + f"Frontend snapshot not found: {snapshot_path}\n" + "Run with --from-frontend and a local validmind/frontend checkout to create it." + ) - settings = parse_settings_index(frontend_root) - nav = parse_sidebar_nav(frontend_root) - file_links = scan_frontend_doc_links(frontend_root) routes = merge_routes(settings, nav, file_links) all_doc_paths = collect_all_doc_qmd_paths(site_dir) @@ -560,13 +662,13 @@ def main() -> int: path: { "label": r.label, "group": r.group, - "primary_docs": [{"path": d.path, "anchor": d.anchor} for d in r.primary_docs], - "related_docs": [{"path": d.path, "anchor": d.anchor} for d in r.related_docs], + "primary_docs": [doc_ref_to_dict(d) for d in r.primary_docs], + "related_docs": [doc_ref_to_dict(d) for d in r.related_docs], "notes": r.notes, } for path, r in sorted(routes.items()) } - args.json_output.write_text(json.dumps(payload, indent=2), encoding="utf-8") + args.json_output.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") print(f"Wrote {args.json_output}") return 0 diff --git a/site/scripts/test_generate_chatbot_product_map.py b/site/scripts/test_generate_chatbot_product_map.py index 546ec3e2cf..dd9665c186 100644 --- a/site/scripts/test_generate_chatbot_product_map.py +++ b/site/scripts/test_generate_chatbot_product_map.py @@ -46,6 +46,37 @@ def test_is_user_facing_doc(self) -> None: self.assertFalse(gen.is_user_facing_doc("/_source/release-notes/foo.html")) self.assertFalse(gen.is_user_facing_doc("/guide/workflows/_partial.html")) + def test_frontend_snapshot_roundtrip(self) -> None: + payload = { + "version": 1, + "settings": [ + { + "path": "/settings/workflows", + "label": "Workflows", + "group": "Governance", + "primary_docs": [ + { + "path": "/guide/workflows/setting-up-workflows.html", + "anchor": None, + } + ], + } + ], + "nav": [], + "file_links": {}, + } + site = Path(__file__).resolve().parents[1] + snapshot_path = site / "llm" / ".test-snapshot.json" + try: + gen.write_frontend_snapshot(snapshot_path, payload) + settings, nav, file_links = gen.load_frontend_snapshot(snapshot_path) + self.assertEqual(len(settings), 1) + self.assertEqual(settings[0].path, "/settings/workflows") + self.assertEqual(nav, []) + self.assertEqual(file_links, {}) + finally: + snapshot_path.unlink(missing_ok=True) + if __name__ == "__main__": unittest.main() From 8bace6967b89609f49430103442548fece5844b4 Mon Sep 17 00:00:00 2001 From: Nik Richers Date: Tue, 19 May 2026 18:59:11 -0700 Subject: [PATCH 3/5] Document LLM corpus and frontend snapshot in site/llm/README.md Give maintainers a single entry point for render-llm, product map artifacts, and when to refresh the vendored frontend snapshot. --- site/llm/README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 site/llm/README.md diff --git a/site/llm/README.md b/site/llm/README.md new file mode 100644 index 0000000000..ee80bfeefb --- /dev/null +++ b/site/llm/README.md @@ -0,0 +1,54 @@ +# LLM corpus (markdown for RAG) + +This directory holds tooling that renders the documentation site to plain Markdown for ingestion into LanceDB and the in-app assistant (Valerie). + +## Output + +Rendered files land in `site/llm/_llm-output/` (gitignored). CI publishes that directory after `make render-llm`. Deployed environments only pick up new assistant context after the LanceDB artifact is rebuilt from a fresh render. + +## Render locally + +From `site/`: + +```bash +make render-llm +``` + +This runs `llm/render.sh` (temporary minimal `_quarto.yml`, Quarto → GFM) and `llm/clean.sh` (Pandoc cleanup). Equivalent to the **Validate LLM markdown render** step in `.github/workflows/validate-docs-site.yaml`. + +Excluded from the LLM render (among others): notebooks, internal pages, contributor style guide, and most of `about/contributing/`. **Included:** `about/contributing/using-the-documentation.qmd` (docs IA hub for agents). + +Copied into `_llm-output/` after render: + +| File | Source | +|------|--------| +| `AGENTS.md` | Repo root | +| `chatbot-product-map.md` | Generated (see below) | +| `about/contributing/using-the-documentation.md` | Quarto render | + +See also [`AGENTS.md`](../../AGENTS.md) for how agents should use the corpus. + +## Chatbot product map + +Valerie needs routes in the product UI (especially **Settings**) mapped to documentation URLs. Human docs sidebars are organized by topic (Configuration, Workflows, Inventory); the product groups features differently. + +| Artifact | Purpose | +|----------|---------| +| `chatbot-product-map-frontend-snapshot.json` | Vendored extract from `validmind/frontend` (Settings tree, sidebar nav, `helpLink` / docs URLs) | +| `chatbot-product-map.md` | Retrieval-oriented map: routes → primary/related docs + section headings from `.qmd` | + +### Why a frontend snapshot? + +CI does **not** check out `validmind/frontend` (private repo; cross-repo PAT scope). The snapshot is committed in this repo so pipelines can regenerate the map without frontend access. It may lag the live product until someone refreshes it locally. + +### Maintenance + +| Change | Command (from `site/`) | +|--------|-------------------------| +| Docs only (new `.qmd`, heading updates) | `make generate-chatbot-product-map` | +| Product routes or in-app help links | `make refresh-chatbot-product-map` (requires a sibling `../frontend` checkout) | + +Commit both `chatbot-product-map.md` and `chatbot-product-map-frontend-snapshot.json` when the snapshot changes. CI fails if either file is out of date after regeneration. + +Generator: `site/scripts/generate_chatbot_product_map.py` +Tests: `python3 -m unittest site/scripts/test_generate_chatbot_product_map.py` (run from `site/scripts/`). From 87026200717b46b8c18c9ca6c575a87da1b49813 Mon Sep 17 00:00:00 2001 From: Nik Richers Date: Wed, 20 May 2026 09:54:08 -0700 Subject: [PATCH 4/5] Fix nondeterministic chatbot product map generation for CI. Sort doc paths and related-doc suggestions so Linux and macOS produce the same map, regenerate the committed artifact, and use unittest discover to avoid the stdlib site module import collision. --- .github/workflows/validate-docs-site.yaml | 2 +- site/llm/README.md | 2 +- site/llm/chatbot-product-map.md | 254 +++++++++--------- site/scripts/generate_chatbot_product_map.py | 3 +- .../test_generate_chatbot_product_map.py | 18 ++ 5 files changed, 151 insertions(+), 128 deletions(-) diff --git a/.github/workflows/validate-docs-site.yaml b/.github/workflows/validate-docs-site.yaml index d9f6595898..d731c29026 100644 --- a/.github/workflows/validate-docs-site.yaml +++ b/.github/workflows/validate-docs-site.yaml @@ -172,7 +172,7 @@ jobs: git diff --exit-code site/llm/chatbot-product-map.md site/llm/chatbot-product-map-frontend-snapshot.json - name: Test chatbot product map generator - run: python3 -m unittest site/scripts/test_generate_chatbot_product_map.py -v + run: python3 -m unittest discover -s site/scripts -p 'test_generate_chatbot_product_map.py' -v - name: Validate LLM markdown render run: bash llm/render.sh && bash llm/clean.sh diff --git a/site/llm/README.md b/site/llm/README.md index ee80bfeefb..83e6ca897b 100644 --- a/site/llm/README.md +++ b/site/llm/README.md @@ -51,4 +51,4 @@ CI does **not** check out `validmind/frontend` (private repo; cross-repo PAT sco Commit both `chatbot-product-map.md` and `chatbot-product-map-frontend-snapshot.json` when the snapshot changes. CI fails if either file is out of date after regeneration. Generator: `site/scripts/generate_chatbot_product_map.py` -Tests: `python3 -m unittest site/scripts/test_generate_chatbot_product_map.py` (run from `site/scripts/`). +Tests: `python3 -m unittest discover -s site/scripts -p 'test_generate_chatbot_product_map.py' -v` (from repo root). diff --git a/site/llm/chatbot-product-map.md b/site/llm/chatbot-product-map.md index d8f5ad2a3c..8674bcf1b5 100644 --- a/site/llm/chatbot-product-map.md +++ b/site/llm/chatbot-product-map.md @@ -18,18 +18,18 @@ **Docs (related):** -- `/guide/attestation/submit-attestations.html` - - Sections: Prerequisites; Steps -- `/guide/attestation/working-with-attestations.html` - - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations -- `/guide/attestation/review-attestations.html` +- `/guide/attestation/approve-attestations.html` - Sections: Prerequisites; Steps - `/guide/attestation/manage-attestations.html` - Sections: Prerequisites; Add attestation templates; Test attestation schedules; Edit attestation periods; Cancel attestation periods; View attestations dashboard; Progress; Responses -- `/guide/attestation/approve-attestations.html` +- `/guide/attestation/review-attestations.html` - Sections: Prerequisites; Steps -- `/guide/templates/manage-document-templates.html` - - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates +- `/guide/attestation/submit-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions - *No direct help link in frontend; related docs inferred from keywords.* @@ -45,14 +45,14 @@ **Docs (related):** -- `/guide/attestation/submit-attestations.html` +- `/guide/attestation/approve-attestations.html` - Sections: Prerequisites; Steps -- `/guide/attestation/working-with-attestations.html` - - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations - `/guide/attestation/review-attestations.html` - Sections: Prerequisites; Steps -- `/guide/attestation/approve-attestations.html` +- `/guide/attestation/submit-attestations.html` - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations ### Organization @@ -79,18 +79,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/working-with-document-templates.html` - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions ### Main navigation @@ -112,18 +112,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates - `/guide/templates/manage-documents.html` - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents -- `/guide/templates/working-with-document-templates.html` - - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions - `/guide/templates/manage-text-block-library.html` - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks +- `/guide/templates/working-with-document-templates.html` + - Sections: What's next ### Organization @@ -150,18 +150,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. -- `/guide/templates/working-with-document-templates.html` - - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks #### `/settings/finding-severities` — Artifact Severities @@ -172,18 +172,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. -- `/guide/templates/working-with-document-templates.html` - - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks #### `/settings/finding-types` — Artifact Types @@ -194,18 +194,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. -- `/guide/templates/working-with-document-templates.html` - - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks ### Users & Access @@ -218,10 +218,10 @@ **Docs (related):** -- `/guide/configuration/managing-users.html` - - Sections: Key concepts; Key terms; Default roles; User management - `/guide/configuration/manage-users.html` - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management ### Main navigation @@ -240,15 +240,16 @@ - `/faq/faq-integrations.html` - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more -- `/guide/configuration/set-up-your-organization.html` - - Sections: Prerequisites; Manage business units; Manage use cases; Add use cases and use case groups; Remove use cases and use case groups; Manage risk areas; What's next -- `/guide/configuration/manage-record-stakeholder-types.html` - - Sections: Prerequisites; Add record stakeholder types; Edit record stakeholder types; Assign stakeholder types to record types; Configure stakeholder types on registration; Manage stakeholder type permissions; Manage stakeholder types on records -- `/guide/configuration/personalizing-validmind.html` +- `/guide/configuration/configure-aws-privatelink.html` + - Sections: Prerequisites; VPC service information; Steps; What's next +- `/guide/configuration/configure-azure-private-link.html` + - Sections: Prerequisites; VNet service information; Steps; What's next - `/guide/configuration/configure-google-private-service-connect.html` - Sections: Prerequisites; VPC service information; Configure your Google Cloud Platform project; Request access from }; Prepare your network for connection; Create an endpoint to connect to }; Steps; Create an endpoint to connect to the } authentication service -- `/guide/configuration/manage-roles.html` - - Sections: Prerequisites; Add or update roles; Two special default roles provided by } have unique characteristics.; Manage role permissions; Manage role users; Rename existing roles +- `/guide/configuration/customize-your-dashboard.html` + - Sections: Prerequisites; Manage dashboards; Add dashboards; Edit or remove dashboards; Manage widgets; Arrange widgets; Add widgets; Remove widgets +- `/guide/configuration/manage-groups.html` + - Sections: Prerequisites; View group details; Add new groups; Remove groups; Add or remove group members - *No direct help link in frontend; related docs inferred from keywords.* @@ -260,13 +261,14 @@ - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more - `/faq/faq-reporting.html` - Sections: What analytic features are offered by }?; Learn more -- `/guide/reporting/export-inventory.html` - - Sections: Prerequisites; Export lists of models -- `/guide/reporting/working-with-analytics.html` - - Sections: Prerequisites; Default report pages; Arrange report widgets; Work with analytics -- `/guide/reporting/view-report-data.html` - - Sections: Prerequisites; View report data -- `/guide/reporting/generating-exports.html` +- `/guide/configuration/configure-aws-privatelink.html` + - Sections: Prerequisites; VPC service information; Steps; What's next +- `/guide/configuration/configure-azure-private-link.html` + - Sections: Prerequisites; VNet service information; Steps; What's next +- `/guide/configuration/configure-google-private-service-connect.html` + - Sections: Prerequisites; VPC service information; Configure your Google Cloud Platform project; Request access from }; Prepare your network for connection; Create an endpoint to connect to }; Steps; Create an endpoint to connect to the } authentication service +- `/guide/configuration/customize-your-dashboard.html` + - Sections: Prerequisites; Manage dashboards; Add dashboards; Edit or remove dashboards; Manage widgets; Arrange widgets; Add widgets; Remove widgets - *No direct help link in frontend; related docs inferred from keywords.* @@ -276,15 +278,16 @@ - `/faq/faq-integrations.html` - Sections: Which languages, libraries, and environments do you support?; Currently, we support **Python }** and the most popular AI/ML and data science libraries.; What test ingestion or modeling techniques are supported?; What large language model (LLM) features are offered?; What deployment options are supported by }?; Learn more -- `/guide/configuration/set-up-your-organization.html` - - Sections: Prerequisites; Manage business units; Manage use cases; Add use cases and use case groups; Remove use cases and use case groups; Manage risk areas; What's next -- `/guide/configuration/manage-record-stakeholder-types.html` - - Sections: Prerequisites; Add record stakeholder types; Edit record stakeholder types; Assign stakeholder types to record types; Configure stakeholder types on registration; Manage stakeholder type permissions; Manage stakeholder types on records -- `/guide/configuration/personalizing-validmind.html` +- `/guide/configuration/configure-aws-privatelink.html` + - Sections: Prerequisites; VPC service information; Steps; What's next +- `/guide/configuration/configure-azure-private-link.html` + - Sections: Prerequisites; VNet service information; Steps; What's next - `/guide/configuration/configure-google-private-service-connect.html` - Sections: Prerequisites; VPC service information; Configure your Google Cloud Platform project; Request access from }; Prepare your network for connection; Create an endpoint to connect to }; Steps; Create an endpoint to connect to the } authentication service -- `/guide/configuration/manage-roles.html` - - Sections: Prerequisites; Add or update roles; Two special default roles provided by } have unique characteristics.; Manage role permissions; Manage role users; Rename existing roles +- `/guide/configuration/customize-your-dashboard.html` + - Sections: Prerequisites; Manage dashboards; Add dashboards; Edit or remove dashboards; Manage widgets; Arrange widgets; Add widgets; Remove widgets +- `/guide/configuration/manage-groups.html` + - Sections: Prerequisites; View group details; Add new groups; Remove groups; Add or remove group members - *No direct help link in frontend; related docs inferred from keywords.* @@ -319,10 +322,10 @@ **Docs (related):** -- `/guide/configuration/managing-users.html` - - Sections: Key concepts; Key terms; Default roles; User management - `/guide/configuration/manage-users.html` - Sections: Prerequisites; View and search for users; Manage user invitations; Invite new users; Monitor user invitations; Manage user roles +- `/guide/configuration/managing-users.html` + - Sections: Key concepts; Key terms; Default roles; User management ### Main navigation @@ -344,9 +347,9 @@ **Docs (related):** -- `/guide/configuration/personalizing-validmind.html` - `/guide/configuration/manage-your-profile.html` - Sections: Prerequisites; Access your profile; Onboarding; User Interface Preferences; Terms; Localization; Access Keys; To revoke and regenerate keys, click **Revoke & Regenerate Keys**. +- `/guide/configuration/personalizing-validmind.html` ### Main navigation @@ -413,18 +416,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks - `/guide/templates/working-with-document-templates.html` - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions ### Your Account @@ -456,16 +459,16 @@ - `/faq/faq-workflows.html` - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more -- `/guide/workflows/setting-up-workflows.html` - - Sections: View, sort, and filter workflows; Sort workflows; Filter workflows; How do I create effective filters?; Set up workflows; What's next -- `/guide/workflows/workflow-configuration-examples.html` - - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples -- `/guide/workflows/manage-workflow-tasks.html` - - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows -- `/guide/workflows/transition-workflows.html` - - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/integrations/integrations-examples/use-webhooks-with-workflows.html` + - Sections: Prerequisites; Start a workflow via webhook; a. Configure workflow in }; b. Start workflow from external system; Trigger a paused workflow to continue; a. Configure workflow in }; b. Trigger workflow to continue from external system - `/guide/workflows/conditional-step-requirements.html` - Sections: Prerequisites; Configure conditional requirements +- `/guide/workflows/configure-workflows.html` + - Sections: Prerequisites; Create custom workflows; 1. Add new workflows; 2. Configure workflow steps; 3. Link workflow together; Workflow steps relationship unclear on your canvas?; 4. Publish workflow; Clone existing workflows +- `/guide/workflows/introduction-to-workflows.html` + - Sections: Workflow elements; What's next +- `/guide/workflows/manage-record-stages.html` + - Sections: Prerequisites; Add record stages; Edit or delete record stages #### `/settings/workflows` — Workflows @@ -478,16 +481,16 @@ - `/faq/faq-workflows.html` - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more -- `/guide/workflows/workflow-configuration-examples.html` - - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples -- `/guide/workflows/manage-workflow-tasks.html` - - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows -- `/guide/workflows/transition-workflows.html` - - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/integrations/integrations-examples/use-webhooks-with-workflows.html` + - Sections: Prerequisites; Start a workflow via webhook; a. Configure workflow in }; b. Start workflow from external system; Trigger a paused workflow to continue; a. Configure workflow in }; b. Trigger workflow to continue from external system - `/guide/workflows/conditional-step-requirements.html` - Sections: Prerequisites; Configure conditional requirements +- `/guide/workflows/configure-workflows.html` + - Sections: Prerequisites; Create custom workflows; 1. Add new workflows; 2. Configure workflow steps; 3. Link workflow together; Workflow steps relationship unclear on your canvas?; 4. Publish workflow; Clone existing workflows - `/guide/workflows/introduction-to-workflows.html` - Sections: Workflow elements; What's next +- `/guide/workflows/manage-record-stages.html` + - Sections: Prerequisites; Add record stages; Edit or delete record stages ## Main application @@ -497,15 +500,16 @@ - `/faq/faq-reporting.html` - Sections: What analytic features are offered by }?; Learn more -- `/guide/reporting/export-inventory.html` - - Sections: Prerequisites; Export lists of models -- `/guide/reporting/working-with-analytics.html` - - Sections: Prerequisites; Default report pages; Arrange report widgets; Work with analytics -- `/guide/reporting/view-report-data.html` - - Sections: Prerequisites; View report data -- `/guide/reporting/generating-exports.html` -- `/guide/reporting/manage-custom-reports.html` - - Sections: Visualization configuration options; Prerequisites; Add custom analytics; Edit custom analytics; Remove custom analytics +- `/guide/monitoring/enable-monitoring.html` + - Sections: Prerequisites; Steps; 1. Get monitoring code snippet; 2. Select monitoring template; 3. Run code snippet; A template must already be applied to your selected document to populate monitoring test results in the }.; What's next +- `/guide/monitoring/ongoing-monitoring.html` + - Sections: Monitoring scenarios; Ongoing monitoring plan; Key concepts; Design and implementation; Testing; Manage ongoing monitoring; Code samples; } Available tests +- `/guide/monitoring/review-monitoring-results.html` + - Sections: Prerequisites; Steps; Example monitoring test results; [} Satisfactory]; [} Requires Attention] +- `/guide/monitoring/set-thresholds-and-alerts.html` + - Sections: Prerequisites; Use a custom function; Set the `passed` parameter; Output examples; Alert notifications +- `/guide/monitoring/work-with-metrics-over-time.html` + - Sections: **Log metrics over time }**; Prerequisites; Add metrics over time; Add integration metrics; Use the global time range; View metric over time metadata - *No direct help link in frontend; related docs inferred from keywords.* @@ -513,18 +517,18 @@ **Docs (related):** +- `/guide/templates/customize-document-checker.html` + - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/customize-document-templates.html` + - Sections: Prerequisites; Edit template outlines; Configure assessment options[^4]; Edit YAML templates; Template schema; Troubleshooting YAML templates; Add text blocks to templates; Add text blocks via template outlines - `/guide/templates/manage-document-templates.html` - Sections: Prerequisites; View document templates; Edit document template outlines; Swap document templates; View currently applied templates; Swap between templates; Duplicate document templates; Delete document templates -- `/guide/templates/manage-documents.html` - - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents - `/guide/templates/manage-document-types.html` - Sections: Prerequisites; Add document types; Edit or delete document types; Development, Validation, and Monitoring document types are stock types and cannot be deleted. -- `/guide/templates/working-with-document-templates.html` - - Sections: What's next -- `/guide/templates/working-with-documents.html` - - Sections: What's next -- `/guide/templates/customize-document-checker.html` - - Sections: Prerequisites; Manage regulations and policies; Manage assessments; Default assessments provided by } cannot be edited, only cloned.; Add or clone assessments; Add or edit assessment questions; Add assessment questions; Edit assessment questions +- `/guide/templates/manage-documents.html` + - Sections: Prerequisites; Add record documents; How do I get the best results when converting PDFs into editable documents?; How can I trust that the conversion is accurate?; Troubleshooting; My PDF conversion is stuck. What can I do?; Edit record documents; Delete record documents +- `/guide/templates/manage-text-block-library.html` + - Sections: Prerequisites; Add text blocks; Add existing text blocks to library; Duplicate text blocks; Edit text blocks; Delete text blocks - *No direct help link in frontend; related docs inferred from keywords.* @@ -532,16 +536,16 @@ **Docs (related):** -- `/guide/attestation/submit-attestations.html` - - Sections: Prerequisites; Steps -- `/guide/attestation/working-with-attestations.html` - - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations -- `/guide/attestation/review-attestations.html` +- `/guide/attestation/approve-attestations.html` - Sections: Prerequisites; Steps - `/guide/attestation/manage-attestations.html` - Sections: Prerequisites; Add attestation templates; Test attestation schedules; Edit attestation periods; Cancel attestation periods; View attestations dashboard; Progress; Responses -- `/guide/attestation/approve-attestations.html` +- `/guide/attestation/review-attestations.html` - Sections: Prerequisites; Steps +- `/guide/attestation/submit-attestations.html` + - Sections: Prerequisites; Steps +- `/guide/attestation/working-with-attestations.html` + - Sections: Prerequisites; Key concepts; Where do I access attestations?; How does the attestation process work?; How are attestation submissions organized?; How do I create meaningful attestation questionnaires?; Manage attestations - *No direct help link in frontend; related docs inferred from keywords.* @@ -564,16 +568,16 @@ - `/faq/faq-workflows.html` - Sections: Can I customize workflows within }?; What statuses are available for use in workflows?; Can we work with disconnected workflows?; You can also leverage the } once you are ready to document a specific model for review and validation.; Learn more -- `/guide/workflows/setting-up-workflows.html` - - Sections: View, sort, and filter workflows; Sort workflows; Filter workflows; How do I create effective filters?; Set up workflows; What's next -- `/guide/workflows/workflow-configuration-examples.html` - - Sections: Example workflows; On registration; On validation; On deployment; Workflow step type examples -- `/guide/workflows/manage-workflow-tasks.html` - - Sections: Prerequisites; View workflow tasks; View tasks; Access details; Transition workflows -- `/guide/workflows/transition-workflows.html` - - Sections: Prerequisites; Transition workflows; Reset workflows +- `/guide/integrations/integrations-examples/use-webhooks-with-workflows.html` + - Sections: Prerequisites; Start a workflow via webhook; a. Configure workflow in }; b. Start workflow from external system; Trigger a paused workflow to continue; a. Configure workflow in }; b. Trigger workflow to continue from external system - `/guide/workflows/conditional-step-requirements.html` - Sections: Prerequisites; Configure conditional requirements +- `/guide/workflows/configure-workflows.html` + - Sections: Prerequisites; Create custom workflows; 1. Add new workflows; 2. Configure workflow steps; 3. Link workflow together; Workflow steps relationship unclear on your canvas?; 4. Publish workflow; Clone existing workflows +- `/guide/workflows/introduction-to-workflows.html` + - Sections: Workflow elements; What's next +- `/guide/workflows/manage-record-stages.html` + - Sections: Prerequisites; Add record stages; Edit or delete record stages - *No direct help link in frontend; related docs inferred from keywords.* diff --git a/site/scripts/generate_chatbot_product_map.py b/site/scripts/generate_chatbot_product_map.py index 46d88a37f0..3134388b4a 100644 --- a/site/scripts/generate_chatbot_product_map.py +++ b/site/scripts/generate_chatbot_product_map.py @@ -331,7 +331,7 @@ def collect_all_doc_qmd_paths(site_dir: Path) -> list[str]: continue url = "/" + rel[:-4] + ".html" paths.append(url) - return paths + return sorted(paths) def is_user_facing_doc(path: str) -> bool: @@ -358,6 +358,7 @@ def suggest_related_docs(route: ProductRoute, all_doc_paths: list[str]) -> list[ inner = doc_path.lower() if any(seg in inner for seg in segments): related.append(DocRef(path=doc_path)) + related.sort(key=lambda r: r.path) return related[:6] diff --git a/site/scripts/test_generate_chatbot_product_map.py b/site/scripts/test_generate_chatbot_product_map.py index dd9665c186..62e380ea70 100644 --- a/site/scripts/test_generate_chatbot_product_map.py +++ b/site/scripts/test_generate_chatbot_product_map.py @@ -46,6 +46,24 @@ def test_is_user_facing_doc(self) -> None: self.assertFalse(gen.is_user_facing_doc("/_source/release-notes/foo.html")) self.assertFalse(gen.is_user_facing_doc("/guide/workflows/_partial.html")) + def test_collect_all_doc_qmd_paths_sorted(self) -> None: + site = Path(__file__).resolve().parents[1] + paths = gen.collect_all_doc_qmd_paths(site) + self.assertEqual(paths, sorted(paths)) + + def test_suggest_related_docs_sorted_and_stable(self) -> None: + site = Path(__file__).resolve().parents[1] + all_paths = gen.collect_all_doc_qmd_paths(site) + route = gen.ProductRoute( + path="/settings/templates", + label="Templates", + group="Configuration", + ) + first = gen.suggest_related_docs(route, all_paths) + second = gen.suggest_related_docs(route, all_paths) + self.assertEqual(first, second) + self.assertEqual([r.path for r in first], sorted(r.path for r in first)) + def test_frontend_snapshot_roundtrip(self) -> None: payload = { "version": 1, From b02d4f1196a565bf4028b346fb7796fdaf969f2e Mon Sep 17 00:00:00 2001 From: Nik Richers Date: Fri, 22 May 2026 18:47:26 -0700 Subject: [PATCH 5/5] Address comments from Kam --- ...generate_chatbot_product_map.cpython-314.pyc | Bin 0 -> 39824 bytes ...generate_chatbot_product_map.cpython-314.pyc | Bin 0 -> 9184 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 site/scripts/__pycache__/generate_chatbot_product_map.cpython-314.pyc create mode 100644 site/scripts/__pycache__/test_generate_chatbot_product_map.cpython-314.pyc diff --git a/site/scripts/__pycache__/generate_chatbot_product_map.cpython-314.pyc b/site/scripts/__pycache__/generate_chatbot_product_map.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b898ef5f9da050cb6a87657d6d5d574b030872a7 GIT binary patch literal 39824 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 f{WTbaBlWe1yFVZZUT4RDZmKNBsW+&5U=t literal 0 HcmV?d00001 diff --git a/site/scripts/__pycache__/test_generate_chatbot_product_map.cpython-314.pyc b/site/scripts/__pycache__/test_generate_chatbot_product_map.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80669ed83476e5982dc61d38853d4618f92db2dc GIT binary patch literal 9184 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 Jh<4H*{sS;pp%DN8 literal 0 HcmV?d00001