codymaster 7.0.1 → 7.0.3
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/CHANGELOG.md +64 -0
- package/README.md +117 -2
- package/dist/agent/codex.js +73 -21
- package/dist/agent-dispatch.js +63 -48
- package/dist/cli/commands/brain.js +18 -0
- package/dist/cli/commands/design-studio.js +1 -1
- package/dist/cm-suggest.js +3 -3
- package/dist/dashboard-project-summary.js +9 -0
- package/dist/dashboard.js +11 -5
- package/dist/execution-analyzer.js +9 -1
- package/dist/judge.js +16 -15
- package/dist/mcp-context-server.js +45 -23
- package/dist/mcp-skills-tools.js +2 -2
- package/dist/skill-chain.js +26 -3
- package/dist/skill-token-report.js +105 -0
- package/dist/sprint-pipeline.js +3 -3
- package/dist/ui/onboarding.js +3 -4
- package/dist/utils/design-taste.js +1 -1
- package/dist/utils/output-compress.js +8 -0
- package/package.json +3 -2
- package/public/dashboard/app.js +40 -13
- package/public/dashboard/index.html +190 -5
- package/public/dashboard/style.css +1 -1
- package/scripts/build-skills.mjs +36 -2
- package/scripts/mcp-bridge.js +41 -24
- package/scripts/pack-plugin.mjs +206 -0
- package/skills/cm-ads-tracker/SKILL.md +401 -0
- package/skills/cm-ads-tracker/evals/evals.json +55 -0
- package/skills/cm-ads-tracker/references/gtm-architecture.md +321 -0
- package/skills/cm-ads-tracker/references/industry-events.md +294 -0
- package/skills/cm-ads-tracker/references/platforms-api.md +238 -0
- package/skills/cm-ads-tracker/templates/capi-payload.md +79 -0
- package/skills/cm-ads-tracker/templates/datalayer-push.js +104 -0
- package/skills/cm-ads-tracker/templates/gtm-variables.js +56 -0
- package/skills/cm-auto-publisher/SKILL.md +81 -0
- package/skills/cm-booking-calendar/SKILL.md +521 -0
- package/skills/cm-booking-calendar/references/industry-patterns.md +527 -0
- package/skills/cm-booking-calendar/templates/booking-form.css +626 -0
- package/skills/cm-booking-calendar/templates/booking-form.html +477 -0
- package/skills/cm-booking-calendar/templates/calendar-engine.js +419 -0
- package/skills/cm-booking-calendar/templates/calendar-export.js +395 -0
- package/skills/cm-booking-calendar/templates/reminder-config.js +629 -0
- package/skills/cm-brainstorm-idea/SKILL.md +5 -5
- package/skills/cm-code-review/SKILL.md +2 -2
- package/skills/cm-codeintell/SKILL.md +47 -580
- package/skills/cm-codeintell/references/integration-workflows.md +23 -0
- package/skills/cm-codeintell/references/layer-0-skeleton.md +54 -0
- package/skills/cm-codeintell/references/layer-1-codegraph.md +58 -0
- package/skills/cm-codeintell/references/layer-2-architecture.md +31 -0
- package/skills/cm-codeintell/references/layer-3-context-builder.md +32 -0
- package/skills/cm-content-factory/.content-factory-state.json +132 -0
- package/skills/cm-content-factory/.git 2/logs/refs/heads/main +1 -0
- package/skills/cm-content-factory/.git 2/logs/refs/remotes/origin/main +1 -0
- package/skills/cm-content-factory/.git 2/objects/02/fb0956734b5f8ba3f918b7defd04a89cfe0076 +0 -0
- package/skills/cm-content-factory/.git 2/objects/08/1e129d75dc6feac6c02037272e6bd1a04e3324 +0 -0
- package/skills/cm-content-factory/.git 2/objects/0c/5393416f3c5e01c9a655a802bff0dd52f76f0a +0 -0
- package/skills/cm-content-factory/.git 2/objects/10/0b9be46978a946a77188f68be725098a122001 +0 -0
- package/skills/cm-content-factory/.git 2/objects/10/cf041167fc9843610eb3d90259ef3396315fdc +0 -0
- package/skills/cm-content-factory/.git 2/objects/12/5e19538dd6e1338ffe74f6c4c165b00435bf48 +0 -0
- package/skills/cm-content-factory/.git 2/objects/16/a9b9d0088d5c1347628b45a2620b479d8ad57c +0 -0
- package/skills/cm-content-factory/.git 2/objects/17/8c2a9ef93c33ae4eec9d58e82321f9229843a1 +0 -0
- package/skills/cm-content-factory/.git 2/objects/25/397ae41d09104d763bdcac2695209d85cdea89 +0 -0
- package/skills/cm-content-factory/.git 2/objects/2f/a836b7947f2d458e1f639788bf4bb0983a3305 +0 -0
- package/skills/cm-content-factory/.git 2/objects/3a/baaaf0a1c0909c0828335791557125fba911e0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/42/2924221b81f5ce3c4e4daac9a64a24f9b01f9a +0 -0
- package/skills/cm-content-factory/.git 2/objects/42/ec0ce707447dc11446a34c9995fb8533801731 +0 -0
- package/skills/cm-content-factory/.git 2/objects/46/e43ce92866d56ce74b1d750db307cfe6154a15 +0 -0
- package/skills/cm-content-factory/.git 2/objects/48/5e41b633c63f55b8277bcc59f44f67681f671a +0 -0
- package/skills/cm-content-factory/.git 2/objects/49/49c596a3a89fa240642acd95dd3258e261eb09 +0 -0
- package/skills/cm-content-factory/.git 2/objects/50/9d42d8412ef8eaf7f7e138476bac2e4d10ce60 +0 -0
- package/skills/cm-content-factory/.git 2/objects/55/0c8c389d981b463ef849aeb792d8be3ccb6ec8 +0 -0
- package/skills/cm-content-factory/.git 2/objects/5d/82d3b18410cdda3ace3677436f0cb599dbe2d2 +0 -0
- package/skills/cm-content-factory/.git 2/objects/60/0617c58e871a38b33bf29e282d132bb3c381ad +0 -0
- package/skills/cm-content-factory/.git 2/objects/6a/8369a99c687b7245c92ffaf0e0f0dab9014504 +0 -0
- package/skills/cm-content-factory/.git 2/objects/79/bea435d40ab531c1aaf6be0432c6a5b7aaed21 +0 -0
- package/skills/cm-content-factory/.git 2/objects/7e/5ebd79251c2f14e4aceb86c74b6b6daae6b500 +0 -0
- package/skills/cm-content-factory/.git 2/objects/81/98a822a60178d6d5023ddb3e222cddf048742e +0 -0
- package/skills/cm-content-factory/.git 2/objects/86/0a0e1943dfe53411d2e499a1f16f46a96ef758 +0 -0
- package/skills/cm-content-factory/.git 2/objects/86/971fb55fdc081fdbae52376f0f13e57a4e9b04 +0 -0
- package/skills/cm-content-factory/.git 2/objects/88/b89dd609a0a03f8d4fe8bfde20d5b8fc1d326d +0 -0
- package/skills/cm-content-factory/.git 2/objects/90/8737edb6b7809e32cc01590b4e08ba42a9d40d +0 -0
- package/skills/cm-content-factory/.git 2/objects/93/d5a8a9a7d4fb7f11491cb596a6880528725118 +0 -0
- package/skills/cm-content-factory/.git 2/objects/98/46a2ab81d0c3b3eb00ef88fc56989aa7e9f316 +0 -0
- package/skills/cm-content-factory/.git 2/objects/9b/d8dd1e49cf274eaf9c555f3ab39dce7af5715e +0 -0
- package/skills/cm-content-factory/.git 2/objects/a1/13329fb0cec96ae78b222d33a24c3b5bc7fa1f +0 -0
- package/skills/cm-content-factory/.git 2/objects/a9/e6effe626e8a3aea3a8fc3364b492191c6e7d0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ad/6de7e48d9782cca9353d1ff0aa1aab7fe1df85 +0 -0
- package/skills/cm-content-factory/.git 2/objects/af/54ae316f771ff692e299ffcd8bf2f06b413b59 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b0/4cb8b0b00dad633e731c1472161419e738d674 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b3/094abb0b9ed46419b269e4a4e36a459690e3b0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b9/435c5d4baac2cfc5c83009ddd27b46b60db5f1 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ba/5da17dbaec5ec2dcfdfd126aead518d1171d5c +0 -0
- package/skills/cm-content-factory/.git 2/objects/c0/bf58703aa258ba5dd63083bebaec8f223d844c +0 -0
- package/skills/cm-content-factory/.git 2/objects/c4/701a34edf1fc1bad58ccc57bd03f9426acb59a +0 -0
- package/skills/cm-content-factory/.git 2/objects/c7/5ccce9a4e5cc74d9b3174550cf6d993ca43638 +0 -0
- package/skills/cm-content-factory/.git 2/objects/c7/710d59b5a35b0f1f0a0399386643a0bd94c929 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d1/fe58237112e953e5fec52da22cf38e08be3df9 +5 -0
- package/skills/cm-content-factory/.git 2/objects/d2/2bbe9fd2f74c95bc5583e803f5e435f1e2cd86 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d7/e72852ea2bff74581dbf247d400120086229f4 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d8/d4c3b5553e4fd72807e1d4b49ef07d9ef3ac35 +0 -0
- package/skills/cm-content-factory/.git 2/objects/dc/75050c2876f6a02ae2a53a3c886f395b622977 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ee/e8546f95acec500187c08a28a8b9ee02db0dec +0 -0
- package/skills/cm-content-factory/.git 2/objects/ef/263c059208b416c2146434f10cb2b9fabcba16 +0 -0
- package/skills/cm-content-factory/.git 2/objects/f3/ae597e84d9a59b88acd21c99bde2eaf686d785 +0 -0
- package/skills/cm-content-factory/.git 2/objects/f3/f6f5673c821d3d8e76fa267a9e882e7a5387ea +0 -0
- package/skills/cm-content-factory/.git 2/objects/f9/6e6d0ad02624dd11d5848594d056caef7a5e8b +0 -0
- package/skills/cm-content-factory/.git 2/objects/ff/278988fc1edf0db3abcf18de795f4cc0b4f3e1 +0 -0
- package/skills/cm-content-factory/.git 2/refs/heads/main +1 -0
- package/skills/cm-content-factory/.git 2/refs/remotes/origin/main +1 -0
- package/skills/cm-content-factory/.pytest_cache 2/v/cache/nodeids +76 -0
- package/skills/cm-content-factory/.pytest_cache 2/v/cache/stepwise +1 -0
- package/skills/cm-content-factory/AGENTS.md +61 -0
- package/skills/cm-content-factory/CLAUDE.md +63 -0
- package/skills/cm-content-factory/CURSOR.md +43 -0
- package/skills/cm-content-factory/Content Factory.zip +0 -0
- package/skills/cm-content-factory/SKILL.md +416 -0
- package/skills/cm-content-factory/cf +313 -0
- package/skills/cm-content-factory/config.schema.json +397 -0
- package/skills/cm-content-factory/dashboard/app.js +556 -0
- package/skills/cm-content-factory/dashboard/index.html +397 -0
- package/skills/cm-content-factory/dashboard/style.css +1211 -0
- package/skills/cm-content-factory/examples/01-real-estate.config.json +146 -0
- package/skills/cm-content-factory/examples/02-personal-finance.config.json +146 -0
- package/skills/cm-content-factory/examples/03-health-wellness.config.json +147 -0
- package/skills/cm-content-factory/examples/04-saas-software.config.json +147 -0
- package/skills/cm-content-factory/examples/05-legal-services.config.json +147 -0
- package/skills/cm-content-factory/examples/06-insurance.config.json +146 -0
- package/skills/cm-content-factory/examples/07-ecommerce-dropship.config.json +146 -0
- package/skills/cm-content-factory/examples/08-online-education.config.json +147 -0
- package/skills/cm-content-factory/examples/09-crypto-defi.config.json +147 -0
- package/skills/cm-content-factory/examples/10-beauty-skincare.config.json +147 -0
- package/skills/cm-content-factory/examples/11-home-services.config.json +146 -0
- package/skills/cm-content-factory/examples/12-dental-clinic.config.json +147 -0
- package/skills/cm-content-factory/examples/13-pet-care.config.json +147 -0
- package/skills/cm-content-factory/examples/14-travel-hospitality.config.json +147 -0
- package/skills/cm-content-factory/examples/15-ai-automation.config.json +147 -0
- package/skills/cm-content-factory/examples/16-wedding-events.config.json +147 -0
- package/skills/cm-content-factory/examples/17-fitness-coaching.config.json +148 -0
- package/skills/cm-content-factory/examples/18-cybersecurity.config.json +147 -0
- package/skills/cm-content-factory/examples/19-food-restaurant.config.json +148 -0
- package/skills/cm-content-factory/examples/20-solar-energy.config.json +147 -0
- package/skills/cm-content-factory/examples/fitness-blog.config.json +116 -0
- package/skills/cm-content-factory/examples/tech-blog.config.json +107 -0
- package/skills/cm-content-factory/extensions/EXTENSION_GUIDE.md +72 -0
- package/skills/cm-content-factory/extensions/hooks.py +126 -0
- package/skills/cm-content-factory/extensions/openclaw_adapter.py +132 -0
- package/skills/cm-content-factory/landing/docs/content/changelog.md +36 -0
- package/skills/cm-content-factory/landing/docs/content/deployment.md +46 -0
- package/skills/cm-content-factory/landing/docs/content/execution-flow.md +67 -0
- package/skills/cm-content-factory/landing/docs/content/openspace.md +27 -0
- package/skills/cm-content-factory/landing/docs/content/openviking.md +33 -0
- package/skills/cm-content-factory/landing/docs/content/use-cases.md +26 -0
- package/skills/cm-content-factory/landing/docs/content/v5-intro.md +28 -0
- package/skills/cm-content-factory/landing/docs/index.html +240 -0
- package/skills/cm-content-factory/landing/index.html +680 -0
- package/skills/cm-content-factory/landing/script.js +143 -0
- package/skills/cm-content-factory/landing/style.css +1216 -0
- package/skills/cm-content-factory/landing/translations.js +508 -0
- package/skills/cm-content-factory/logs/events.jsonl +11 -0
- package/skills/cm-content-factory/profiles/_template.profile.json +231 -0
- package/skills/cm-content-factory/profiles/finance.profile.json +278 -0
- package/skills/cm-content-factory/profiles/legal.profile.json +263 -0
- package/skills/cm-content-factory/profiles/medical-research.profile.json +321 -0
- package/skills/cm-content-factory/profiles/technology.profile.json +275 -0
- package/skills/cm-content-factory/scripts/agent_dispatcher.py +266 -0
- package/skills/cm-content-factory/scripts/audit.py +106 -0
- package/skills/cm-content-factory/scripts/dashboard_server.py +225 -0
- package/skills/cm-content-factory/scripts/deploy.py +146 -0
- package/skills/cm-content-factory/scripts/extract.py +132 -0
- package/skills/cm-content-factory/scripts/landing_generator.py +459 -0
- package/skills/cm-content-factory/scripts/memory.py +521 -0
- package/skills/cm-content-factory/scripts/monetize.py +239 -0
- package/skills/cm-content-factory/scripts/pipeline.py +357 -0
- package/skills/cm-content-factory/scripts/plan.py +163 -0
- package/skills/cm-content-factory/scripts/publish.py +145 -0
- package/skills/cm-content-factory/scripts/research.py +337 -0
- package/skills/cm-content-factory/scripts/scaffold.py +464 -0
- package/skills/cm-content-factory/scripts/scoreboard.py +336 -0
- package/skills/cm-content-factory/scripts/seo.py +90 -0
- package/skills/cm-content-factory/scripts/state_manager.py +320 -0
- package/skills/cm-content-factory/scripts/token_manager.py +268 -0
- package/skills/cm-content-factory/scripts/validate.py +221 -0
- package/skills/cm-content-factory/scripts/wizard.py +329 -0
- package/skills/cm-content-factory/scripts/write.py +93 -0
- package/skills/cm-content-factory/sites/docs-site/src/assets/houston.webp +0 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/architecture.md +90 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/data-flow.md +54 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/deployment.md +38 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/index.md +65 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/lc-content-lifecycle.md +48 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/seq-write-mode.md +39 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/uj-first-batch.md +42 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/wf-content-pipeline.md +51 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/wf-learning-cycle.md +52 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/configuration.md +86 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/installation.md +80 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/intro.md +58 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/index.md +102 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/index.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/optimize-seo.md +29 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/scale-content-production.md +55 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/standardize-quality.md +29 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/buyer-cmo-huong.md +41 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/buyer-content-lead-khoa.md +40 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/index.md +56 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-content-manager-lan.md +46 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-seo-minh.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-writer-tu.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/content-pipeline.md +108 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/index.md +22 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/memory-system.md +52 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/seo-optimization.md +58 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/troubleshooting-guide.md +92 -0
- package/skills/cm-content-factory/sites/docs-site/src/styles/custom.css +575 -0
- package/skills/cm-content-factory/tests/conftest.py +66 -0
- package/skills/cm-content-factory/tests/test_agent_dispatcher.py +125 -0
- package/skills/cm-content-factory/tests/test_memory.py +128 -0
- package/skills/cm-content-factory/tests/test_pipeline.py +107 -0
- package/skills/cm-content-factory/tests/test_research.py +56 -0
- package/skills/cm-content-factory/tests/test_state_manager.py +131 -0
- package/skills/cm-content-factory/tests/test_token_manager.py +110 -0
- package/skills/cm-content-factory/tests/test_wizard.py +121 -0
- package/skills/cm-continuity/SKILL.md +49 -480
- package/skills/cm-continuity/references/cm-uri-scheme.md +23 -0
- package/skills/cm-continuity/references/continuity-template.md +48 -0
- package/skills/cm-continuity/references/mcp-context-server.md +27 -0
- package/skills/cm-continuity/references/memory-architecture.md +26 -0
- package/skills/cm-continuity/references/memory-audit.md +18 -0
- package/skills/cm-continuity/references/session-protocol.md +31 -0
- package/skills/cm-continuity/references/storage-formats.md +20 -0
- package/skills/cm-cro-methodology/SKILL.md +290 -0
- package/skills/cm-cro-methodology/references/COPYWRITING.md +178 -0
- package/skills/cm-cro-methodology/references/OBJECTIONS.md +135 -0
- package/skills/cm-cro-methodology/references/PERSUASION.md +158 -0
- package/skills/cm-cro-methodology/references/RESEARCH.md +220 -0
- package/skills/cm-cro-methodology/references/funnel-analysis.md +365 -0
- package/skills/cm-cro-methodology/references/testing-methodology.md +330 -0
- package/skills/cm-design-system/SKILL.md +5 -6
- package/skills/cm-execution/SKILL.md +61 -379
- package/skills/cm-execution/references/mode-a-batch.md +28 -0
- package/skills/cm-execution/references/mode-b-subagent.md +46 -0
- package/skills/cm-execution/references/mode-c-parallel.md +39 -0
- package/skills/cm-execution/references/mode-d-rarv.md +62 -0
- package/skills/cm-execution/references/mode-e-triz-parallel.md +53 -0
- package/skills/cm-execution/references/mode-f-party.md +61 -0
- package/skills/cm-execution/references/persona-dispatch.md +22 -0
- package/skills/cm-execution/references/security-rules.md +47 -0
- package/skills/cm-google-form/SKILL.md +266 -0
- package/skills/cm-google-form/templates/apps-script.js +55 -0
- package/skills/cm-google-form/templates/form-markup.html +110 -0
- package/skills/cm-google-form/templates/form-submit.js +201 -0
- package/skills/cm-google-form/templates/toast.css +152 -0
- package/skills/cm-growth-hacking/SKILL.md +282 -0
- package/skills/cm-growth-hacking/bottom-sheet-engine.md +261 -0
- package/skills/cm-growth-hacking/calendar-integration.md +264 -0
- package/skills/cm-growth-hacking/references/engagement-patterns.md +346 -0
- package/skills/cm-growth-hacking/templates/bottom-sheet.css +528 -0
- package/skills/cm-growth-hacking/templates/bottom-sheet.js +269 -0
- package/skills/cm-growth-hacking/templates/calendar-cta.js +213 -0
- package/skills/cm-growth-hacking/templates/tracking-events.js +211 -0
- package/skills/cm-growth-hacking/templates/trigger-manager.js +254 -0
- package/skills/cm-growth-hacking/tracking-events.md +246 -0
- package/skills/cm-growth-hacking/trigger-system.md +342 -0
- package/skills/cm-how-it-work/SKILL.md +8 -9
- package/skills/cm-identity-guard/SKILL.md +4 -4
- package/skills/cm-jtbd/SKILL.md +98 -0
- package/skills/cm-notebooklm/SKILL.md +156 -0
- package/skills/cm-notebooklm/references/command_reference.md +94 -0
- package/skills/cm-notebooklm/references/workflows.md +60 -0
- package/skills/cm-notebooklm/resources/knowledge_sources.md +106 -0
- package/skills/cm-notebooklm/scripts/brain-sync.sh +453 -0
- package/skills/cm-notebooklm/scripts/graduate_wisdom.py +101 -0
- package/skills/cm-planning/SKILL.md +3 -3
- package/skills/cm-project-bootstrap/SKILL.md +2 -2
- package/skills/cm-quality-gate/SKILL.md +1 -1
- package/skills/cm-readit/SKILL.md +289 -0
- package/skills/cm-readit/audio-player.md +206 -0
- package/skills/cm-readit/examples/blog-reader.js +352 -0
- package/skills/cm-readit/examples/voice-cro.js +390 -0
- package/skills/cm-readit/tts-engine.md +262 -0
- package/skills/cm-readit/ui-patterns.md +362 -0
- package/skills/cm-readit/voice-cro.md +223 -0
- package/skills/cm-safe-deploy/SKILL.md +80 -510
- package/skills/cm-safe-deploy/references/gate-0-5-security-scan.md +31 -0
- package/skills/cm-safe-deploy/references/gate-0-secret-hygiene.md +68 -0
- package/skills/cm-safe-deploy/references/gate-1-syntax.md +23 -0
- package/skills/cm-safe-deploy/references/gate-2-test-suite.md +28 -0
- package/skills/cm-safe-deploy/references/gate-3-i18n.md +19 -0
- package/skills/cm-safe-deploy/references/gate-4-5-build-dist.md +16 -0
- package/skills/cm-safe-deploy/references/gate-6-deploy-smoke.md +18 -0
- package/skills/cm-safe-deploy/references/rollback.md +17 -0
- package/skills/cm-safe-deploy/references/setup-new-project.md +20 -0
- package/skills/cm-skill-index/SKILL.md +15 -15
- package/skills/cm-start/SKILL.md +1 -1
- package/skills/cm-tdd/SKILL.md +51 -356
- package/skills/cm-tdd/references/bugfix-example.md +15 -0
- package/skills/cm-tdd/references/rationalizations.md +20 -0
- package/skills/cm-tdd/references/red-green-refactor.md +33 -0
- package/skills/cm-tdd/references/stuck-debugging.md +18 -0
- package/skills/cm-tdd/references/test-quality.md +19 -0
- package/skills/cm-ux-master/SKILL.md +368 -115
- package/skills/profiles/core.txt +1 -4
- package/skills/profiles/design.txt +1 -2
- package/skills/profiles/full.txt +10 -16
- package/skills/profiles/growth.txt +9 -9
- package/skills/profiles/top35.json +13 -13
- package/skills/cm-conductor-worktrees/SKILL.archive.md +0 -28
- package/skills/cm-conductor-worktrees/SKILL.md +0 -26
- package/skills/cm-dashboard/SKILL.archive.md +0 -15
- package/skills/cm-dashboard/SKILL.md +0 -26
- package/skills/cm-dashboard/ui/app.js +0 -1278
- package/skills/cm-dashboard/ui/index.html +0 -206
- package/skills/cm-dashboard/ui/style.css +0 -440
- package/skills/cm-design-studio/SKILL.archive.md +0 -34
- package/skills/cm-design-studio/SKILL.md +0 -26
- package/skills/cm-engineering-meta/SKILL.archive.md +0 -73
- package/skills/cm-engineering-meta/SKILL.md +0 -26
- package/skills/cm-git-worktrees/SKILL.archive.md +0 -157
- package/skills/cm-git-worktrees/SKILL.md +0 -26
- package/skills/cm-post-deploy-canary/SKILL.archive.md +0 -22
- package/skills/cm-post-deploy-canary/SKILL.md +0 -26
- package/skills/cm-qa-visual-cli/SKILL.archive.md +0 -22
- package/skills/cm-qa-visual-cli/SKILL.md +0 -26
- package/skills/cm-second-opinion-cli/SKILL.archive.md +0 -23
- package/skills/cm-second-opinion-cli/SKILL.md +0 -26
- package/skills/cm-secret-shield/SKILL.archive.md +0 -580
- package/skills/cm-secret-shield/SKILL.md +0 -26
- package/skills/cm-security-gate/SKILL.archive.md +0 -239
- package/skills/cm-security-gate/SKILL.md +0 -26
- package/skills/cm-skill-health/SKILL.archive.md +0 -83
- package/skills/cm-skill-health/SKILL.md +0 -26
- package/skills/cm-skill-mastery/SKILL.archive.md +0 -156
- package/skills/cm-skill-mastery/SKILL.md +0 -26
- package/skills/cm-skill-search/SKILL.archive.md +0 -49
- package/skills/cm-skill-search/SKILL.md +0 -26
- package/skills/cm-skill-share/SKILL.archive.md +0 -58
- package/skills/cm-skill-share/SKILL.md +0 -26
- package/skills/cm-test-gate/SKILL.archive.md +0 -245
- package/skills/cm-test-gate/SKILL.md +0 -26
- package/skills/cm-ui-preview/SKILL.archive.md +0 -153
- package/skills/cm-ui-preview/SKILL.md +0 -26
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Tests for agent_dispatcher.py — Task queue, claiming, and stale detection."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import pytest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from datetime import datetime, timedelta
|
|
8
|
+
|
|
9
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
10
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestAgentDispatcher:
|
|
14
|
+
"""Test AgentDispatcher class."""
|
|
15
|
+
|
|
16
|
+
def test_enqueue(self, tmp_path):
|
|
17
|
+
from agent_dispatcher import AgentDispatcher
|
|
18
|
+
d = AgentDispatcher(str(tmp_path))
|
|
19
|
+
task = d.enqueue("task-1", "write", {"topic": "SEO"})
|
|
20
|
+
assert task["id"] == "task-1"
|
|
21
|
+
assert task["status"] == "queued"
|
|
22
|
+
assert task["type"] == "write"
|
|
23
|
+
|
|
24
|
+
def test_no_duplicate_enqueue(self, tmp_path):
|
|
25
|
+
from agent_dispatcher import AgentDispatcher
|
|
26
|
+
d = AgentDispatcher(str(tmp_path))
|
|
27
|
+
d.enqueue("task-1", "write")
|
|
28
|
+
d.enqueue("task-1", "write")
|
|
29
|
+
q = d.get_queue()
|
|
30
|
+
assert q["total"] == 1
|
|
31
|
+
|
|
32
|
+
def test_claim_next(self, tmp_path):
|
|
33
|
+
from agent_dispatcher import AgentDispatcher
|
|
34
|
+
d = AgentDispatcher(str(tmp_path))
|
|
35
|
+
d.enqueue("task-1", "write")
|
|
36
|
+
task = d.claim_next("agent-1")
|
|
37
|
+
assert task is not None
|
|
38
|
+
assert task["status"] == "claimed"
|
|
39
|
+
assert task["claimed_by"] == "agent-1"
|
|
40
|
+
|
|
41
|
+
def test_claim_empty_queue_returns_none(self, tmp_path):
|
|
42
|
+
from agent_dispatcher import AgentDispatcher
|
|
43
|
+
d = AgentDispatcher(str(tmp_path))
|
|
44
|
+
assert d.claim_next("agent-1") is None
|
|
45
|
+
|
|
46
|
+
def test_claim_by_type(self, tmp_path):
|
|
47
|
+
from agent_dispatcher import AgentDispatcher
|
|
48
|
+
d = AgentDispatcher(str(tmp_path))
|
|
49
|
+
d.enqueue("task-1", "write")
|
|
50
|
+
d.enqueue("task-2", "audit")
|
|
51
|
+
task = d.claim_next("agent-1", task_type="audit")
|
|
52
|
+
assert task["id"] == "task-2"
|
|
53
|
+
|
|
54
|
+
def test_complete(self, tmp_path):
|
|
55
|
+
from agent_dispatcher import AgentDispatcher
|
|
56
|
+
d = AgentDispatcher(str(tmp_path))
|
|
57
|
+
d.enqueue("task-1", "write")
|
|
58
|
+
d.claim_next("agent-1")
|
|
59
|
+
d.complete("task-1", "agent-1", {"result": "ok"})
|
|
60
|
+
q = d.get_queue()
|
|
61
|
+
assert q["done"] == 1
|
|
62
|
+
|
|
63
|
+
def test_fail_with_retry(self, tmp_path):
|
|
64
|
+
from agent_dispatcher import AgentDispatcher
|
|
65
|
+
d = AgentDispatcher(str(tmp_path))
|
|
66
|
+
d.enqueue("task-1", "write")
|
|
67
|
+
d.claim_next("agent-1")
|
|
68
|
+
d.fail("task-1", "agent-1", "timeout")
|
|
69
|
+
q = d.get_queue()
|
|
70
|
+
task = next(t for t in q["tasks"] if t["id"] == "task-1")
|
|
71
|
+
assert task["status"] == "queued" # requeued for retry
|
|
72
|
+
assert task["retries"] == 1
|
|
73
|
+
|
|
74
|
+
def test_fail_exceeds_max_retries(self, tmp_path):
|
|
75
|
+
from agent_dispatcher import AgentDispatcher
|
|
76
|
+
d = AgentDispatcher(str(tmp_path))
|
|
77
|
+
d.enqueue("task-1", "write")
|
|
78
|
+
for _ in range(3):
|
|
79
|
+
d.claim_next("agent-1")
|
|
80
|
+
d.fail("task-1", "agent-1", "error")
|
|
81
|
+
q = d.get_queue()
|
|
82
|
+
task = next(t for t in q["tasks"] if t["id"] == "task-1")
|
|
83
|
+
assert task["status"] == "failed"
|
|
84
|
+
assert task["retries"] == 3
|
|
85
|
+
|
|
86
|
+
def test_priority_ordering(self, tmp_path):
|
|
87
|
+
from agent_dispatcher import AgentDispatcher
|
|
88
|
+
d = AgentDispatcher(str(tmp_path))
|
|
89
|
+
d.enqueue("low", "write", priority=10)
|
|
90
|
+
d.enqueue("high", "write", priority=1)
|
|
91
|
+
d.enqueue("mid", "write", priority=5)
|
|
92
|
+
task = d.claim_next("agent-1")
|
|
93
|
+
assert task["id"] == "high"
|
|
94
|
+
|
|
95
|
+
def test_enqueue_batch(self, tmp_path):
|
|
96
|
+
from agent_dispatcher import AgentDispatcher
|
|
97
|
+
d = AgentDispatcher(str(tmp_path))
|
|
98
|
+
tasks = [
|
|
99
|
+
{"id": "b-1", "type": "write"},
|
|
100
|
+
{"id": "b-2", "type": "write"},
|
|
101
|
+
{"id": "b-3", "type": "audit"},
|
|
102
|
+
]
|
|
103
|
+
added = d.enqueue_batch(tasks)
|
|
104
|
+
assert len(added) == 3
|
|
105
|
+
q = d.get_queue()
|
|
106
|
+
assert q["total"] == 3
|
|
107
|
+
|
|
108
|
+
def test_queue_summary(self, tmp_path):
|
|
109
|
+
from agent_dispatcher import AgentDispatcher
|
|
110
|
+
d = AgentDispatcher(str(tmp_path))
|
|
111
|
+
d.enqueue("t1", "write")
|
|
112
|
+
d.enqueue("t2", "write")
|
|
113
|
+
d.claim_next("agent-1")
|
|
114
|
+
q = d.get_queue()
|
|
115
|
+
assert q["queued"] == 1
|
|
116
|
+
assert q["claimed"] == 1
|
|
117
|
+
|
|
118
|
+
def test_reset(self, tmp_path):
|
|
119
|
+
from agent_dispatcher import AgentDispatcher
|
|
120
|
+
d = AgentDispatcher(str(tmp_path))
|
|
121
|
+
d.enqueue("t1", "write")
|
|
122
|
+
d.enqueue("t2", "write")
|
|
123
|
+
d.reset()
|
|
124
|
+
q = d.get_queue()
|
|
125
|
+
assert q["total"] == 0
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Tests for memory.py — MemoryEngine 3-layer memory system."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
9
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestMemoryEngineInit:
|
|
13
|
+
"""Test MemoryEngine initialization."""
|
|
14
|
+
|
|
15
|
+
def test_creates_directory_structure(self, tmp_project):
|
|
16
|
+
"""Should create semantic/, episodic/, working/ directories."""
|
|
17
|
+
from memory import MemoryEngine
|
|
18
|
+
_, config_path, _ = tmp_project
|
|
19
|
+
engine = MemoryEngine(str(config_path))
|
|
20
|
+
|
|
21
|
+
memory_dir = tmp_project[0] / "memory"
|
|
22
|
+
assert (memory_dir / "semantic").is_dir()
|
|
23
|
+
assert (memory_dir / "episodic").is_dir()
|
|
24
|
+
assert (memory_dir / "working").is_dir()
|
|
25
|
+
|
|
26
|
+
def test_creates_semantic_files(self, tmp_project):
|
|
27
|
+
"""Should initialize default semantic memory files."""
|
|
28
|
+
from memory import MemoryEngine
|
|
29
|
+
_, config_path, _ = tmp_project
|
|
30
|
+
engine = MemoryEngine(str(config_path))
|
|
31
|
+
|
|
32
|
+
semantic_dir = tmp_project[0] / "memory" / "semantic"
|
|
33
|
+
assert (semantic_dir / "writing_patterns.json").exists()
|
|
34
|
+
assert (semantic_dir / "seo_patterns.json").exists()
|
|
35
|
+
assert (semantic_dir / "niche_knowledge.json").exists()
|
|
36
|
+
assert (semantic_dir / "mistakes.json").exists()
|
|
37
|
+
|
|
38
|
+
def test_creates_working_session(self, tmp_project):
|
|
39
|
+
"""Should initialize current_session.json."""
|
|
40
|
+
from memory import MemoryEngine
|
|
41
|
+
_, config_path, _ = tmp_project
|
|
42
|
+
engine = MemoryEngine(str(config_path))
|
|
43
|
+
|
|
44
|
+
session_file = tmp_project[0] / "memory" / "working" / "current_session.json"
|
|
45
|
+
assert session_file.exists()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestWritingPatterns:
|
|
49
|
+
"""Test writing pattern add/update."""
|
|
50
|
+
|
|
51
|
+
def test_add_new_pattern(self, tmp_project):
|
|
52
|
+
"""Should add a new pattern with default confidence 0.5."""
|
|
53
|
+
from memory import MemoryEngine
|
|
54
|
+
_, config_path, _ = tmp_project
|
|
55
|
+
engine = MemoryEngine(str(config_path))
|
|
56
|
+
|
|
57
|
+
engine.add_writing_pattern("test_pattern", {"description": "Use short sentences"})
|
|
58
|
+
|
|
59
|
+
data = engine.load_semantic("writing_patterns")
|
|
60
|
+
assert "test_pattern" in data["patterns"]
|
|
61
|
+
assert data["patterns"]["test_pattern"]["confidence"] == 0.5
|
|
62
|
+
assert data["patterns"]["test_pattern"]["applications"] == 1
|
|
63
|
+
|
|
64
|
+
def test_update_existing_pattern_increases_confidence(self, tmp_project):
|
|
65
|
+
"""Applying same pattern again should increase confidence and applications."""
|
|
66
|
+
from memory import MemoryEngine
|
|
67
|
+
_, config_path, _ = tmp_project
|
|
68
|
+
engine = MemoryEngine(str(config_path))
|
|
69
|
+
|
|
70
|
+
engine.add_writing_pattern("p1", {"description": "Be concise"})
|
|
71
|
+
engine.add_writing_pattern("p1", {"description": "Be concise"})
|
|
72
|
+
|
|
73
|
+
data = engine.load_semantic("writing_patterns")
|
|
74
|
+
assert data["patterns"]["p1"]["applications"] == 2
|
|
75
|
+
assert data["patterns"]["p1"]["confidence"] == 0.55 # 0.5 + 0.05
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestWritingContext:
|
|
79
|
+
"""Test get_writing_context() aggregation."""
|
|
80
|
+
|
|
81
|
+
def test_empty_context_returns_structure(self, tmp_project):
|
|
82
|
+
"""Even with no data, should return proper dict structure."""
|
|
83
|
+
from memory import MemoryEngine
|
|
84
|
+
_, config_path, _ = tmp_project
|
|
85
|
+
engine = MemoryEngine(str(config_path))
|
|
86
|
+
|
|
87
|
+
ctx = engine.get_writing_context()
|
|
88
|
+
assert "style" in ctx
|
|
89
|
+
assert "top_patterns" in ctx
|
|
90
|
+
assert "avoid" in ctx
|
|
91
|
+
assert "seo_rules" in ctx
|
|
92
|
+
assert "domain_knowledge" in ctx
|
|
93
|
+
|
|
94
|
+
def test_context_includes_added_patterns(self, tmp_project):
|
|
95
|
+
"""Added patterns should appear in top_patterns."""
|
|
96
|
+
from memory import MemoryEngine
|
|
97
|
+
_, config_path, _ = tmp_project
|
|
98
|
+
engine = MemoryEngine(str(config_path))
|
|
99
|
+
|
|
100
|
+
engine.add_writing_pattern("p1", {"description": "Short sentences"})
|
|
101
|
+
ctx = engine.get_writing_context()
|
|
102
|
+
assert len(ctx["top_patterns"]) >= 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestSessionLifecycle:
|
|
106
|
+
"""Test session start/end cycle."""
|
|
107
|
+
|
|
108
|
+
def test_start_session_returns_id(self, tmp_project):
|
|
109
|
+
"""start_session should return a session ID string."""
|
|
110
|
+
from memory import MemoryEngine
|
|
111
|
+
_, config_path, _ = tmp_project
|
|
112
|
+
engine = MemoryEngine(str(config_path))
|
|
113
|
+
|
|
114
|
+
sid = engine.start_session(phase="write")
|
|
115
|
+
assert sid.startswith("ses-")
|
|
116
|
+
assert len(sid) > 4
|
|
117
|
+
|
|
118
|
+
def test_start_session_creates_working_file(self, tmp_project):
|
|
119
|
+
"""Should update current_session.json with new session data."""
|
|
120
|
+
from memory import MemoryEngine
|
|
121
|
+
_, config_path, _ = tmp_project
|
|
122
|
+
engine = MemoryEngine(str(config_path))
|
|
123
|
+
|
|
124
|
+
engine.start_session(phase="audit")
|
|
125
|
+
session_file = tmp_project[0] / "memory" / "working" / "current_session.json"
|
|
126
|
+
data = json.loads(session_file.read_text())
|
|
127
|
+
assert data["phase"] == "audit"
|
|
128
|
+
assert data["session_id"].startswith("ses-")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Tests for pipeline.py — Config loading, validation, hooks, and phase execution."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from unittest.mock import patch, MagicMock
|
|
7
|
+
|
|
8
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
9
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestValidateConfig:
|
|
13
|
+
"""Test validate_config() function."""
|
|
14
|
+
|
|
15
|
+
def test_valid_config_passes(self, minimal_config):
|
|
16
|
+
"""Happy path: valid config returns True."""
|
|
17
|
+
from pipeline import validate_config
|
|
18
|
+
assert validate_config(minimal_config) is True
|
|
19
|
+
|
|
20
|
+
def test_missing_niche_fails(self, minimal_config):
|
|
21
|
+
"""Missing top-level 'niche' key should fail."""
|
|
22
|
+
from pipeline import validate_config
|
|
23
|
+
del minimal_config["niche"]
|
|
24
|
+
assert validate_config(minimal_config) is False
|
|
25
|
+
|
|
26
|
+
def test_missing_brand_name_fails(self, bad_config_missing_brand):
|
|
27
|
+
"""Missing brand.name should fail."""
|
|
28
|
+
from pipeline import validate_config
|
|
29
|
+
assert validate_config(bad_config_missing_brand) is False
|
|
30
|
+
|
|
31
|
+
def test_missing_sources_type_fails(self, minimal_config):
|
|
32
|
+
"""Missing sources.type should fail."""
|
|
33
|
+
from pipeline import validate_config
|
|
34
|
+
del minimal_config["sources"]["type"]
|
|
35
|
+
assert validate_config(minimal_config) is False
|
|
36
|
+
|
|
37
|
+
def test_missing_output_content_dir_fails(self, minimal_config):
|
|
38
|
+
"""Missing output.content_dir should fail."""
|
|
39
|
+
from pipeline import validate_config
|
|
40
|
+
del minimal_config["output"]["content_dir"]
|
|
41
|
+
assert validate_config(minimal_config) is False
|
|
42
|
+
|
|
43
|
+
def test_empty_config_fails(self):
|
|
44
|
+
"""Empty dict should fail."""
|
|
45
|
+
from pipeline import validate_config
|
|
46
|
+
assert validate_config({}) is False
|
|
47
|
+
|
|
48
|
+
def test_missing_content_article_types_fails(self, minimal_config):
|
|
49
|
+
"""Missing content.article_types should fail."""
|
|
50
|
+
from pipeline import validate_config
|
|
51
|
+
del minimal_config["content"]["article_types"]
|
|
52
|
+
assert validate_config(minimal_config) is False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestFireHooks:
|
|
56
|
+
"""Test fire_hooks() function."""
|
|
57
|
+
|
|
58
|
+
def test_no_hooks_returns_silently(self, minimal_config):
|
|
59
|
+
"""Config without hooks should not raise."""
|
|
60
|
+
from pipeline import fire_hooks
|
|
61
|
+
# Should not raise
|
|
62
|
+
fire_hooks(minimal_config, "pre_write")
|
|
63
|
+
|
|
64
|
+
def test_empty_hooks_returns_silently(self):
|
|
65
|
+
"""Config with empty hooks dict should not raise."""
|
|
66
|
+
from pipeline import fire_hooks
|
|
67
|
+
config = {"extensions": {"hooks": {}}}
|
|
68
|
+
fire_hooks(config, "pre_write")
|
|
69
|
+
|
|
70
|
+
def test_nonexistent_hook_name_returns_silently(self):
|
|
71
|
+
"""Requesting a hook name that doesn't exist should not raise."""
|
|
72
|
+
from pipeline import fire_hooks
|
|
73
|
+
config = {"extensions": {"hooks": {"post_write": ["some_script.py"]}}}
|
|
74
|
+
fire_hooks(config, "pre_audit") # Different hook name
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestLoadConfig:
|
|
78
|
+
"""Test load_config() function."""
|
|
79
|
+
|
|
80
|
+
def test_load_valid_config(self, tmp_project):
|
|
81
|
+
"""Should load and return config dict from file."""
|
|
82
|
+
tmp_path, config_path, expected_config = tmp_project
|
|
83
|
+
from pipeline import load_config, CONFIG_FILE
|
|
84
|
+
|
|
85
|
+
# Monkey-patch CONFIG_FILE to our temp file
|
|
86
|
+
import pipeline
|
|
87
|
+
original = pipeline.CONFIG_FILE
|
|
88
|
+
pipeline.CONFIG_FILE = config_path
|
|
89
|
+
try:
|
|
90
|
+
result = load_config()
|
|
91
|
+
assert result["niche"] == "test-niche"
|
|
92
|
+
assert result["brand"]["name"] == "Test Brand"
|
|
93
|
+
finally:
|
|
94
|
+
pipeline.CONFIG_FILE = original
|
|
95
|
+
|
|
96
|
+
def test_load_missing_config_exits(self, tmp_path):
|
|
97
|
+
"""Should sys.exit(1) when config file doesn't exist."""
|
|
98
|
+
import pipeline
|
|
99
|
+
original = pipeline.CONFIG_FILE
|
|
100
|
+
pipeline.CONFIG_FILE = tmp_path / "nonexistent.json"
|
|
101
|
+
try:
|
|
102
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
103
|
+
from pipeline import load_config
|
|
104
|
+
load_config()
|
|
105
|
+
assert exc_info.value.code == 1
|
|
106
|
+
finally:
|
|
107
|
+
pipeline.CONFIG_FILE = original
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Tests for research.py — ResearchEngine initialization and query generation."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
8
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestResearchEngineInit:
|
|
12
|
+
"""Test ResearchEngine initialization."""
|
|
13
|
+
|
|
14
|
+
def test_loads_config(self, tmp_project):
|
|
15
|
+
"""Should load config from file path."""
|
|
16
|
+
from research import ResearchEngine
|
|
17
|
+
_, config_path, config = tmp_project
|
|
18
|
+
engine = ResearchEngine(str(config_path))
|
|
19
|
+
assert engine.config["niche"] == "test-niche"
|
|
20
|
+
|
|
21
|
+
def test_sets_project_root(self, tmp_project):
|
|
22
|
+
"""Should derive project_root from config path."""
|
|
23
|
+
from research import ResearchEngine
|
|
24
|
+
_, config_path, _ = tmp_project
|
|
25
|
+
engine = ResearchEngine(str(config_path))
|
|
26
|
+
assert engine.project_root == tmp_project[0]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestGenerateQueries:
|
|
30
|
+
"""Test _generate_queries() method."""
|
|
31
|
+
|
|
32
|
+
def test_returns_list(self, tmp_project):
|
|
33
|
+
"""Should return a list of query strings."""
|
|
34
|
+
from research import ResearchEngine
|
|
35
|
+
_, config_path, _ = tmp_project
|
|
36
|
+
engine = ResearchEngine(str(config_path))
|
|
37
|
+
queries = engine._generate_queries("massage therapy benefits")
|
|
38
|
+
assert isinstance(queries, list)
|
|
39
|
+
assert len(queries) > 0
|
|
40
|
+
|
|
41
|
+
def test_queries_contain_topic(self, tmp_project):
|
|
42
|
+
"""At least one query should contain the topic words."""
|
|
43
|
+
from research import ResearchEngine
|
|
44
|
+
_, config_path, _ = tmp_project
|
|
45
|
+
engine = ResearchEngine(str(config_path))
|
|
46
|
+
queries = engine._generate_queries("acupuncture")
|
|
47
|
+
assert any("acupuncture" in q.lower() for q in queries)
|
|
48
|
+
|
|
49
|
+
def test_queries_contain_niche(self, tmp_project):
|
|
50
|
+
"""Queries should incorporate niche context."""
|
|
51
|
+
from research import ResearchEngine
|
|
52
|
+
_, config_path, _ = tmp_project
|
|
53
|
+
engine = ResearchEngine(str(config_path))
|
|
54
|
+
queries = engine._generate_queries("benefits")
|
|
55
|
+
# Should generate meaningful queries, not just empty strings
|
|
56
|
+
assert all(len(q) > 0 for q in queries)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Tests for state_manager.py — State persistence, events, and thread safety."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
8
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestStateManager:
|
|
12
|
+
"""Test StateManager class."""
|
|
13
|
+
|
|
14
|
+
def test_default_state(self, tmp_path):
|
|
15
|
+
from state_manager import StateManager
|
|
16
|
+
sm = StateManager(str(tmp_path))
|
|
17
|
+
state = sm.load()
|
|
18
|
+
assert state["status"] == "idle"
|
|
19
|
+
assert state["version"] == "2.0"
|
|
20
|
+
assert len(state["pipeline"]["phases"]) == 6
|
|
21
|
+
|
|
22
|
+
def test_save_and_load(self, tmp_path):
|
|
23
|
+
from state_manager import StateManager
|
|
24
|
+
sm = StateManager(str(tmp_path))
|
|
25
|
+
sm.update_phase("extract", "running", progress=0.5)
|
|
26
|
+
state = sm.load()
|
|
27
|
+
assert state["pipeline"]["phases"]["extract"]["status"] == "running"
|
|
28
|
+
assert state["pipeline"]["phases"]["extract"]["progress"] == 0.5
|
|
29
|
+
|
|
30
|
+
def test_phase_lifecycle(self, tmp_path):
|
|
31
|
+
from state_manager import StateManager
|
|
32
|
+
sm = StateManager(str(tmp_path))
|
|
33
|
+
sm.update_phase("write", "running", progress=0.0)
|
|
34
|
+
sm.update_phase("write", "running", progress=0.5)
|
|
35
|
+
sm.update_phase("write", "done", progress=1.0)
|
|
36
|
+
state = sm.load()
|
|
37
|
+
p = state["pipeline"]["phases"]["write"]
|
|
38
|
+
assert p["status"] == "done"
|
|
39
|
+
assert p["progress"] == 1.0
|
|
40
|
+
assert p["started_at"] is not None
|
|
41
|
+
assert p["finished_at"] is not None
|
|
42
|
+
|
|
43
|
+
def test_add_task(self, tmp_path):
|
|
44
|
+
from state_manager import StateManager
|
|
45
|
+
sm = StateManager(str(tmp_path))
|
|
46
|
+
sm.add_task("task-1", "running", {"topic": "SEO"})
|
|
47
|
+
state = sm.load()
|
|
48
|
+
assert len(state["tasks"]) == 1
|
|
49
|
+
assert state["tasks"][0]["id"] == "task-1"
|
|
50
|
+
assert state["tasks"][0]["meta"]["topic"] == "SEO"
|
|
51
|
+
|
|
52
|
+
def test_update_existing_task(self, tmp_path):
|
|
53
|
+
from state_manager import StateManager
|
|
54
|
+
sm = StateManager(str(tmp_path))
|
|
55
|
+
sm.add_task("task-1", "running")
|
|
56
|
+
sm.add_task("task-1", "done", {"result": "ok"})
|
|
57
|
+
state = sm.load()
|
|
58
|
+
assert len(state["tasks"]) == 1
|
|
59
|
+
assert state["tasks"][0]["status"] == "done"
|
|
60
|
+
|
|
61
|
+
def test_update_tokens(self, tmp_path):
|
|
62
|
+
from state_manager import StateManager
|
|
63
|
+
sm = StateManager(str(tmp_path))
|
|
64
|
+
sm.update_tokens("gemini", input_tokens=100, output_tokens=50, cost_usd=0.001)
|
|
65
|
+
sm.update_tokens("gemini", input_tokens=200, output_tokens=100, cost_usd=0.002)
|
|
66
|
+
state = sm.load()
|
|
67
|
+
assert state["tokens"]["total_input"] == 300
|
|
68
|
+
assert state["tokens"]["total_output"] == 150
|
|
69
|
+
assert state["tokens"]["total_cost_usd"] == 0.003
|
|
70
|
+
assert state["tokens"]["providers"]["gemini"]["requests"] == 2
|
|
71
|
+
|
|
72
|
+
def test_budget_check(self, tmp_path):
|
|
73
|
+
from state_manager import StateManager
|
|
74
|
+
sm = StateManager(str(tmp_path))
|
|
75
|
+
sm.set_budget(0.01)
|
|
76
|
+
assert sm.check_budget() is True
|
|
77
|
+
sm.update_tokens("gemini", cost_usd=0.011)
|
|
78
|
+
assert sm.check_budget() is False
|
|
79
|
+
|
|
80
|
+
def test_add_error(self, tmp_path):
|
|
81
|
+
from state_manager import StateManager
|
|
82
|
+
sm = StateManager(str(tmp_path))
|
|
83
|
+
sm.add_error("write", "API timeout")
|
|
84
|
+
state = sm.load()
|
|
85
|
+
assert len(state["errors"]) == 1
|
|
86
|
+
assert state["errors"][0]["message"] == "API timeout"
|
|
87
|
+
|
|
88
|
+
def test_event_logging(self, tmp_path):
|
|
89
|
+
from state_manager import StateManager
|
|
90
|
+
sm = StateManager(str(tmp_path))
|
|
91
|
+
sm.log_event("info", "test event", {"key": "val"})
|
|
92
|
+
sm.log_event("error", "test error")
|
|
93
|
+
events = sm.get_recent_events(10)
|
|
94
|
+
assert len(events) == 2
|
|
95
|
+
assert events[0]["level"] == "info"
|
|
96
|
+
assert events[1]["level"] == "error"
|
|
97
|
+
|
|
98
|
+
def test_reset(self, tmp_path):
|
|
99
|
+
from state_manager import StateManager
|
|
100
|
+
sm = StateManager(str(tmp_path))
|
|
101
|
+
sm.update_phase("write", "done", progress=1.0)
|
|
102
|
+
sm.add_task("t1", "done")
|
|
103
|
+
sm.reset()
|
|
104
|
+
state = sm.load()
|
|
105
|
+
assert state["status"] == "idle"
|
|
106
|
+
assert len(state["tasks"]) == 0
|
|
107
|
+
|
|
108
|
+
def test_get_snapshot(self, tmp_path):
|
|
109
|
+
from state_manager import StateManager
|
|
110
|
+
sm = StateManager(str(tmp_path))
|
|
111
|
+
sm.log_event("info", "snapshot test")
|
|
112
|
+
snap = sm.get_snapshot()
|
|
113
|
+
assert "recent_events" in snap
|
|
114
|
+
assert len(snap["recent_events"]) >= 1
|
|
115
|
+
|
|
116
|
+
def test_overall_status_completed(self, tmp_path):
|
|
117
|
+
from state_manager import StateManager
|
|
118
|
+
sm = StateManager(str(tmp_path))
|
|
119
|
+
for phase in ["extract", "plan", "write", "audit", "seo", "publish"]:
|
|
120
|
+
sm.update_phase(phase, "done", progress=1.0)
|
|
121
|
+
state = sm.load()
|
|
122
|
+
assert state["status"] == "completed"
|
|
123
|
+
|
|
124
|
+
def test_overall_status_error(self, tmp_path):
|
|
125
|
+
from state_manager import StateManager
|
|
126
|
+
sm = StateManager(str(tmp_path))
|
|
127
|
+
sm.update_phase("extract", "done")
|
|
128
|
+
sm.update_phase("plan", "failed", error="Network error")
|
|
129
|
+
state = sm.load()
|
|
130
|
+
assert state["status"] == "error"
|
|
131
|
+
assert len(state["errors"]) == 1
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tests for token_manager.py — Token tracking, budget, circuit breaker, and rate limiting."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import pytest
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts"
|
|
8
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestTokenManager:
|
|
12
|
+
"""Test TokenManager class."""
|
|
13
|
+
|
|
14
|
+
def test_estimate_cost_gemini(self):
|
|
15
|
+
from token_manager import TokenManager
|
|
16
|
+
tm = TokenManager()
|
|
17
|
+
cost = tm.estimate_cost("gemini", 1_000_000, 1_000_000)
|
|
18
|
+
assert cost == 0.75 # 0.15 + 0.60
|
|
19
|
+
|
|
20
|
+
def test_estimate_cost_unknown_provider(self):
|
|
21
|
+
from token_manager import TokenManager
|
|
22
|
+
tm = TokenManager()
|
|
23
|
+
cost = tm.estimate_cost("unknown", 1_000_000, 1_000_000)
|
|
24
|
+
assert cost == 4.0 # fallback 1.0 + 3.0
|
|
25
|
+
|
|
26
|
+
def test_record_usage(self, tmp_path):
|
|
27
|
+
from token_manager import TokenManager
|
|
28
|
+
tm = TokenManager(str(tmp_path))
|
|
29
|
+
cost = tm.record_usage("gemini", input_tokens=1000, output_tokens=500, task_id="test-1")
|
|
30
|
+
assert cost > 0
|
|
31
|
+
s = tm.get_summary()
|
|
32
|
+
assert s["total_input_tokens"] == 1000
|
|
33
|
+
assert s["total_output_tokens"] == 500
|
|
34
|
+
assert s["total_requests"] == 1
|
|
35
|
+
assert s["providers"]["gemini"]["requests"] == 1
|
|
36
|
+
|
|
37
|
+
def test_multiple_providers(self, tmp_path):
|
|
38
|
+
from token_manager import TokenManager
|
|
39
|
+
tm = TokenManager(str(tmp_path))
|
|
40
|
+
tm.record_usage("gemini", input_tokens=1000, output_tokens=500)
|
|
41
|
+
tm.record_usage("claude-sonnet", input_tokens=2000, output_tokens=1000)
|
|
42
|
+
s = tm.get_summary()
|
|
43
|
+
assert len(s["providers"]) == 2
|
|
44
|
+
assert s["total_input_tokens"] == 3000
|
|
45
|
+
|
|
46
|
+
def test_budget_within(self, tmp_path):
|
|
47
|
+
from token_manager import TokenManager
|
|
48
|
+
tm = TokenManager(str(tmp_path), budget_usd=10.0)
|
|
49
|
+
tm.record_usage("gemini", input_tokens=1000, output_tokens=500)
|
|
50
|
+
assert tm.check_budget() is True
|
|
51
|
+
|
|
52
|
+
def test_budget_exceeded(self, tmp_path):
|
|
53
|
+
from token_manager import TokenManager
|
|
54
|
+
tm = TokenManager(str(tmp_path), budget_usd=0.0001)
|
|
55
|
+
tm.record_usage("gemini", input_tokens=100000, output_tokens=50000)
|
|
56
|
+
assert tm.check_budget() is False
|
|
57
|
+
|
|
58
|
+
def test_budget_status(self, tmp_path):
|
|
59
|
+
from token_manager import TokenManager
|
|
60
|
+
tm = TokenManager(str(tmp_path), budget_usd=1.0)
|
|
61
|
+
tm.record_usage("gemini", input_tokens=1000, output_tokens=500)
|
|
62
|
+
status = tm.get_budget_status()
|
|
63
|
+
assert status["limit_usd"] == 1.0
|
|
64
|
+
assert status["within_budget"] is True
|
|
65
|
+
assert status["percentage_used"] >= 0
|
|
66
|
+
|
|
67
|
+
def test_circuit_breaker_closed(self, tmp_path):
|
|
68
|
+
from token_manager import TokenManager
|
|
69
|
+
tm = TokenManager(str(tmp_path))
|
|
70
|
+
assert tm.is_circuit_open("gemini") is False
|
|
71
|
+
|
|
72
|
+
def test_circuit_breaker_opens(self, tmp_path):
|
|
73
|
+
from token_manager import TokenManager
|
|
74
|
+
tm = TokenManager(str(tmp_path))
|
|
75
|
+
for _ in range(5):
|
|
76
|
+
tm.record_usage("gemini", input_tokens=100, output_tokens=50, success=False)
|
|
77
|
+
assert tm.is_circuit_open("gemini") is True
|
|
78
|
+
|
|
79
|
+
def test_circuit_breaker_resets_on_success(self, tmp_path):
|
|
80
|
+
from token_manager import TokenManager
|
|
81
|
+
tm = TokenManager(str(tmp_path))
|
|
82
|
+
for _ in range(3):
|
|
83
|
+
tm.record_usage("gemini", input_tokens=100, output_tokens=50, success=False)
|
|
84
|
+
tm.record_usage("gemini", input_tokens=100, output_tokens=50, success=True)
|
|
85
|
+
assert tm.is_circuit_open("gemini") is False
|
|
86
|
+
|
|
87
|
+
def test_backoff_seconds(self, tmp_path):
|
|
88
|
+
from token_manager import TokenManager
|
|
89
|
+
tm = TokenManager(str(tmp_path))
|
|
90
|
+
assert tm.get_backoff_seconds("gemini") == 0.0
|
|
91
|
+
tm.record_usage("gemini", success=False)
|
|
92
|
+
assert tm.get_backoff_seconds("gemini") == 2.0
|
|
93
|
+
tm.record_usage("gemini", success=False)
|
|
94
|
+
assert tm.get_backoff_seconds("gemini") == 4.0
|
|
95
|
+
|
|
96
|
+
def test_reset(self, tmp_path):
|
|
97
|
+
from token_manager import TokenManager
|
|
98
|
+
tm = TokenManager(str(tmp_path))
|
|
99
|
+
tm.record_usage("gemini", input_tokens=1000, output_tokens=500)
|
|
100
|
+
tm.reset()
|
|
101
|
+
s = tm.get_summary()
|
|
102
|
+
assert s["total_input_tokens"] == 0
|
|
103
|
+
assert len(s["providers"]) == 0
|
|
104
|
+
|
|
105
|
+
def test_failure_tracking(self, tmp_path):
|
|
106
|
+
from token_manager import TokenManager
|
|
107
|
+
tm = TokenManager(str(tmp_path))
|
|
108
|
+
tm.record_usage("gemini", input_tokens=100, success=False)
|
|
109
|
+
s = tm.get_summary()
|
|
110
|
+
assert s["providers"]["gemini"]["failures"] == 1
|