switchroom 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/bin/autoaccept.exp +81 -0
- package/bin/boot-self-test.sh +149 -0
- package/bin/bridge-watchdog.sh +967 -0
- package/bin/handoff-briefing.sh +206 -0
- package/bin/run-hook.sh +228 -0
- package/bin/switchroom.ts +4 -0
- package/bin/timezone-hook.sh +67 -0
- package/bin/user-profile-refresh-hook.sh +38 -0
- package/bin/workspace-dynamic-hook.sh +142 -0
- package/bin/workspace-stable-hook.sh +57 -0
- package/dist/cli/autoaccept-poll.js +118 -0
- package/dist/cli/switchroom.js +48557 -0
- package/package.json +95 -0
- package/profiles/_base/settings.json.hbs +15 -0
- package/profiles/_base/start.sh.hbs +383 -0
- package/profiles/_shared/telegram-style.md.hbs +140 -0
- package/profiles/coding/CLAUDE.md.hbs +57 -0
- package/profiles/coding/skills/architecture/SKILL.md +70 -0
- package/profiles/coding/skills/code-review/SKILL.md +58 -0
- package/profiles/coding/workspace/SOUL.md.hbs +25 -0
- package/profiles/default/CLAUDE.md +238 -0
- package/profiles/default/CLAUDE.md.hbs +113 -0
- package/profiles/default/workspace/CLAUDE.md.hbs +126 -0
- package/profiles/default/workspace/HEARTBEAT.md.hbs +40 -0
- package/profiles/default/workspace/IDENTITY.md.hbs +32 -0
- package/profiles/default/workspace/MEMORY.md.hbs +29 -0
- package/profiles/default/workspace/SOUL.md.hbs +61 -0
- package/profiles/default/workspace/TOOLS.md.hbs +29 -0
- package/profiles/default/workspace/USER.md.hbs +52 -0
- package/profiles/default/workspace/memory/.gitkeep +0 -0
- package/profiles/executive-assistant/CLAUDE.md.hbs +51 -0
- package/profiles/executive-assistant/skills/daily-briefing/SKILL.md +55 -0
- package/profiles/executive-assistant/skills/meeting-prep/SKILL.md +58 -0
- package/profiles/executive-assistant/workspace/SOUL.md.hbs +25 -0
- package/profiles/health-coach/CLAUDE.md.hbs +45 -0
- package/profiles/health-coach/skills/check-in/SKILL.md +41 -0
- package/profiles/health-coach/skills/weekly-review/SKILL.md +53 -0
- package/profiles/health-coach/workspace/SOUL.md.hbs +25 -0
- package/skills/buildkite-agent-infrastructure/SKILL.md +302 -0
- package/skills/buildkite-agent-infrastructure/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-infrastructure/references/audit-logging.md +87 -0
- package/skills/buildkite-agent-infrastructure/references/graphql-mutations.md +690 -0
- package/skills/buildkite-agent-infrastructure/references/instance-shapes.md +38 -0
- package/skills/buildkite-agent-infrastructure/references/pipeline-templates.md +73 -0
- package/skills/buildkite-agent-infrastructure/references/self-hosted-agents.md +137 -0
- package/skills/buildkite-agent-infrastructure/references/sso-saml.md +92 -0
- package/skills/buildkite-agent-runtime/SKILL.md +476 -0
- package/skills/buildkite-agent-runtime/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-runtime/references/flag-reference.md +417 -0
- package/skills/buildkite-agent-runtime/references/patterns-and-recipes.md +555 -0
- package/skills/buildkite-api/SKILL.md +285 -0
- package/skills/buildkite-api/agents/openai.yaml +6 -0
- package/skills/buildkite-api/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-api/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-api/references/graphql-reference.md +195 -0
- package/skills/buildkite-api/references/patterns.md +44 -0
- package/skills/buildkite-api/references/webhooks.md +161 -0
- package/skills/buildkite-cli/SKILL.md +379 -0
- package/skills/buildkite-cli/agents/openai.yaml +6 -0
- package/skills/buildkite-cli/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-cli/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-cli/references/command-reference.md +181 -0
- package/skills/buildkite-migration/SKILL.md +182 -0
- package/skills/buildkite-pipelines/SKILL.md +464 -0
- package/skills/buildkite-pipelines/agents/openai.yaml +6 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-pipelines/examples/basic-pipeline.yml +24 -0
- package/skills/buildkite-pipelines/examples/optimized-pipeline.yml +100 -0
- package/skills/buildkite-pipelines/references/advanced-patterns.md +286 -0
- package/skills/buildkite-pipelines/references/retry-and-error-codes.md +131 -0
- package/skills/buildkite-pipelines/references/step-types-reference.md +225 -0
- package/skills/buildkite-secure-delivery/SKILL.md +168 -0
- package/skills/buildkite-secure-delivery/agents/openai.yaml +6 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-secure-delivery/references/oidc-cloud-providers.md +83 -0
- package/skills/buildkite-secure-delivery/references/package-publishing.md +100 -0
- package/skills/buildkite-test-engine/SKILL.md +239 -0
- package/skills/buildkite-test-engine/agents/openai.yaml +6 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-test-engine/examples/bktec-splitting.yml +16 -0
- package/skills/buildkite-test-engine/examples/collector-pipeline.yml +11 -0
- package/skills/buildkite-test-engine/references/collectors.md +198 -0
- package/skills/buildkite-test-engine/references/splitting-examples.md +93 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +590 -0
- package/skills/docx/VENDORED.md +32 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +318 -0
- package/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/docx/scripts/office/pack.py +159 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/office/soffice.py +183 -0
- package/skills/docx/scripts/office/unpack.py +132 -0
- package/skills/docx/scripts/office/validate.py +111 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/base.py +847 -0
- package/skills/docx/scripts/office/validators/docx.py +446 -0
- package/skills/docx/scripts/office/validators/pptx.py +275 -0
- package/skills/docx/scripts/office/validators/redlining.py +247 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/file-bug/SKILL.md +129 -0
- package/skills/humanizer/LICENSE +21 -0
- package/skills/humanizer/SKILL.md +559 -0
- package/skills/humanizer/VENDORED.md +38 -0
- package/skills/humanizer-calibrate/SKILL.md +144 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +236 -0
- package/skills/mcp-builder/VENDORED.md +32 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +314 -0
- package/skills/pdf/VENDORED.md +32 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +232 -0
- package/skills/pptx/VENDORED.md +32 -0
- package/skills/pptx/editing.md +205 -0
- package/skills/pptx/pptxgenjs.md +420 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +195 -0
- package/skills/pptx/scripts/clean.py +286 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/pptx/scripts/office/pack.py +159 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +183 -0
- package/skills/pptx/scripts/office/unpack.py +132 -0
- package/skills/pptx/scripts/office/validate.py +111 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +847 -0
- package/skills/pptx/scripts/office/validators/docx.py +446 -0
- package/skills/pptx/scripts/office/validators/pptx.py +275 -0
- package/skills/pptx/scripts/office/validators/redlining.py +247 -0
- package/skills/pptx/scripts/thumbnail.py +289 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/VENDORED.md +32 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/switchroom-architecture/SKILL.md +60 -0
- package/skills/switchroom-architecture/cascade.md +112 -0
- package/skills/switchroom-architecture/sub-agents.md +87 -0
- package/skills/switchroom-architecture/telegram.md +94 -0
- package/skills/switchroom-cli/SKILL.md +274 -0
- package/skills/switchroom-health/SKILL.md +101 -0
- package/skills/switchroom-install/SKILL.md +116 -0
- package/skills/switchroom-manage/SKILL.md +90 -0
- package/skills/switchroom-status/SKILL.md +69 -0
- package/skills/switchroom-status/scripts/status.sh +69 -0
- package/skills/telegram-test-harness/SKILL.md +191 -0
- package/skills/token-helpers/SKILL.md +73 -0
- package/skills/token-helpers/scripts/google-cal-token.sh +62 -0
- package/skills/token-helpers/scripts/ms-graph-token.sh +70 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/VENDORED.md +32 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +292 -0
- package/skills/xlsx/VENDORED.md +32 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/xlsx/scripts/office/pack.py +159 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +183 -0
- package/skills/xlsx/scripts/office/unpack.py +132 -0
- package/skills/xlsx/scripts/office/validate.py +111 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +847 -0
- package/skills/xlsx/scripts/office/validators/docx.py +446 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
- package/skills/xlsx/scripts/recalc.py +184 -0
- package/telegram-plugin/.claude-plugin/plugin.json +20 -0
- package/telegram-plugin/.mcp.json +14 -0
- package/telegram-plugin/LICENSE +21 -0
- package/telegram-plugin/README.md +352 -0
- package/telegram-plugin/active-pins-sweep.ts +204 -0
- package/telegram-plugin/active-pins.ts +146 -0
- package/telegram-plugin/active-reactions-sweep.ts +79 -0
- package/telegram-plugin/active-reactions.ts +134 -0
- package/telegram-plugin/admin-commands/dispatch.test.ts +149 -0
- package/telegram-plugin/admin-commands/index.ts +106 -0
- package/telegram-plugin/answer-stream.ts +565 -0
- package/telegram-plugin/ask-user.ts +179 -0
- package/telegram-plugin/attachment-path.ts +80 -0
- package/telegram-plugin/auth-code-redact.ts +83 -0
- package/telegram-plugin/auth-dashboard.ts +1104 -0
- package/telegram-plugin/auth-slot-parser.ts +497 -0
- package/telegram-plugin/auto-fallback-dispatcher.ts +68 -0
- package/telegram-plugin/auto-fallback.ts +348 -0
- package/telegram-plugin/bridge/bridge.ts +687 -0
- package/telegram-plugin/bridge/ipc-client.ts +326 -0
- package/telegram-plugin/bun.lock +218 -0
- package/telegram-plugin/card-format.ts +62 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +56 -0
- package/telegram-plugin/channel-envelope-safety.ts +56 -0
- package/telegram-plugin/chat-lock.ts +65 -0
- package/telegram-plugin/context-exhaustion.ts +38 -0
- package/telegram-plugin/credits-watch.ts +220 -0
- package/telegram-plugin/dist/bridge/bridge.js +24758 -0
- package/telegram-plugin/dist/foreman/foreman.js +30723 -0
- package/telegram-plugin/dist/gateway/gateway.js +46497 -0
- package/telegram-plugin/dist/server.js +24551 -0
- package/telegram-plugin/dm-command-gate.ts +56 -0
- package/telegram-plugin/docs/gateway-server-split.md +133 -0
- package/telegram-plugin/docs/multi-agent-card-design.md +847 -0
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +144 -0
- package/telegram-plugin/docs/stream-json-daemon-mode.md +477 -0
- package/telegram-plugin/docs/waiting-ux-spec.md +233 -0
- package/telegram-plugin/draft-stream.ts +442 -0
- package/telegram-plugin/draft-transport.ts +72 -0
- package/telegram-plugin/first-paint.ts +246 -0
- package/telegram-plugin/fleet-state.ts +246 -0
- package/telegram-plugin/foreman/foreman-create-flow.ts +202 -0
- package/telegram-plugin/foreman/foreman-handlers.ts +493 -0
- package/telegram-plugin/foreman/foreman.ts +1130 -0
- package/telegram-plugin/foreman/setup-flow.ts +345 -0
- package/telegram-plugin/foreman/setup-state.ts +239 -0
- package/telegram-plugin/foreman/state.ts +203 -0
- package/telegram-plugin/format.ts +685 -0
- package/telegram-plugin/gateway/access-validator.test.ts +95 -0
- package/telegram-plugin/gateway/access-validator.ts +37 -0
- package/telegram-plugin/gateway/boot-card.ts +582 -0
- package/telegram-plugin/gateway/boot-probes.ts +863 -0
- package/telegram-plugin/gateway/boot-reason.ts +51 -0
- package/telegram-plugin/gateway/boot-sweep-filter.test.ts +54 -0
- package/telegram-plugin/gateway/boot-sweep-filter.ts +32 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +183 -0
- package/telegram-plugin/gateway/disconnect-flush.ts +109 -0
- package/telegram-plugin/gateway/gateway.ts +10202 -0
- package/telegram-plugin/gateway/inbound-coalesce.ts +147 -0
- package/telegram-plugin/gateway/inject-handler.test.ts +221 -0
- package/telegram-plugin/gateway/inject-handler.ts +190 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +151 -0
- package/telegram-plugin/gateway/ipc-server.ts +494 -0
- package/telegram-plugin/gateway/pid-file.ts +103 -0
- package/telegram-plugin/gateway/poll-health.ts +156 -0
- package/telegram-plugin/gateway/preamble-suppressor.ts +154 -0
- package/telegram-plugin/gateway/quota-cache.ts +125 -0
- package/telegram-plugin/gateway/resolve-calling-subagent.ts +78 -0
- package/telegram-plugin/gateway/restart-watchdog.ts +200 -0
- package/telegram-plugin/gateway/session-marker.ts +83 -0
- package/telegram-plugin/gateway/shutdown-drain.ts +162 -0
- package/telegram-plugin/gateway/startup-mutex.ts +285 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +142 -0
- package/telegram-plugin/gateway/turn-active-marker.ts +176 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +78 -0
- package/telegram-plugin/handoff-continuity.ts +200 -0
- package/telegram-plugin/history.ts +468 -0
- package/telegram-plugin/hooks/hooks.json +58 -0
- package/telegram-plugin/hooks/secret-guard-pretool.mjs +208 -0
- package/telegram-plugin/hooks/secret-scrub-stop.mjs +98 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +111 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +296 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +261 -0
- package/telegram-plugin/html-sanitize.ts +244 -0
- package/telegram-plugin/idle-footer.ts +65 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +166 -0
- package/telegram-plugin/interrupt-marker.ts +66 -0
- package/telegram-plugin/issues-card.ts +371 -0
- package/telegram-plugin/issues-watcher.ts +125 -0
- package/telegram-plugin/model-unavailable.ts +325 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/operator-events-history.ts +94 -0
- package/telegram-plugin/operator-events.fixtures.json +161 -0
- package/telegram-plugin/operator-events.ts +421 -0
- package/telegram-plugin/package.json +55 -0
- package/telegram-plugin/permission-rule.ts +133 -0
- package/telegram-plugin/permission-title.ts +117 -0
- package/telegram-plugin/pin-event-log.ts +76 -0
- package/telegram-plugin/plugin-logger.ts +136 -0
- package/telegram-plugin/progress-card-driver.ts +2697 -0
- package/telegram-plugin/progress-card-pin-manager.ts +589 -0
- package/telegram-plugin/progress-card-pin-watchdog.ts +98 -0
- package/telegram-plugin/progress-card.ts +1409 -0
- package/telegram-plugin/pty-partial-handler.ts +247 -0
- package/telegram-plugin/pty-tail.ts +730 -0
- package/telegram-plugin/quota-check.ts +474 -0
- package/telegram-plugin/recent-outbound-dedup.ts +169 -0
- package/telegram-plugin/registry/api-registry.test.ts +201 -0
- package/telegram-plugin/registry/subagents-bugs.test.ts +454 -0
- package/telegram-plugin/registry/subagents-schema.ts +509 -0
- package/telegram-plugin/registry/subagents.test.ts +476 -0
- package/telegram-plugin/registry/turns-schema.test.ts +101 -0
- package/telegram-plugin/registry/turns-schema.ts +417 -0
- package/telegram-plugin/retry-api-call.ts +172 -0
- package/telegram-plugin/scripts/build.mjs +78 -0
- package/telegram-plugin/secret-detect/audit.ts +66 -0
- package/telegram-plugin/secret-detect/chunker.ts +37 -0
- package/telegram-plugin/secret-detect/entropy.ts +20 -0
- package/telegram-plugin/secret-detect/gitleaks-loader.ts +74 -0
- package/telegram-plugin/secret-detect/gitleaks.toml +27 -0
- package/telegram-plugin/secret-detect/index.ts +218 -0
- package/telegram-plugin/secret-detect/kv-scanner.ts +60 -0
- package/telegram-plugin/secret-detect/mask.ts +13 -0
- package/telegram-plugin/secret-detect/patterns.ts +115 -0
- package/telegram-plugin/secret-detect/pipeline.ts +144 -0
- package/telegram-plugin/secret-detect/rewrite.ts +26 -0
- package/telegram-plugin/secret-detect/secretlint-source.ts +95 -0
- package/telegram-plugin/secret-detect/slug.ts +44 -0
- package/telegram-plugin/secret-detect/staging.ts +85 -0
- package/telegram-plugin/secret-detect/suppressor.ts +34 -0
- package/telegram-plugin/secret-detect/url-redact.ts +60 -0
- package/telegram-plugin/secret-detect/vault-write.ts +56 -0
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/server.ts +171 -0
- package/telegram-plugin/session-tail.ts +884 -0
- package/telegram-plugin/shared/bot-runtime.ts +324 -0
- package/telegram-plugin/silent-reply.ts +58 -0
- package/telegram-plugin/slot-banner-driver.ts +147 -0
- package/telegram-plugin/slot-banner.ts +86 -0
- package/telegram-plugin/start.js +26 -0
- package/telegram-plugin/startup-reset.ts +45 -0
- package/telegram-plugin/status-reactions.ts +332 -0
- package/telegram-plugin/steering.ts +155 -0
- package/telegram-plugin/sticker-aliases.ts +249 -0
- package/telegram-plugin/stream-controller.ts +311 -0
- package/telegram-plugin/stream-reply-handler.ts +664 -0
- package/telegram-plugin/streaming-metrics.ts +134 -0
- package/telegram-plugin/streaming-report.ts +204 -0
- package/telegram-plugin/subagent-watcher.ts +880 -0
- package/telegram-plugin/telegram-button-constraints.ts +191 -0
- package/telegram-plugin/telegraph.ts +381 -0
- package/telegram-plugin/tests/HARNESS.md +340 -0
- package/telegram-plugin/tests/_progress-card-harness.ts +105 -0
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +211 -0
- package/telegram-plugin/tests/active-pins-sweep.test.ts +309 -0
- package/telegram-plugin/tests/active-pins.test.ts +187 -0
- package/telegram-plugin/tests/active-reactions-sweep.test.ts +116 -0
- package/telegram-plugin/tests/active-reactions.test.ts +198 -0
- package/telegram-plugin/tests/answer-stream-dedup.test.ts +352 -0
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +236 -0
- package/telegram-plugin/tests/answer-stream.test.ts +878 -0
- package/telegram-plugin/tests/ask-user.test.ts +203 -0
- package/telegram-plugin/tests/attachment-path.test.ts +199 -0
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +118 -0
- package/telegram-plugin/tests/auth-code-auto-capture.test.ts +144 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +248 -0
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +260 -0
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +140 -0
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +559 -0
- package/telegram-plugin/tests/auth-dashboard.test.ts +1045 -0
- package/telegram-plugin/tests/auth-login-url-button.test.ts +122 -0
- package/telegram-plugin/tests/auth-slot-commands.test.ts +640 -0
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +183 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +381 -0
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +137 -0
- package/telegram-plugin/tests/boot-card-dedupe.test.ts +154 -0
- package/telegram-plugin/tests/boot-card-probe-target.test.ts +194 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +103 -0
- package/telegram-plugin/tests/boot-card-render.test.ts +219 -0
- package/telegram-plugin/tests/boot-probes.test.ts +451 -0
- package/telegram-plugin/tests/bot-api.harness.ts +116 -0
- package/telegram-plugin/tests/bot-runtime.test.ts +190 -0
- package/telegram-plugin/tests/bridge-anonymous-refuse.test.ts +60 -0
- package/telegram-plugin/tests/context-exhaustion.test.ts +114 -0
- package/telegram-plugin/tests/credits-watch.test.ts +221 -0
- package/telegram-plugin/tests/dm-command-gate.test.ts +176 -0
- package/telegram-plugin/tests/draft-stream.test.ts +752 -0
- package/telegram-plugin/tests/draft-transport.test.ts +141 -0
- package/telegram-plugin/tests/e2e.test.ts +436 -0
- package/telegram-plugin/tests/fake-bot-api.test.ts +213 -0
- package/telegram-plugin/tests/fake-bot-api.ts +617 -0
- package/telegram-plugin/tests/false-restart-banner.test.ts +253 -0
- package/telegram-plugin/tests/first-paint.test.ts +257 -0
- package/telegram-plugin/tests/fixtures/pty-tail-tmux-fragment.bin +6 -0
- package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +3624 -0
- package/telegram-plugin/tests/fleet-state-watcher.test.ts +101 -0
- package/telegram-plugin/tests/fleet-state.test.ts +185 -0
- package/telegram-plugin/tests/foreman-create-flow.test.ts +359 -0
- package/telegram-plugin/tests/foreman-handlers.test.ts +347 -0
- package/telegram-plugin/tests/foreman-state.test.ts +164 -0
- package/telegram-plugin/tests/foreman-write-ops.test.ts +214 -0
- package/telegram-plugin/tests/gateway-409-retry-banner.test.ts +173 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +72 -0
- package/telegram-plugin/tests/gateway-bridge.test.ts +811 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +414 -0
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +144 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +133 -0
- package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +103 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +127 -0
- package/telegram-plugin/tests/gateway-startup-mutex.test.ts +284 -0
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +185 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +72 -0
- package/telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts +125 -0
- package/telegram-plugin/tests/handoff-continuity.test.ts +249 -0
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +243 -0
- package/telegram-plugin/tests/harness-parse-mode-validation.test.ts +114 -0
- package/telegram-plugin/tests/history.test.ts +364 -0
- package/telegram-plugin/tests/html-balanced.ts +63 -0
- package/telegram-plugin/tests/html-sanitize.test.ts +146 -0
- package/telegram-plugin/tests/idle-footer-wiring.test.ts +88 -0
- package/telegram-plugin/tests/idle-footer.test.ts +66 -0
- package/telegram-plugin/tests/inbound-coalesce.test.ts +127 -0
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +150 -0
- package/telegram-plugin/tests/interrupt-marker.test.ts +126 -0
- package/telegram-plugin/tests/ipc-protocol.test.ts +218 -0
- package/telegram-plugin/tests/ipc-server-anonymous-refuse.test.ts +82 -0
- package/telegram-plugin/tests/ipc-server-client.test.ts +323 -0
- package/telegram-plugin/tests/ipc-server-race.test.ts +183 -0
- package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +91 -0
- package/telegram-plugin/tests/ipc-server-validate-pty-partial.test.ts +64 -0
- package/telegram-plugin/tests/ipc-server-validate-update-placeholder.test.ts +77 -0
- package/telegram-plugin/tests/ipc-validator.test.ts +274 -0
- package/telegram-plugin/tests/issues-card.test.ts +495 -0
- package/telegram-plugin/tests/issues-watcher.test.ts +165 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +303 -0
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +159 -0
- package/telegram-plugin/tests/operator-events-history.test.ts +125 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +192 -0
- package/telegram-plugin/tests/operator-events.test.ts +331 -0
- package/telegram-plugin/tests/outbound-ordering.test.ts +293 -0
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +164 -0
- package/telegram-plugin/tests/permission-rule.test.ts +121 -0
- package/telegram-plugin/tests/permission-title.test.ts +106 -0
- package/telegram-plugin/tests/pin-event-log.test.ts +124 -0
- package/telegram-plugin/tests/plugin-logger.test.ts +97 -0
- package/telegram-plugin/tests/poll-health.test.ts +86 -0
- package/telegram-plugin/tests/preamble-suppressor.test.ts +285 -0
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +73 -0
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +272 -0
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +258 -0
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +81 -0
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +80 -0
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +215 -0
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +123 -0
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +76 -0
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +62 -0
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +84 -0
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +139 -0
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +773 -0
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +66 -0
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +64 -0
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +190 -0
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +146 -0
- package/telegram-plugin/tests/progress-update.test.ts +236 -0
- package/telegram-plugin/tests/protocol-fixtures.test.ts +59 -0
- package/telegram-plugin/tests/protocol-fixtures.ts +198 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +326 -0
- package/telegram-plugin/tests/pty-tail-real-fixture.test.ts +114 -0
- package/telegram-plugin/tests/pty-tail-tmux-fragment.test.ts +71 -0
- package/telegram-plugin/tests/pty-tail.test.ts +525 -0
- package/telegram-plugin/tests/quota-cache.test.ts +187 -0
- package/telegram-plugin/tests/quota-check.test.ts +622 -0
- package/telegram-plugin/tests/races.test.ts +842 -0
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +123 -0
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +82 -0
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +114 -0
- package/telegram-plugin/tests/real-gateway-harness.ts +699 -0
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +313 -0
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +299 -0
- package/telegram-plugin/tests/real-gateway-spec.test.ts +487 -0
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +101 -0
- package/telegram-plugin/tests/recent-outbound-dedup.test.ts +192 -0
- package/telegram-plugin/tests/registry-turns.test.ts +432 -0
- package/telegram-plugin/tests/reply-terminal-reaction.test.ts +149 -0
- package/telegram-plugin/tests/resolve-calling-subagent.test.ts +269 -0
- package/telegram-plugin/tests/restart-watchdog.test.ts +224 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +287 -0
- package/telegram-plugin/tests/secret-detect-audit.test.ts +58 -0
- package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +83 -0
- package/telegram-plugin/tests/secret-detect-gitleaks.test.ts +32 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +308 -0
- package/telegram-plugin/tests/secret-detect-pipeline.test.ts +123 -0
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +101 -0
- package/telegram-plugin/tests/secret-detect-staging.test.ts +45 -0
- package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +67 -0
- package/telegram-plugin/tests/secret-detect.test.ts +223 -0
- package/telegram-plugin/tests/secret-guard-pretool.test.ts +194 -0
- package/telegram-plugin/tests/send-typing-action-validation.test.ts +61 -0
- package/telegram-plugin/tests/session-tail-capped.test.ts +285 -0
- package/telegram-plugin/tests/session-tail.test.ts +524 -0
- package/telegram-plugin/tests/setup-flow.test.ts +510 -0
- package/telegram-plugin/tests/setup-state.test.ts +146 -0
- package/telegram-plugin/tests/silent-reply-guard.test.ts +122 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +350 -0
- package/telegram-plugin/tests/slot-banner.test.ts +74 -0
- package/telegram-plugin/tests/snapshot-serializer.ts +79 -0
- package/telegram-plugin/tests/spawn-detached-cgroup-escape.test.ts +51 -0
- package/telegram-plugin/tests/status-accent.test.ts +186 -0
- package/telegram-plugin/tests/status-reactions-allowed-filter.test.ts +132 -0
- package/telegram-plugin/tests/status-reactions.test.ts +314 -0
- package/telegram-plugin/tests/steering.test.ts +282 -0
- package/telegram-plugin/tests/sticker-aliases.test.ts +232 -0
- package/telegram-plugin/tests/stream-controller-html-fallback.test.ts +127 -0
- package/telegram-plugin/tests/stream-controller.test.ts +262 -0
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +208 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +1292 -0
- package/telegram-plugin/tests/streaming-e2e.test.ts +389 -0
- package/telegram-plugin/tests/streaming-metrics.test.ts +201 -0
- package/telegram-plugin/tests/streaming-orchestration.test.ts +756 -0
- package/telegram-plugin/tests/subagent-registry-bugs.test.ts +725 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +213 -0
- package/telegram-plugin/tests/subagent-watcher-parent-marker.test.ts +274 -0
- package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +243 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +877 -0
- package/telegram-plugin/tests/subagents-schema-init-order.test.ts +109 -0
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +116 -0
- package/telegram-plugin/tests/telegram-button-constraints.test.ts +194 -0
- package/telegram-plugin/tests/telegram-format.test.ts +1093 -0
- package/telegram-plugin/tests/telegraph.test.ts +246 -0
- package/telegram-plugin/tests/tool-labels.test.ts +383 -0
- package/telegram-plugin/tests/turn-active-marker.test.ts +195 -0
- package/telegram-plugin/tests/turn-end-regressions.test.ts +489 -0
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +218 -0
- package/telegram-plugin/tests/turn-flush-dedup-controller.test.ts +144 -0
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +78 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +189 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +107 -0
- package/telegram-plugin/tests/turns-writer.test.ts +323 -0
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +131 -0
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +120 -0
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +87 -0
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +211 -0
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +62 -0
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +68 -0
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +110 -0
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +128 -0
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +58 -0
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +133 -0
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +155 -0
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +117 -0
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +143 -0
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +149 -0
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +105 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +141 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +147 -0
- package/telegram-plugin/tests/update-factory-edited-and-reactions.test.ts +108 -0
- package/telegram-plugin/tests/update-factory.ts +305 -0
- package/telegram-plugin/tests/vault-grant-wizard.test.ts +84 -0
- package/telegram-plugin/tests/vault-grants-revoke.test.ts +265 -0
- package/telegram-plugin/tests/vault-subcommands.test.ts +234 -0
- package/telegram-plugin/tests/voice-transcribe.test.ts +196 -0
- package/telegram-plugin/tests/waiting-ux-harness.ts +381 -0
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +233 -0
- package/telegram-plugin/tests/welcome-text.test.ts +407 -0
- package/telegram-plugin/tool-error-filter.ts +89 -0
- package/telegram-plugin/tool-labels.ts +330 -0
- package/telegram-plugin/tool-names.ts +53 -0
- package/telegram-plugin/turn-flush-prose-recovery.ts +40 -0
- package/telegram-plugin/turn-flush-safety.ts +109 -0
- package/telegram-plugin/turn-signal-tracker.ts +79 -0
- package/telegram-plugin/two-zone-card.ts +249 -0
- package/telegram-plugin/typing-wrap.ts +92 -0
- package/telegram-plugin/voice-transcribe.ts +166 -0
- package/telegram-plugin/welcome-text.ts +359 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the Anthropic OAuth browser-code detection added in issue #44.
|
|
3
|
+
*
|
|
4
|
+
* Channel A — anchored pattern (`anthropic_oauth_code`):
|
|
5
|
+
* Positive: two URL-safe base64 segments (≥20 chars each) separated by `#`.
|
|
6
|
+
* Negative: ordinary URL fragments, short anchors, markdown link targets.
|
|
7
|
+
*
|
|
8
|
+
* Channel B — context rule (`awaitingAuthCodeAt` map in gateway.ts):
|
|
9
|
+
* Tested structurally: verify the gateway source wires the map, sets it
|
|
10
|
+
* when emitting the "Paste the browser code here" prompt, checks it on
|
|
11
|
+
* inbound, and clears it after one message.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
14
|
+
import { readFileSync } from 'node:fs'
|
|
15
|
+
import { detectSecrets } from '../secret-detect/index.js'
|
|
16
|
+
import { runPipeline } from '../secret-detect/pipeline.js'
|
|
17
|
+
import { setAuditSink } from '../secret-detect/audit.js'
|
|
18
|
+
import type { VaultWriteFn, VaultListFn } from '../secret-detect/vault-write.js'
|
|
19
|
+
|
|
20
|
+
// ─── Channel A — pattern unit tests ──────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
describe('anthropic_oauth_code pattern — Channel A', () => {
|
|
23
|
+
it('detects a bare auth code (two long url-safe-b64 segments with #)', () => {
|
|
24
|
+
// Shape emitted by the claude.com/cai authorize flow
|
|
25
|
+
const code = 'tle0rmXYZabc123defGHIjkl#00EySjXYZabc123defGHIjklmno'
|
|
26
|
+
const d = detectSecrets(code)
|
|
27
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(true)
|
|
28
|
+
const hit = d.find((h) => h.rule_id === 'anthropic_oauth_code')!
|
|
29
|
+
expect(hit.confidence).toBe('high')
|
|
30
|
+
expect(hit.matched_text).toBe(code)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('detects a code embedded in prose (prefixed with "here is the code:")', () => {
|
|
34
|
+
const code = 'aBcDeFgHiJkLmNoPqRsTuVwX#xYz123456789abcdefghijkl'
|
|
35
|
+
const text = `here is the browser code: ${code} please use it`
|
|
36
|
+
const d = detectSecrets(text)
|
|
37
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(true)
|
|
38
|
+
const hit = d.find((h) => h.rule_id === 'anthropic_oauth_code')!
|
|
39
|
+
expect(hit.matched_text).toBe(code)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('does NOT match https:// URL fragments (section anchor)', () => {
|
|
43
|
+
const text = 'see https://example.com/docs#installation for more'
|
|
44
|
+
const d = detectSecrets(text)
|
|
45
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('does NOT match a short first segment (ordinary markdown anchor)', () => {
|
|
49
|
+
// "callback" is only 8 chars — below the 20-char minimum
|
|
50
|
+
const text = 'https://claude.ai/callback#state123456789012345'
|
|
51
|
+
const d = detectSecrets(text)
|
|
52
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('does NOT match a markdown link target like [link](/foo#bar)', () => {
|
|
56
|
+
// "/foo" is 4 chars — well below minimum; "bar" is 3 chars — also below
|
|
57
|
+
const text = '[see here](/features#quickstart-guide)'
|
|
58
|
+
const d = detectSecrets(text)
|
|
59
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('does NOT match when either segment is shorter than 20 chars', () => {
|
|
63
|
+
// First segment: 19 chars (one short)
|
|
64
|
+
const text = 'aBcDeFgHiJkLmNoPqRs#xYz123456789abcdefghijkl'
|
|
65
|
+
const d = detectSecrets(text)
|
|
66
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
67
|
+
|
|
68
|
+
// Second segment: 19 chars
|
|
69
|
+
const text2 = 'aBcDeFgHiJkLmNoPqRsT#xYz123456789abcde'
|
|
70
|
+
const d2 = detectSecrets(text2)
|
|
71
|
+
// "xYz123456789abcde" is 18 chars — below minimum
|
|
72
|
+
expect(d2.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('flows through runPipeline and stores the code in the vault', () => {
|
|
76
|
+
const store = new Map<string, string>()
|
|
77
|
+
const write: VaultWriteFn = (slug, value) => { store.set(slug, value); return { ok: true, output: 'ok' } }
|
|
78
|
+
const list: VaultListFn = () => ({ ok: true, keys: [...store.keys()] })
|
|
79
|
+
|
|
80
|
+
const code = 'tle0rmXYZabc123defGHIjkl#00EySjXYZabc123defGHIjklmno'
|
|
81
|
+
const res = runPipeline({
|
|
82
|
+
chat_id: 'test-chat',
|
|
83
|
+
message_id: 1,
|
|
84
|
+
text: code,
|
|
85
|
+
passphrase: 'pw',
|
|
86
|
+
vaultWrite: write,
|
|
87
|
+
vaultList: list,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(res.stored).toHaveLength(1)
|
|
91
|
+
expect(res.stored[0]!.detection.rule_id).toBe('anthropic_oauth_code')
|
|
92
|
+
expect(res.rewritten_text).not.toContain(code)
|
|
93
|
+
expect(res.rewritten_text).toContain('[secret stored as vault:')
|
|
94
|
+
expect([...store.values()]).toContain(code)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// ─── Channel B — context rule structural tests ────────────────────────────────
|
|
99
|
+
|
|
100
|
+
describe('auth-flow context rule — Channel B (structural wiring in gateway.ts)', () => {
|
|
101
|
+
const src = readFileSync(
|
|
102
|
+
new URL('../gateway/gateway.ts', import.meta.url),
|
|
103
|
+
'utf8',
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
it('declares awaitingAuthCodeAt map and AUTH_CODE_CONTEXT_TTL_MS constant', () => {
|
|
107
|
+
expect(src).toMatch(/const awaitingAuthCodeAt = new Map<string, number>/)
|
|
108
|
+
expect(src).toMatch(/AUTH_CODE_CONTEXT_TTL_MS\s*=\s*5\s*\*\s*60_000/)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('sets awaitingAuthCodeAt near the "Paste the browser code here" prompt (before or after)', () => {
|
|
112
|
+
// awaitingAuthCodeAt.set is armed before the ForceReply attempt so that a
|
|
113
|
+
// switchroomReply throw doesn't leave Channel B unarmed. Verify it lives
|
|
114
|
+
// within 500 chars of the prompt string in either direction.
|
|
115
|
+
const promptIdx = src.indexOf("'📋 Paste the browser code here ↓'")
|
|
116
|
+
expect(promptIdx).toBeGreaterThan(0)
|
|
117
|
+
const start = Math.max(0, promptIdx - 500)
|
|
118
|
+
const window = src.slice(start, promptIdx + 500)
|
|
119
|
+
expect(window).toMatch(/awaitingAuthCodeAt\.set\(/)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('clears awaitingAuthCodeAt (delete) in the inbound handler when the flag is active', () => {
|
|
123
|
+
// The inbound handler must call delete after reading the flag
|
|
124
|
+
expect(src).toMatch(/awaitingAuthCodeAt\.delete\(chat_id\)/)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('checks isAuthFlowContext in the secret-detect block (passphrase path)', () => {
|
|
128
|
+
const pipelineIdx = src.indexOf('runPipeline({')
|
|
129
|
+
expect(pipelineIdx).toBeGreaterThan(0)
|
|
130
|
+
// After the pipeline call, isAuthFlowContext must gate the fallback branch
|
|
131
|
+
const tail = src.slice(pipelineIdx, pipelineIdx + 2000)
|
|
132
|
+
expect(tail).toMatch(/isAuthFlowContext/)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('checks isAuthFlowContext in the no-passphrase path (deferred branch)', () => {
|
|
136
|
+
// The no-passphrase branch must also check isAuthFlowContext so a context
|
|
137
|
+
// hit is deferred even without a cached passphrase. (Window widened in
|
|
138
|
+
// #44's PR — the comment block above the branch grew when the legacy
|
|
139
|
+
// "/vault list + re-paste" UX got replaced with the inline-button
|
|
140
|
+
// flow; the structural invariant we're pinning is unchanged.)
|
|
141
|
+
const noPpIdx = src.indexOf('No passphrase cached — detect, but defer')
|
|
142
|
+
expect(noPpIdx).toBeGreaterThan(0)
|
|
143
|
+
const window = src.slice(noPpIdx, noPpIdx + 1200)
|
|
144
|
+
expect(window).toMatch(/isAuthFlowContext/)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('reaps awaitingAuthCodeAt in the TTL reaper alongside other pending-state maps', () => {
|
|
148
|
+
// The pendingStateReaper interval must sweep expired auth-code-context entries
|
|
149
|
+
const reaperIdx = src.indexOf('pendingStateReaper = setInterval')
|
|
150
|
+
expect(reaperIdx).toBeGreaterThan(0)
|
|
151
|
+
const reaperBlock = src.slice(reaperIdx, reaperIdx + 800)
|
|
152
|
+
expect(reaperBlock).toMatch(/awaitingAuthCodeAt/)
|
|
153
|
+
expect(reaperBlock).toMatch(/AUTH_CODE_CONTEXT_TTL_MS/)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('auth-flow context rule sits BEFORE recordInbound() and broadcast()', () => {
|
|
157
|
+
const contextIdx = src.indexOf('isAuthFlowContext')
|
|
158
|
+
const recordIdx = src.indexOf('recordInbound(', contextIdx)
|
|
159
|
+
const broadcastIdx = src.indexOf('ipcServer.broadcast(inboundMsg)', contextIdx)
|
|
160
|
+
expect(contextIdx).toBeGreaterThan(0)
|
|
161
|
+
expect(recordIdx).toBeGreaterThan(contextIdx)
|
|
162
|
+
expect(broadcastIdx).toBeGreaterThan(contextIdx)
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// ─── New tests for reviewer-requested coverage ────────────────────────────────
|
|
167
|
+
|
|
168
|
+
// Test 1: Negative URL test — Channel A regex must NOT match real URLs with
|
|
169
|
+
// long path segments + long fragment anchors (Blocker 2 regression test).
|
|
170
|
+
describe('anthropic_oauth_code pattern — URL false-positive regression', () => {
|
|
171
|
+
it('does NOT match a URL with long path segment and long fragment (inline in sentence)', () => {
|
|
172
|
+
const text = 'see https://docs.com/getting-started-tutorial#installation-and-setup-guide for details'
|
|
173
|
+
const d = detectSecrets(text)
|
|
174
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('does NOT match a markdown link with long path + long anchor', () => {
|
|
178
|
+
const text = '[docs](https://docs.com/getting-started-tutorial#installation-and-setup-guide)'
|
|
179
|
+
const d = detectSecrets(text)
|
|
180
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('does NOT match a GitHub permalink URL (long path + long heading anchor)', () => {
|
|
184
|
+
const text = 'See https://github.com/owner/repo/blob/main/README.md#installation-and-setup-guide for info.'
|
|
185
|
+
const d = detectSecrets(text)
|
|
186
|
+
expect(d.some((h) => h.rule_id === 'anthropic_oauth_code')).toBe(false)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Test 2: Blocker 1 sequencing — structural check that deleteMessage is called
|
|
191
|
+
// inside the pendingReauthFlows intercept path, before setMessageReaction.
|
|
192
|
+
describe('pendingReauthFlows intercept — deleteMessage sequencing (Blocker 1)', () => {
|
|
193
|
+
const src = readFileSync(
|
|
194
|
+
new URL('../gateway/gateway.ts', import.meta.url),
|
|
195
|
+
'utf8',
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
it('redacts the OAuth code message inside the pendingReauthFlows intercept', () => {
|
|
199
|
+
// Locate the pendingReauthFlows intercept block. Was previously
|
|
200
|
+
// checked by greppinng for `bot.api.deleteMessage(...)` and
|
|
201
|
+
// `setMessageReaction(...)` literals, but #488 consolidated all 6
|
|
202
|
+
// auth-code paste paths through `redactAuthCodeMessage`. The pin
|
|
203
|
+
// is now: the intercept block must call the helper.
|
|
204
|
+
const interceptIdx = src.indexOf('// Auth-code intercept')
|
|
205
|
+
expect(interceptIdx).toBeGreaterThan(0)
|
|
206
|
+
|
|
207
|
+
// Find the end of this intercept block — the next blank line after
|
|
208
|
+
// the redaction call. Bounds the window so we don't accidentally
|
|
209
|
+
// match a downstream auth-code path's redaction.
|
|
210
|
+
const window = src.slice(interceptIdx, interceptIdx + 2000)
|
|
211
|
+
// Allow the optional 4th `log` argument added in #561 (diagnostic
|
|
212
|
+
// sink for redaction failures) — required is the first three args.
|
|
213
|
+
// `bot.api` may be cast (e.g. `bot.api as never`) for the local
|
|
214
|
+
// BotApi-vs-grammy-Api type mismatch cleanup in #623; `msgId` may
|
|
215
|
+
// be narrowed (`msgId ?? null`).
|
|
216
|
+
expect(window).toMatch(/redactAuthCodeMessage\(bot\.api(?:\s+as\s+\w+)?,\s*chat_id,\s*msgId(?:\s*\?\?\s*null)?(?:,\s*[^)]+)?\)/)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('redaction lands AFTER the success/error reply renders', () => {
|
|
220
|
+
// Sequencing pin: the user-visible reply (success or error) must
|
|
221
|
+
// be queued before the redaction so the user sees the auth result
|
|
222
|
+
// even if their original message disappears mid-render. Same
|
|
223
|
+
// ordering the helper preserves — fire-and-forget redaction
|
|
224
|
+
// happens after `await switchroomReply(...)`.
|
|
225
|
+
const interceptIdx = src.indexOf('// Auth-code intercept')
|
|
226
|
+
const window = src.slice(interceptIdx, interceptIdx + 2000)
|
|
227
|
+
const replyIdx = window.indexOf('switchroomReply(ctx,')
|
|
228
|
+
const redactIdx = window.indexOf('redactAuthCodeMessage(')
|
|
229
|
+
expect(replyIdx).toBeGreaterThan(0)
|
|
230
|
+
expect(redactIdx).toBeGreaterThan(0)
|
|
231
|
+
expect(replyIdx).toBeLessThan(redactIdx)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// Test 3: Flag consumption — awaitingAuthCodeAt is NOT cleared on a non-detection
|
|
236
|
+
// inbound (a stray "ok" within the 5-min window should not disarm Channel B).
|
|
237
|
+
describe('awaitingAuthCodeAt flag — consumption only on actual detection', () => {
|
|
238
|
+
const src = readFileSync(
|
|
239
|
+
new URL('../gateway/gateway.ts', import.meta.url),
|
|
240
|
+
'utf8',
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
it('delete(chat_id) does NOT appear at the top of the isAuthFlowContext block (no early consume)', () => {
|
|
244
|
+
// The old bug: delete fired unconditionally inside `if (isAuthFlowContext)`.
|
|
245
|
+
// After the fix, there must be no `awaitingAuthCodeAt.delete` within
|
|
246
|
+
// the isAuthFlowContext guard block itself — only in the downstream branches.
|
|
247
|
+
const contextIdx = src.indexOf('const isAuthFlowContext =')
|
|
248
|
+
expect(contextIdx).toBeGreaterThan(0)
|
|
249
|
+
// Grab the isAuthFlowContext if-block (ends at the next blank line after the log line)
|
|
250
|
+
const logLineEnd = src.indexOf('\n', src.indexOf('[secret-detect] auth-flow context rule active', contextIdx))
|
|
251
|
+
const guardBlock = src.slice(contextIdx, logLineEnd + 5)
|
|
252
|
+
// Must NOT contain a delete call in the guard itself
|
|
253
|
+
expect(guardBlock).not.toMatch(/awaitingAuthCodeAt\.delete/)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('awaitingAuthCodeAt.delete appears inside the Channel B fallback branch (pipeRes.stored === 0)', () => {
|
|
257
|
+
// Verify the consume is co-located with the actual auth-flow handling
|
|
258
|
+
const fallbackIdx = src.indexOf('Channel B fallback: pattern didn\'t fire')
|
|
259
|
+
expect(fallbackIdx).toBeGreaterThan(0)
|
|
260
|
+
// Within 400 chars after the comment, delete must appear
|
|
261
|
+
const window = src.slice(fallbackIdx, fallbackIdx + 400)
|
|
262
|
+
expect(window).toMatch(/awaitingAuthCodeAt\.delete\(chat_id\)/)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('awaitingAuthCodeAt.delete appears inside the no-passphrase hasHigh branch', () => {
|
|
266
|
+
const noPpIdx = src.indexOf('No passphrase cached — detect, but defer')
|
|
267
|
+
expect(noPpIdx).toBeGreaterThan(0)
|
|
268
|
+
// Window widened — see the matching note on the
|
|
269
|
+
// "checks isAuthFlowContext in the no-passphrase path" test.
|
|
270
|
+
const window = src.slice(noPpIdx, noPpIdx + 1200)
|
|
271
|
+
// Conditional consume: only fires if isAuthFlowContext
|
|
272
|
+
expect(window).toMatch(/if \(isAuthFlowContext\)/)
|
|
273
|
+
expect(window).toMatch(/awaitingAuthCodeAt\.delete\(chat_id\)/)
|
|
274
|
+
})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Test 4: Armer-outside-catch — awaitingAuthCodeAt.set is before the try block,
|
|
278
|
+
// so if switchroomReply throws, Channel B is still armed.
|
|
279
|
+
describe('awaitingAuthCodeAt.set — arming is unconditional (outside catch)', () => {
|
|
280
|
+
const src = readFileSync(
|
|
281
|
+
new URL('../gateway/gateway.ts', import.meta.url),
|
|
282
|
+
'utf8',
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
it('awaitingAuthCodeAt.set appears BEFORE switchroomReply inside the formatted.url block', () => {
|
|
286
|
+
const urlBlockIdx = src.indexOf('if (formatted.url)')
|
|
287
|
+
expect(urlBlockIdx).toBeGreaterThan(0)
|
|
288
|
+
const urlBlock = src.slice(urlBlockIdx, urlBlockIdx + 800)
|
|
289
|
+
const setIdx = urlBlock.indexOf('awaitingAuthCodeAt.set(')
|
|
290
|
+
const replyIdx = urlBlock.indexOf("'📋 Paste the browser code here ↓'")
|
|
291
|
+
expect(setIdx).toBeGreaterThan(0)
|
|
292
|
+
expect(replyIdx).toBeGreaterThan(0)
|
|
293
|
+
// The .set must come BEFORE the ForceReply switchroomReply call
|
|
294
|
+
expect(setIdx).toBeLessThan(replyIdx)
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('awaitingAuthCodeAt.set is NOT inside the inner try block', () => {
|
|
298
|
+
// The inner try starts with "await switchroomReply(ctx, '📋 Paste..."
|
|
299
|
+
// The .set should appear before that try keyword in the formatted.url block
|
|
300
|
+
const urlBlockIdx = src.indexOf('if (formatted.url)')
|
|
301
|
+
const urlBlock = src.slice(urlBlockIdx, urlBlockIdx + 800)
|
|
302
|
+
const setIdx = urlBlock.indexOf('awaitingAuthCodeAt.set(')
|
|
303
|
+
// Find the try block that wraps the ForceReply call
|
|
304
|
+
const innerTryIdx = urlBlock.indexOf('try {', setIdx)
|
|
305
|
+
// The inner try (ForceReply try block) must come AFTER the .set call
|
|
306
|
+
expect(innerTryIdx).toBeGreaterThan(setIdx)
|
|
307
|
+
})
|
|
308
|
+
})
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { runPipeline } from '../secret-detect/pipeline.js'
|
|
3
|
+
import { setAuditSink } from '../secret-detect/audit.js'
|
|
4
|
+
import type { VaultWriteFn, VaultListFn } from '../secret-detect/vault-write.js'
|
|
5
|
+
|
|
6
|
+
function mkFakeVault(): {
|
|
7
|
+
write: VaultWriteFn
|
|
8
|
+
list: VaultListFn
|
|
9
|
+
store: Map<string, string>
|
|
10
|
+
} {
|
|
11
|
+
const store = new Map<string, string>()
|
|
12
|
+
const write: VaultWriteFn = (slug, value) => {
|
|
13
|
+
store.set(slug, value)
|
|
14
|
+
return { ok: true, output: 'ok' }
|
|
15
|
+
}
|
|
16
|
+
const list: VaultListFn = () => ({ ok: true, keys: [...store.keys()] })
|
|
17
|
+
return { write, list, store }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('pipeline.runPipeline', () => {
|
|
21
|
+
const captured: string[] = []
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
captured.length = 0
|
|
24
|
+
setAuditSink((line) => captured.push(line))
|
|
25
|
+
})
|
|
26
|
+
afterEach(() => setAuditSink(null))
|
|
27
|
+
|
|
28
|
+
it('stores a high-confidence hit and rewrites the prompt', () => {
|
|
29
|
+
const { write, list, store } = mkFakeVault()
|
|
30
|
+
const text = 'hey here is my key: sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22 thanks'
|
|
31
|
+
const res = runPipeline({
|
|
32
|
+
chat_id: '-100',
|
|
33
|
+
message_id: 5,
|
|
34
|
+
text,
|
|
35
|
+
passphrase: 'pw',
|
|
36
|
+
vaultWrite: write,
|
|
37
|
+
vaultList: list,
|
|
38
|
+
})
|
|
39
|
+
expect(res.stored).toHaveLength(1)
|
|
40
|
+
expect(res.rewritten_text).toContain('[secret stored as vault:')
|
|
41
|
+
expect(res.rewritten_text).not.toContain('sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22')
|
|
42
|
+
// The raw secret made it to the vault under the generated slug.
|
|
43
|
+
expect([...store.values()]).toContain('sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22')
|
|
44
|
+
// Audit emitted once with action=stored.
|
|
45
|
+
const storedLogs = captured.filter((l) => l.includes('"action":"stored"'))
|
|
46
|
+
expect(storedLogs).toHaveLength(1)
|
|
47
|
+
// Raw value never in logs.
|
|
48
|
+
expect(captured.some((l) => l.includes('Apq13yqRnPzx4MxK0TfAbY98Qw22'))).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('stages ambiguous hits without writing to the vault', () => {
|
|
52
|
+
const { write, list, store } = mkFakeVault()
|
|
53
|
+
const text = 'my_password=B4k9NzQ1mT5vR8wP2xY7jH3fL6cD0sA'
|
|
54
|
+
const res = runPipeline({
|
|
55
|
+
chat_id: '-100',
|
|
56
|
+
message_id: 6,
|
|
57
|
+
text,
|
|
58
|
+
passphrase: 'pw',
|
|
59
|
+
vaultWrite: write,
|
|
60
|
+
vaultList: list,
|
|
61
|
+
})
|
|
62
|
+
expect(res.stored).toHaveLength(0)
|
|
63
|
+
expect(res.ambiguous.length).toBeGreaterThan(0)
|
|
64
|
+
// Nothing stored.
|
|
65
|
+
expect(store.size).toBe(0)
|
|
66
|
+
// Text unchanged.
|
|
67
|
+
expect(res.rewritten_text).toBe(text)
|
|
68
|
+
// Audit action=ambiguous.
|
|
69
|
+
expect(captured.some((l) => l.includes('"action":"ambiguous"'))).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('treats suppressed high-confidence hits as ambiguous', () => {
|
|
73
|
+
const { write, list, store } = mkFakeVault()
|
|
74
|
+
const text = 'test sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22'
|
|
75
|
+
const res = runPipeline({
|
|
76
|
+
chat_id: 'c',
|
|
77
|
+
message_id: 1,
|
|
78
|
+
text,
|
|
79
|
+
passphrase: 'pw',
|
|
80
|
+
vaultWrite: write,
|
|
81
|
+
vaultList: list,
|
|
82
|
+
})
|
|
83
|
+
expect(res.stored).toHaveLength(0)
|
|
84
|
+
expect(res.ambiguous.length).toBeGreaterThan(0)
|
|
85
|
+
expect(store.size).toBe(0)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('avoids slug collisions when writing multiple hits', () => {
|
|
89
|
+
const { write, list, store } = mkFakeVault()
|
|
90
|
+
store.set('anthropic_api_key_20260423', 'preexisting')
|
|
91
|
+
const text =
|
|
92
|
+
'first: sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22 second: sk-ant-BqZ13yqRnPzx4MxK0TfAbY98Qw22'
|
|
93
|
+
const res = runPipeline({
|
|
94
|
+
chat_id: 'c',
|
|
95
|
+
message_id: 2,
|
|
96
|
+
text,
|
|
97
|
+
passphrase: 'pw',
|
|
98
|
+
vaultWrite: write,
|
|
99
|
+
vaultList: list,
|
|
100
|
+
})
|
|
101
|
+
expect(res.stored).toHaveLength(2)
|
|
102
|
+
const slugs = res.stored.map((s) => s.actual_slug)
|
|
103
|
+
// All unique and none collide with the preexisting key.
|
|
104
|
+
expect(new Set(slugs).size).toBe(slugs.length)
|
|
105
|
+
expect(slugs).not.toContain('anthropic_api_key_20260423')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('reports failures without crashing', () => {
|
|
109
|
+
const failingWrite: VaultWriteFn = () => ({ ok: false, output: 'vault busy' })
|
|
110
|
+
const list: VaultListFn = () => ({ ok: true, keys: [] })
|
|
111
|
+
const res = runPipeline({
|
|
112
|
+
chat_id: 'c',
|
|
113
|
+
message_id: 3,
|
|
114
|
+
text: 'key is sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
115
|
+
passphrase: 'pw',
|
|
116
|
+
vaultWrite: failingWrite,
|
|
117
|
+
vaultList: list,
|
|
118
|
+
})
|
|
119
|
+
expect(res.stored).toHaveLength(0)
|
|
120
|
+
expect(res.failed).toHaveLength(1)
|
|
121
|
+
expect(captured.some((l) => l.includes('"action":"failed"'))).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
detectViaSecretlint,
|
|
4
|
+
detectSecretsAsync,
|
|
5
|
+
} from '../secret-detect/index.js'
|
|
6
|
+
|
|
7
|
+
// All three fixtures are assembled at runtime so the source file never
|
|
8
|
+
// contains a contiguous token pattern. Matches the corresponding
|
|
9
|
+
// secretlint regex when evaluated; evades GitHub Push Protection's
|
|
10
|
+
// static-text scan. See CLAUDE.md "Secrets in tests".
|
|
11
|
+
const SLACK_FIXTURE = ['xoxb', '0000000000', '0000000000000', 'FIXTURE0NOTAREALTOKEN000'].join('-')
|
|
12
|
+
const GITHUB_FIXTURE = 'ghp' + '_' + '16C7e42F292c6912E7710c838347Ae178B4a'
|
|
13
|
+
const NPM_FIXTURE = 'npm' + '_' + 'AbCdEfGhIjKlMnOpQrStUvWxYz0123456789'
|
|
14
|
+
|
|
15
|
+
describe('secretlint-source.detectViaSecretlint', () => {
|
|
16
|
+
it('catches a realistic-looking Slack bot token', async () => {
|
|
17
|
+
const text = 'Slack: ' + SLACK_FIXTURE
|
|
18
|
+
const hits = await detectViaSecretlint(text)
|
|
19
|
+
expect(hits.length).toBeGreaterThan(0)
|
|
20
|
+
const slack = hits.find((h) => h.rule_id.includes('slack'))
|
|
21
|
+
expect(slack).toBeDefined()
|
|
22
|
+
expect(slack!.confidence).toBe('high')
|
|
23
|
+
expect(slack!.suppressed).toBe(false)
|
|
24
|
+
// rule_id normalized from @secretlint/secretlint-rule-slack → secretlint_slack
|
|
25
|
+
expect(slack!.rule_id).toMatch(/^secretlint_/)
|
|
26
|
+
expect(slack!.rule_id).toContain('slack')
|
|
27
|
+
// Matched text should be the actual token bytes.
|
|
28
|
+
expect(slack!.matched_text.startsWith('xoxb-')).toBe(true)
|
|
29
|
+
// Slug derived from rule_id + date (rule_id fallback path).
|
|
30
|
+
expect(slack!.suggested_slug).toMatch(/^secretlint_slack_\d{8}/)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('catches a GitHub personal access token', async () => {
|
|
34
|
+
const text = 'token=' + GITHUB_FIXTURE + ' rest of message'
|
|
35
|
+
const hits = await detectViaSecretlint(text)
|
|
36
|
+
const gh = hits.find((h) => h.rule_id.includes('github'))
|
|
37
|
+
expect(gh).toBeDefined()
|
|
38
|
+
expect(gh!.confidence).toBe('high')
|
|
39
|
+
expect(gh!.matched_text.startsWith('ghp_')).toBe(true)
|
|
40
|
+
expect(gh!.rule_id).toContain('github')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('catches an NPM access token', async () => {
|
|
44
|
+
const text = 'NPM_TOKEN=' + NPM_FIXTURE
|
|
45
|
+
const hits = await detectViaSecretlint(text)
|
|
46
|
+
const npm = hits.find((h) => h.rule_id.includes('npm'))
|
|
47
|
+
expect(npm).toBeDefined()
|
|
48
|
+
expect(npm!.confidence).toBe('high')
|
|
49
|
+
expect(npm!.matched_text.startsWith('npm_')).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('returns empty for empty input', async () => {
|
|
53
|
+
expect(await detectViaSecretlint('')).toEqual([])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('returns empty for text with no secrets', async () => {
|
|
57
|
+
expect(await detectViaSecretlint('hello how are you today')).toEqual([])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('marks nearby test/mock markers as suppressed', async () => {
|
|
61
|
+
const text = 'test example: ' + SLACK_FIXTURE
|
|
62
|
+
const hits = await detectViaSecretlint(text)
|
|
63
|
+
const slack = hits.find((h) => h.rule_id.includes('slack'))
|
|
64
|
+
expect(slack).toBeDefined()
|
|
65
|
+
expect(slack!.suppressed).toBe(true)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('detectSecretsAsync merge', () => {
|
|
70
|
+
it('merges Secretlint hits with vendored pattern hits, deduped by range', async () => {
|
|
71
|
+
// Slack token that matches both the vendored anchored pattern AND Secretlint.
|
|
72
|
+
const text = 'a ' + SLACK_FIXTURE + ' end'
|
|
73
|
+
const hits = await detectSecretsAsync(text)
|
|
74
|
+
// One entry for the Slack token — not two. Vendored wins on ties.
|
|
75
|
+
const slackHits = hits.filter(
|
|
76
|
+
(h) => h.matched_text.startsWith('xoxb-'),
|
|
77
|
+
)
|
|
78
|
+
expect(slackHits).toHaveLength(1)
|
|
79
|
+
// Vendored rule id wins on exact-range ties (listed first in merge).
|
|
80
|
+
expect(slackHits[0]!.rule_id).toBe('slack_token')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('adds Secretlint-only hits for providers the vendored list misses', async () => {
|
|
84
|
+
// Shopify is covered by Secretlint preset-recommend but not by our
|
|
85
|
+
// vendored ANCHORED_PATTERNS.
|
|
86
|
+
const text = 'SHOPIFY=shpss_1234567890abcdef1234567890abcdef and go'
|
|
87
|
+
const hits = await detectSecretsAsync(text)
|
|
88
|
+
const shopify = hits.find((h) => h.matched_text.startsWith('shpss_'))
|
|
89
|
+
expect(shopify).toBeDefined()
|
|
90
|
+
expect(shopify!.rule_id).toMatch(/secretlint_shopify/)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('produces unique slugs across the merged detection list', async () => {
|
|
94
|
+
const text =
|
|
95
|
+
'tok1=' + GITHUB_FIXTURE +
|
|
96
|
+
' and tok2=' + SLACK_FIXTURE
|
|
97
|
+
const hits = await detectSecretsAsync(text)
|
|
98
|
+
const slugs = hits.map((h) => h.suggested_slug)
|
|
99
|
+
expect(new Set(slugs).size).toBe(slugs.length)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { StagingMap } from '../secret-detect/staging.js'
|
|
3
|
+
import type { Detection } from '../secret-detect/index.js'
|
|
4
|
+
|
|
5
|
+
function mkDetection(slug = 'FOO'): Detection {
|
|
6
|
+
return {
|
|
7
|
+
rule_id: 'kv_entropy',
|
|
8
|
+
matched_text: 'abc',
|
|
9
|
+
start: 0,
|
|
10
|
+
end: 3,
|
|
11
|
+
confidence: 'ambiguous',
|
|
12
|
+
suppressed: false,
|
|
13
|
+
suggested_slug: slug,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('StagingMap', () => {
|
|
18
|
+
it('round-trips a set/get', () => {
|
|
19
|
+
const map = new StagingMap(5_000)
|
|
20
|
+
map.set({ chat_id: 'c', message_id: 1, detection: mkDetection(), staged_at: Date.now() })
|
|
21
|
+
const found = map.get('c', 1)
|
|
22
|
+
expect(found).toBeDefined()
|
|
23
|
+
expect(found!.detection.suggested_slug).toBe('FOO')
|
|
24
|
+
})
|
|
25
|
+
it('expires entries past the TTL', () => {
|
|
26
|
+
const map = new StagingMap(10)
|
|
27
|
+
map.set({ chat_id: 'c', message_id: 1, detection: mkDetection(), staged_at: Date.now() - 1_000 })
|
|
28
|
+
expect(map.get('c', 1)).toBeUndefined()
|
|
29
|
+
})
|
|
30
|
+
it('latestForChat returns the newest non-expired entry', () => {
|
|
31
|
+
const map = new StagingMap(60_000)
|
|
32
|
+
const now = Date.now()
|
|
33
|
+
map.set({ chat_id: 'c', message_id: 1, detection: mkDetection('A'), staged_at: now - 3000 })
|
|
34
|
+
map.set({ chat_id: 'c', message_id: 2, detection: mkDetection('B'), staged_at: now - 2000 })
|
|
35
|
+
map.set({ chat_id: 'c', message_id: 3, detection: mkDetection('C'), staged_at: now - 1000 })
|
|
36
|
+
const latest = map.latestForChat('c')
|
|
37
|
+
expect(latest?.detection.suggested_slug).toBe('C')
|
|
38
|
+
})
|
|
39
|
+
it('delete removes the entry', () => {
|
|
40
|
+
const map = new StagingMap(5_000)
|
|
41
|
+
map.set({ chat_id: 'c', message_id: 1, detection: mkDetection(), staged_at: Date.now() })
|
|
42
|
+
expect(map.delete('c', 1)).toBe(true)
|
|
43
|
+
expect(map.get('c', 1)).toBeUndefined()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { detectSecrets } from '../secret-detect/index.js'
|
|
3
|
+
import { runPipeline } from '../secret-detect/pipeline.js'
|
|
4
|
+
import type { VaultWriteFn, VaultListFn } from '../secret-detect/vault-write.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Regression: a suppressor hit (test/mock/example/dummy/fixture nearby)
|
|
8
|
+
* must NEVER silent-allow a structured-pattern match. The asymmetric
|
|
9
|
+
* risk model is: a false-negative leaks a real credential into the
|
|
10
|
+
* agent's session log, a false-positive just briefly redacts a fake
|
|
11
|
+
* one and the user can `ignore` / `forget` to dismiss.
|
|
12
|
+
*
|
|
13
|
+
* The contract:
|
|
14
|
+
* 1. detectSecrets() returns the detection with `suppressed: true`,
|
|
15
|
+
* not an empty list.
|
|
16
|
+
* 2. runPipeline() routes suppressed-high hits into the `ambiguous`
|
|
17
|
+
* bucket (NOT `stored`, NOT silently dropped).
|
|
18
|
+
*
|
|
19
|
+
* Both layers are exercised here so future refactors can't regress
|
|
20
|
+
* this without breaking a test.
|
|
21
|
+
*/
|
|
22
|
+
describe('suppressor: never silent-allows on structured matches', () => {
|
|
23
|
+
const phrasings = [
|
|
24
|
+
'this is a test, here is sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
25
|
+
'mock token: sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
26
|
+
'example: sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
27
|
+
'dummy sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
28
|
+
'fixture sk-ant-Apq13yqRnPzx4MxK0TfAbY98Qw22',
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
for (const text of phrasings) {
|
|
32
|
+
it(`detectSecrets surfaces suppressed=true for: ${text.slice(0, 32)}…`, () => {
|
|
33
|
+
const detections = detectSecrets(text)
|
|
34
|
+
expect(detections.length).toBeGreaterThan(0)
|
|
35
|
+
const hit = detections.find((d) => d.rule_id === 'anthropic_api_key')
|
|
36
|
+
expect(hit).toBeDefined()
|
|
37
|
+
expect(hit!.suppressed).toBe(true)
|
|
38
|
+
// confidence is still 'high' — suppressed is the orthogonal flag
|
|
39
|
+
// that downgrades it into the ambiguous tier.
|
|
40
|
+
expect(hit!.confidence).toBe('high')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it(`runPipeline routes the suppressed hit to ambiguous (not stored, not dropped) for: ${text.slice(0, 32)}…`, () => {
|
|
44
|
+
const store = new Map<string, string>()
|
|
45
|
+
const write: VaultWriteFn = (slug, value) => {
|
|
46
|
+
store.set(slug, value)
|
|
47
|
+
return { ok: true, output: 'ok' }
|
|
48
|
+
}
|
|
49
|
+
const list: VaultListFn = () => ({ ok: true, keys: [...store.keys()] })
|
|
50
|
+
const res = runPipeline({
|
|
51
|
+
chat_id: 'c',
|
|
52
|
+
message_id: 1,
|
|
53
|
+
text,
|
|
54
|
+
passphrase: 'pw',
|
|
55
|
+
vaultWrite: write,
|
|
56
|
+
vaultList: list,
|
|
57
|
+
})
|
|
58
|
+
// Critical: suppressed hit must NOT silent-pass.
|
|
59
|
+
expect(res.ambiguous.length).toBeGreaterThan(0)
|
|
60
|
+
// Critical: suppressed hit must NOT auto-store.
|
|
61
|
+
expect(res.stored).toHaveLength(0)
|
|
62
|
+
expect(store.size).toBe(0)
|
|
63
|
+
// Text unchanged — the user gets to decide.
|
|
64
|
+
expect(res.rewritten_text).toBe(text)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
})
|