projscan 4.0.0 → 4.2.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.
Files changed (39) hide show
  1. package/README.md +480 -24
  2. package/dist/cli/commands/route.js +1 -0
  3. package/dist/cli/commands/route.js.map +1 -1
  4. package/dist/cli/commands/semanticGraph.js +27 -0
  5. package/dist/cli/commands/semanticGraph.js.map +1 -1
  6. package/dist/cli/commands/start.js +1095 -2
  7. package/dist/cli/commands/start.js.map +1 -1
  8. package/dist/core/dependencyAnalyzer.js +172 -0
  9. package/dist/core/dependencyAnalyzer.js.map +1 -1
  10. package/dist/core/intentRouter.d.ts +8 -1
  11. package/dist/core/intentRouter.js +2186 -22
  12. package/dist/core/intentRouter.js.map +1 -1
  13. package/dist/core/issueEngine.js +6 -7
  14. package/dist/core/issueEngine.js.map +1 -1
  15. package/dist/core/onboarding.d.ts +2 -2
  16. package/dist/core/onboarding.js +29 -5
  17. package/dist/core/onboarding.js.map +1 -1
  18. package/dist/core/start.d.ts +1 -0
  19. package/dist/core/start.js +3047 -10
  20. package/dist/core/start.js.map +1 -1
  21. package/dist/mcp/server.d.ts +1 -1
  22. package/dist/mcp/server.js +14 -5
  23. package/dist/mcp/server.js.map +1 -1
  24. package/dist/mcp/tools/start.js +6 -1
  25. package/dist/mcp/tools/start.js.map +1 -1
  26. package/dist/projscan-sbom.cdx.json +6 -6
  27. package/dist/reporters/consoleReporter.js +19 -0
  28. package/dist/reporters/consoleReporter.js.map +1 -1
  29. package/dist/reporters/markdownReporter.js +19 -0
  30. package/dist/reporters/markdownReporter.js.map +1 -1
  31. package/dist/tool-manifest.json +6 -2
  32. package/dist/types.d.ts +275 -0
  33. package/docs/GUIDE.md +1567 -0
  34. package/docs/ROADMAP.md +219 -0
  35. package/docs/demos/projscan-4-1-demo.html +677 -0
  36. package/docs/projscan-mission-control.png +0 -0
  37. package/docs/projscan-proof-router.png +0 -0
  38. package/package.json +8 -1
  39. package/scripts/capture-readme-assets.mjs +60 -0
@@ -12,6 +12,16 @@
12
12
  * this router) is a breaking change reserved for 4.0.
13
13
  */
14
14
  export const ROUTE_CATALOG = [
15
+ {
16
+ intent: 'Review local privacy and trust boundaries',
17
+ category: 'Trust',
18
+ tool: 'projscan_privacy_check',
19
+ cli: 'projscan privacy-check',
20
+ what: 'Local privacy, ignore, telemetry, plugin, write, and network boundary report.',
21
+ why: 'Before trusting the tool on a repo, verify what it can read, write, or contact.',
22
+ example: 'projscan privacy-check --offline',
23
+ keywords: ['privacy', 'trust', 'boundary', 'read', 'env', 'values', 'upload', 'code', 'source', 'leave', 'machine', 'telemetry', 'network', 'offline', 'local', 'contact', 'contacted', 'write', 'writes', 'check', 'projscan'],
24
+ },
15
25
  {
16
26
  intent: 'Understand a repo before editing',
17
27
  category: 'Understand',
@@ -20,7 +30,37 @@ export const ROUTE_CATALOG = [
20
30
  what: 'Cited repo/flow/contract/change/verify maps.',
21
31
  why: 'Orient in an unfamiliar codebase before making a change.',
22
32
  example: 'projscan understand --view map --format json',
23
- keywords: ['understand', 'orient', 'overview', 'map', 'comprehend', 'unfamiliar', 'learn', 'explore', 'architecture'],
33
+ keywords: ['understand', 'orient', 'overview', 'map', 'read', 'summarize', 'summary', 'contract', 'contracts', 'public', 'api', 'apis', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible', 'comprehend', 'unfamiliar', 'learn', 'explore', 'architecture', 'repo', 'codebase', 'service', 'services', 'main', 'entrypoint', 'entrypoints', 'entry', 'point', 'important', 'look', 'first', 'tour', 'walk', 'through', 'new', 'onboard', 'onboarding', 'run', 'runs', 'project', 'command', 'commands', 'dev', 'server', 'start', 'app', 'setup', 'set', 'local', 'locally', 'install', 'docker', 'compose', 'npm', 'script', 'scripts', 'test', 'tests', 'e2e', 'unit', 'integration', 'storybook', 'cypress', 'playwright', 'eslint', 'prettier', 'format', 'lint', 'typecheck', 'typechecking', 'verify', 'verification', 'proof', 'prove', 'checks', 'env', 'environment', 'environments', 'vars', 'variable', 'variables', 'missing', 'required', 'config', 'configuration', 'feature', 'endpoint', 'button', 'put', 'need', 'change', 'files', 'add', 'implement', 'build', 'create', 'wire', 'route', 'component', 'page', 'screen', 'view', 'webhook', 'login', 'support', 'checkout', 'search', 'doc', 'docs', 'document', 'documentation', 'readme', 'examples', 'example', 'database', 'migration', 'migrations', 'migrate', 'migrates', 'db', 'schema', 'table', 'column', 'seed', 'seeds', 'reset', 'resets', 'guide', 'update', 'updating'],
34
+ },
35
+ {
36
+ intent: 'Inspect a specific file',
37
+ category: 'Understand',
38
+ tool: 'projscan_file',
39
+ cli: 'projscan file',
40
+ what: 'Per-file purpose, imports, exports, smells, risk, ownership, and related issues.',
41
+ why: 'When a developer asks what a named file does before editing it.',
42
+ example: 'projscan file src/index.ts --format json',
43
+ keywords: ['file', 'explain', 'inspect', 'purpose', 'read', 'review', 'reviewer', 'reviewers', 'ownership', 'owner', 'owns', 'risk', 'risks', 'risky', 'dangerous', 'imports', 'exports', 'last', 'touched', 'touch', 'changed', 'recently', 'history', 'author', 'authors', 'blame', 'add', 'write', 'coverage', 'covered', 'uncovered', 'test', 'tests'],
44
+ },
45
+ {
46
+ intent: 'Answer a targeted graph question',
47
+ category: 'Understand',
48
+ tool: 'projscan_semantic_graph',
49
+ cli: 'projscan semantic-graph',
50
+ what: 'Targeted imports, exports, importers, symbol definitions, or package importer query.',
51
+ why: 'When a developer asks who imports a file, what a file imports/exports, or where a symbol is defined.',
52
+ example: 'projscan semantic-graph --query importers --file src/auth.ts --format json',
53
+ keywords: ['imports', 'importers', 'import', 'exports', 'export', 'defined', 'definition', 'defines', 'uses', 'depend', 'depends', 'installed'],
54
+ },
55
+ {
56
+ intent: 'Inspect module coupling and cycles',
57
+ category: 'Architecture',
58
+ tool: 'projscan_coupling',
59
+ cli: 'projscan coupling',
60
+ what: 'Per-file fan-in, fan-out, instability, cross-package edges, and circular-import cycles.',
61
+ why: 'When a developer asks about circular dependencies, import cycles, tight coupling, or unstable module boundaries.',
62
+ example: 'projscan coupling --cycles-only --format json',
63
+ keywords: ['coupling', 'coupled', 'tightly', 'module', 'modules', 'circular', 'cycle', 'cycles', 'dependency', 'dependencies', 'import', 'imports', 'fan', 'fan-in', 'fan-out', 'instability', 'architecture', 'boundary', 'boundaries'],
24
64
  },
25
65
  {
26
66
  intent: 'Review a PR or a set of changes',
@@ -30,7 +70,27 @@ export const ROUTE_CATALOG = [
30
70
  what: 'One-call structural PR review with a verdict.',
31
71
  why: 'Assess risk of a diff: cycles, taint, dataflow, contracts.',
32
72
  example: 'projscan review --format json',
33
- keywords: ['review', 'pr', 'pull', 'request', 'diff', 'changes', 'verdict', 'assess'],
73
+ keywords: ['review', 'pr', 'pull', 'request', 'branch', 'diff', 'change', 'changes', 'secure', 'security', 'issues', 'check', 'risk', 'risks', 'risky', 'verdict', 'assess'],
74
+ },
75
+ {
76
+ intent: 'Prepare reviewer evidence or a PR comment',
77
+ category: 'Review',
78
+ tool: 'projscan_evidence_pack',
79
+ cli: 'projscan evidence-pack',
80
+ what: 'Approval-ready evidence packet and paste-ready PR comment.',
81
+ why: 'When a developer needs a reviewer-facing summary with verdict, risks, owner routing, and proof commands.',
82
+ example: 'projscan evidence-pack --pr-comment',
83
+ keywords: ['evidence', 'proof', 'approval', 'approve', 'comment', 'summarize', 'changes', 'description', 'draft', 'say', 'checklist', 'tell', 'team', 'change', 'share', 'reviewer', 'reviewers', 'summary', 'packet', 'paste', 'who', 'review', 'ready', 'open', 'opening', 'before', 'prepare', 'owner', 'owners', 'owns', 'routing', 'changed', 'file', 'files', 'pr'],
84
+ },
85
+ {
86
+ intent: 'Inspect what changed in a PR',
87
+ category: 'Review',
88
+ tool: 'projscan_pr_diff',
89
+ cli: 'projscan pr-diff',
90
+ what: 'Structural diff of changed exports, imports, call sites, complexity, and fan-in.',
91
+ why: 'Before a full review verdict, see exactly what changed in the PR.',
92
+ example: 'projscan pr-diff --format json',
93
+ keywords: ['commit', 'message', 'summarize', 'summary', 'pr', 'diff', 'changed', 'changes', 'change', 'since', 'branch', 'stale', 'main', 'base', 'head', 'compare', 'branched', 'behind', 'ahead', 'sync', 'synced', 'large', 'big', 'size', 'sizes', 'exports', 'imports', 'calls', 'callers'],
34
94
  },
35
95
  {
36
96
  intent: 'See what breaks if I change something',
@@ -40,7 +100,7 @@ export const ROUTE_CATALOG = [
40
100
  what: 'Transitive blast radius for a file or symbol.',
41
101
  why: 'Before renaming or deleting, see every caller that breaks.',
42
102
  example: 'projscan impact --symbol buildCodeGraph --format json',
43
- keywords: ['impact', 'breaks', 'break', 'blast', 'radius', 'rename', 'delete', 'depends', 'affect', 'callers', 'breaking'],
103
+ keywords: ['impact', 'breaks', 'break', 'blast', 'radius', 'rename', 'delete', 'remove', 'drop', 'depends', 'affect', 'callers', 'used', 'usage', 'referenced', 'called', 'breaking', 'api', 'apis', 'endpoint', 'endpoints', 'client', 'clients', 'contract', 'contracts', 'change', 'changes', 'changing', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible', 'version', 'versions', 'schema', 'table', 'column', 'database', 'db', 'migration', 'migrations', 'revert', 'rollback', 'undo', 'backout', 'back', 'out', 'recover'],
44
104
  },
45
105
  {
46
106
  intent: 'Check if it is safe to edit / commit / merge',
@@ -50,7 +110,7 @@ export const ROUTE_CATALOG = [
50
110
  what: 'proceed / caution / block verdict with evidence.',
51
111
  why: 'A safety gate before an edit, commit, or merge.',
52
112
  example: 'projscan preflight --mode before_commit --format json',
53
- keywords: ['safe', 'safety', 'gate', 'commit', 'merge', 'edit', 'proceed', 'block', 'preflight', 'allowed', 'risky'],
113
+ keywords: ['safe', 'safety', 'gate', 'commit', 'merge', 'ready', 'edit', 'proceed', 'block', 'blocked', 'blocker', 'blockers', 'blocking', 'preflight', 'allowed', 'risk', 'risks', 'risky', 'rebase', 'rebasing', 'conflict', 'conflicts', 'resolve', 'resolving', 'wrong', 'stuck'],
54
114
  },
55
115
  {
56
116
  intent: 'Find the riskiest files / where to start',
@@ -60,7 +120,17 @@ export const ROUTE_CATALOG = [
60
120
  what: 'Files ranked by churn × complexity × issues.',
61
121
  why: 'Decide where to focus review or refactoring.',
62
122
  example: 'projscan hotspots --format json',
63
- keywords: ['hotspot', 'risky', 'riskiest', 'where', 'start', 'focus', 'churn', 'complexity', 'dangerous'],
123
+ keywords: ['hotspot', 'risky', 'files', 'file', 'touch', 'riskiest', 'where', 'start', 'focus', 'churn', 'complexity', 'complex', 'refactor', 'refactoring', 'simplify', 'simplification', 'tech', 'debt', 'duplicate', 'duplicated', 'duplication', 'over', 'engineered', 'dangerous', 'performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'],
124
+ },
125
+ {
126
+ intent: 'Check swarm coordination status',
127
+ category: 'Swarm coordination',
128
+ tool: 'projscan_coordinate',
129
+ cli: 'projscan coordinate',
130
+ what: 'One-call readiness verdict from collisions, claims, and merge-risk.',
131
+ why: 'Before continuing parallel work, see whether the swarm is clear, cautious, or conflicted.',
132
+ example: 'projscan coordinate --format json',
133
+ keywords: ['who', 'else', 'working', 'editing', 'coordinate', 'coordination', 'status', 'readiness', 'parallel', 'agents', 'agent', 'collide', 'colliding', 'swarm', 'conflict', 'conflicts', 'conflicting', 'conflicted', 'worktree', 'worktrees', 'active', 'overlap'],
64
134
  },
65
135
  {
66
136
  intent: 'Detect conflicts between parallel agents',
@@ -70,7 +140,7 @@ export const ROUTE_CATALOG = [
70
140
  what: 'Same-file + dependency overlaps across worktrees.',
71
141
  why: 'Two agents editing one repo: surface collisions pre-merge.',
72
142
  example: 'projscan collisions --format json',
73
- keywords: ['coordinate', 'coordination', 'parallel', 'agents', 'swarm', 'conflict', 'conflicts', 'collision', 'worktree', 'worktrees', 'overlap', 'simultaneous'],
143
+ keywords: ['coordinate', 'coordination', 'parallel', 'agents', 'swarm', 'conflict', 'conflicts', 'collision', 'collide', 'colliding', 'worktree', 'worktrees', 'overlap', 'overlapping', 'changes', 'simultaneous', 'avoid'],
74
144
  },
75
145
  {
76
146
  intent: 'Claim a file so other agents know who owns it',
@@ -80,7 +150,7 @@ export const ROUTE_CATALOG = [
80
150
  what: 'Advisory claims/leases over files, dirs, symbols.',
81
151
  why: 'Tell the swarm who is working where; warn on contention.',
82
152
  example: 'projscan claim add src/auth.ts --agent me',
83
- keywords: ['claim', 'lease', 'owns', 'ownership', 'who', 'reserve', 'lock', 'coordinate', 'parallel', 'agents', 'swarm'],
153
+ keywords: ['claim', 'claims', 'lease', 'leases', 'active', 'owns', 'ownership', 'who', 'reserve', 'lock', 'coordinate', 'parallel', 'agents', 'swarm'],
84
154
  },
85
155
  {
86
156
  intent: 'Decide the order to merge in-flight branches',
@@ -90,7 +160,7 @@ export const ROUTE_CATALOG = [
90
160
  what: 'Safe integration order + conflict hotspots.',
91
161
  why: 'Multiple in-flight worktrees: which to merge first.',
92
162
  example: 'projscan merge-risk --format json',
93
- keywords: ['merge', 'integrate', 'integration', 'order', 'sequence', 'first', 'conflict', 'hotspot', 'coordinate', 'parallel', 'swarm'],
163
+ keywords: ['merge', 'integrate', 'integration', 'order', 'sequence', 'first', 'branch', 'safest', 'conflict', 'hotspot', 'coordinate', 'parallel', 'swarm'],
94
164
  },
95
165
  {
96
166
  intent: 'Run a project health check',
@@ -100,7 +170,17 @@ export const ROUTE_CATALOG = [
100
170
  what: 'Health score + detected issues.',
101
171
  why: 'A quick overall health read on a repo.',
102
172
  example: 'projscan doctor --format json',
103
- keywords: ['health', 'doctor', 'score', 'issues', 'check', 'quality', 'lint'],
173
+ keywords: ['health', 'doctor', 'score', 'issues', 'check', 'quality', 'lint', 'dead', 'unused', 'orphaned', 'cleanup', 'clean', 'safe', 'safely', 'delete', 'remove'],
174
+ },
175
+ {
176
+ intent: 'See the quality and risk picture',
177
+ category: 'Health',
178
+ tool: 'projscan_quality_scorecard',
179
+ cli: 'projscan quality-scorecard',
180
+ what: 'Dimensioned quality view with health, security, tests, maintainability, coordination, and top risks.',
181
+ why: 'When a developer asks what is risky or wants the overall quality picture before choosing the next task.',
182
+ example: 'projscan quality-scorecard --format json',
183
+ keywords: ['quality', 'scorecard', 'risk', 'risks', 'risky', 'picture', 'dimensions'],
104
184
  },
105
185
  {
106
186
  intent: 'Search the codebase',
@@ -110,7 +190,7 @@ export const ROUTE_CATALOG = [
110
190
  what: 'Symbol / file / content search (BM25 + optional semantic).',
111
191
  why: 'Find where something is defined or used.',
112
192
  example: 'projscan search "auth token" --format json',
113
- keywords: ['search', 'find', 'locate', 'where', 'grep', 'lookup', 'symbol'],
193
+ keywords: ['search', 'find', 'locate', 'where', 'show', 'check', 'checked', 'test', 'tests', 'spec', 'specs', 'cover', 'covers', 'covering', 'code', 'handles', 'handled', 'handler', 'contains', 'logic', 'implemented', 'configured', 'created', 'creates', 'loaded', 'loader', 'parse', 'parses', 'parsed', 'middleware', 'api', 'apis', 'route', 'routes', 'router', 'routers', 'endpoint', 'endpoints', 'webhook', 'webhooks', 'openapi', 'swagger', 'trpc', 'grpc', 'protobuf', 'proto', 'protos', 'resolver', 'resolvers', 'docker', 'dockerfile', 'compose', 'containerfile', 'kubernetes', 'k8s', 'manifest', 'manifests', 'helm', 'chart', 'charts', 'terraform', 'tf', 'module', 'modules', 'cloudformation', 'cdk', 'pulumi', 'vercel', 'netlify', 'railway', 'fly', 'workflow', 'workflows', 'deploy', 'deploys', 'deployment', 'staging', 'production', 'password', 'reset', 'invite', 'invites', 'onboarding', 'flow', 'flows', 'csv', 'export', 'exports', 'audit', 'entries', 'refund', 'handling', 'payments', 'subscription', 'renewal', 'users', 'billing', 'settings', 'checkout', 'welcome', 'template', 'templates', 'copy', 'push', 'sms', 'verification', 'receipt', 'invoice', 'invoices', 'pdf', 'prisma', 'drizzle', 'typeorm', 'sequelize', 'sql', 'model', 'models', 'entity', 'entities', 'repository', 'repositories', 'dao', 'daos', 'saves', 'save', 'persist', 'persists', 'orders', 'call', 'calls', 'called', 'client', 'clients', 'sdk', 'sdks', 'integration', 'integrations', 'stripe', 'sendgrid', 's3', 'github', 'graphql', 'websocket', 'websockets', 'socket', 'sockets', 'connection', 'connections', 'rest', 'http', 'fetch', 'fetches', 'fetched', 'axios', 'external', 'service', 'services', 'upload', 'uploads', 'uploaded', 'sent', 'form', 'forms', 'submit', 'submits', 'submitted', 'loading', 'state', 'store', 'stores', 'stored', 'redux', 'slice', 'slices', 'selector', 'selectors', 'zustand', 'jotai', 'recoil', 'context', 'provider', 'providers', 'supplies', 'supplied', 'provides', 'provided', 'hook', 'hooks', 'react', 'mutation', 'mutations', 'sidebar', 'nav', 'navigation', 'menu', 'item', 'items', 'breadcrumb', 'breadcrumbs', 'layout', 'next', 'js', 'title', 'metadata', 'meta', 'empty', 'results', 'boundary', 'toast', 'notification', 'notifications', 'success', 'keyboard', 'shortcut', 'shortcuts', 'command', 'palette', 'action', 'actions', 'modal', 'opened', 'component', 'page', 'segment', 'segments', 'not', 'found', '404', 'i18n', 'translation', 'translations', 'aria', 'label', 'button', 'buttons', 'focus', 'trap', 'design', 'token', 'tokens', 'tailwind', 'theme', 'themes', 'css', 'global', 'imported', 'style', 'styles', 'styled', 'class', 'classes', 'dark', 'mode', 'breakpoint', 'breakpoints', 'color', 'colors', 'input', 'validation', 'validate', 'validates', 'validator', 'schema', 'schemas', 'zod', 'params', 'param', 'query', 'queries', 'json', 'serialize', 'serializes', 'serialization', 'response', 'format', 'formats', 'formatting', 'date', 'transaction', 'transactions', 'wrap', 'wraps', 'started', 'database', 'db', 'lock', 'locks', 'locking', 'row', 'optimistic', 'unique', 'uniqueness', 'enforced', 'email', 'pagination', 'cursor', 'cursors', 'builds', 'feature', 'features', 'flag', 'flags', 'env', 'var', 'vars', 'variable', 'variables', 'process', 'processes', 'used', 'controls', 'control', 'error', 'errors', 'message', 'messages', 'throw', 'throws', 'thrown', 'log', 'logs', 'logged', 'logging', 'metric', 'metrics', 'prometheus', 'analytics', 'event', 'events', 'alert', 'alerts', 'sentry', 'datadog', 'dashboard', 'dashboards', 'emit', 'emits', 'emitted', 'send', 'sends', 'initialize', 'initialise', 'init', 'rate', 'rates', 'limit', 'limits', 'limiting', 'throttle', 'throttling', 'cache', 'caches', 'cached', 'redis', 'invalidate', 'invalidates', 'invalidated', 'invalidation', 'retry', 'retries', 'retried', 'backoff', 'timeout', 'timeouts', 'request', 'requests', 'failed', 'set', 'sets', 'circuit', 'breaker', 'idempotency', 'idempotent', 'key', 'keys', 'signature', 'signatures', 'verified', 'verify', 'verification', 'debounce', 'debounced', 'protect', 'protects', 'permission', 'permissions', 'role', 'roles', 'access', 'admin', 'guard', 'guards', 'authorization', 'authorize', 'authorized', 'policy', 'policies', 'rbac', 'require', 'requires', 'required', 'login', 'seed', 'seeds', 'data', 'fixture', 'fixtures', 'mock', 'mocks', 'factory', 'factories', 'storybook', 'story', 'stories', 'render', 'renders', 'rendered', 'background', 'job', 'jobs', 'cron', 'scheduled', 'schedule', 'scheduler', 'schedulers', 'worker', 'workers', 'queue', 'queues', 'processor', 'processors', 'task', 'tasks', 'defined', 'config', 'configuration', 'tsconfig', 'typescript', 'vite', 'vitest', 'jest', 'babel', 'webpack', 'pnpm', 'yarn', 'npm', 'package', 'manager', 'workspace', 'workspaces', 'lockfile', 'lockfiles', 'path', 'paths', 'alias', 'aliases', 'define', 'defines', 'migration', 'migrations', 'generated', 'exist', 'exists', 'ran', 'file', 'files', 'owner', 'owners', 'ownership', 'owns', 'team', 'area', 'ask', 'help', 'knows', 'expert', 'experts', 'contact', 'contacts', 'doc', 'docs', 'document', 'documentation', 'documented', 'readme', 'examples', 'example', 'guide', 'grep', 'lookup', 'symbol'],
114
194
  },
115
195
  {
116
196
  intent: 'Trace dataflow / find injection risk',
@@ -120,7 +200,7 @@ export const ROUTE_CATALOG = [
120
200
  what: 'Source-to-sink dataflow risks.',
121
201
  why: 'Spot request-data reaching dangerous sinks.',
122
202
  example: 'projscan dataflow --format json',
123
- keywords: ['dataflow', 'taint', 'security', 'injection', 'source', 'sink', 'vulnerability', 'sql', 'xss'],
203
+ keywords: ['dataflow', 'taint', 'security', 'injection', 'source', 'sink', 'sinks', 'vulnerability', 'sql', 'xss', 'secret', 'secrets', 'expose', 'exposes', 'exposed', 'sanitize', 'sanitized', 'request', 'data', 'reach', 'reaches', 'exec', 'auth', 'bypass', 'pii', 'gdpr', 'compliance', 'personal', 'customer', 'email', 'emails', 'password', 'token', 'tokens', 'leak', 'leaks', 'logged', 'logging', 'log', 'logs', 'store', 'stores', 'retention', 'handled', 'handles', 'process', 'processes', 'processing'],
124
204
  },
125
205
  {
126
206
  intent: 'Get a fix for an issue',
@@ -132,6 +212,16 @@ export const ROUTE_CATALOG = [
132
212
  example: 'projscan fix-suggest <issue-id> --format json',
133
213
  keywords: ['fix', 'suggest', 'resolve', 'repair', 'remediate', 'how', 'issue'],
134
214
  },
215
+ {
216
+ intent: 'Explain an issue before fixing it',
217
+ category: 'Fix',
218
+ tool: 'projscan_explain_issue',
219
+ cli: 'projscan explain-issue',
220
+ what: 'Deep issue context: excerpt, related issues, past fixes, and suggested action.',
221
+ why: 'Before fixing an issue, understand the surrounding code and prior fix evidence.',
222
+ example: 'projscan explain-issue <issue-id> --format json',
223
+ keywords: ['explain', 'issue', 'context', 'why', 'details', 'surrounding'],
224
+ },
135
225
  {
136
226
  intent: 'Orient on first use / set up projscan',
137
227
  category: 'Onboarding',
@@ -150,7 +240,57 @@ export const ROUTE_CATALOG = [
150
240
  what: 'Ordered agent execution plan with verification.',
151
241
  why: 'Turn evidence into prioritized, verifiable tasks.',
152
242
  example: 'projscan workplan --mode bug_hunt --format json',
153
- keywords: ['plan', 'workplan', 'tasks', 'next', 'todo', 'prioritize', 'roadmap'],
243
+ keywords: ['plan', 'workplan', 'tasks', 'do', 'next', 'todo', 'prioritize', 'roadmap'],
244
+ },
245
+ {
246
+ intent: 'Brief the next agent',
247
+ category: 'Agent planning',
248
+ tool: 'projscan_agent_brief',
249
+ cli: 'projscan agent-brief',
250
+ what: 'Compact next-agent context packet with focus items, guardrails, and suggested next actions.',
251
+ why: 'When handing work to another agent or developer and they need enough context to resume quickly.',
252
+ example: 'projscan agent-brief --intent next_agent --format json',
253
+ keywords: ['brief', 'handoff', 'next', 'agent', 'context', 'resume', 'guardrails'],
254
+ },
255
+ {
256
+ intent: 'Resume from session context',
257
+ category: 'Agent planning',
258
+ tool: 'projscan_session',
259
+ cli: 'projscan session',
260
+ what: 'Durable session summary, touched files, and event log.',
261
+ why: 'When resuming work or checking what prior agents and tools touched before editing again.',
262
+ example: 'projscan session touched --format json',
263
+ keywords: ['session', 'touched', 'touch', 'resume', 'leave', 'left', 'off', 'last', 'agent', 'previous', 'changed', 'asleep', 'slept', 'away', 'offline', 'events', 'history'],
264
+ },
265
+ {
266
+ intent: 'Diagnose failing CI or tests',
267
+ category: 'Regression',
268
+ tool: 'projscan_regression_plan',
269
+ cli: 'projscan regression-plan',
270
+ what: 'Focused verification matrix for failing CI, tests, and regression signals.',
271
+ why: 'When a PR or branch has failing CI/tests, choose the smallest proof loop before editing.',
272
+ example: 'projscan regression-plan --level focused --format json',
273
+ keywords: ['ci', 'github', 'action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines', 'job', 'jobs', 'fail', 'build', 'builds', 'failing', 'lint', 'typecheck', 'typechecking', 'install', 'failed', 'error', 'errors', 'failure', 'failures', 'debug', 'stack', 'trace', 'production', 'prod', 'down', 'outage', 'triage', 'incident', 'runtime', 'crash', 'crashes', 'crashing', 'connection', 'refused', 'port', 'ports', 'eaddrinuse', 'listen', 'address', 'permission', 'denied', 'enoent', 'eresolve', 'peer', 'root', 'cause', 'returning', 'returns', 'log', 'logs', '500', '502', '503', '504', '404', '403', '401', 'smoke', 'regression', 'focused', 'full', 'verification', 'verify', 'checks', 'proof', 'prove', 'commands', 'command', 'works', 'push', 'pushing', 'run', 'rerun', 'test', 'tests', 'slow', 'slower', 'speed', 'speedup', 'faster', 'benchmark', 'benchmarks', 'reproduce', 'reproduces', 'reproducing', 'flake', 'flaky', 'flakes', 'intermittent', 'intermittently', 'nondeterministic', 'nondeterminism', 'race', 'condition', 'stabilize', 'stabilise', 'quarantine', 'pr'],
274
+ },
275
+ {
276
+ intent: 'Find the scariest untested files',
277
+ category: 'Tests',
278
+ tool: 'projscan_coverage',
279
+ cli: 'projscan coverage',
280
+ what: 'Coverage gaps joined with hotspot risk.',
281
+ why: 'Pick the next file that most deserves regression coverage.',
282
+ example: 'projscan coverage --format json',
283
+ keywords: ['coverage', 'scariest', 'untested', 'uncovered', 'test', 'tests', 'file', 'files', 'no', 'missing', 'without', 'gap', 'gaps'],
284
+ },
285
+ {
286
+ intent: 'Prepare release readiness',
287
+ category: 'Release',
288
+ tool: 'projscan_release_train',
289
+ cli: 'projscan release-train',
290
+ what: 'Product-line release readiness evidence and next release actions.',
291
+ why: 'Before tagging, deploying, publishing, or approving a release, collect readiness evidence and blockers.',
292
+ example: 'projscan release-train --format json',
293
+ keywords: ['release', 'releasing', 'deploy', 'deploying', 'deployed', 'deployment', 'ship', 'shipping', 'publish', 'tag', 'changelog', 'sbom', 'package', 'readiness', 'prepare', 'check', 'note', 'notes', 'draft', 'entry', 'summarize', 'summary', 'change', 'changes', 'changed', 'since', 'last'],
154
294
  },
155
295
  {
156
296
  intent: 'Find bugs to fix',
@@ -160,7 +300,47 @@ export const ROUTE_CATALOG = [
160
300
  what: 'Ranked fix queue from doctor/preflight/hotspots.',
161
301
  why: 'Decide which bugs to tackle first.',
162
302
  example: 'projscan bug-hunt --format json',
163
- keywords: ['bug', 'bugs', 'hunt', 'queue', 'defect', 'broken'],
303
+ keywords: ['bug', 'bugs', 'hunt', 'queue', 'defect', 'broken', 'first', 'fastest', 'quickest', 'quick', 'smallest', 'small', 'low', 'lowest', 'improve', 'improvement', 'useful', 'easy', 'beginner', 'starter', 'intern', 'interns', 'task', 'tasks', 'five', 'minutes', 'today', 'find', 'win', 'wins', 'fix', 'pr'],
304
+ },
305
+ {
306
+ intent: 'Inventory declared dependencies',
307
+ category: 'Dependencies',
308
+ tool: 'projscan_dependencies',
309
+ cli: 'projscan dependencies',
310
+ what: 'Declared production/dev dependency counts, risks, and workspace breakdown.',
311
+ why: 'When a developer asks what packages the repo uses before changing supply-chain or package code.',
312
+ example: 'projscan dependencies --format json',
313
+ keywords: ['dependencies', 'dependency', 'deps', 'package', 'packages', 'inventory', 'declared', 'supply-chain', 'bundle', 'bundles', 'size', 'sizes', 'large', 'heavy', 'bloat', 'bloated', 'weight', 'footprint', 'reduce', 'slim', 'license', 'licenses', 'gpl', 'copyleft', 'third', 'party', 'notice', 'notices', 'open', 'source', 'compliance'],
314
+ },
315
+ {
316
+ intent: 'Map monorepo workspaces',
317
+ category: 'Dependencies',
318
+ tool: 'projscan_workspaces',
319
+ cli: 'projscan workspaces',
320
+ what: 'Detected monorepo workspace packages with names, paths, and versions.',
321
+ why: 'When a developer asks which workspace/package owns code or where package-scoped work should start.',
322
+ example: 'projscan workspaces --format json',
323
+ keywords: ['workspace', 'workspaces', 'monorepo', 'package', 'packages', 'map', 'list', 'owns', 'contains', 'put', 'change'],
324
+ },
325
+ {
326
+ intent: 'Preview package upgrade impact',
327
+ category: 'Dependencies',
328
+ tool: 'projscan_upgrade',
329
+ cli: 'projscan upgrade',
330
+ what: 'Offline upgrade impact: version drift, CHANGELOG breaking markers, and importers.',
331
+ why: 'Before bumping a package, see who imports it and whether the local changelog signals breakage.',
332
+ example: 'projscan upgrade chalk --format json',
333
+ keywords: ['upgrade', 'bump', 'update', 'remove', 'drop', 'uninstall', 'package', 'dependency', 'dependencies', 'version', 'breaking'],
334
+ },
335
+ {
336
+ intent: 'Audit dependency vulnerabilities',
337
+ category: 'Dependencies',
338
+ tool: 'projscan_audit',
339
+ cli: 'projscan audit',
340
+ what: 'npm audit vulnerability summary with package-scoped findings.',
341
+ why: 'When a developer asks about CVEs, vulnerable packages, or dependency security.',
342
+ example: 'projscan audit --format json',
343
+ keywords: ['audit', 'cve', 'cves', 'vulnerable', 'vulnerability', 'vulnerabilities', 'security', 'secure', 'safe', 'dependency', 'dependencies', 'package', 'packages', 'npm'],
164
344
  },
165
345
  {
166
346
  intent: 'Check dependency health / outdated / audit',
@@ -175,7 +355,7 @@ export const ROUTE_CATALOG = [
175
355
  ];
176
356
  const STOPWORDS = new Set([
177
357
  'the', 'a', 'an', 'i', 'to', 'my', 'is', 'it', 'of', 'in', 'on', 'and', 'or', 'for', 'this', 'that',
178
- 'do', 'how', 'what', 'me', 'we', 'with', 'can', 'should', 'if', 'be', 'am', 'are',
358
+ 'how', 'what', 'me', 'we', 'with', 'can', 'should', 'if', 'be', 'am', 'are',
179
359
  ]);
180
360
  function tokenize(text) {
181
361
  return text
@@ -191,23 +371,2007 @@ function tokenize(text) {
191
371
  export function routeIntent(intent) {
192
372
  if (!intent || intent.trim() === '') {
193
373
  const grouped = [...ROUTE_CATALOG].sort((a, b) => a.category.localeCompare(b.category));
194
- return { intent: null, matched: grouped.length > 0, matches: grouped };
374
+ return {
375
+ intent: null,
376
+ matched: grouped.length > 0,
377
+ matches: grouped.map((entry, index) => routeMatch(entry, index + 1, [])),
378
+ };
195
379
  }
196
380
  const tokens = new Set(tokenize(intent));
381
+ const hasFilePath = hasFilePathTarget(intent);
382
+ const hasPackageRemoval = !hasFilePath && hasPackageRemovalTarget(intent);
383
+ const hasPackageChange = !hasFilePath && hasPackageChangeTarget(intent);
384
+ const hasEnvVar = hasEnvVarTarget(intent);
385
+ const hasQuotedText = hasQuotedTextTarget(intent);
197
386
  const scored = ROUTE_CATALOG.map((entry, index) => {
198
- let score = 0;
199
- for (const kw of entry.keywords) {
200
- if (tokens.has(kw))
201
- score += 1;
202
- }
203
- return { entry, score, index };
387
+ const matchedKeywords = entry.keywords.filter((kw) => routeKeywordMatches(entry, kw, tokens, hasFilePath, hasPackageRemoval, hasPackageChange, hasEnvVar, hasQuotedText));
388
+ return { entry, score: routeScore(entry, matchedKeywords), matchedKeywords, index };
204
389
  })
205
390
  .filter((s) => s.score > 0)
206
391
  .sort((a, b) => b.score - a.score || a.index - b.index);
207
392
  return {
208
393
  intent,
209
394
  matched: scored.length > 0,
210
- matches: scored.map((s) => s.entry),
395
+ matches: scored.map((s, index) => routeMatch(s.entry, index + 1, s.matchedKeywords)),
396
+ };
397
+ }
398
+ function routeKeywordMatches(entry, keyword, tokens, hasFilePath, hasPackageRemoval, hasPackageChange, hasEnvVar, hasQuotedText) {
399
+ if (!tokens.has(keyword))
400
+ return false;
401
+ if (entry.tool === 'projscan_privacy_check' && searchIntegrationContextMatches(tokens))
402
+ return false;
403
+ if (entry.tool === 'projscan_privacy_check' && searchUiInteractionContextMatches(tokens))
404
+ return false;
405
+ if (entry.tool === 'projscan_privacy_check' && !privacyCheckKeywordMatches(keyword, tokens))
406
+ return false;
407
+ if (entry.tool === 'projscan_understand' && !understandKeywordMatches(keyword, tokens))
408
+ return false;
409
+ if (entry.tool === 'projscan_understand' && searchEnvLookupContextMatches(tokens, hasEnvVar))
410
+ return false;
411
+ if (entry.tool === 'projscan_understand' && searchQuotedDebugTextContextMatches(tokens, hasQuotedText))
412
+ return false;
413
+ if (entry.tool === 'projscan_understand' && searchTestDataContextMatches(tokens))
414
+ return false;
415
+ if (entry.tool === 'projscan_understand' && searchDataContractContextMatches(tokens))
416
+ return false;
417
+ if (entry.tool === 'projscan_understand' && searchUiInteractionContextMatches(tokens))
418
+ return false;
419
+ if (entry.tool === 'projscan_understand' && searchIntegrationContextMatches(tokens))
420
+ return false;
421
+ if (entry.tool === 'projscan_understand' && searchApiContractContextMatches(tokens))
422
+ return false;
423
+ if (entry.tool === 'projscan_understand' && searchInfraArtifactContextMatches(tokens) && !localServiceSetupCommandContextMatches(tokens))
424
+ return false;
425
+ if (entry.tool === 'projscan_understand' && searchDomainWorkflowContextMatches(tokens))
426
+ return false;
427
+ if (entry.tool === 'projscan_understand' && searchCommunicationArtifactContextMatches(tokens))
428
+ return false;
429
+ if (entry.tool === 'projscan_understand' && searchStateManagementContextMatches(tokens))
430
+ return false;
431
+ if (entry.tool === 'projscan_understand' && searchDataAccessContextMatches(tokens))
432
+ return false;
433
+ if (entry.tool === 'projscan_understand' && searchNavigationLayoutContextMatches(tokens))
434
+ return false;
435
+ if (entry.tool === 'projscan_understand' && searchFrontendPageRouteContextMatches(tokens))
436
+ return false;
437
+ if (entry.tool === 'projscan_understand' && searchStyleSystemContextMatches(tokens))
438
+ return false;
439
+ if (entry.tool === 'projscan_review' && !reviewKeywordMatches(keyword, tokens))
440
+ return false;
441
+ if (entry.tool === 'projscan_coupling' && !couplingKeywordMatches(keyword, tokens))
442
+ return false;
443
+ if (entry.tool === 'projscan_explain_issue' && styleSystemFailureContextMatches(tokens))
444
+ return false;
445
+ if (entry.tool === 'projscan_explain_issue' && toolingFailureContextMatches(tokens))
446
+ return false;
447
+ if (entry.tool === 'projscan_dataflow' && !dataflowKeywordMatches(keyword, tokens))
448
+ return false;
449
+ if (entry.tool === 'projscan_dataflow' && domainWorkflowPlanningContextMatches(tokens))
450
+ return false;
451
+ if (entry.tool === 'projscan_dataflow' && stateManagementPlanningContextMatches(tokens))
452
+ return false;
453
+ if (entry.tool === 'projscan_dataflow' && dataAccessPlanningContextMatches(tokens))
454
+ return false;
455
+ if (entry.tool === 'projscan_dataflow' && ['process', 'processes'].includes(keyword) && searchEnvLookupContextMatches(tokens, hasEnvVar))
456
+ return false;
457
+ if (entry.tool === 'projscan_dataflow' && searchQuotedDebugTextContextMatches(tokens, hasQuotedText) && !explicitDataflowContextMatches(tokens))
458
+ return false;
459
+ if (entry.tool === 'projscan_dataflow' && searchBackgroundWorkContextMatches(tokens) && !explicitDataflowContextMatches(tokens))
460
+ return false;
461
+ if (entry.tool === 'projscan_dataflow' && searchObservabilityContextMatches(tokens) && !explicitDataflowContextMatches(tokens))
462
+ return false;
463
+ if (entry.tool === 'projscan_dataflow' && searchTestDataContextMatches(tokens) && !explicitDataflowContextMatches(tokens))
464
+ return false;
465
+ if (entry.tool === 'projscan_dataflow' && searchReliabilityContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
466
+ return false;
467
+ if (entry.tool === 'projscan_dataflow' && searchDataContractContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
468
+ return false;
469
+ if (entry.tool === 'projscan_dataflow' && searchUiInteractionContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
470
+ return false;
471
+ if (entry.tool === 'projscan_dataflow' && searchIntegrationContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
472
+ return false;
473
+ if (entry.tool === 'projscan_dataflow' && searchApiContractContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
474
+ return false;
475
+ if (entry.tool === 'projscan_dataflow' && searchInfraArtifactContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
476
+ return false;
477
+ if (entry.tool === 'projscan_dataflow' && searchDomainWorkflowContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
478
+ return false;
479
+ if (entry.tool === 'projscan_dataflow' && searchCommunicationArtifactContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
480
+ return false;
481
+ if (entry.tool === 'projscan_dataflow' && searchStateManagementContextMatches(tokens) && !explicitDataflowRiskContextMatches(tokens))
482
+ return false;
483
+ if (entry.tool === 'projscan_dataflow' && searchDataAccessContextMatches(tokens))
484
+ return false;
485
+ if (entry.tool === 'projscan_dataflow' && searchNavigationLayoutContextMatches(tokens))
486
+ return false;
487
+ if (entry.tool === 'projscan_dataflow' && searchFrontendPageRouteContextMatches(tokens))
488
+ return false;
489
+ if (entry.tool === 'projscan_dataflow' && searchStyleSystemContextMatches(tokens))
490
+ return false;
491
+ if (hasFilePath && keyword === 'start' && ['projscan_hotspots', 'projscan_start'].includes(entry.tool))
492
+ return false;
493
+ if (entry.tool === 'projscan_file' && keyword === 'read')
494
+ return hasFilePath;
495
+ if (entry.tool === 'projscan_file' && ['review', 'reviewer', 'reviewers'].includes(keyword))
496
+ return hasFilePath;
497
+ if (entry.tool === 'projscan_file' && keyword === 'owns')
498
+ return hasFilePath;
499
+ if (entry.tool === 'projscan_file' && ['risk', 'risks', 'risky', 'dangerous'].includes(keyword))
500
+ return hasFilePath;
501
+ if (entry.tool === 'projscan_file' && ['last', 'touched', 'touch', 'changed', 'recently', 'history', 'author', 'authors', 'blame'].includes(keyword)) {
502
+ return hasFilePath && fileHistoryContextMatches(tokens);
503
+ }
504
+ if (entry.tool === 'projscan_file' && ['add', 'write', 'coverage', 'covered', 'uncovered', 'test', 'tests'].includes(keyword)) {
505
+ return hasFilePath && fileTestContextMatches(tokens);
506
+ }
507
+ if (entry.tool === 'projscan_file' && keyword === 'file' && searchConfigLookupContextMatches(tokens))
508
+ return false;
509
+ if (entry.tool === 'projscan_file' && keyword === 'file' && searchToolingConfigContextMatches(tokens))
510
+ return false;
511
+ if (entry.tool === 'projscan_hotspots' && searchUiInteractionContextMatches(tokens))
512
+ return false;
513
+ if (entry.tool === 'projscan_hotspots' && searchNavigationLayoutContextMatches(tokens))
514
+ return false;
515
+ if (entry.tool === 'projscan_hotspots' && searchFrontendPageRouteContextMatches(tokens))
516
+ return false;
517
+ if (entry.tool === 'projscan_hotspots' && searchStyleSystemContextMatches(tokens))
518
+ return false;
519
+ if (entry.tool === 'projscan_hotspots' && ['where', 'start'].includes(keyword) && !hotspotWhereContextMatches(tokens, hasFilePath))
520
+ return false;
521
+ if (entry.tool === 'projscan_impact' && ['delete', 'remove'].includes(keyword) && !hasFilePath && !impactDeleteContextMatches(tokens))
522
+ return false;
523
+ if (entry.tool === 'projscan_impact' && hasEnvVar && ['depends', 'affect', 'callers', 'used', 'usage', 'referenced', 'called', 'api', 'apis'].includes(keyword))
524
+ return false;
525
+ if (entry.tool === 'projscan_impact' && ['used', 'usage', 'referenced', 'called'].includes(keyword) && searchTestDataContextMatches(tokens))
526
+ return false;
527
+ if (entry.tool === 'projscan_impact' && ['used', 'usage', 'referenced', 'called'].includes(keyword) && searchReliabilityContextMatches(tokens))
528
+ return false;
529
+ if (entry.tool === 'projscan_impact' && ['drop', 'schema', 'table', 'column', 'database', 'db', 'migration', 'migrations'].includes(keyword) && searchDataContractContextMatches(tokens))
530
+ return false;
531
+ if (entry.tool === 'projscan_impact' && ['drop', 'schema', 'table', 'column', 'database', 'db', 'migration', 'migrations'].includes(keyword) && !impactDatabaseContextMatches(tokens))
532
+ return false;
533
+ if (entry.tool === 'projscan_impact' && impactApiKeywordMatches(keyword) && !impactApiContextMatches(tokens))
534
+ return false;
535
+ if (entry.tool === 'projscan_impact' && ['revert', 'rollback', 'undo', 'backout', 'back', 'out', 'recover'].includes(keyword) && !impactRollbackContextMatches(keyword, tokens))
536
+ return false;
537
+ if (entry.tool === 'projscan_semantic_graph' && ['uses', 'depend', 'depends', 'installed'].includes(keyword) && !packageDependencyLookupContextMatches(tokens, hasFilePath))
538
+ return false;
539
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchBackgroundWorkContextMatches(tokens))
540
+ return false;
541
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchTestDataContextMatches(tokens))
542
+ return false;
543
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchAuthorizationContextMatches(tokens))
544
+ return false;
545
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchDataContractContextMatches(tokens))
546
+ return false;
547
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchDataAccessContextMatches(tokens))
548
+ return false;
549
+ if (entry.tool === 'projscan_semantic_graph' && ['defined', 'definition'].includes(keyword) && searchStyleSystemContextMatches(tokens))
550
+ return false;
551
+ if (entry.tool === 'projscan_doctor' && ['safe', 'safely', 'delete', 'remove'].includes(keyword) && !doctorCleanupDeleteContextMatches(tokens, hasFilePath, hasPackageRemoval))
552
+ return false;
553
+ if (entry.tool === 'projscan_evidence_pack' && !evidencePackKeywordMatches(keyword, tokens))
554
+ return false;
555
+ if (entry.tool === 'projscan_search' && ['search', 'find', 'locate', 'where', 'show', 'code'].includes(keyword) && doctorCleanupDiscoveryContextMatches(tokens))
556
+ return false;
557
+ if (entry.tool === 'projscan_search' && ['test', 'tests', 'spec', 'specs', 'cover', 'covers', 'covering'].includes(keyword) && !searchTestLocationContextMatches(tokens, hasFilePath))
558
+ return false;
559
+ if (entry.tool === 'projscan_search' && ['feature', 'features', 'flag', 'flags'].includes(keyword) && !searchFeatureFlagContextMatches(tokens))
560
+ return false;
561
+ if (entry.tool === 'projscan_search' && ['env', 'var', 'vars', 'variable', 'variables', 'process', 'used', 'controls', 'control'].includes(keyword) && !searchEnvLookupContextMatches(tokens, hasEnvVar) && !searchTestDataContextMatches(tokens))
562
+ return false;
563
+ if (entry.tool === 'projscan_search' && ['error', 'errors', 'message', 'messages', 'throw', 'throws', 'thrown', 'log', 'logs', 'logged', 'logging'].includes(keyword) && !searchQuotedDebugTextContextMatches(tokens, hasQuotedText) && !searchObservabilityContextMatches(tokens) && !searchUiInteractionContextMatches(tokens) && !searchDomainWorkflowContextMatches(tokens))
564
+ return false;
565
+ if (entry.tool === 'projscan_search' && keyword === 'check' && !searchObservabilityContextMatches(tokens))
566
+ return false;
567
+ if (entry.tool === 'projscan_search' && ['metric', 'metrics', 'prometheus', 'analytics', 'event', 'events', 'alert', 'alerts', 'sentry', 'datadog', 'dashboard', 'dashboards', 'emit', 'emits', 'emitted', 'send', 'sends', 'initialize', 'initialise', 'init'].includes(keyword) && !searchObservabilityContextMatches(tokens) && !searchIntegrationContextMatches(tokens) && !searchCommunicationArtifactContextMatches(tokens) && !searchNavigationLayoutContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens))
568
+ return false;
569
+ if (entry.tool === 'projscan_search' && ['input', 'validation', 'validate', 'validates', 'validator', 'schema', 'schemas', 'zod', 'params', 'param', 'query', 'queries', 'parsed', 'json', 'serialize', 'serializes', 'serialization', 'response', 'format', 'formats', 'formatting', 'date', 'transaction', 'transactions', 'wrap', 'wraps', 'started', 'database', 'db', 'lock', 'locks', 'locking', 'row', 'optimistic', 'unique', 'uniqueness', 'enforced', 'email', 'pagination', 'cursor', 'cursors', 'builds'].includes(keyword) && !searchDataContractContextMatches(tokens) && !searchIntegrationContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchCommunicationArtifactContextMatches(tokens) && !searchStateManagementContextMatches(tokens) && !searchDataAccessContextMatches(tokens))
570
+ return false;
571
+ if (entry.tool === 'projscan_search' && ['openapi', 'swagger', 'trpc', 'grpc', 'protobuf', 'proto', 'protos', 'router', 'routers', 'resolver', 'resolvers'].includes(keyword) && !searchApiContractContextMatches(tokens))
572
+ return false;
573
+ if (entry.tool === 'projscan_search' && ['docker', 'dockerfile', 'compose', 'containerfile', 'kubernetes', 'k8s', 'manifest', 'manifests', 'helm', 'chart', 'charts', 'terraform', 'tf', 'module', 'modules', 'cloudformation', 'cdk', 'pulumi', 'vercel', 'netlify', 'railway', 'fly', 'workflow', 'workflows', 'deploy', 'deploys', 'deployment', 'staging', 'production'].includes(keyword) && !searchInfraArtifactContextMatches(tokens) && !searchStyleSystemContextMatches(tokens))
574
+ return false;
575
+ if (entry.tool === 'projscan_search' && ['password', 'reset', 'invite', 'invites', 'onboarding', 'flow', 'flows', 'csv', 'export', 'exports', 'audit', 'entries', 'refund', 'handling', 'payments', 'subscription', 'renewal', 'users'].includes(keyword) && !searchDomainWorkflowContextMatches(tokens) && !searchDataAccessContextMatches(tokens))
576
+ return false;
577
+ if (entry.tool === 'projscan_search' && ['welcome', 'template', 'templates', 'copy', 'push', 'sms', 'verification', 'receipt', 'invoice', 'pdf'].includes(keyword) && !searchCommunicationArtifactContextMatches(tokens))
578
+ return false;
579
+ if (entry.tool === 'projscan_search' && ['store', 'stores', 'stored', 'redux', 'slice', 'slices', 'selector', 'selectors', 'zustand', 'jotai', 'recoil', 'context', 'provider', 'providers', 'supplies', 'supplied', 'provides', 'provided', 'hook', 'hooks', 'react', 'mutation', 'mutations', 'fetches', 'fetched', 'invoices'].includes(keyword) && !searchStateManagementContextMatches(tokens) && !searchDataAccessContextMatches(tokens))
580
+ return false;
581
+ if (entry.tool === 'projscan_search' && ['prisma', 'drizzle', 'typeorm', 'sequelize', 'sql', 'model', 'models', 'entity', 'entities', 'repository', 'repositories', 'dao', 'daos', 'saves', 'save', 'persist', 'persists', 'orders'].includes(keyword) && !searchDataAccessContextMatches(tokens))
582
+ return false;
583
+ if (entry.tool === 'projscan_search' && ['sidebar', 'nav', 'navigation', 'menu', 'item', 'items', 'breadcrumb', 'breadcrumbs', 'layout', 'next', 'js', 'title', 'metadata', 'meta', 'billing', 'settings', 'checkout'].includes(keyword) && !searchNavigationLayoutContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens))
584
+ return false;
585
+ if (entry.tool === 'projscan_search' && ['segment', 'segments', 'not', 'found', '404'].includes(keyword) && !searchFrontendPageRouteContextMatches(tokens))
586
+ return false;
587
+ if (entry.tool === 'projscan_search' && ['design', 'token', 'tokens', 'tailwind', 'theme', 'themes', 'css', 'global', 'imported', 'style', 'styles', 'styled', 'class', 'classes', 'dark', 'mode', 'breakpoint', 'breakpoints', 'color', 'colors'].includes(keyword) && !searchStyleSystemContextMatches(tokens) && !searchStateManagementContextMatches(tokens))
588
+ return false;
589
+ if (entry.tool === 'projscan_search' && ['form', 'forms', 'submit', 'submits', 'submitted', 'loading', 'state', 'empty', 'results', 'boundary', 'toast', 'notification', 'notifications', 'success', 'keyboard', 'shortcut', 'shortcuts', 'command', 'palette', 'action', 'actions', 'modal', 'opened', 'component', 'page', 'i18n', 'translation', 'translations', 'aria', 'label', 'button', 'buttons', 'focus', 'trap'].includes(keyword) && !searchUiInteractionContextMatches(tokens) && !searchIntegrationContextMatches(tokens) && !searchCommunicationArtifactContextMatches(tokens) && !searchStateManagementContextMatches(tokens) && !searchNavigationLayoutContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens) && !searchStyleSystemContextMatches(tokens))
590
+ return false;
591
+ if (entry.tool === 'projscan_search' && ['call', 'calls', 'called', 'client', 'clients', 'sdk', 'sdks', 'integration', 'integrations', 'stripe', 'sendgrid', 's3', 'github', 'graphql', 'websocket', 'websockets', 'socket', 'sockets', 'connection', 'connections', 'rest', 'http', 'fetch', 'axios', 'external', 'service', 'services', 'upload', 'uploads', 'uploaded', 'sent', 'opened'].includes(keyword) && !searchIntegrationContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchInfraArtifactContextMatches(tokens) && !searchStateManagementContextMatches(tokens))
592
+ return false;
593
+ if (entry.tool === 'projscan_search' && ['rate', 'rates', 'limit', 'limits', 'limiting', 'throttle', 'throttling', 'cache', 'caches', 'cached', 'redis', 'invalidate', 'invalidates', 'invalidated', 'invalidation', 'retry', 'retries', 'retried', 'backoff', 'timeout', 'timeouts', 'request', 'requests', 'failed', 'set', 'sets', 'circuit', 'breaker', 'idempotency', 'idempotent', 'key', 'keys', 'signature', 'signatures', 'verified', 'verify', 'verification', 'debounce', 'debounced', 'protect', 'protects'].includes(keyword) && !searchReliabilityContextMatches(tokens) && !searchDataContractContextMatches(tokens) && !searchCommunicationArtifactContextMatches(tokens) && !searchNavigationLayoutContextMatches(tokens))
594
+ return false;
595
+ if (entry.tool === 'projscan_search' && ['permission', 'permissions', 'checked', 'role', 'roles', 'access', 'admin', 'guard', 'guards', 'authorization', 'authorize', 'authorized', 'policy', 'policies', 'rbac', 'require', 'requires', 'required', 'login'].includes(keyword) && !searchAuthorizationContextMatches(tokens))
596
+ return false;
597
+ if (entry.tool === 'projscan_search' && ['seed', 'seeds', 'data', 'fixture', 'fixtures', 'mock', 'mocks', 'factory', 'factories', 'storybook', 'story', 'stories', 'render', 'renders', 'rendered'].includes(keyword) && !searchTestDataContextMatches(tokens) && !searchUiInteractionContextMatches(tokens) && !searchNavigationLayoutContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens))
598
+ return false;
599
+ if (entry.tool === 'projscan_search' && ['background', 'job', 'jobs', 'cron', 'scheduled', 'schedule', 'scheduler', 'schedulers', 'worker', 'workers', 'queue', 'queues', 'processor', 'processors', 'task', 'tasks', 'defined', 'processes'].includes(keyword) && !searchBackgroundWorkContextMatches(tokens) && !searchTestDataContextMatches(tokens) && !searchAuthorizationContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchStyleSystemContextMatches(tokens))
600
+ return false;
601
+ if (entry.tool === 'projscan_search' && ['config', 'configuration', 'alias', 'aliases', 'define', 'defines'].includes(keyword) && !searchConfigLookupContextMatches(tokens) && !searchToolingConfigContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchInfraArtifactContextMatches(tokens))
602
+ return false;
603
+ if (entry.tool === 'projscan_search' && ['tsconfig', 'typescript', 'vite', 'vitest', 'jest', 'babel', 'webpack', 'pnpm', 'yarn', 'npm', 'package', 'manager', 'workspace', 'workspaces', 'lockfile', 'lockfiles', 'path', 'paths'].includes(keyword) && !searchToolingConfigContextMatches(tokens) && !searchConfigLookupContextMatches(tokens))
604
+ return false;
605
+ if (entry.tool === 'projscan_search' && ['api', 'apis', 'route', 'routes', 'endpoint', 'endpoints'].includes(keyword) && !searchRouteHandlerContextMatches(tokens) && !searchAuthorizationContextMatches(tokens) && !searchDataContractContextMatches(tokens) && !searchIntegrationContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchNavigationLayoutContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens))
606
+ return false;
607
+ if (entry.tool === 'projscan_search' && ['migration', 'migrations', 'generated', 'exist', 'exists', 'ran', 'show', 'file', 'files'].includes(keyword) && !searchMigrationLookupContextMatches(tokens) && !searchGeneratedContextMatches(tokens) && !searchConfigLookupContextMatches(tokens) && !searchToolingConfigContextMatches(tokens) && !searchBackgroundWorkContextMatches(tokens) && !searchTestDataContextMatches(tokens) && !searchDataContractContextMatches(tokens) && !searchCommunicationArtifactContextMatches(tokens))
608
+ return false;
609
+ if (entry.tool === 'projscan_search' && ['code', 'handles', 'handled', 'handler', 'contains', 'logic', 'implemented', 'configured', 'created', 'creates', 'loaded', 'loader', 'parse', 'parses', 'parsed', 'middleware'].includes(keyword) && !searchCodeLocationContextMatches(tokens) && !searchGeneratedContextMatches(tokens) && !searchRouteHandlerContextMatches(tokens) && !searchReliabilityContextMatches(tokens) && !searchDataContractContextMatches(tokens) && !searchUiInteractionContextMatches(tokens) && !searchIntegrationContextMatches(tokens) && !searchApiContractContextMatches(tokens) && !searchDomainWorkflowContextMatches(tokens) && !searchDataAccessContextMatches(tokens) && !searchFrontendPageRouteContextMatches(tokens))
610
+ return false;
611
+ if (entry.tool === 'projscan_search' && ['owner', 'owners', 'ownership', 'owns', 'team', 'area', 'ask', 'help', 'knows', 'expert', 'experts', 'contact', 'contacts'].includes(keyword) && !searchOwnershipContextMatches(tokens, hasFilePath) && !searchDomainWorkflowContextMatches(tokens))
612
+ return false;
613
+ if (entry.tool === 'projscan_search' && ['doc', 'docs', 'document', 'documentation', 'documented', 'readme', 'examples', 'example', 'guide'].includes(keyword) && !searchDocumentationContextMatches(tokens) && !searchApiContractContextMatches(tokens))
614
+ return false;
615
+ if (entry.tool === 'projscan_pr_diff' && !prDiffKeywordMatches(keyword, tokens))
616
+ return false;
617
+ if (entry.tool === 'projscan_coverage' && !coverageKeywordMatches(keyword, tokens))
618
+ return false;
619
+ if (entry.tool === 'projscan_dependencies' && !dependenciesKeywordMatches(keyword, tokens))
620
+ return false;
621
+ if (['projscan_audit', 'projscan_outdated', 'projscan_upgrade'].includes(entry.tool) && ['dependency', 'dependencies', 'package', 'packages'].includes(keyword) && dependencyCycleContextMatches(tokens))
622
+ return false;
623
+ if (entry.tool === 'projscan_audit' && !auditKeywordMatches(keyword, tokens))
624
+ return false;
625
+ if (entry.tool === 'projscan_workspaces' && searchToolingConfigContextMatches(tokens))
626
+ return false;
627
+ if (entry.tool === 'projscan_workspaces' && !workspacesKeywordMatches(keyword, tokens))
628
+ return false;
629
+ if (entry.tool === 'projscan_upgrade' && keyword === 'update' && !hasPackageChange)
630
+ return false;
631
+ if (entry.tool === 'projscan_upgrade' && keyword === 'package' && packageImporterContextMatches(tokens))
632
+ return false;
633
+ if (entry.tool === 'projscan_upgrade' && ['package', 'dependency', 'dependencies', 'npm'].includes(keyword) && regressionLocalSetupContextMatches(tokens))
634
+ return false;
635
+ if (entry.tool === 'projscan_upgrade' && ['remove', 'drop', 'uninstall'].includes(keyword) && !hasPackageRemoval)
636
+ return false;
637
+ if (entry.tool === 'projscan_outdated' && keyword === 'npm' && !outdatedNpmContextMatches(tokens))
638
+ return false;
639
+ if (entry.tool === 'projscan_preflight' && keyword === 'ready' && !preflightReadyContextMatches(tokens))
640
+ return false;
641
+ if (entry.tool === 'projscan_preflight' && ['risk', 'risks'].includes(keyword) && !preflightRiskContextMatches(tokens))
642
+ return false;
643
+ if (entry.tool === 'projscan_preflight' && ['rebase', 'rebasing', 'conflict', 'conflicts', 'resolve', 'resolving', 'wrong', 'stuck'].includes(keyword) && !preflightBranchRecoveryContextMatches(tokens))
644
+ return false;
645
+ if (entry.tool === 'projscan_hotspots' && ['files', 'file', 'touch'].includes(keyword) && !hotspotFileRiskContextMatches(tokens))
646
+ return false;
647
+ if (entry.tool === 'projscan_hotspots' && ['performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'].includes(keyword) && !hotspotPerformanceContextMatches(tokens))
648
+ return false;
649
+ if (entry.tool === 'projscan_coordinate' && keyword === 'agent' && !coordinateAgentContextMatches(tokens))
650
+ return false;
651
+ if (entry.tool === 'projscan_coordinate' && ['who', 'else', 'working'].includes(keyword) && !coordinateWorkingContextMatches(tokens))
652
+ return false;
653
+ if (entry.tool === 'projscan_coordinate' && keyword === 'editing' && !coordinateWorkingContextMatches(tokens))
654
+ return false;
655
+ if (entry.tool === 'projscan_coordinate' && keyword === 'active' && !coordinateActiveContextMatches(tokens))
656
+ return false;
657
+ if (entry.tool === 'projscan_coordinate' && ['conflict', 'conflicts', 'conflicting', 'conflicted'].includes(keyword) && !coordinateConflictContextMatches(tokens))
658
+ return false;
659
+ if (entry.tool === 'projscan_claim' && searchDataContractContextMatches(tokens))
660
+ return false;
661
+ if (entry.tool === 'projscan_claim' && !claimKeywordMatches(keyword, tokens))
662
+ return false;
663
+ if (entry.tool === 'projscan_collision' && ['conflict', 'conflicts'].includes(keyword) && !collisionConflictContextMatches(tokens))
664
+ return false;
665
+ if (entry.tool === 'projscan_collision' && keyword === 'changes' && !collisionChangeContextMatches(tokens))
666
+ return false;
667
+ if (entry.tool === 'projscan_merge_risk' && !mergeRiskKeywordMatches(keyword, tokens))
668
+ return false;
669
+ if (entry.tool === 'projscan_session' && ['leave', 'left', 'off'].includes(keyword) && !sessionLeaveOffContextMatches(tokens))
670
+ return false;
671
+ if (entry.tool === 'projscan_session' && ['away', 'asleep', 'slept', 'offline'].includes(keyword) && !sessionAwayContextMatches(tokens))
672
+ return false;
673
+ if (entry.tool === 'projscan_session' && keyword === 'agent' && !sessionAgentContextMatches(tokens))
674
+ return false;
675
+ if (entry.tool === 'projscan_workplan' && keyword === 'do' && !workplanDoContextMatches(tokens))
676
+ return false;
677
+ if (entry.tool === 'projscan_bug_hunt' && keyword === 'first' && ['merge', 'merged', 'merging'].some((token) => tokens.has(token)))
678
+ return false;
679
+ if (entry.tool === 'projscan_bug_hunt' && ['fastest', 'quickest', 'quick', 'smallest'].includes(keyword) && !bugHuntSpeedContextMatches(tokens))
680
+ return false;
681
+ if (entry.tool === 'projscan_bug_hunt' && ['small', 'low', 'lowest', 'improve', 'improvement', 'useful', 'easy', 'beginner', 'starter', 'intern', 'interns', 'task', 'tasks', 'five', 'minutes', 'today', 'win', 'wins'].includes(keyword) && !bugHuntOpportunityContextMatches(tokens))
682
+ return false;
683
+ if (entry.tool === 'projscan_regression_plan' && ['github', 'action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines', 'job', 'jobs'].includes(keyword) && !regressionCiPlatformContextMatches(tokens))
684
+ return false;
685
+ if (entry.tool === 'projscan_regression_plan' && searchQuotedDebugTextContextMatches(tokens, hasQuotedText))
686
+ return false;
687
+ if (entry.tool === 'projscan_regression_plan' && searchReliabilityContextMatches(tokens))
688
+ return false;
689
+ if (entry.tool === 'projscan_regression_plan' && searchUiInteractionContextMatches(tokens))
690
+ return false;
691
+ if (entry.tool === 'projscan_regression_plan' && searchIntegrationContextMatches(tokens))
692
+ return false;
693
+ if (entry.tool === 'projscan_regression_plan' && searchApiContractContextMatches(tokens))
694
+ return false;
695
+ if (entry.tool === 'projscan_regression_plan' && searchInfraArtifactContextMatches(tokens))
696
+ return false;
697
+ if (entry.tool === 'projscan_regression_plan' && searchDomainWorkflowContextMatches(tokens))
698
+ return false;
699
+ if (entry.tool === 'projscan_regression_plan' && searchCommunicationArtifactContextMatches(tokens))
700
+ return false;
701
+ if (entry.tool === 'projscan_regression_plan' && searchStateManagementContextMatches(tokens))
702
+ return false;
703
+ if (entry.tool === 'projscan_regression_plan' && searchDataAccessContextMatches(tokens))
704
+ return false;
705
+ if (entry.tool === 'projscan_regression_plan' && searchNavigationLayoutContextMatches(tokens))
706
+ return false;
707
+ if (entry.tool === 'projscan_regression_plan' && searchFrontendPageRouteContextMatches(tokens))
708
+ return false;
709
+ if (entry.tool === 'projscan_regression_plan' && searchStyleSystemContextMatches(tokens))
710
+ return false;
711
+ if (entry.tool === 'projscan_regression_plan' && verificationPlanningContextMatches(tokens))
712
+ return false;
713
+ if (entry.tool === 'projscan_regression_plan' && packageScriptDiscoveryContextMatches(tokens))
714
+ return false;
715
+ if (entry.tool === 'projscan_regression_plan' && ['port', 'ports', 'eaddrinuse', 'listen', 'address', 'permission', 'denied', 'enoent', 'eresolve', 'peer'].includes(keyword) && !regressionLocalSetupContextMatches(tokens))
716
+ return false;
717
+ if (entry.tool === 'projscan_regression_plan' && ['build', 'builds', 'lint', 'typecheck', 'typechecking', 'install', 'debug', 'stack', 'trace', 'error', 'errors', 'failure', 'failures', 'production', 'prod', 'down', 'outage', 'incident', 'triage', 'runtime', 'crash', 'crashes', 'crashing', 'connection', 'refused', 'root', 'cause', 'returning', 'returns', 'log', 'logs', '500', '502', '503', '504', '404', '403', '401'].includes(keyword) && !regressionFailureContextMatches(tokens) && !regressionPerformanceContextMatches(tokens))
718
+ return false;
719
+ if (entry.tool === 'projscan_regression_plan' && ['test', 'tests'].includes(keyword) && (testCoverageLookupContextMatches(tokens) || coverageGapContextMatches(tokens)))
720
+ return false;
721
+ if (entry.tool === 'projscan_regression_plan' && ['run', 'rerun'].includes(keyword) && !testRunContextMatches(tokens))
722
+ return false;
723
+ if (entry.tool === 'projscan_regression_plan' && ['commands', 'command', 'works'].includes(keyword) && !proofCommandContextMatches(tokens) && !regressionBenchmarkContextMatches(tokens) && !regressionFlakeContextMatches(tokens))
724
+ return false;
725
+ if (entry.tool === 'projscan_regression_plan' && ['slow', 'slower', 'speed', 'speedup', 'faster', 'benchmark', 'benchmarks'].includes(keyword) && !regressionPerformanceContextMatches(tokens))
726
+ return false;
727
+ if (entry.tool === 'projscan_regression_plan' && ['reproduce', 'reproduces', 'reproducing', 'flake', 'flaky', 'flakes', 'intermittent', 'intermittently', 'nondeterministic', 'nondeterminism', 'race', 'condition', 'stabilize', 'stabilise', 'quarantine'].includes(keyword) && !regressionFlakeContextMatches(tokens))
728
+ return false;
729
+ if (entry.tool === 'projscan_release_train' && searchInfraArtifactContextMatches(tokens))
730
+ return false;
731
+ if (entry.tool === 'projscan_release_train' && !releaseTrainKeywordMatches(keyword, tokens))
732
+ return false;
733
+ return true;
734
+ }
735
+ function evidencePackKeywordMatches(keyword, tokens) {
736
+ const reviewerRoutingContext = ['who', 'owner', 'owners', 'routing', 'reviewer', 'reviewers'].some((token) => tokens.has(token));
737
+ const prReviewContext = ['pr', 'pull', 'request', 'review', 'reviewer', 'reviewers', 'comment'].some((token) => tokens.has(token));
738
+ const prNarrativeContext = ['pr', 'pull', 'request'].some((token) => tokens.has(token));
739
+ const reviewerSummaryContext = (tokens.has('summarize') || tokens.has('summary')) &&
740
+ tokens.has('changes') &&
741
+ ['reviewer', 'reviewers', 'pr', 'pull', 'request'].some((token) => tokens.has(token));
742
+ const teamNarrativeContext = ['tell', 'share', 'team'].some((token) => tokens.has(token)) &&
743
+ ['change', 'changes', 'changed', 'pr', 'pull', 'request'].some((token) => tokens.has(token));
744
+ const prReadinessContext = prReviewContext && ['ready', 'open', 'opening', 'before', 'prepare'].some((token) => tokens.has(token));
745
+ const changedFileOwnerContext = tokens.has('changed') &&
746
+ (tokens.has('file') || tokens.has('files')) &&
747
+ ['who', 'owner', 'owners', 'owns'].some((token) => tokens.has(token));
748
+ if (['description', 'draft', 'say'].includes(keyword))
749
+ return prNarrativeContext;
750
+ if (keyword === 'checklist')
751
+ return prNarrativeContext;
752
+ if (['tell', 'team', 'share', 'change'].includes(keyword))
753
+ return teamNarrativeContext;
754
+ if (['summarize', 'changes'].includes(keyword))
755
+ return reviewerSummaryContext || teamNarrativeContext;
756
+ if (keyword === 'summary')
757
+ return prReviewContext || reviewerSummaryContext;
758
+ if (keyword === 'review')
759
+ return reviewerRoutingContext || prReadinessContext;
760
+ if (['ready', 'open', 'opening', 'before', 'prepare'].includes(keyword))
761
+ return prReadinessContext;
762
+ if (['changed', 'file', 'files'].includes(keyword))
763
+ return changedFileOwnerContext;
764
+ if (['who', 'owner', 'owners', 'owns', 'routing'].includes(keyword)) {
765
+ return changedFileOwnerContext || ['review', 'reviewer', 'reviewers', 'pr', 'pull', 'request', 'comment'].some((token) => tokens.has(token));
766
+ }
767
+ return true;
768
+ }
769
+ function reviewKeywordMatches(keyword, tokens) {
770
+ const reviewContext = ['review', 'pr', 'pull', 'request', 'branch', 'diff', 'change', 'changes'].some((token) => tokens.has(token));
771
+ if (['risk', 'risks', 'risky', 'branch'].includes(keyword))
772
+ return reviewContext;
773
+ if (['secure', 'security'].includes(keyword))
774
+ return reviewContext || tokens.has('check');
775
+ if (['issues', 'check'].includes(keyword))
776
+ return tokens.has('security') && reviewContext;
777
+ if (keyword === 'change')
778
+ return tokens.has('secure') || tokens.has('security') || tokens.has('review');
779
+ return true;
780
+ }
781
+ function dataflowKeywordMatches(keyword, tokens) {
782
+ const inherent = ['dataflow', 'taint', 'security', 'injection', 'source', 'sink', 'sinks', 'vulnerability', 'sql', 'xss'];
783
+ if (inherent.includes(keyword))
784
+ return true;
785
+ const privacySubject = ['pii', 'gdpr', 'personal', 'customer', 'email', 'emails', 'password', 'token', 'tokens', 'secret', 'secrets', 'data'].some((token) => tokens.has(token));
786
+ const privacyAction = ['leak', 'leaks', 'expose', 'exposes', 'exposed', 'logged', 'logging', 'log', 'logs', 'store', 'stores', 'retention', 'handled', 'handles', 'process', 'processes', 'processing'].some((token) => tokens.has(token));
787
+ if (['pii', 'gdpr', 'personal', 'customer', 'email', 'emails'].includes(keyword))
788
+ return true;
789
+ if (keyword === 'compliance')
790
+ return privacySubject;
791
+ if (['password', 'token', 'tokens', 'leak', 'leaks', 'logged', 'logging', 'log', 'logs', 'store', 'stores', 'retention', 'handled', 'handles', 'process', 'processes', 'processing'].includes(keyword)) {
792
+ return privacySubject || privacyAction;
793
+ }
794
+ const flowContext = [
795
+ 'security',
796
+ 'secure',
797
+ 'vulnerable',
798
+ 'vulnerability',
799
+ 'secret',
800
+ 'secrets',
801
+ 'expose',
802
+ 'exposes',
803
+ 'exposed',
804
+ 'sanitize',
805
+ 'sanitized',
806
+ 'request',
807
+ 'data',
808
+ 'reach',
809
+ 'reaches',
810
+ 'exec',
811
+ 'auth',
812
+ 'bypass',
813
+ 'risk',
814
+ 'risks',
815
+ 'xss',
816
+ 'sql',
817
+ 'sink',
818
+ 'sinks',
819
+ 'pii',
820
+ 'gdpr',
821
+ 'personal',
822
+ 'customer',
823
+ 'email',
824
+ 'emails',
825
+ 'password',
826
+ 'token',
827
+ 'tokens',
828
+ 'leak',
829
+ 'leaks',
830
+ 'logged',
831
+ 'logging',
832
+ 'store',
833
+ 'stores',
834
+ 'retention',
835
+ 'handled',
836
+ 'handles',
837
+ 'process',
838
+ 'processes',
839
+ 'processing',
840
+ ].some((token) => tokens.has(token));
841
+ if (['auth'].includes(keyword))
842
+ return tokens.has('bypass') || tokens.has('security') || tokens.has('risk') || tokens.has('risks');
843
+ return flowContext;
844
+ }
845
+ function auditKeywordMatches(keyword, tokens) {
846
+ const auditSignal = ['audit', 'cve', 'cves', 'vulnerable', 'vulnerability', 'vulnerabilities'].some((token) => tokens.has(token));
847
+ const dependencyContext = ['dependency', 'dependencies', 'package', 'packages', 'npm'].some((token) => tokens.has(token));
848
+ const securityContext = ['security', 'secure', 'safe'].some((token) => tokens.has(token));
849
+ if (['audit', 'cve', 'cves', 'vulnerable', 'vulnerability', 'vulnerabilities'].includes(keyword))
850
+ return true;
851
+ if (['security', 'secure', 'safe'].includes(keyword))
852
+ return auditSignal || dependencyContext;
853
+ if (['dependency', 'dependencies', 'package', 'packages', 'npm'].includes(keyword)) {
854
+ return auditSignal || securityContext || tokens.has('audit');
855
+ }
856
+ return false;
857
+ }
858
+ function packageImporterContextMatches(tokens) {
859
+ return ((tokens.has('import') || tokens.has('imports') || tokens.has('importers')) &&
860
+ (tokens.has('package') || tokens.has('dependency')) &&
861
+ ['who', 'which', 'what', 'files'].some((token) => tokens.has(token)));
862
+ }
863
+ function packageDependencyLookupContextMatches(tokens, hasFilePath) {
864
+ if (hasFilePath)
865
+ return false;
866
+ const lookupSignal = ['who', 'what', 'which', 'why'].some((token) => tokens.has(token));
867
+ const dependencySignal = ['uses', 'depend', 'depends', 'installed'].some((token) => tokens.has(token));
868
+ return lookupSignal && dependencySignal;
869
+ }
870
+ function outdatedNpmContextMatches(tokens) {
871
+ return ['dependency', 'dependencies', 'outdated', 'audit', 'upgrade', 'vulnerable', 'vulnerability', 'vulnerabilities', 'package', 'packages'].some((token) => tokens.has(token));
872
+ }
873
+ function workspacesKeywordMatches(keyword, tokens) {
874
+ const workspaceContext = ['workspace', 'workspaces', 'monorepo'].some((token) => tokens.has(token));
875
+ const packageContext = ['package', 'packages'].some((token) => tokens.has(token));
876
+ const mapContext = ['map', 'list', 'owns', 'contains', 'put', 'change'].some((token) => tokens.has(token));
877
+ if (['workspace', 'workspaces', 'monorepo'].includes(keyword))
878
+ return true;
879
+ if (['package', 'packages'].includes(keyword))
880
+ return workspaceContext || mapContext;
881
+ if (['map', 'list', 'owns', 'contains', 'put', 'change'].includes(keyword))
882
+ return workspaceContext || packageContext;
883
+ return false;
884
+ }
885
+ function dependenciesKeywordMatches(keyword, tokens) {
886
+ if (dependencyCycleContextMatches(tokens) && ['dependency', 'dependencies', 'deps', 'package', 'packages'].includes(keyword))
887
+ return false;
888
+ if (['dependencies', 'dependency', 'deps', 'packages', 'inventory', 'declared', 'supply-chain'].includes(keyword))
889
+ return true;
890
+ const licenseContext = ['license', 'licenses', 'gpl', 'copyleft', 'notice', 'notices', 'third', 'party', 'open', 'source', 'compliance'].some((token) => tokens.has(token));
891
+ const bloatContext = dependencyBloatContextMatches(tokens);
892
+ if (['bundle', 'bundles', 'size', 'sizes', 'large', 'heavy', 'bloat', 'bloated', 'weight', 'footprint', 'reduce', 'slim'].includes(keyword))
893
+ return bloatContext;
894
+ if (keyword === 'package')
895
+ return licenseContext || bloatContext;
896
+ if (!licenseContext)
897
+ return false;
898
+ if (keyword === 'source')
899
+ return tokens.has('open') || tokens.has('compliance') || tokens.has('license') || tokens.has('licenses');
900
+ if (keyword === 'open')
901
+ return tokens.has('source') || tokens.has('compliance');
902
+ if (keyword === 'compliance') {
903
+ return ((tokens.has('open') && tokens.has('source')) ||
904
+ ['license', 'licenses', 'gpl', 'copyleft', 'notice', 'notices', 'third', 'party', 'dependency', 'dependencies', 'package', 'packages'].some((token) => tokens.has(token)));
905
+ }
906
+ if (['third', 'party'].includes(keyword))
907
+ return tokens.has('third') && tokens.has('party') && (tokens.has('notice') || tokens.has('notices') || tokens.has('license') || tokens.has('licenses') || tokens.has('compliance'));
908
+ return true;
909
+ }
910
+ function couplingKeywordMatches(keyword, tokens) {
911
+ const cycleSignal = ['circular', 'cycle', 'cycles'].some((token) => tokens.has(token));
912
+ const couplingSignal = ['coupling', 'coupled', 'tightly', 'instability', 'fan'].some((token) => tokens.has(token));
913
+ const architectureSubject = ['dependency', 'dependencies', 'import', 'imports', 'module', 'modules', 'architecture', 'boundary', 'boundaries'].some((token) => tokens.has(token));
914
+ if (['circular', 'cycle', 'cycles', 'coupling', 'coupled', 'instability'].includes(keyword))
915
+ return true;
916
+ if (keyword === 'tightly')
917
+ return tokens.has('coupled');
918
+ if (keyword === 'fan')
919
+ return architectureSubject || tokens.has('out') || tokens.has('in');
920
+ if (['dependency', 'dependencies', 'import', 'imports'].includes(keyword)) {
921
+ return dependencyCycleContextMatches(tokens) || (couplingSignal && architectureSubject);
922
+ }
923
+ if (['module', 'modules', 'architecture', 'boundary', 'boundaries'].includes(keyword))
924
+ return cycleSignal || couplingSignal;
925
+ return cycleSignal || (couplingSignal && architectureSubject);
926
+ }
927
+ function dependencyCycleContextMatches(tokens) {
928
+ const cycleSignal = ['circular', 'cycle', 'cycles'].some((token) => tokens.has(token));
929
+ const couplingSignal = ['coupling', 'coupled', 'tightly', 'instability', 'fan'].some((token) => tokens.has(token));
930
+ const architectureSubject = ['dependency', 'dependencies', 'import', 'imports', 'module', 'modules', 'architecture', 'boundary', 'boundaries'].some((token) => tokens.has(token));
931
+ return architectureSubject && (cycleSignal || couplingSignal);
932
+ }
933
+ function dependencyBloatContextMatches(tokens) {
934
+ const bloatSignal = ['bundle', 'bundles', 'size', 'sizes', 'large', 'heavy', 'bloat', 'bloated', 'weight', 'footprint', 'reduce', 'slim'].some((token) => tokens.has(token));
935
+ const packageSubject = ['dependency', 'dependencies', 'deps', 'package', 'packages', 'bundle', 'bundles', 'app'].some((token) => tokens.has(token));
936
+ return bloatSignal && packageSubject;
937
+ }
938
+ function understandKeywordMatches(keyword, tokens) {
939
+ if (['api', 'apis', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible'].includes(keyword)) {
940
+ return true;
941
+ }
942
+ if (['repo', 'codebase', 'service', 'services', 'architecture', 'main', 'entrypoint', 'entrypoints', 'entry', 'point', 'important', 'look', 'first', 'tour', 'walk', 'through', 'new', 'onboard', 'onboarding'].includes(keyword)) {
943
+ if (keyword === 'repo' && (tokens.has('summarize') || tokens.has('summary')))
944
+ return false;
945
+ return repoOrientationContextMatches(tokens);
946
+ }
947
+ if (['run', 'runs', 'project', 'command', 'commands', 'dev', 'server', 'start', 'app'].includes(keyword)) {
948
+ return packageScriptDiscoveryContextMatches(tokens) || verificationPlanningContextMatches(tokens) || databaseSetupCommandContextMatches(tokens) || localServiceSetupCommandContextMatches(tokens) || repoRunContextMatches(tokens) || repoOrientationContextMatches(tokens);
949
+ }
950
+ if (['local', 'locally', 'docker', 'compose'].includes(keyword))
951
+ return localServiceSetupCommandContextMatches(tokens) || repoSetupContextMatches(tokens);
952
+ if (['npm', 'script', 'scripts'].includes(keyword)) {
953
+ return npmScriptsContextMatches(tokens) || packageScriptDiscoveryContextMatches(tokens);
954
+ }
955
+ if (['test', 'tests', 'e2e', 'unit', 'integration', 'storybook', 'cypress', 'playwright', 'eslint', 'prettier', 'format', 'lint', 'typecheck', 'typechecking'].includes(keyword)) {
956
+ return packageScriptDiscoveryContextMatches(tokens) || verificationPlanningContextMatches(tokens);
957
+ }
958
+ if (['verify', 'verification', 'proof', 'prove', 'checks'].includes(keyword)) {
959
+ return verificationPlanningContextMatches(tokens);
960
+ }
961
+ if (['setup', 'set', 'locally', 'install'].includes(keyword)) {
962
+ return repoSetupContextMatches(tokens);
963
+ }
964
+ if (['env', 'environment', 'environments', 'vars', 'variable', 'variables', 'missing', 'required', 'config', 'configuration'].includes(keyword)) {
965
+ return repoConfigContextMatches(tokens);
966
+ }
967
+ if (['seed', 'seeds', 'reset', 'resets', 'migrate', 'migrates'].includes(keyword)) {
968
+ return databaseSetupCommandContextMatches(tokens);
969
+ }
970
+ if (['feature', 'endpoint', 'button', 'put', 'need', 'change', 'files', 'add', 'implement', 'build', 'create', 'wire', 'route', 'component', 'page', 'screen', 'view', 'webhook', 'login', 'support', 'checkout', 'search', 'password', 'reset', 'invite', 'onboarding', 'refund', 'subscription', 'doc', 'docs', 'document', 'documentation', 'readme', 'examples', 'example', 'migration', 'migrations', 'database', 'db', 'schema', 'table', 'column', 'guide', 'update', 'updating'].includes(keyword)) {
971
+ if (keyword === 'add' && tokens.has('where'))
972
+ return false;
973
+ if (['doc', 'docs', 'document', 'documentation', 'readme', 'examples', 'example', 'guide', 'update', 'updating'].includes(keyword) && !documentationPlanningContextMatches(tokens))
974
+ return false;
975
+ if (['migration', 'migrations', 'database', 'db'].includes(keyword) && databaseSetupCommandContextMatches(tokens))
976
+ return true;
977
+ if (['migration', 'migrations', 'database', 'db', 'schema', 'table', 'column'].includes(keyword) && !databaseChangePlanningContextMatches(tokens))
978
+ return false;
979
+ if (keyword === 'change' && apiChangePlanningContextMatches(tokens))
980
+ return true;
981
+ return featurePlacementContextMatches(tokens);
982
+ }
983
+ return true;
984
+ }
985
+ function privacyCheckKeywordMatches(keyword, tokens) {
986
+ const privacySubjectContext = [
987
+ 'privacy',
988
+ 'trust',
989
+ 'boundary',
990
+ 'upload',
991
+ 'leave',
992
+ 'machine',
993
+ 'telemetry',
994
+ 'network',
995
+ 'contact',
996
+ 'contacted',
997
+ 'projscan',
998
+ ].some((token) => tokens.has(token));
999
+ if (keyword === 'offline')
1000
+ return privacySubjectContext || tokens.has('mode');
1001
+ if (keyword === 'read')
1002
+ return privacySubjectContext;
1003
+ if (['env', 'values', 'code', 'source', 'local'].includes(keyword))
1004
+ return privacySubjectContext || (tokens.has('read') && tokens.has('projscan'));
1005
+ if (['write', 'writes'].includes(keyword))
1006
+ return privacySubjectContext;
1007
+ if (keyword === 'check')
1008
+ return tokens.has('privacy') || tokens.has('trust') || tokens.has('boundary');
1009
+ if (keyword === 'projscan') {
1010
+ return ['read', 'upload', 'telemetry', 'privacy', 'write', 'writes', 'contact', 'contacted'].some((token) => tokens.has(token));
1011
+ }
1012
+ return true;
1013
+ }
1014
+ function repoRunContextMatches(tokens) {
1015
+ if (tokens.has('test') || tokens.has('tests'))
1016
+ return false;
1017
+ const runAction = ['run', 'start', 'command', 'dev', 'server'].some((token) => tokens.has(token));
1018
+ const repoSubject = ['project', 'repo', 'app', 'dev', 'server'].some((token) => tokens.has(token));
1019
+ return runAction && repoSubject;
1020
+ }
1021
+ function localServiceSetupCommandContextMatches(tokens) {
1022
+ if ([
1023
+ 'fail',
1024
+ 'failing',
1025
+ 'failed',
1026
+ 'failure',
1027
+ 'failures',
1028
+ 'error',
1029
+ 'errors',
1030
+ 'broken',
1031
+ 'connection',
1032
+ 'refused',
1033
+ 'port',
1034
+ 'ports',
1035
+ 'eaddrinuse',
1036
+ 'permission',
1037
+ 'denied',
1038
+ 'enoent',
1039
+ 'eresolve',
1040
+ 'peer',
1041
+ ].some((token) => tokens.has(token))) {
1042
+ return false;
1043
+ }
1044
+ const action = ['run', 'runs', 'start', 'starts', 'command', 'commands', 'setup'].some((token) => tokens.has(token)) || (tokens.has('set') && tokens.has('up'));
1045
+ const localSubject = tokens.has('local') || tokens.has('locally') || tokens.has('dev');
1046
+ const serviceSubject = ['service', 'services', 'server', 'app'].some((token) => tokens.has(token));
1047
+ const dockerComposeSubject = tokens.has('docker') && tokens.has('compose');
1048
+ return action && ((localSubject && serviceSubject) || dockerComposeSubject);
1049
+ }
1050
+ function databaseSetupCommandContextMatches(tokens) {
1051
+ if ([
1052
+ 'fail',
1053
+ 'failing',
1054
+ 'failed',
1055
+ 'failure',
1056
+ 'failures',
1057
+ 'error',
1058
+ 'errors',
1059
+ 'broken',
1060
+ 'connection',
1061
+ 'refused',
1062
+ 'port',
1063
+ 'ports',
1064
+ 'eaddrinuse',
1065
+ 'permission',
1066
+ 'denied',
1067
+ 'enoent',
1068
+ 'eresolve',
1069
+ 'peer',
1070
+ ].some((token) => tokens.has(token))) {
1071
+ return false;
1072
+ }
1073
+ if (['where', 'find', 'locate', 'search', 'lookup', 'defined'].some((token) => tokens.has(token)))
1074
+ return false;
1075
+ const databaseSubject = ['database', 'db', 'migration', 'migrations'].some((token) => tokens.has(token));
1076
+ const dataSubject = tokens.has('data') && (tokens.has('seed') || tokens.has('seeds') || tokens.has('load'));
1077
+ const setupAction = ['seed', 'seeds', 'reset', 'resets', 'migrate', 'migrates', 'run', 'runs', 'command', 'load', 'locally'].some((token) => tokens.has(token));
1078
+ return (databaseSubject || dataSubject) && setupAction;
1079
+ }
1080
+ function npmScriptsContextMatches(tokens) {
1081
+ const scriptSubject = tokens.has('script') || tokens.has('scripts');
1082
+ const lookupSignal = ['npm', 'command', 'commands', 'run', 'runs', 'start', 'exist', 'exists', 'list', 'show'].some((token) => tokens.has(token));
1083
+ return scriptSubject && lookupSignal;
1084
+ }
1085
+ function packageScriptDiscoveryContextMatches(tokens) {
1086
+ const failureSignal = [
1087
+ 'fail',
1088
+ 'failing',
1089
+ 'failed',
1090
+ 'failure',
1091
+ 'failures',
1092
+ 'error',
1093
+ 'errors',
1094
+ 'broken',
1095
+ 'debug',
1096
+ 'flake',
1097
+ 'flaky',
1098
+ 'flakes',
1099
+ 'slow',
1100
+ 'slower',
1101
+ 'rerun',
1102
+ 'reproduce',
1103
+ 'reproduces',
1104
+ 'reproducing',
1105
+ 'quarantine',
1106
+ ].some((token) => tokens.has(token));
1107
+ if (failureSignal)
1108
+ return false;
1109
+ const scriptSubject = ['script', 'scripts', 'command', 'commands'].some((token) => tokens.has(token));
1110
+ const runSignal = ['run', 'runs', 'start'].some((token) => tokens.has(token));
1111
+ const scriptTarget = ['test', 'tests', 'e2e', 'unit', 'integration', 'storybook', 'cypress', 'playwright', 'eslint', 'prettier', 'format', 'lint', 'typecheck', 'typechecking', 'build'].some((token) => tokens.has(token));
1112
+ const directScriptTarget = ['e2e', 'storybook', 'cypress', 'playwright', 'eslint', 'prettier', 'format', 'lint', 'typecheck', 'typechecking', 'build'].some((token) => tokens.has(token));
1113
+ if ((tokens.has('npm') || tokens.has('package')) && (tokens.has('script') || tokens.has('scripts')))
1114
+ return true;
1115
+ if (directScriptTarget && runSignal && !tokens.has('should'))
1116
+ return true;
1117
+ return scriptTarget && scriptSubject;
1118
+ }
1119
+ function repoSetupContextMatches(tokens) {
1120
+ return (tokens.has('setup') ||
1121
+ tokens.has('locally') ||
1122
+ tokens.has('install') ||
1123
+ (tokens.has('set') && tokens.has('up')));
1124
+ }
1125
+ function repoConfigContextMatches(tokens) {
1126
+ if (tokens.has('privacy') || tokens.has('trust') || tokens.has('boundary'))
1127
+ return false;
1128
+ if (tokens.has('projscan') && ['read', 'upload', 'telemetry', 'write', 'writes'].some((token) => tokens.has(token)))
1129
+ return false;
1130
+ const envSubject = ['env', 'environment', 'environments', 'vars', 'variable', 'variables'].some((token) => tokens.has(token));
1131
+ return (tokens.has('vars') ||
1132
+ tokens.has('variables') ||
1133
+ tokens.has('variable') ||
1134
+ tokens.has('environment') ||
1135
+ tokens.has('environments') ||
1136
+ tokens.has('config') ||
1137
+ tokens.has('configuration') ||
1138
+ (envSubject && ['missing', 'need', 'needed', 'required', 'requires', 'uses', 'repo', 'project', 'app'].some((token) => tokens.has(token))));
1139
+ }
1140
+ function repoOrientationContextMatches(tokens) {
1141
+ if (['bug', 'bugs', 'fix', 'issue', 'issues', 'test', 'tests'].some((token) => tokens.has(token)))
1142
+ return false;
1143
+ const orientationSubject = ['repo', 'repository', 'codebase', 'service', 'services', 'architecture', 'entrypoint', 'entrypoints'].some((token) => tokens.has(token)) ||
1144
+ (tokens.has('entry') && tokens.has('point')) ||
1145
+ (tokens.has('important') && tokens.has('files'));
1146
+ const orientationAction = [
1147
+ 'understand',
1148
+ 'orient',
1149
+ 'overview',
1150
+ 'map',
1151
+ 'read',
1152
+ 'summarize',
1153
+ 'summary',
1154
+ 'new',
1155
+ 'onboard',
1156
+ 'onboarding',
1157
+ 'start',
1158
+ 'look',
1159
+ 'first',
1160
+ 'tour',
1161
+ 'walk',
1162
+ 'through',
1163
+ 'main',
1164
+ 'important',
1165
+ 'where',
1166
+ 'show',
1167
+ 'give',
1168
+ 'help',
1169
+ 'explain',
1170
+ ].some((token) => tokens.has(token));
1171
+ if (tokens.has('look') && tokens.has('first'))
1172
+ return true;
1173
+ return orientationSubject && orientationAction;
1174
+ }
1175
+ function featurePlacementContextMatches(tokens) {
1176
+ if (tokens.has('test') || tokens.has('tests'))
1177
+ return false;
1178
+ if (documentationPlanningContextMatches(tokens))
1179
+ return true;
1180
+ if (databaseChangePlanningContextMatches(tokens))
1181
+ return true;
1182
+ if (apiChangePlanningContextMatches(tokens))
1183
+ return true;
1184
+ if (stateManagementPlanningContextMatches(tokens))
1185
+ return true;
1186
+ if (dataAccessPlanningContextMatches(tokens))
1187
+ return true;
1188
+ if (navigationLayoutPlanningContextMatches(tokens))
1189
+ return true;
1190
+ if (styleSystemPlanningContextMatches(tokens))
1191
+ return true;
1192
+ const placementSubject = ['feature', 'endpoint', 'button'].some((token) => tokens.has(token));
1193
+ if (placementSubject && ['where', 'put', 'add'].some((token) => tokens.has(token)))
1194
+ return true;
1195
+ if (tokens.has('files') && tokens.has('need') && tokens.has('change'))
1196
+ return true;
1197
+ const kickoffAction = ['add', 'implement', 'build', 'create', 'wire'].some((token) => tokens.has(token));
1198
+ const kickoffSubject = [
1199
+ 'feature',
1200
+ 'endpoint',
1201
+ 'button',
1202
+ 'api',
1203
+ 'apis',
1204
+ 'route',
1205
+ 'component',
1206
+ 'page',
1207
+ 'screen',
1208
+ 'view',
1209
+ 'webhook',
1210
+ 'login',
1211
+ 'support',
1212
+ 'checkout',
1213
+ 'search',
1214
+ 'password',
1215
+ 'reset',
1216
+ 'invite',
1217
+ 'onboarding',
1218
+ 'refund',
1219
+ 'subscription',
1220
+ ].some((token) => tokens.has(token));
1221
+ return kickoffAction && kickoffSubject;
1222
+ }
1223
+ function domainWorkflowPlanningContextMatches(tokens) {
1224
+ const planningAction = ['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token));
1225
+ if (!planningAction)
1226
+ return false;
1227
+ return ((tokens.has('password') && tokens.has('reset')) ||
1228
+ tokens.has('invite') ||
1229
+ tokens.has('invites') ||
1230
+ tokens.has('onboarding') ||
1231
+ tokens.has('refund') ||
1232
+ (tokens.has('subscription') && tokens.has('renewal')));
1233
+ }
1234
+ function stateManagementPlanningContextMatches(tokens) {
1235
+ const planningAction = ['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token));
1236
+ if (!planningAction)
1237
+ return false;
1238
+ return ['redux', 'zustand', 'jotai', 'recoil', 'store', 'stores', 'slice', 'slices', 'selector', 'selectors', 'context', 'provider', 'providers', 'hook', 'hooks'].some((token) => tokens.has(token)) || (tokens.has('react') && tokens.has('query'));
1239
+ }
1240
+ function dataAccessPlanningContextMatches(tokens) {
1241
+ const planningAction = ['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token));
1242
+ if (!planningAction)
1243
+ return false;
1244
+ return ['prisma', 'drizzle', 'typeorm', 'sequelize', 'model', 'models', 'entity', 'entities', 'repository', 'repositories', 'dao', 'daos'].some((token) => tokens.has(token)) || (tokens.has('sql') && (tokens.has('query') || tokens.has('queries')));
1245
+ }
1246
+ function navigationLayoutPlanningContextMatches(tokens) {
1247
+ const planningAction = ['add', 'create', 'implement', 'build', 'plan', 'should', 'todo'].some((token) => tokens.has(token));
1248
+ if (!planningAction)
1249
+ return false;
1250
+ return ['sidebar', 'nav', 'navigation', 'menu', 'breadcrumb', 'breadcrumbs', 'layout', 'title', 'metadata'].some((token) => tokens.has(token)) || (tokens.has('next') && tokens.has('js'));
1251
+ }
1252
+ function styleSystemPlanningContextMatches(tokens) {
1253
+ const planningAction = ['add', 'create', 'implement', 'build', 'plan', 'should', 'todo'].some((token) => tokens.has(token));
1254
+ if (!planningAction)
1255
+ return false;
1256
+ return ((tokens.has('design') && (tokens.has('token') || tokens.has('tokens'))) ||
1257
+ tokens.has('tailwind') ||
1258
+ tokens.has('css') ||
1259
+ (tokens.has('dark') && tokens.has('mode')) ||
1260
+ tokens.has('breakpoint') ||
1261
+ tokens.has('breakpoints') ||
1262
+ tokens.has('theme') ||
1263
+ tokens.has('themes') ||
1264
+ tokens.has('style') ||
1265
+ tokens.has('styles'));
1266
+ }
1267
+ function documentationPlanningContextMatches(tokens) {
1268
+ if (['find', 'where', 'locate', 'search', 'lookup'].some((token) => tokens.has(token)))
1269
+ return false;
1270
+ const docsSubject = ['doc', 'docs', 'document', 'documentation', 'readme', 'examples', 'example', 'migration', 'guide'].some((token) => tokens.has(token));
1271
+ if (!docsSubject)
1272
+ return false;
1273
+ return ['change', 'changes', 'update', 'updating', 'need', 'needs', 'write', 'generate', 'feature', 'api', 'apis'].some((token) => tokens.has(token));
1274
+ }
1275
+ function databaseChangePlanningContextMatches(tokens) {
1276
+ if (tokens.has('test') || tokens.has('tests'))
1277
+ return false;
1278
+ if (['impact', 'breaks', 'break', 'blast', 'radius', 'affect', 'used', 'usage', 'referenced', 'called', 'drop', 'delete', 'remove', 'rollback', 'revert'].some((token) => tokens.has(token)))
1279
+ return false;
1280
+ const databaseSubject = ['migration', 'migrations', 'database', 'db', 'schema', 'table', 'column'].some((token) => tokens.has(token));
1281
+ if (!databaseSubject)
1282
+ return false;
1283
+ return ['where', 'put', 'add', 'change', 'need', 'needs', 'plan', 'deploy', 'zero', 'downtime'].some((token) => tokens.has(token));
1284
+ }
1285
+ function apiChangePlanningContextMatches(tokens) {
1286
+ const apiSubject = ['api', 'apis', 'endpoint', 'endpoints', 'contract', 'contracts', 'public', 'client', 'clients'].some((token) => tokens.has(token));
1287
+ if (!apiSubject)
1288
+ return false;
1289
+ return ['change', 'changes', 'changing', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'break', 'breaks', 'breaking', 'compatibility', 'compatible', 'version', 'versions'].some((token) => tokens.has(token));
1290
+ }
1291
+ function verificationPlanningContextMatches(tokens) {
1292
+ if (['smoke', 'focused', 'full', 'regression'].some((token) => tokens.has(token)) ||
1293
+ regressionFailureContextMatches(tokens) ||
1294
+ regressionPerformanceContextMatches(tokens) ||
1295
+ regressionFlakeContextMatches(tokens) ||
1296
+ testCoverageLookupContextMatches(tokens) ||
1297
+ coverageGapContextMatches(tokens) ||
1298
+ packageScriptDiscoveryContextMatches(tokens)) {
1299
+ return false;
1300
+ }
1301
+ const testSubject = ['test', 'tests', 'spec', 'specs', 'e2e', 'unit', 'integration', 'lint', 'typecheck', 'typechecking', 'build'].some((token) => tokens.has(token));
1302
+ const proofSignal = ['verify', 'verification', 'proof', 'prove', 'checks'].some((token) => tokens.has(token));
1303
+ const runSignal = ['run', 'rerun', 'execute'].some((token) => tokens.has(token));
1304
+ const gateSignal = ['before', 'push', 'pushing', 'commit', 'committing', 'review', 'merge', 'pr'].some((token) => tokens.has(token));
1305
+ const shouldSignal = ['should', 'need', 'needs', 'must'].some((token) => tokens.has(token));
1306
+ const querySignal = ['which', 'what'].some((token) => tokens.has(token));
1307
+ const changeSignal = ['change', 'changes', 'diff', 'branch'].some((token) => tokens.has(token));
1308
+ return (testSubject && (shouldSignal || gateSignal || proofSignal || (runSignal && (querySignal || changeSignal)))) || (proofSignal && (runSignal || gateSignal || testSubject));
1309
+ }
1310
+ function searchTestLocationContextMatches(tokens, hasFilePath) {
1311
+ if (testCoverageLookupContextMatches(tokens))
1312
+ return true;
1313
+ if (!['where', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token)))
1314
+ return false;
1315
+ if (['run', 'rerun', 'write', 'add', 'generate', 'plan', 'case', 'cases', 'cover', 'coverage', 'edge'].some((token) => tokens.has(token))) {
1316
+ return false;
1317
+ }
1318
+ return hasFilePath || ['test', 'tests', 'spec', 'specs'].some((token) => tokens.has(token));
1319
+ }
1320
+ function testCoverageLookupContextMatches(tokens) {
1321
+ if (['run', 'rerun', 'write', 'add', 'generate', 'plan', 'case', 'cases', 'edge', 'should'].some((token) => tokens.has(token)))
1322
+ return false;
1323
+ const testSubject = ['test', 'tests', 'spec', 'specs'].some((token) => tokens.has(token));
1324
+ const coverSignal = ['cover', 'covers', 'covering'].some((token) => tokens.has(token));
1325
+ const lookupSignal = ['which', 'what', 'where', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1326
+ return testSubject && coverSignal && (lookupSignal || tokens.size >= 3);
1327
+ }
1328
+ function coverageKeywordMatches(keyword, tokens) {
1329
+ if (['coverage', 'scariest', 'untested', 'uncovered', 'gap', 'gaps'].includes(keyword))
1330
+ return true;
1331
+ if (['test', 'tests'].includes(keyword))
1332
+ return coverageGapContextMatches(tokens);
1333
+ if (['file', 'files', 'no', 'missing', 'without'].includes(keyword))
1334
+ return missingTestCoverageContextMatches(tokens);
1335
+ return false;
1336
+ }
1337
+ function coverageGapContextMatches(tokens) {
1338
+ if (['coverage', 'scariest', 'untested', 'uncovered', 'gap', 'gaps'].some((token) => tokens.has(token)))
1339
+ return true;
1340
+ return missingTestCoverageContextMatches(tokens);
1341
+ }
1342
+ function missingTestCoverageContextMatches(tokens) {
1343
+ const testSubject = tokens.has('test') || tokens.has('tests');
1344
+ const fileSubject = tokens.has('file') || tokens.has('files');
1345
+ const missingSignal = ['no', 'missing', 'without'].some((token) => tokens.has(token));
1346
+ return testSubject && (fileSubject || tokens.has('code')) && missingSignal;
1347
+ }
1348
+ function searchCodeLocationContextMatches(tokens) {
1349
+ if (['run', 'rerun', 'test', 'tests', 'spec', 'specs'].some((token) => tokens.has(token)))
1350
+ return false;
1351
+ const locator = ['where', 'find', 'locate', 'search', 'lookup', 'which'].some((token) => tokens.has(token));
1352
+ const codeSubject = ['code', 'file', 'files', 'logic', 'handler', 'middleware', 'loader'].some((token) => tokens.has(token));
1353
+ const codeAction = ['handles', 'handled', 'contains', 'implemented', 'configured', 'created', 'loaded', 'parse', 'parses'].some((token) => tokens.has(token));
1354
+ return locator || (codeSubject && codeAction);
1355
+ }
1356
+ function searchRouteHandlerContextMatches(tokens) {
1357
+ if (['should', 'add', 'put', 'new', 'need', 'needs', 'change', 'implement', 'build', 'create', 'wire'].some((token) => tokens.has(token)))
1358
+ return false;
1359
+ const routeSubject = ['api', 'apis', 'route', 'routes', 'endpoint', 'endpoints', 'login', 'checkout', 'webhook'].some((token) => tokens.has(token));
1360
+ if (!routeSubject)
1361
+ return false;
1362
+ const handlerSignal = ['handler', 'handles', 'handled', 'implemented', 'defined'].some((token) => tokens.has(token));
1363
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1364
+ return handlerSignal || (locator && (tokens.has('handler') || tokens.has('route') || tokens.has('endpoint') || tokens.has('endpoints')));
1365
+ }
1366
+ function searchFeatureFlagContextMatches(tokens) {
1367
+ const flagSubject = tokens.has('flag') || tokens.has('flags') || (tokens.has('feature') && tokens.has('flags'));
1368
+ if (!flagSubject)
1369
+ return false;
1370
+ return ['where', 'which', 'find', 'locate', 'search', 'lookup', 'exist', 'exists', 'configured', 'loaded'].some((token) => tokens.has(token));
1371
+ }
1372
+ function searchEnvLookupContextMatches(tokens, hasEnvVar) {
1373
+ if (hasEnvVar) {
1374
+ return ['where', 'find', 'locate', 'search', 'lookup', 'used', 'referenced', 'process'].some((token) => tokens.has(token));
1375
+ }
1376
+ const envSubject = ['env', 'environment', 'var', 'vars', 'variable', 'variables'].some((token) => tokens.has(token));
1377
+ if (!envSubject)
1378
+ return false;
1379
+ if (['need', 'needs', 'required', 'requires', 'missing', 'contract', 'contracts', 'repo', 'project'].some((token) => tokens.has(token)))
1380
+ return false;
1381
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'controls', 'control', 'used', 'uses'].some((token) => tokens.has(token));
1382
+ }
1383
+ function searchQuotedDebugTextContextMatches(tokens, hasQuotedText) {
1384
+ if (!hasQuotedText)
1385
+ return false;
1386
+ const debugSubject = ['error', 'errors', 'message', 'messages', 'throw', 'throws', 'thrown', 'log', 'logs', 'logged', 'logging'].some((token) => tokens.has(token));
1387
+ const locator = ['where', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1388
+ return debugSubject || locator;
1389
+ }
1390
+ function searchBackgroundWorkContextMatches(tokens) {
1391
+ if (['add', 'create', 'implement', 'build', 'put', 'new', 'change', 'plan', 'should', 'todo', 'next', 'do'].some((token) => tokens.has(token))) {
1392
+ return false;
1393
+ }
1394
+ const explicitSubject = ['background', 'cron', 'scheduled', 'schedule', 'scheduler', 'schedulers', 'worker', 'workers', 'queue', 'queues', 'processor', 'processors'].some((token) => tokens.has(token));
1395
+ const jobSubject = (tokens.has('job') || tokens.has('jobs')) && ['background', 'cron', 'scheduled', 'schedule'].some((token) => tokens.has(token));
1396
+ const taskSubject = (tokens.has('task') || tokens.has('tasks')) && ['scheduled', 'schedule'].some((token) => tokens.has(token));
1397
+ if (!explicitSubject && !jobSubject && !taskSubject)
1398
+ return false;
1399
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'exist', 'exists', 'defined', 'handles', 'handled', 'process', 'processes'].some((token) => tokens.has(token)) || tokens.size >= 3;
1400
+ }
1401
+ function searchObservabilityContextMatches(tokens) {
1402
+ if (['privacy', 'trust', 'upload', 'projscan'].some((token) => tokens.has(token)))
1403
+ return false;
1404
+ const observabilitySubject = ['metric', 'metrics', 'prometheus', 'analytics', 'event', 'events', 'alert', 'alerts', 'sentry', 'datadog', 'dashboard', 'dashboards'].some((token) => tokens.has(token));
1405
+ const logSubject = (tokens.has('log') || tokens.has('logs')) && ['check', 'where', 'which', 'what', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1406
+ if (!observabilitySubject && !logSubject)
1407
+ return false;
1408
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'check', 'configured', 'initialize', 'initialise', 'init', 'emit', 'emits', 'emitted', 'send', 'sends', 'handled', 'code'].some((token) => tokens.has(token)) || tokens.size >= 2;
1409
+ }
1410
+ function searchTestDataContextMatches(tokens) {
1411
+ if (packageScriptDiscoveryContextMatches(tokens))
1412
+ return false;
1413
+ if (databaseSetupCommandContextMatches(tokens))
1414
+ return false;
1415
+ if (['add', 'write', 'create', 'generate', 'plan', 'should', 'next', 'todo'].some((token) => tokens.has(token)))
1416
+ return false;
1417
+ const seedSubject = tokens.has('seed') || tokens.has('seeds') || ((tokens.has('data') || tokens.has('database')) && (tokens.has('seed') || tokens.has('seeds')));
1418
+ const fixtureSubject = tokens.has('fixture') || tokens.has('fixtures');
1419
+ const mockSubject = tokens.has('mock') || tokens.has('mocks');
1420
+ const factorySubject = tokens.has('factory') || tokens.has('factories');
1421
+ const storySubject = tokens.has('storybook') || tokens.has('story') || tokens.has('stories');
1422
+ if (!seedSubject && !fixtureSubject && !mockSubject && !factorySubject && !storySubject)
1423
+ return false;
1424
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'used', 'defined', 'configured', 'for', 'render', 'renders'].some((token) => tokens.has(token)) || tokens.size >= 3;
1425
+ }
1426
+ function searchAuthorizationContextMatches(tokens) {
1427
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1428
+ return false;
1429
+ if (['fail', 'failing', 'failed', 'failure', 'failures', 'error', 'errors', 'returning', 'returns', 'runtime', 'incident', 'crash', 'crashes', 'crashing', 'outage', 'denied', '500', '502', '503', '504', '404', '403', '401'].some((token) => tokens.has(token)))
1430
+ return false;
1431
+ const subject = [
1432
+ 'permission',
1433
+ 'permissions',
1434
+ 'role',
1435
+ 'roles',
1436
+ 'access',
1437
+ 'admin',
1438
+ 'guard',
1439
+ 'guards',
1440
+ 'authorization',
1441
+ 'authorize',
1442
+ 'authorized',
1443
+ 'policy',
1444
+ 'policies',
1445
+ 'rbac',
1446
+ 'login',
1447
+ ].some((token) => tokens.has(token));
1448
+ if (!subject)
1449
+ return false;
1450
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'checked', 'configured', 'defined', 'require', 'requires', 'required', 'access'].some((token) => tokens.has(token)) || tokens.size >= 3;
1451
+ }
1452
+ function searchReliabilityContextMatches(tokens) {
1453
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1454
+ return false;
1455
+ const rateLimitSubject = ((tokens.has('rate') || tokens.has('rates')) && (tokens.has('limit') || tokens.has('limits') || tokens.has('limiting'))) ||
1456
+ tokens.has('throttle') ||
1457
+ tokens.has('throttling');
1458
+ const cacheSubject = ['cache', 'caches', 'cached', 'redis', 'invalidate', 'invalidates', 'invalidated', 'invalidation'].some((token) => tokens.has(token));
1459
+ const retrySubject = ['retry', 'retries', 'retried', 'backoff'].some((token) => tokens.has(token));
1460
+ const timeoutSubject = tokens.has('timeout') || tokens.has('timeouts');
1461
+ const circuitSubject = tokens.has('circuit') && tokens.has('breaker');
1462
+ const idempotencySubject = tokens.has('idempotency') || tokens.has('idempotent');
1463
+ const signatureSubject = (tokens.has('signature') || tokens.has('signatures')) && (tokens.has('webhook') || tokens.has('verified') || tokens.has('verify') || tokens.has('verification'));
1464
+ const debounceSubject = tokens.has('debounce') || tokens.has('debounced');
1465
+ const subject = rateLimitSubject || cacheSubject || retrySubject || timeoutSubject || circuitSubject || idempotencySubject || signatureSubject || debounceSubject;
1466
+ if (!subject)
1467
+ return false;
1468
+ return [
1469
+ 'where',
1470
+ 'which',
1471
+ 'what',
1472
+ 'find',
1473
+ 'locate',
1474
+ 'search',
1475
+ 'lookup',
1476
+ 'code',
1477
+ 'logic',
1478
+ 'configured',
1479
+ 'defined',
1480
+ 'handled',
1481
+ 'handling',
1482
+ 'used',
1483
+ 'set',
1484
+ 'sets',
1485
+ 'protect',
1486
+ 'protects',
1487
+ 'invalidate',
1488
+ 'invalidates',
1489
+ 'invalidated',
1490
+ 'retry',
1491
+ 'retries',
1492
+ 'verified',
1493
+ 'verify',
1494
+ ].some((token) => tokens.has(token));
1495
+ }
1496
+ function searchDataContractContextMatches(tokens) {
1497
+ if (packageScriptDiscoveryContextMatches(tokens))
1498
+ return false;
1499
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1500
+ return false;
1501
+ const validationSubject = ['validation', 'validate', 'validates', 'validator', 'schema', 'schemas', 'zod'].some((token) => tokens.has(token)) ||
1502
+ (tokens.has('input') && ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token)));
1503
+ const parsingSubject = ['params', 'param'].some((token) => tokens.has(token)) &&
1504
+ ['request', 'query', 'parse', 'parses', 'parsed'].some((token) => tokens.has(token));
1505
+ const serializationSubject = ['json', 'serialize', 'serializes', 'serialization', 'response'].some((token) => tokens.has(token)) ||
1506
+ (['date', 'format', 'formats', 'formatting'].some((token) => tokens.has(token)) && ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'defined'].some((token) => tokens.has(token)));
1507
+ const transactionSubject = tokens.has('transaction') || tokens.has('transactions');
1508
+ const lockingSubject = ['lock', 'locks', 'locking', 'optimistic'].some((token) => tokens.has(token)) && ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'row', 'order', 'logic'].some((token) => tokens.has(token));
1509
+ const uniquenessSubject = ['unique', 'uniqueness', 'enforced'].some((token) => tokens.has(token));
1510
+ const paginationSubject = ['pagination', 'cursor', 'cursors'].some((token) => tokens.has(token));
1511
+ const subject = validationSubject || parsingSubject || serializationSubject || transactionSubject || lockingSubject || uniquenessSubject || paginationSubject;
1512
+ if (!subject)
1513
+ return false;
1514
+ return [
1515
+ 'where',
1516
+ 'which',
1517
+ 'what',
1518
+ 'find',
1519
+ 'locate',
1520
+ 'search',
1521
+ 'lookup',
1522
+ 'input',
1523
+ 'validates',
1524
+ 'validation',
1525
+ 'parsed',
1526
+ 'parses',
1527
+ 'serializes',
1528
+ 'handled',
1529
+ 'defined',
1530
+ 'started',
1531
+ 'wrap',
1532
+ 'wraps',
1533
+ 'lock',
1534
+ 'locking',
1535
+ 'enforced',
1536
+ 'builds',
1537
+ ].some((token) => tokens.has(token));
1538
+ }
1539
+ function searchDataAccessContextMatches(tokens) {
1540
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1541
+ return false;
1542
+ if (['sink', 'sinks', 'source', 'taint', 'injection', 'xss', 'vulnerability', 'security', 'sanitize', 'sanitized', 'reach', 'reaches'].some((token) => tokens.has(token)))
1543
+ return false;
1544
+ if (tokens.has('drop') || tokens.has('delete') || tokens.has('remove'))
1545
+ return false;
1546
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1547
+ const orm = ['prisma', 'drizzle', 'typeorm', 'sequelize'].some((token) => tokens.has(token));
1548
+ const ormArtifact = ['model', 'models', 'schema', 'schemas', 'entity', 'entities'].some((token) => tokens.has(token));
1549
+ const sqlSubject = tokens.has('sql') && (tokens.has('query') || tokens.has('queries'));
1550
+ const repositorySubject = ['repository', 'repositories', 'dao', 'daos'].some((token) => tokens.has(token)) &&
1551
+ ['saves', 'save', 'persist', 'persists', 'orders', 'payments', 'find', 'where', 'which', 'what'].some((token) => tokens.has(token));
1552
+ const subject = (orm && ormArtifact) || sqlSubject || repositorySubject;
1553
+ if (!subject)
1554
+ return false;
1555
+ return locator || ['defined', 'configured', 'saves', 'save', 'persist', 'persists'].some((token) => tokens.has(token)) || tokens.size >= 3;
1556
+ }
1557
+ function searchIntegrationContextMatches(tokens) {
1558
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1559
+ return false;
1560
+ if (tokens.has('github') && ['action', 'actions', 'workflow', 'workflows', 'job', 'jobs', 'ci'].some((token) => tokens.has(token)))
1561
+ return false;
1562
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1563
+ const namedService = ['stripe', 'sendgrid', 's3', 'github', 'graphql', 'websocket', 'websockets', 'socket', 'sockets', 'axios'].some((token) => tokens.has(token));
1564
+ const genericService = ['integration', 'integrations', 'external', 'service', 'services', 'client', 'clients', 'sdk', 'sdks', 'api', 'apis'].some((token) => tokens.has(token));
1565
+ const transportSubject = ['fetch', 'http', 'rest'].some((token) => tokens.has(token));
1566
+ const callAction = ['call', 'calls', 'called', 'send', 'sends', 'sent', 'upload', 'uploads', 'uploaded'].some((token) => tokens.has(token));
1567
+ const emailProviderSubject = tokens.has('email') && (tokens.has('sendgrid') || ['send', 'sends', 'sent', 'through'].some((token) => tokens.has(token)));
1568
+ const storageSubject = tokens.has('s3') && ['upload', 'uploads', 'uploaded', 'client', 'sdk', 'bucket'].some((token) => tokens.has(token));
1569
+ const graphSubject = tokens.has('graphql') && ['query', 'queries', 'client', 'api'].some((token) => tokens.has(token));
1570
+ const socketSubject = ['websocket', 'websockets', 'socket', 'sockets'].some((token) => tokens.has(token)) &&
1571
+ ['connection', 'connections', 'opened', 'client'].some((token) => tokens.has(token));
1572
+ const apiClientSubject = namedService && ['api', 'apis', 'client', 'clients', 'sdk', 'sdks'].some((token) => tokens.has(token));
1573
+ const subject = emailProviderSubject ||
1574
+ storageSubject ||
1575
+ graphSubject ||
1576
+ socketSubject ||
1577
+ apiClientSubject ||
1578
+ (namedService && (callAction || locator || genericService)) ||
1579
+ ((genericService || transportSubject) && callAction);
1580
+ if (!subject)
1581
+ return false;
1582
+ return locator || callAction || tokens.size >= 3;
1583
+ }
1584
+ function searchApiContractContextMatches(tokens) {
1585
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1586
+ return false;
1587
+ if (tokens.has('public') && (tokens.has('contract') || tokens.has('contracts')))
1588
+ return false;
1589
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1590
+ const artifactAction = ['defined', 'defines', 'configured', 'generated', 'handles', 'handled'].some((token) => tokens.has(token));
1591
+ const openApiSubject = tokens.has('openapi') ||
1592
+ tokens.has('swagger') ||
1593
+ ((tokens.has('api') || tokens.has('apis')) && (tokens.has('spec') || tokens.has('specs') || tokens.has('docs')));
1594
+ const trpcSubject = tokens.has('trpc') && (tokens.has('router') || tokens.has('routers') || locator);
1595
+ const graphqlSubject = tokens.has('graphql') &&
1596
+ ['schema', 'schemas', 'resolver', 'resolvers', 'query', 'queries'].some((token) => tokens.has(token));
1597
+ const protoSubject = ['protobuf', 'proto', 'protos'].some((token) => tokens.has(token)) ||
1598
+ (tokens.has('grpc') && ['service', 'services', 'client', 'clients'].some((token) => tokens.has(token)));
1599
+ const subject = openApiSubject || trpcSubject || graphqlSubject || protoSubject;
1600
+ if (!subject)
1601
+ return false;
1602
+ return locator || artifactAction || tokens.size >= 3;
1603
+ }
1604
+ function searchInfraArtifactContextMatches(tokens) {
1605
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1606
+ return false;
1607
+ if (['fail', 'failing', 'failed', 'failure', 'failures', 'error', 'errors', 'flake', 'flaky', 'slow', 'slower'].some((token) => tokens.has(token)))
1608
+ return false;
1609
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1610
+ const dockerSubject = tokens.has('dockerfile') ||
1611
+ tokens.has('containerfile') ||
1612
+ (tokens.has('docker') && tokens.has('compose'));
1613
+ const orchestrationSubject = tokens.has('kubernetes') ||
1614
+ tokens.has('k8s') ||
1615
+ tokens.has('manifest') ||
1616
+ tokens.has('manifests') ||
1617
+ tokens.has('helm') ||
1618
+ tokens.has('chart') ||
1619
+ tokens.has('charts');
1620
+ const iacSubject = tokens.has('terraform') ||
1621
+ tokens.has('tf') ||
1622
+ tokens.has('cloudformation') ||
1623
+ tokens.has('cdk') ||
1624
+ tokens.has('pulumi') ||
1625
+ ((tokens.has('module') || tokens.has('modules')) && ['terraform', 'tf', 's3'].some((token) => tokens.has(token)));
1626
+ const hostedConfigSubject = ['vercel', 'netlify', 'railway', 'fly'].some((token) => tokens.has(token)) &&
1627
+ ['config', 'configuration', 'deploy', 'deployment'].some((token) => tokens.has(token));
1628
+ const workflowSubject = tokens.has('github') &&
1629
+ (tokens.has('workflow') || tokens.has('workflows')) &&
1630
+ ['deploy', 'deploys', 'deployment', 'staging', 'production'].some((token) => tokens.has(token));
1631
+ const subject = dockerSubject || orchestrationSubject || iacSubject || hostedConfigSubject || workflowSubject;
1632
+ if (!subject)
1633
+ return false;
1634
+ return locator || ['defined', 'configured', 'deploy', 'deploys', 'deployment'].some((token) => tokens.has(token)) || tokens.size >= 3;
1635
+ }
1636
+ function searchDomainWorkflowContextMatches(tokens) {
1637
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1638
+ return false;
1639
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1640
+ const action = ['handled', 'handles', 'implemented', 'creates', 'created', 'generated', 'sent', 'export', 'exports'].some((token) => tokens.has(token));
1641
+ const passwordReset = tokens.has('password') && tokens.has('reset');
1642
+ const inviteFlow = tokens.has('invite') || tokens.has('invites');
1643
+ const onboardingFlow = tokens.has('onboarding') && (tokens.has('flow') || tokens.has('flows'));
1644
+ const csvExport = tokens.has('csv') && (tokens.has('export') || tokens.has('exports'));
1645
+ const auditLog = tokens.has('audit') && (tokens.has('log') || tokens.has('logs') || tokens.has('entries'));
1646
+ const refundFlow = tokens.has('refund');
1647
+ const subscriptionRenewal = tokens.has('subscription') && tokens.has('renewal');
1648
+ const subject = passwordReset || inviteFlow || onboardingFlow || csvExport || auditLog || refundFlow || subscriptionRenewal;
1649
+ if (!subject)
1650
+ return false;
1651
+ return locator || action || tokens.size >= 3;
1652
+ }
1653
+ function searchCommunicationArtifactContextMatches(tokens) {
1654
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1655
+ return false;
1656
+ if (['leak', 'leaks', 'leaking', 'logged', 'logging', 'store', 'stores', 'retention', 'pii', 'gdpr', 'security'].some((token) => tokens.has(token)))
1657
+ return false;
1658
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1659
+ const artifact = ['template', 'templates', 'copy', 'pdf'].some((token) => tokens.has(token));
1660
+ const emailSubject = (tokens.has('email') || tokens.has('emails')) && (artifact || tokens.has('welcome') || tokens.has('receipt') || tokens.has('reset'));
1661
+ const pushSubject = tokens.has('push') && (tokens.has('notification') || tokens.has('notifications')) && tokens.has('copy');
1662
+ const smsSubject = tokens.has('sms') && (tokens.has('template') || tokens.has('verification'));
1663
+ const receiptSubject = tokens.has('receipt') && (tokens.has('email') || tokens.has('template'));
1664
+ const invoiceSubject = tokens.has('invoice') && tokens.has('pdf');
1665
+ const subject = emailSubject || pushSubject || smsSubject || receiptSubject || invoiceSubject;
1666
+ if (!subject)
1667
+ return false;
1668
+ return locator || ['send', 'sends', 'sent', 'generated', 'created'].some((token) => tokens.has(token)) || tokens.size >= 3;
1669
+ }
1670
+ function searchStateManagementContextMatches(tokens) {
1671
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1672
+ return false;
1673
+ if (['leak', 'leaks', 'leaking', 'logged', 'logging', 'retention', 'pii', 'gdpr', 'security', 'secret', 'secrets', 'token', 'tokens', 'password', 'customer', 'personal'].some((token) => tokens.has(token)))
1674
+ return false;
1675
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1676
+ const frameworkSubject = ['redux', 'zustand', 'jotai', 'recoil'].some((token) => tokens.has(token));
1677
+ const storeSubject = (tokens.has('state') && ['store', 'stores', 'stored'].some((token) => tokens.has(token))) ||
1678
+ (frameworkSubject && ['store', 'stores', 'slice', 'slices', 'selector', 'selectors'].some((token) => tokens.has(token)));
1679
+ const sliceSubject = frameworkSubject && (tokens.has('slice') || tokens.has('slices'));
1680
+ const selectorSubject = frameworkSubject && (tokens.has('selector') || tokens.has('selectors'));
1681
+ const contextSubject = tokens.has('context') &&
1682
+ ['provider', 'providers', 'supplies', 'supplied', 'provides', 'provided'].some((token) => tokens.has(token));
1683
+ const hookSubject = (tokens.has('hook') || tokens.has('hooks')) &&
1684
+ ['fetch', 'fetches', 'fetched', 'query', 'queries', 'mutation', 'mutations'].some((token) => tokens.has(token));
1685
+ const reactQuerySubject = tokens.has('react') &&
1686
+ tokens.has('query') &&
1687
+ ['query', 'queries', 'mutation', 'mutations', 'fetch', 'fetches', 'fetched'].some((token) => tokens.has(token));
1688
+ const subject = storeSubject || sliceSubject || selectorSubject || contextSubject || hookSubject || reactQuerySubject;
1689
+ if (!subject)
1690
+ return false;
1691
+ return locator || ['stored', 'fetch', 'fetches', 'fetched', 'supplies', 'supplied', 'provides', 'provided'].some((token) => tokens.has(token)) || tokens.size >= 3;
1692
+ }
1693
+ function searchNavigationLayoutContextMatches(tokens) {
1694
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo'].some((token) => tokens.has(token)))
1695
+ return false;
1696
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1697
+ const navSubject = ['sidebar', 'nav', 'navigation', 'menu'].some((token) => tokens.has(token)) &&
1698
+ ['item', 'items', 'billing', 'settings', 'checkout', 'route', 'routes'].some((token) => tokens.has(token));
1699
+ const breadcrumbSubject = tokens.has('breadcrumb') || tokens.has('breadcrumbs');
1700
+ const titleSubject = (tokens.has('page') && tokens.has('title')) ||
1701
+ tokens.has('metadata') ||
1702
+ tokens.has('meta');
1703
+ const layoutSubject = tokens.has('layout') &&
1704
+ (tokens.has('next') || tokens.has('js') || tokens.has('dashboard') || tokens.has('page') || tokens.has('route'));
1705
+ const subject = navSubject || breadcrumbSubject || titleSubject || layoutSubject;
1706
+ if (!subject)
1707
+ return false;
1708
+ return locator || ['renders', 'render', 'set', 'sets', 'configured', 'defined'].some((token) => tokens.has(token)) || tokens.size >= 3;
1709
+ }
1710
+ function searchFrontendPageRouteContextMatches(tokens) {
1711
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo'].some((token) => tokens.has(token)))
1712
+ return false;
1713
+ if (['why', 'returning', 'returns', 'failing', 'failed', 'failure', 'failures', 'production', 'prod', 'down', 'outage', 'incident', 'runtime', 'crash', 'crashes', 'crashing'].some((token) => tokens.has(token)))
1714
+ return false;
1715
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1716
+ const renderSignal = ['render', 'renders', 'rendered', 'handled', 'defined', 'located', 'lives'].some((token) => tokens.has(token));
1717
+ const pageSubject = tokens.has('page') &&
1718
+ (renderSignal ||
1719
+ ['billing', 'settings', 'checkout', 'dashboard', 'admin'].some((token) => tokens.has(token)) ||
1720
+ (tokens.has('not') && tokens.has('found')) ||
1721
+ tokens.has('404'));
1722
+ const routeSegmentSubject = (tokens.has('route') || tokens.has('routes')) &&
1723
+ (tokens.has('segment') || tokens.has('segments'));
1724
+ const notFoundSubject = tokens.has('page') && tokens.has('not') && tokens.has('found');
1725
+ const statusPageSubject = tokens.has('page') && tokens.has('404');
1726
+ const subject = pageSubject || routeSegmentSubject || notFoundSubject || statusPageSubject;
1727
+ if (!subject)
1728
+ return false;
1729
+ return locator || renderSignal || tokens.size >= 3;
1730
+ }
1731
+ function searchStyleSystemContextMatches(tokens) {
1732
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo'].some((token) => tokens.has(token)))
1733
+ return false;
1734
+ if (['why', 'failing', 'failed', 'failure', 'failures', 'broken', 'error', 'errors', 'runtime', 'production', 'prod', 'outage', 'incident'].some((token) => tokens.has(token)))
1735
+ return false;
1736
+ const locator = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1737
+ const styleSignal = ['defined', 'define', 'defines', 'configured', 'created', 'loaded', 'imported', 'implemented', 'handled', 'styles', 'style', 'styled', 'sets', 'set'].some((token) => tokens.has(token));
1738
+ const designTokenSubject = tokens.has('design') && (tokens.has('token') || tokens.has('tokens'));
1739
+ const tailwindSubject = tokens.has('tailwind') && (tokens.has('theme') || tokens.has('themes') || tokens.has('config') || tokens.has('configuration') || styleSignal);
1740
+ const cssSubject = tokens.has('css') &&
1741
+ (tokens.has('global') ||
1742
+ tokens.has('module') ||
1743
+ tokens.has('modules') ||
1744
+ ['imported', 'styles', 'style', 'styled', 'defined', 'configured'].some((token) => tokens.has(token)));
1745
+ const darkModeSubject = tokens.has('dark') && tokens.has('mode');
1746
+ const breakpointSubject = tokens.has('breakpoint') || tokens.has('breakpoints');
1747
+ const colorSubject = ['color', 'colors', 'palette', 'palettes'].some((token) => tokens.has(token)) && (tokens.has('theme') || tokens.has('design') || styleSignal);
1748
+ const subject = designTokenSubject || tailwindSubject || cssSubject || darkModeSubject || breakpointSubject || colorSubject;
1749
+ if (!subject)
1750
+ return false;
1751
+ return locator || styleSignal || tokens.size >= 3;
1752
+ }
1753
+ function styleSystemFailureContextMatches(tokens) {
1754
+ const styleSubject = (tokens.has('dark') && tokens.has('mode')) ||
1755
+ (tokens.has('design') && (tokens.has('token') || tokens.has('tokens'))) ||
1756
+ tokens.has('tailwind') ||
1757
+ tokens.has('css') ||
1758
+ tokens.has('breakpoint') ||
1759
+ tokens.has('breakpoints');
1760
+ return styleSubject && regressionFailureContextMatches(tokens);
1761
+ }
1762
+ function toolingFailureContextMatches(tokens) {
1763
+ const toolingSubject = ['vite', 'vitest', 'jest', 'babel', 'webpack', 'tsconfig', 'typescript', 'pnpm', 'yarn', 'npm'].some((token) => tokens.has(token));
1764
+ return toolingSubject && regressionFailureContextMatches(tokens);
1765
+ }
1766
+ function searchUiInteractionContextMatches(tokens) {
1767
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'next'].some((token) => tokens.has(token)))
1768
+ return false;
1769
+ const formSubject = (tokens.has('form') || tokens.has('forms')) && ['submit', 'submits', 'submitted', 'handles', 'handled'].some((token) => tokens.has(token));
1770
+ const stateSubject = (tokens.has('state') && ['loading', 'empty', 'error'].some((token) => tokens.has(token))) ||
1771
+ (tokens.has('empty') && tokens.has('results'));
1772
+ const boundarySubject = tokens.has('boundary') && tokens.has('error');
1773
+ const notificationSubject = ['toast', 'notification', 'notifications'].some((token) => tokens.has(token));
1774
+ const shortcutSubject = tokens.has('keyboard') ||
1775
+ tokens.has('shortcut') ||
1776
+ tokens.has('shortcuts') ||
1777
+ (tokens.has('command') && tokens.has('palette'));
1778
+ const modalSubject = tokens.has('modal');
1779
+ const componentSubject = tokens.has('component') && (tokens.has('page') || tokens.has('renders') || tokens.has('render'));
1780
+ const translationSubject = tokens.has('i18n') || tokens.has('translation') || tokens.has('translations');
1781
+ const accessibilitySubject = tokens.has('aria') || (tokens.has('focus') && tokens.has('trap'));
1782
+ const subject = formSubject ||
1783
+ stateSubject ||
1784
+ boundarySubject ||
1785
+ notificationSubject ||
1786
+ shortcutSubject ||
1787
+ modalSubject ||
1788
+ componentSubject ||
1789
+ translationSubject ||
1790
+ accessibilitySubject;
1791
+ if (!subject)
1792
+ return false;
1793
+ return [
1794
+ 'where',
1795
+ 'which',
1796
+ 'what',
1797
+ 'find',
1798
+ 'locate',
1799
+ 'search',
1800
+ 'lookup',
1801
+ 'handles',
1802
+ 'handled',
1803
+ 'renders',
1804
+ 'render',
1805
+ 'shown',
1806
+ 'triggers',
1807
+ 'triggered',
1808
+ 'opened',
1809
+ 'implemented',
1810
+ ].some((token) => tokens.has(token)) || tokens.size >= 3;
1811
+ }
1812
+ function explicitDataflowContextMatches(tokens) {
1813
+ return ['dataflow', 'taint', 'source', 'sink', 'sinks', 'reach', 'reaches', 'request', 'exec', 'injection', 'sql', 'xss', 'sanitize', 'sanitized', 'security', 'vulnerability', 'bypass'].some((token) => tokens.has(token));
1814
+ }
1815
+ function explicitDataflowRiskContextMatches(tokens) {
1816
+ return ['dataflow', 'taint', 'source', 'sink', 'sinks', 'reach', 'reaches', 'exec', 'injection', 'sql', 'xss', 'sanitize', 'sanitized', 'security', 'vulnerability', 'bypass', 'secret', 'secrets', 'expose', 'exposes', 'exposed', 'pii', 'gdpr', 'token', 'tokens', 'leak', 'leaks'].some((token) => tokens.has(token));
1817
+ }
1818
+ function searchConfigLookupContextMatches(tokens) {
1819
+ const configSubject = tokens.has('config') || tokens.has('configuration');
1820
+ if (!configSubject)
1821
+ return false;
1822
+ if (['need', 'needs', 'required', 'requires', 'missing', 'contract', 'contracts', 'env', 'environment', 'vars', 'variables'].some((token) => tokens.has(token)))
1823
+ return false;
1824
+ const lookup = ['where', 'which', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1825
+ const configDefinition = (tokens.has('file') || tokens.has('files')) && ['define', 'defines', 'alias', 'aliases', 'configured', 'configures'].some((token) => tokens.has(token));
1826
+ return lookup || configDefinition;
1827
+ }
1828
+ function searchToolingConfigContextMatches(tokens) {
1829
+ if (['add', 'create', 'implement', 'build', 'plan', 'should', 'todo', 'update', 'upgrade', 'bump', 'remove', 'drop', 'uninstall'].some((token) => tokens.has(token)))
1830
+ return false;
1831
+ if (['why', 'failing', 'failed', 'failure', 'failures', 'broken', 'error', 'errors', 'runtime', 'production', 'prod', 'outage', 'incident'].some((token) => tokens.has(token)))
1832
+ return false;
1833
+ const lookup = ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show'].some((token) => tokens.has(token));
1834
+ const configSignal = ['config', 'configuration', 'configured', 'file', 'files', 'defined', 'define', 'defines'].some((token) => tokens.has(token));
1835
+ const configTool = ['vite', 'vitest', 'jest', 'babel', 'webpack'].some((token) => tokens.has(token)) && (configSignal || lookup);
1836
+ const tsconfigSubject = tokens.has('tsconfig') ||
1837
+ (tokens.has('typescript') && ['config', 'configuration', 'path', 'paths', 'alias', 'aliases', 'strict'].some((token) => tokens.has(token)));
1838
+ const pathAliasSubject = ['path', 'paths'].some((token) => tokens.has(token)) &&
1839
+ ['alias', 'aliases'].some((token) => tokens.has(token)) &&
1840
+ (tokens.has('tsconfig') || tokens.has('typescript') || tokens.has('config'));
1841
+ const packageManagerSubject = tokens.has('package') && tokens.has('manager');
1842
+ const workspaceFileSubject = ['pnpm', 'yarn', 'npm'].some((token) => tokens.has(token)) &&
1843
+ ['workspace', 'workspaces', 'lockfile', 'lockfiles', 'package', 'manager'].some((token) => tokens.has(token));
1844
+ const subject = configTool || tsconfigSubject || pathAliasSubject || packageManagerSubject || workspaceFileSubject;
1845
+ if (!subject)
1846
+ return false;
1847
+ return lookup || configSignal || tokens.size >= 3;
1848
+ }
1849
+ function searchMigrationLookupContextMatches(tokens) {
1850
+ const migrationSubject = tokens.has('migration') || tokens.has('migrations');
1851
+ if (!migrationSubject)
1852
+ return false;
1853
+ if (['should', 'put', 'add', 'change', 'need', 'needs', 'plan', 'deploy', 'zero', 'downtime'].some((token) => tokens.has(token)))
1854
+ return false;
1855
+ if (['impact', 'breaks', 'break', 'breaking', 'blast', 'radius', 'affect', 'drop', 'delete', 'remove', 'rollback', 'revert'].some((token) => tokens.has(token)))
1856
+ return false;
1857
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show', 'exist', 'exists', 'ran', 'file', 'files'].some((token) => tokens.has(token));
1858
+ }
1859
+ function searchGeneratedContextMatches(tokens) {
1860
+ if (!tokens.has('generated'))
1861
+ return false;
1862
+ if (['write', 'create', 'generate', 'regenerate', 'build', 'emit'].some((token) => tokens.has(token)))
1863
+ return false;
1864
+ return ['where', 'which', 'what', 'find', 'locate', 'search', 'lookup', 'show', 'is', 'code', 'file', 'files'].some((token) => tokens.has(token));
1865
+ }
1866
+ function searchOwnershipContextMatches(tokens, hasFilePath) {
1867
+ if (hasFilePath)
1868
+ return false;
1869
+ if (claimContextMatches(tokens))
1870
+ return false;
1871
+ if (tokens.has('changed') && (tokens.has('file') || tokens.has('files')))
1872
+ return false;
1873
+ if (['pr', 'pull', 'request', 'review', 'reviewer', 'reviewers'].some((token) => tokens.has(token)))
1874
+ return false;
1875
+ const ownershipSignal = ['owner', 'owners', 'ownership', 'owns', 'team'].some((token) => tokens.has(token));
1876
+ const helpSignal = ['ask', 'help', 'knows', 'expert', 'experts', 'contact', 'contacts'].some((token) => tokens.has(token));
1877
+ const lookupSignal = ['who', 'which', 'find', 'locate', 'search', 'where'].some((token) => tokens.has(token));
1878
+ return (ownershipSignal && (lookupSignal || tokens.has('area'))) || (helpSignal && lookupSignal);
1879
+ }
1880
+ function searchDocumentationContextMatches(tokens) {
1881
+ const locator = ['where', 'find', 'locate', 'search', 'lookup'].some((token) => tokens.has(token));
1882
+ const docsSubject = ['doc', 'docs', 'document', 'documentation', 'documented', 'readme', 'examples', 'example', 'guide'].some((token) => tokens.has(token));
1883
+ return locator && docsSubject;
1884
+ }
1885
+ function fileHistoryContextMatches(tokens) {
1886
+ return ['last', 'recently', 'history', 'author', 'authors', 'blame', 'touched', 'touch', 'changed'].some((token) => tokens.has(token));
1887
+ }
1888
+ function fileTestContextMatches(tokens) {
1889
+ if (['coverage', 'covered', 'uncovered', 'add', 'write'].some((token) => tokens.has(token)))
1890
+ return true;
1891
+ const testQuestion = tokens.has('test') || tokens.has('tests');
1892
+ const runOrLocationQuestion = ['run', 'rerun', 'where', 'find', 'locate', 'search'].some((token) => tokens.has(token));
1893
+ return testQuestion && !runOrLocationQuestion;
1894
+ }
1895
+ function impactDeleteContextMatches(tokens) {
1896
+ return ['impact', 'breaks', 'break', 'blast', 'radius', 'depends', 'affect', 'callers', 'used', 'usage', 'referenced', 'called', 'breaking', 'dead', 'unused', 'orphaned'].some((token) => tokens.has(token));
1897
+ }
1898
+ function impactDatabaseContextMatches(tokens) {
1899
+ const databaseSubject = ['database', 'db', 'schema', 'table', 'column', 'sql'].some((token) => tokens.has(token));
1900
+ const destructiveSubject = ['drop', 'schema', 'table', 'column', 'sql'].some((token) => tokens.has(token));
1901
+ const migrationSubject = tokens.has('migration') || tokens.has('migrations');
1902
+ const migrationImpactSignal = ['impact', 'breaks', 'break', 'breaking', 'blast', 'radius', 'affect', 'drop', 'delete', 'remove', 'rollback', 'revert'].some((token) => tokens.has(token));
1903
+ const breakQuestion = ['breaks', 'break', 'breaking'].some((token) => tokens.has(token));
1904
+ return destructiveSubject || (databaseSubject && breakQuestion) || (migrationSubject && migrationImpactSignal);
1905
+ }
1906
+ function impactApiKeywordMatches(keyword) {
1907
+ return ['api', 'apis', 'endpoint', 'endpoints', 'client', 'clients', 'contract', 'contracts', 'change', 'changes', 'changing', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible', 'version', 'versions'].includes(keyword);
1908
+ }
1909
+ function impactApiContextMatches(tokens) {
1910
+ const apiSubject = ['api', 'apis', 'endpoint', 'endpoints', 'client', 'clients', 'contract', 'contracts', 'public'].some((token) => tokens.has(token));
1911
+ const breakingSignal = ['impact', 'break', 'breaks', 'breaking', 'blast', 'radius', 'affect', 'callers', 'used', 'usage', 'referenced', 'called', 'rename', 'remove', 'delete', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'change', 'changes', 'changing', 'compatibility', 'compatible', 'version', 'versions'].some((token) => tokens.has(token));
1912
+ return apiSubject && breakingSignal;
1913
+ }
1914
+ function impactRollbackContextMatches(keyword, tokens) {
1915
+ if (['revert', 'rollback', 'undo', 'backout'].includes(keyword))
1916
+ return true;
1917
+ if (['back', 'out'].includes(keyword))
1918
+ return tokens.has('back') && tokens.has('out');
1919
+ if (keyword === 'recover')
1920
+ return tokens.has('bad') || tokens.has('deploy') || tokens.has('deployment');
1921
+ return false;
1922
+ }
1923
+ function doctorCleanupDeleteContextMatches(tokens, hasFilePath, hasPackageRemoval) {
1924
+ if (hasFilePath || hasPackageRemoval)
1925
+ return false;
1926
+ return ((tokens.has('safe') || tokens.has('safely') || tokens.has('cleanup') || tokens.has('clean')) &&
1927
+ (tokens.has('delete') || tokens.has('remove')) &&
1928
+ !impactDeleteContextMatches(tokens));
1929
+ }
1930
+ function doctorCleanupDiscoveryContextMatches(tokens) {
1931
+ const cleanupSignal = ['dead', 'unused', 'orphaned', 'cleanup', 'clean'].some((token) => tokens.has(token));
1932
+ if (!cleanupSignal)
1933
+ return false;
1934
+ return ['code', 'export', 'exports', 'delete', 'remove', 'find', 'search', 'locate', 'where'].some((token) => tokens.has(token));
1935
+ }
1936
+ function preflightReadyContextMatches(tokens) {
1937
+ return ['safe', 'safety', 'gate', 'preflight', 'commit', 'merge', 'edit', 'proceed', 'block', 'blocked', 'blocker', 'blockers', 'blocking', 'allowed'].some((token) => tokens.has(token));
1938
+ }
1939
+ function preflightRiskContextMatches(tokens) {
1940
+ return ['safe', 'safety', 'gate', 'preflight', 'commit', 'merge', 'merged', 'merging', 'proceed', 'block', 'blocked', 'blocker', 'blockers', 'blocking', 'allowed'].some((token) => tokens.has(token));
1941
+ }
1942
+ function preflightBranchRecoveryContextMatches(tokens) {
1943
+ if (tokens.has('test') || tokens.has('tests'))
1944
+ return false;
1945
+ const rebaseSignal = tokens.has('rebase') || tokens.has('rebasing');
1946
+ const conflictSignal = tokens.has('conflict') || tokens.has('conflicts');
1947
+ const resolveSignal = tokens.has('resolve') || tokens.has('resolving');
1948
+ const troubleSignal = tokens.has('wrong') || tokens.has('stuck');
1949
+ const mergeSignal = tokens.has('merge') || tokens.has('merged') || tokens.has('merging') || tokens.has('main') || tokens.has('branch');
1950
+ return rebaseSignal || ((conflictSignal || resolveSignal) && mergeSignal) || (troubleSignal && (rebaseSignal || conflictSignal));
1951
+ }
1952
+ function hotspotFileRiskContextMatches(tokens) {
1953
+ return ['risk', 'risks', 'risky', 'riskiest', 'dangerous', 'hotspot', 'hotspots', 'complex', 'complexity', 'refactor', 'refactoring', 'simplify', 'simplification', 'tech', 'debt', 'duplicate', 'duplicated', 'duplication', 'over', 'engineered', 'performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'].some((token) => tokens.has(token));
1954
+ }
1955
+ function hotspotWhereContextMatches(tokens, hasFilePath) {
1956
+ return (['focus', 'risky', 'riskiest', 'dangerous', 'hotspot', 'hotspots', 'churn', 'complexity', 'complex', 'refactor', 'refactoring', 'simplify', 'simplification', 'tech', 'debt', 'duplicate', 'duplicated', 'duplication', 'over', 'engineered', 'performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'].some((token) => tokens.has(token)) ||
1957
+ (!hasFilePath && tokens.has('start') && !localServiceSetupCommandContextMatches(tokens)));
1958
+ }
1959
+ function hotspotPerformanceContextMatches(tokens) {
1960
+ if (tokens.has('slow') && regressionPerformanceContextMatches(tokens))
1961
+ return false;
1962
+ return ['performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'].some((token) => tokens.has(token));
1963
+ }
1964
+ function prDiffKeywordMatches(keyword, tokens) {
1965
+ const commitMessageContext = tokens.has('commit') && tokens.has('message');
1966
+ const commitSummaryContext = tokens.has('commit') &&
1967
+ (tokens.has('summarize') || tokens.has('summary')) &&
1968
+ (tokens.has('change') || tokens.has('changes') || tokens.has('changed') || tokens.has('diff'));
1969
+ if (keyword === 'commit')
1970
+ return commitMessageContext || commitSummaryContext;
1971
+ if (keyword === 'message')
1972
+ return commitMessageContext && tokens.has('message');
1973
+ if (['summarize', 'summary'].includes(keyword))
1974
+ return commitSummaryContext;
1975
+ if (['large', 'big', 'size', 'sizes'].includes(keyword))
1976
+ return prSizeContextMatches(tokens);
1977
+ if (['compare', 'stale', 'branched', 'behind', 'ahead', 'sync', 'synced'].includes(keyword))
1978
+ return branchSyncDiffContextMatches(tokens);
1979
+ if (keyword === 'change') {
1980
+ return prSizeContextMatches(tokens) || ['did', 'since', 'branch', 'main', 'base', 'head', 'pr', 'diff'].some((token) => tokens.has(token));
1981
+ }
1982
+ if (['since', 'branch', 'main', 'base', 'head'].includes(keyword)) {
1983
+ return branchSyncDiffContextMatches(tokens) || ['change', 'changed', 'changes', 'diff', 'pr', 'pull', 'request'].some((token) => tokens.has(token));
1984
+ }
1985
+ return true;
1986
+ }
1987
+ function prSizeContextMatches(tokens) {
1988
+ if (dependencyBloatContextMatches(tokens))
1989
+ return false;
1990
+ const sizeSignal = ['large', 'big', 'size', 'sizes'].some((token) => tokens.has(token));
1991
+ const changeSubject = ['pr', 'pull', 'request', 'change', 'changes', 'diff', 'branch'].some((token) => tokens.has(token));
1992
+ return sizeSignal && changeSubject;
1993
+ }
1994
+ function branchSyncDiffContextMatches(tokens) {
1995
+ if (['conflict', 'conflicts', 'resolve', 'resolving', 'rebase', 'rebasing'].some((token) => tokens.has(token)))
1996
+ return false;
1997
+ const syncSignal = ['compare', 'stale', 'branched', 'behind', 'ahead', 'sync', 'synced'].some((token) => tokens.has(token));
1998
+ const branchSubject = ['branch', 'main', 'base', 'head'].some((token) => tokens.has(token));
1999
+ return syncSignal && branchSubject;
2000
+ }
2001
+ function claimContextMatches(tokens) {
2002
+ return ['claim', 'claims', 'lease', 'leases', 'reserve', 'lock'].some((token) => tokens.has(token));
2003
+ }
2004
+ function claimKeywordMatches(keyword, tokens) {
2005
+ if (['claim', 'claims', 'lease', 'leases', 'reserve', 'lock'].includes(keyword))
2006
+ return true;
2007
+ return claimContextMatches(tokens);
2008
+ }
2009
+ function coordinateAgentContextMatches(tokens) {
2010
+ return [
2011
+ 'coordinate',
2012
+ 'coordination',
2013
+ 'status',
2014
+ 'readiness',
2015
+ 'parallel',
2016
+ 'swarm',
2017
+ 'collide',
2018
+ 'colliding',
2019
+ 'conflict',
2020
+ 'conflicts',
2021
+ 'conflicting',
2022
+ 'conflicted',
2023
+ 'worktree',
2024
+ 'worktrees',
2025
+ 'overlap',
2026
+ ].some((token) => tokens.has(token));
2027
+ }
2028
+ function coordinateWorkingContextMatches(tokens) {
2029
+ return (tokens.has('working') || tokens.has('editing')) && (tokens.has('who') || tokens.has('else'));
2030
+ }
2031
+ function coordinateActiveContextMatches(tokens) {
2032
+ return ['worktree', 'worktrees', 'agent', 'agents', 'parallel', 'swarm'].some((token) => tokens.has(token));
2033
+ }
2034
+ function coordinateConflictContextMatches(tokens) {
2035
+ return ['coordinate', 'coordination', 'parallel', 'agents', 'agent', 'swarm', 'worktree', 'worktrees', 'collide', 'colliding', 'overlap', 'overlapping'].some((token) => tokens.has(token));
2036
+ }
2037
+ function collisionConflictContextMatches(tokens) {
2038
+ return ['coordinate', 'coordination', 'parallel', 'agents', 'agent', 'swarm', 'worktree', 'worktrees', 'collision', 'collide', 'colliding', 'overlap', 'overlapping'].some((token) => tokens.has(token));
2039
+ }
2040
+ function collisionChangeContextMatches(tokens) {
2041
+ return ['overlap', 'overlapping', 'conflict', 'conflicts', 'collision', 'collide', 'colliding', 'worktree', 'worktrees'].some((token) => tokens.has(token));
2042
+ }
2043
+ function mergeRiskKeywordMatches(keyword, tokens) {
2044
+ if (keyword === 'first')
2045
+ return ['merge', 'merged', 'merging', 'integrate', 'integration'].some((token) => tokens.has(token));
2046
+ if (keyword === 'branch')
2047
+ return ['first', 'order', 'sequence', 'integrate', 'integration'].some((token) => tokens.has(token));
2048
+ return true;
2049
+ }
2050
+ function sessionLeaveOffContextMatches(tokens) {
2051
+ return tokens.has('off') && (tokens.has('leave') || tokens.has('left'));
2052
+ }
2053
+ function sessionAwayContextMatches(tokens) {
2054
+ return ((tokens.has('away') || tokens.has('asleep') || tokens.has('slept') || tokens.has('offline')) &&
2055
+ ['changed', 'changes', 'change', 'last', 'previous', 'session', 'resume'].some((token) => tokens.has(token)));
2056
+ }
2057
+ function sessionAgentContextMatches(tokens) {
2058
+ if (!tokens.has('agent'))
2059
+ return false;
2060
+ if (tokens.has('touch') || tokens.has('touched'))
2061
+ return false;
2062
+ return ['last', 'previous', 'did', 'do', 'changed', 'changes', 'session', 'events', 'history'].some((token) => tokens.has(token));
2063
+ }
2064
+ function workplanDoContextMatches(tokens) {
2065
+ return ['next', 'plan', 'workplan', 'tasks', 'task', 'todo', 'prioritize', 'roadmap'].some((token) => tokens.has(token));
2066
+ }
2067
+ function bugHuntSpeedContextMatches(tokens) {
2068
+ return ['fix', 'issue', 'issues', 'bug', 'bugs', 'defect', 'broken', 'repair'].some((token) => tokens.has(token)) || bugHuntOpportunityContextMatches(tokens);
2069
+ }
2070
+ function bugHuntOpportunityContextMatches(tokens) {
2071
+ const opportunityTokens = ['quick', 'quickest', 'smallest', 'small', 'low', 'lowest', 'improve', 'improvement', 'useful', 'easy', 'beginner', 'starter', 'intern', 'interns', 'task', 'tasks', 'five', 'minutes', 'today', 'win', 'wins'];
2072
+ const count = opportunityTokens.filter((token) => tokens.has(token)).length;
2073
+ return count >= 2 || ['intern', 'interns', 'fix', 'issue', 'issues', 'bug', 'bugs', 'defect', 'broken', 'repair', 'cleanup', 'clean'].some((token) => tokens.has(token));
2074
+ }
2075
+ function testRunContextMatches(tokens) {
2076
+ return tokens.has('test') || tokens.has('tests');
2077
+ }
2078
+ function regressionFailureContextMatches(tokens) {
2079
+ const failureSignal = ['fail', 'failing', 'failed', 'failure', 'failures', 'error', 'errors'].some((token) => tokens.has(token));
2080
+ const statusCodeSignal = ['500', '502', '503', '504', '404', '403', '401'].some((token) => tokens.has(token));
2081
+ const downOutageSignal = tokens.has('down') && ['production', 'prod', 'service', 'site', 'app', 'api', 'outage'].some((token) => tokens.has(token));
2082
+ const outageSignal = downOutageSignal || ['production', 'prod', 'outage', 'incident', 'runtime', 'crash', 'crashes', 'crashing'].some((token) => tokens.has(token));
2083
+ const connectionRefusedSignal = tokens.has('connection') && tokens.has('refused');
2084
+ const localSetupSignal = regressionLocalSetupContextMatches(tokens);
2085
+ const triageSignal = tokens.has('triage') && outageSignal;
2086
+ const stackTraceSignal = tokens.has('stack') && tokens.has('trace');
2087
+ const debugSignal = tokens.has('debug') && (tokens.has('stack') || tokens.has('trace') || tokens.has('error') || tokens.has('errors'));
2088
+ const rootCauseSignal = tokens.has('root') && tokens.has('cause') && (failureSignal || statusCodeSignal || outageSignal);
2089
+ const returningStatusSignal = (tokens.has('returning') || tokens.has('returns')) && statusCodeSignal;
2090
+ const logFailureSignal = (tokens.has('log') || tokens.has('logs')) && (failureSignal || outageSignal || statusCodeSignal);
2091
+ return failureSignal || statusCodeSignal || outageSignal || connectionRefusedSignal || localSetupSignal || triageSignal || stackTraceSignal || debugSignal || rootCauseSignal || returningStatusSignal || logFailureSignal;
2092
+ }
2093
+ function regressionLocalSetupContextMatches(tokens) {
2094
+ const portSignal = tokens.has('eaddrinuse') ||
2095
+ ((tokens.has('port') || tokens.has('ports')) && ['already', 'use', 'used', 'listen', 'address', 'startup', 'start', 'server', 'dev'].some((token) => tokens.has(token)));
2096
+ const permissionSignal = tokens.has('permission') && tokens.has('denied');
2097
+ const packageManagerSignal = tokens.has('enoent') ||
2098
+ tokens.has('eresolve') ||
2099
+ (tokens.has('peer') && ['dependency', 'dependencies', 'conflict', 'install', 'npm', 'pnpm', 'yarn'].some((token) => tokens.has(token)));
2100
+ return portSignal || permissionSignal || packageManagerSignal;
2101
+ }
2102
+ function regressionCiPlatformContextMatches(tokens) {
2103
+ const platformSignal = tokens.has('ci') ||
2104
+ tokens.has('github') ||
2105
+ ['action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines'].some((token) => tokens.has(token));
2106
+ const explicitTroubleSignal = ['fail', 'failing', 'failed', 'failure', 'failures', 'error', 'errors', 'slow', 'slower', 'flake', 'flaky', 'flakes', 'intermittent', 'intermittently'].some((token) => tokens.has(token));
2107
+ return platformSignal && (explicitTroubleSignal || regressionFailureContextMatches(tokens) || regressionPerformanceContextMatches(tokens) || regressionFlakeContextMatches(tokens));
2108
+ }
2109
+ function regressionPerformanceContextMatches(tokens) {
2110
+ if ((tokens.has('find') || tokens.has('locate') || tokens.has('search')) && (tokens.has('test') || tokens.has('tests')) && (tokens.has('slow') || tokens.has('slower'))) {
2111
+ return false;
2112
+ }
2113
+ const performanceSignal = ['slow', 'slower', 'speed', 'speedup', 'faster', 'benchmark', 'benchmarks'].some((token) => tokens.has(token));
2114
+ if (!performanceSignal)
2115
+ return false;
2116
+ return regressionBenchmarkContextMatches(tokens) || ['ci', 'github', 'action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines', 'test', 'tests', 'build', 'builds'].some((token) => tokens.has(token));
2117
+ }
2118
+ function regressionBenchmarkContextMatches(tokens) {
2119
+ return ['benchmark', 'benchmarks'].some((token) => tokens.has(token));
2120
+ }
2121
+ function regressionFlakeContextMatches(tokens) {
2122
+ const flakeSignal = ['flake', 'flaky', 'flakes', 'intermittent', 'intermittently', 'nondeterministic', 'nondeterminism'].some((token) => tokens.has(token));
2123
+ const verificationSubject = ['ci', 'github', 'action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines', 'test', 'tests', 'suite', 'failure', 'failures', 'fail', 'failing', 'failed'].some((token) => tokens.has(token));
2124
+ const reproduceSignal = ['command', 'commands', 'reproduce', 'reproduces', 'reproducing'].some((token) => tokens.has(token));
2125
+ const stabilizationSignal = ['stabilize', 'stabilise', 'quarantine'].some((token) => tokens.has(token));
2126
+ const raceSignal = tokens.has('race') && (tokens.has('condition') || verificationSubject);
2127
+ if (flakeSignal)
2128
+ return verificationSubject || reproduceSignal || stabilizationSignal || raceSignal;
2129
+ if (raceSignal)
2130
+ return true;
2131
+ return (reproduceSignal || stabilizationSignal) && verificationSubject;
2132
+ }
2133
+ function proofCommandContextMatches(tokens) {
2134
+ return ['proof', 'prove', 'verify', 'verification', 'regression', 'test', 'tests', 'push', 'pushing'].some((token) => tokens.has(token));
2135
+ }
2136
+ function releaseReadinessContextMatches(tokens) {
2137
+ return ['release', 'releasing', 'deploy', 'deploying', 'deployed', 'deployment', 'ship', 'shipping', 'publish', 'tag', 'changelog', 'sbom', 'package'].some((token) => tokens.has(token));
2138
+ }
2139
+ function releaseTrainKeywordMatches(keyword, tokens) {
2140
+ if (keyword === 'check')
2141
+ return releaseReadinessContextMatches(tokens);
2142
+ if (['changed', 'since', 'last'].includes(keyword)) {
2143
+ return releaseCommunicationContextMatches(tokens) && (tokens.has('changed') || tokens.has('change') || tokens.has('changes'));
2144
+ }
2145
+ if (['note', 'notes', 'draft', 'entry', 'summarize', 'summary', 'change', 'changes'].includes(keyword)) {
2146
+ return releaseCommunicationContextMatches(tokens);
2147
+ }
2148
+ return true;
2149
+ }
2150
+ function releaseCommunicationContextMatches(tokens) {
2151
+ return ['release', 'releasing', 'deploy', 'deploying', 'deployed', 'deployment', 'ship', 'shipping', 'publish', 'tag', 'changelog'].some((token) => tokens.has(token));
2152
+ }
2153
+ function routeMatch(entry, rank, matchedKeywords) {
2154
+ const score = routeScore(entry, matchedKeywords);
2155
+ return {
2156
+ ...entry,
2157
+ score,
2158
+ rank,
2159
+ confidence: routeConfidence(score),
2160
+ matchedKeywords,
211
2161
  };
212
2162
  }
2163
+ function routeScore(entry, matchedKeywords) {
2164
+ return matchedKeywords.reduce((total, keyword) => total + keywordWeight(entry, keyword), 0);
2165
+ }
2166
+ function keywordWeight(entry, keyword) {
2167
+ if (entry.tool === 'projscan_privacy_check') {
2168
+ if (['privacy', 'trust', 'boundary', 'env', 'upload', 'telemetry', 'network', 'offline', 'leave', 'machine'].includes(keyword))
2169
+ return 3;
2170
+ if (['read', 'code', 'source', 'values', 'contact', 'contacted', 'write', 'writes', 'projscan'].includes(keyword))
2171
+ return 2;
2172
+ }
2173
+ if (entry.tool === 'projscan_understand' && ['read', 'summarize', 'summary', 'contract', 'contracts', 'api', 'apis', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible', 'architecture', 'repo', 'codebase', 'service', 'services', 'main', 'entrypoint', 'entrypoints', 'entry', 'point', 'important', 'look', 'first', 'tour', 'walk', 'through', 'new', 'onboard', 'onboarding', 'run', 'runs', 'start', 'app', 'command', 'commands', 'dev', 'server', 'local', 'locally', 'docker', 'compose', 'npm', 'script', 'scripts', 'test', 'tests', 'e2e', 'unit', 'integration', 'storybook', 'cypress', 'playwright', 'eslint', 'prettier', 'format', 'lint', 'typecheck', 'typechecking', 'verify', 'verification', 'proof', 'prove', 'checks', 'environment', 'environments', 'variable', 'variables', 'missing', 'required', 'feature', 'endpoint', 'button', 'put', 'need', 'change', 'files', 'add', 'implement', 'build', 'create', 'wire', 'route', 'component', 'page', 'screen', 'view', 'webhook', 'login', 'support', 'checkout', 'search', 'doc', 'docs', 'document', 'documentation', 'readme', 'examples', 'example', 'migration', 'migrations', 'migrate', 'migrates', 'database', 'db', 'schema', 'table', 'column', 'seed', 'seeds', 'reset', 'resets', 'guide', 'update', 'updating'].includes(keyword))
2174
+ return 2;
2175
+ if (entry.tool === 'projscan_file' && keyword === 'read')
2176
+ return 3;
2177
+ if (entry.tool === 'projscan_file' && ['review', 'reviewer', 'reviewers'].includes(keyword))
2178
+ return 5;
2179
+ if (entry.tool === 'projscan_file' && ['file', 'explain', 'inspect', 'owns', 'risk', 'risks', 'risky', 'dangerous'].includes(keyword))
2180
+ return 2;
2181
+ if (entry.tool === 'projscan_file' && ['last', 'touched', 'touch', 'changed', 'recently', 'history', 'author', 'authors', 'blame'].includes(keyword))
2182
+ return 2;
2183
+ if (entry.tool === 'projscan_file' && ['add', 'write'].includes(keyword))
2184
+ return 2;
2185
+ if (entry.tool === 'projscan_file' && ['coverage', 'covered', 'uncovered', 'test', 'tests'].includes(keyword))
2186
+ return 2;
2187
+ if (entry.tool === 'projscan_impact' && ['delete', 'remove'].includes(keyword))
2188
+ return 2;
2189
+ if (entry.tool === 'projscan_impact' && ['drop', 'schema', 'table', 'column', 'database', 'db', 'migration', 'migrations'].includes(keyword))
2190
+ return 2;
2191
+ if (entry.tool === 'projscan_impact' && ['revert', 'rollback', 'undo', 'backout', 'back', 'out', 'recover'].includes(keyword))
2192
+ return 2;
2193
+ if (entry.tool === 'projscan_impact' && ['depends', 'used', 'usage', 'referenced', 'called'].includes(keyword))
2194
+ return 2;
2195
+ if (entry.tool === 'projscan_impact' && ['api', 'apis', 'endpoint', 'endpoints', 'client', 'clients', 'contract', 'contracts', 'deprecate', 'deprecates', 'deprecated', 'deprecation', 'compatibility', 'compatible', 'version', 'versions'].includes(keyword))
2196
+ return 2;
2197
+ if (entry.tool === 'projscan_impact' && ['change', 'changes', 'changing'].includes(keyword))
2198
+ return 3;
2199
+ if (entry.tool === 'projscan_explain_issue' && keyword === 'explain')
2200
+ return 2;
2201
+ if (entry.tool === 'projscan_semantic_graph' && ['import', 'imports', 'importers', 'exports', 'defined', 'definition', 'uses', 'depend', 'depends', 'installed'].includes(keyword))
2202
+ return 2;
2203
+ if (entry.tool === 'projscan_coupling') {
2204
+ if (['circular', 'cycle', 'cycles'].includes(keyword))
2205
+ return 3;
2206
+ if (['coupling', 'coupled', 'tightly', 'module', 'modules', 'dependency', 'dependencies', 'import', 'imports', 'fan', 'instability', 'architecture', 'boundary', 'boundaries'].includes(keyword))
2207
+ return 2;
2208
+ }
2209
+ if (entry.tool === 'projscan_coverage' && ['coverage', 'untested', 'uncovered', 'scariest', 'gap', 'gaps', 'test', 'tests', 'file', 'files', 'no', 'missing', 'without'].includes(keyword))
2210
+ return 2;
2211
+ if (entry.tool === 'projscan_dependencies' && ['dependencies', 'dependency', 'deps', 'package', 'packages', 'inventory', 'declared'].includes(keyword))
2212
+ return 2;
2213
+ if (entry.tool === 'projscan_dependencies' && ['bundle', 'bundles', 'size', 'sizes', 'large', 'heavy', 'bloat', 'bloated', 'weight', 'footprint', 'reduce', 'slim'].includes(keyword))
2214
+ return 2;
2215
+ if (entry.tool === 'projscan_dependencies' && ['license', 'licenses', 'gpl', 'copyleft', 'notice', 'notices', 'third', 'party', 'open', 'source', 'compliance'].includes(keyword))
2216
+ return 2;
2217
+ if (entry.tool === 'projscan_workspaces' && ['workspace', 'workspaces', 'monorepo', 'package', 'packages', 'map', 'list', 'owns', 'contains', 'put', 'change'].includes(keyword))
2218
+ return 2;
2219
+ if (entry.tool === 'projscan_upgrade' && ['upgrade', 'bump', 'update', 'remove', 'drop', 'uninstall', 'package'].includes(keyword))
2220
+ return 2;
2221
+ if (entry.tool === 'projscan_audit') {
2222
+ if (['audit', 'cve', 'cves', 'vulnerable', 'vulnerability', 'vulnerabilities', 'security', 'secure', 'safe'].includes(keyword))
2223
+ return 2;
2224
+ if (['dependency', 'dependencies', 'package', 'packages', 'npm'].includes(keyword))
2225
+ return 1;
2226
+ }
2227
+ if (entry.tool === 'projscan_dataflow' && ['dataflow', 'taint', 'security', 'injection', 'secret', 'secrets', 'expose', 'exposes', 'exposed', 'sanitize', 'sanitized', 'request', 'data', 'reach', 'reaches', 'exec', 'auth', 'bypass', 'pii', 'gdpr', 'compliance', 'personal', 'customer', 'email', 'emails', 'password', 'token', 'tokens', 'leak', 'leaks', 'logged', 'logging', 'log', 'logs', 'store', 'stores', 'retention', 'handled', 'handles', 'process', 'processes', 'processing'].includes(keyword))
2228
+ return 2;
2229
+ if (entry.tool === 'projscan_search' && ['welcome', 'template', 'templates', 'copy', 'push', 'sms', 'verification', 'receipt', 'invoice', 'pdf'].includes(keyword))
2230
+ return 2;
2231
+ if (entry.tool === 'projscan_search' && ['password', 'reset', 'invite', 'invites', 'onboarding', 'flow', 'flows', 'csv', 'export', 'exports', 'audit', 'entries', 'refund', 'handling', 'payments', 'subscription', 'renewal', 'users', 'creates'].includes(keyword))
2232
+ return 2;
2233
+ if (entry.tool === 'projscan_search' && ['prisma', 'drizzle', 'typeorm', 'sequelize', 'sql', 'model', 'models', 'entity', 'entities', 'repository', 'repositories', 'dao', 'daos', 'saves', 'save', 'persist', 'persists', 'orders'].includes(keyword))
2234
+ return 2;
2235
+ if (entry.tool === 'projscan_search' && ['docker', 'dockerfile', 'compose', 'containerfile', 'kubernetes', 'k8s', 'manifest', 'manifests', 'helm', 'chart', 'charts', 'terraform', 'tf', 'module', 'modules', 'cloudformation', 'cdk', 'pulumi', 'vercel', 'netlify', 'railway', 'fly', 'workflow', 'workflows', 'deploy', 'deploys', 'deployment', 'staging', 'production'].includes(keyword))
2236
+ return 2;
2237
+ if (entry.tool === 'projscan_search' && ['openapi', 'swagger', 'trpc', 'grpc', 'protobuf', 'proto', 'protos', 'router', 'routers', 'resolver', 'resolvers'].includes(keyword))
2238
+ return 2;
2239
+ if (entry.tool === 'projscan_search' && ['call', 'calls', 'called', 'client', 'clients', 'sdk', 'sdks', 'integration', 'integrations', 'stripe', 'sendgrid', 's3', 'github', 'graphql', 'websocket', 'websockets', 'socket', 'sockets', 'connection', 'connections', 'rest', 'http', 'fetch', 'axios', 'external', 'service', 'services', 'upload', 'uploads', 'uploaded', 'sent', 'opened'].includes(keyword))
2240
+ return 2;
2241
+ if (entry.tool === 'projscan_search' && ['store', 'stores', 'stored', 'redux', 'slice', 'slices', 'selector', 'selectors', 'zustand', 'jotai', 'recoil', 'context', 'provider', 'providers', 'supplies', 'supplied', 'provides', 'provided', 'hook', 'hooks', 'react', 'query', 'queries', 'mutation', 'mutations', 'fetches', 'fetched', 'invoices'].includes(keyword))
2242
+ return 2;
2243
+ if (entry.tool === 'projscan_search' && ['sidebar', 'nav', 'navigation', 'menu', 'item', 'items', 'breadcrumb', 'breadcrumbs', 'layout', 'next', 'js', 'title', 'metadata', 'meta', 'billing', 'settings', 'checkout'].includes(keyword))
2244
+ return 2;
2245
+ if (entry.tool === 'projscan_search' && ['page', 'segment', 'segments', 'not', 'found', '404', 'render', 'renders', 'rendered', 'handled', 'route', 'routes', 'dashboard'].includes(keyword))
2246
+ return 2;
2247
+ if (entry.tool === 'projscan_search' && ['design', 'token', 'tokens', 'tailwind', 'theme', 'themes', 'css', 'global', 'imported', 'style', 'styles', 'styled', 'class', 'classes', 'dark', 'mode', 'breakpoint', 'breakpoints', 'color', 'colors'].includes(keyword))
2248
+ return 2;
2249
+ if (entry.tool === 'projscan_search' && ['form', 'forms', 'submit', 'submits', 'submitted', 'loading', 'state', 'empty', 'results', 'boundary', 'toast', 'notification', 'notifications', 'success', 'keyboard', 'shortcut', 'shortcuts', 'command', 'palette', 'action', 'actions', 'modal', 'opened', 'component', 'page', 'i18n', 'translation', 'translations', 'aria', 'label', 'button', 'buttons', 'focus', 'trap'].includes(keyword))
2250
+ return 2;
2251
+ if (entry.tool === 'projscan_search' && ['check', 'checked', 'test', 'tests', 'spec', 'specs', 'cover', 'covers', 'covering', 'code', 'handles', 'handled', 'handler', 'contains', 'logic', 'implemented', 'configured', 'created', 'loaded', 'loader', 'parse', 'parses', 'middleware', 'api', 'apis', 'route', 'routes', 'endpoint', 'endpoints', 'feature', 'features', 'flag', 'flags', 'env', 'var', 'vars', 'variable', 'variables', 'process', 'processes', 'used', 'controls', 'control', 'error', 'errors', 'message', 'messages', 'throw', 'throws', 'thrown', 'log', 'logs', 'logged', 'logging', 'metric', 'metrics', 'prometheus', 'analytics', 'event', 'events', 'alert', 'alerts', 'sentry', 'datadog', 'dashboard', 'dashboards', 'emit', 'emits', 'emitted', 'send', 'sends', 'initialize', 'initialise', 'init', 'permission', 'permissions', 'role', 'roles', 'access', 'admin', 'guard', 'guards', 'authorization', 'authorize', 'authorized', 'policy', 'policies', 'rbac', 'require', 'requires', 'required', 'login', 'seed', 'seeds', 'data', 'fixture', 'fixtures', 'mock', 'mocks', 'factory', 'factories', 'storybook', 'story', 'stories', 'render', 'renders', 'background', 'job', 'jobs', 'cron', 'scheduled', 'schedule', 'scheduler', 'schedulers', 'worker', 'workers', 'queue', 'queues', 'processor', 'processors', 'task', 'tasks', 'defined', 'config', 'configuration', 'alias', 'aliases', 'define', 'defines', 'migration', 'migrations', 'generated', 'exist', 'exists', 'ran', 'file', 'files', 'show', 'owner', 'owners', 'ownership', 'owns', 'team', 'area', 'ask', 'help', 'knows', 'expert', 'experts', 'contact', 'contacts', 'doc', 'docs', 'document', 'documentation', 'documented', 'readme', 'examples', 'example', 'guide'].includes(keyword))
2252
+ return 2;
2253
+ if (entry.tool === 'projscan_search' && ['tsconfig', 'typescript', 'vite', 'vitest', 'jest', 'babel', 'webpack', 'pnpm', 'yarn', 'npm', 'package', 'manager', 'workspace', 'workspaces', 'lockfile', 'lockfiles', 'path', 'paths'].includes(keyword))
2254
+ return 2;
2255
+ if (entry.tool === 'projscan_regression_plan' && ['port', 'ports', 'eaddrinuse', 'listen', 'address', 'permission', 'denied', 'enoent', 'eresolve', 'peer'].includes(keyword))
2256
+ return 3;
2257
+ if (entry.tool === 'projscan_regression_plan' && ['github', 'action', 'actions', 'workflow', 'workflows', 'pipeline', 'pipelines', 'job', 'jobs', 'fail', 'build', 'builds', 'lint', 'typecheck', 'typechecking', 'install', 'failed', 'error', 'errors', 'failure', 'failures', 'debug', 'stack', 'trace', 'production', 'prod', 'down', 'outage', 'incident', 'triage', 'runtime', 'crash', 'crashes', 'crashing', 'connection', 'refused', 'root', 'cause', 'returning', 'returns', 'log', 'logs', '500', '502', '503', '504', '404', '403', '401', 'proof', 'prove', 'regression', 'verification', 'verify', 'smoke', 'focused', 'full', 'test', 'tests', 'slow', 'slower', 'speed', 'speedup', 'faster', 'benchmark', 'benchmarks', 'reproduce', 'reproduces', 'reproducing', 'flake', 'flaky', 'flakes', 'intermittent', 'intermittently', 'nondeterministic', 'nondeterminism', 'race', 'condition', 'stabilize', 'stabilise', 'quarantine'].includes(keyword))
2258
+ return 2;
2259
+ if (entry.tool === 'projscan_agent_brief' && ['brief', 'handoff', 'agent'].includes(keyword))
2260
+ return 2;
2261
+ if (entry.tool === 'projscan_session' && ['session', 'touched', 'touch', 'resume', 'leave', 'left', 'off', 'agent', 'asleep', 'slept', 'away', 'offline', 'changed', 'events', 'history'].includes(keyword))
2262
+ return 2;
2263
+ if (entry.tool === 'projscan_quality_scorecard' && ['quality', 'scorecard', 'risk', 'risks', 'risky', 'picture'].includes(keyword))
2264
+ return 2;
2265
+ if (entry.tool === 'projscan_hotspots' && ['files', 'file', 'touch', 'complexity', 'complex', 'refactor', 'refactoring', 'simplify', 'simplification', 'tech', 'debt', 'duplicate', 'duplicated', 'duplication', 'over', 'engineered', 'performance', 'perf', 'bottleneck', 'bottlenecks', 'optimize', 'optimise', 'faster', 'slow'].includes(keyword))
2266
+ return 2;
2267
+ if (entry.tool === 'projscan_coordinate' && ['who', 'else', 'working', 'editing', 'coordinate', 'coordination', 'status', 'readiness', 'parallel', 'agents', 'agent', 'collide', 'colliding', 'swarm', 'conflict', 'conflicts', 'conflicting', 'conflicted', 'active'].includes(keyword))
2268
+ return 2;
2269
+ if (entry.tool === 'projscan_preflight' && keyword === 'ready')
2270
+ return 2;
2271
+ if (entry.tool === 'projscan_preflight' && ['block', 'blocked', 'blocker', 'blockers', 'blocking'].includes(keyword))
2272
+ return 2;
2273
+ if (entry.tool === 'projscan_preflight' && ['risk', 'risks'].includes(keyword))
2274
+ return 2;
2275
+ if (entry.tool === 'projscan_preflight' && ['rebase', 'rebasing', 'conflict', 'conflicts', 'resolve', 'resolving', 'wrong', 'stuck'].includes(keyword))
2276
+ return 2;
2277
+ if (entry.tool === 'projscan_claim') {
2278
+ if (keyword === 'active')
2279
+ return 0.5;
2280
+ if (['claim', 'claims', 'lease', 'leases', 'reserve', 'lock'].includes(keyword))
2281
+ return 2;
2282
+ }
2283
+ if (entry.tool === 'projscan_evidence_pack') {
2284
+ if (keyword === 'pr')
2285
+ return 0.25;
2286
+ if (['changed', 'file', 'files'].includes(keyword))
2287
+ return 1;
2288
+ if (['evidence', 'proof', 'approval', 'approve', 'comment', 'summarize', 'changes', 'description', 'draft', 'say', 'checklist', 'tell', 'team', 'change', 'share', 'reviewer', 'reviewers', 'summary', 'packet', 'paste', 'who', 'review', 'ready', 'open', 'opening', 'before', 'prepare', 'owner', 'owners', 'owns', 'routing'].includes(keyword))
2289
+ return 2;
2290
+ }
2291
+ if (entry.tool === 'projscan_doctor') {
2292
+ if (keyword === 'unused')
2293
+ return 3;
2294
+ if (['dead', 'orphaned'].includes(keyword))
2295
+ return 2;
2296
+ if (['delete', 'remove'].includes(keyword))
2297
+ return 2;
2298
+ if (['safe', 'safely'].includes(keyword))
2299
+ return 1;
2300
+ }
2301
+ if (entry.tool === 'projscan_review' && keyword === 'review')
2302
+ return 2;
2303
+ if (entry.tool === 'projscan_review' && keyword === 'pr')
2304
+ return 0.25;
2305
+ if (entry.tool === 'projscan_review' && ['secure', 'security'].includes(keyword))
2306
+ return 2;
2307
+ if (entry.tool === 'projscan_review' && ['risk', 'risks', 'risky'].includes(keyword))
2308
+ return 2;
2309
+ if (entry.tool === 'projscan_regression_plan' && keyword === 'pr')
2310
+ return 0.25;
2311
+ if (entry.tool === 'projscan_pr_diff') {
2312
+ if (keyword === 'pr')
2313
+ return 0.25;
2314
+ if (['since', 'branch', 'main', 'base', 'head'].includes(keyword))
2315
+ return 0.5;
2316
+ if (['commit', 'message', 'summarize', 'summary', 'diff', 'changed', 'changes', 'change', 'compare', 'stale', 'branched', 'behind', 'ahead', 'sync', 'synced', 'large', 'big', 'size', 'sizes', 'exports', 'imports', 'calls', 'callers'].includes(keyword))
2317
+ return 2;
2318
+ }
2319
+ if (entry.tool === 'projscan_collision' && ['overlapping'].includes(keyword))
2320
+ return 3;
2321
+ if (entry.tool === 'projscan_collision' && ['collide', 'colliding'].includes(keyword))
2322
+ return 2;
2323
+ if (entry.tool === 'projscan_merge_risk' && keyword === 'first')
2324
+ return 1;
2325
+ if (entry.tool === 'projscan_release_train') {
2326
+ if (keyword === 'releasing')
2327
+ return 2;
2328
+ if (['deploy', 'deploying', 'deployed', 'deployment'].includes(keyword))
2329
+ return 2;
2330
+ if (['changed', 'since', 'last'].includes(keyword))
2331
+ return 2;
2332
+ if (['changelog', 'note', 'notes', 'entry', 'summarize', 'summary'].includes(keyword))
2333
+ return 2;
2334
+ }
2335
+ if (entry.tool === 'projscan_bug_hunt') {
2336
+ if (['bug', 'bugs', 'hunt', 'defect', 'broken', 'first', 'fastest', 'quickest', 'quick', 'smallest', 'small', 'low', 'lowest', 'improve', 'improvement', 'useful', 'easy', 'beginner', 'starter', 'intern', 'interns', 'task', 'tasks', 'five', 'minutes', 'today', 'win', 'wins'].includes(keyword))
2337
+ return 2;
2338
+ if (['find', 'fix', 'pr'].includes(keyword))
2339
+ return 0.25;
2340
+ }
2341
+ return 1;
2342
+ }
2343
+ function hasFilePathTarget(intent) {
2344
+ return /(?:^|\s)[A-Za-z0-9_./:@-]+\.[A-Za-z0-9]{1,12}(?=[\s?!.,;:]|$)/.test(intent);
2345
+ }
2346
+ function hasEnvVarTarget(intent) {
2347
+ return /\bprocess\.env\.[A-Za-z_][A-Za-z0-9_]*\b/.test(intent) || /\b[A-Z][A-Z0-9]*_[A-Z0-9_]+\b/.test(intent);
2348
+ }
2349
+ function hasQuotedTextTarget(intent) {
2350
+ return /(["'`])\S.{0,200}?\1/.test(intent);
2351
+ }
2352
+ function hasPackageRemovalTarget(intent) {
2353
+ const compactIntent = intent.trim().replace(/[?!\s]+$/g, '');
2354
+ const actionFirst = compactIntent.match(/\b(?:remove|drop|uninstall)\s+(?:the\s+)?(?:(?:package|dependency)\s+)?(@?[A-Za-z0-9][\w.-]*(?:\/[A-Za-z0-9][\w.-]*)?)(?=\s|$)/i);
2355
+ const targetFirst = compactIntent.match(/\b(@?[A-Za-z0-9][\w.-]*(?:\/[A-Za-z0-9][\w.-]*)?)\s+(?:safe\s+to\s+)?(?:remove|drop|uninstall)\b/i);
2356
+ const match = actionFirst ?? targetFirst;
2357
+ const target = match?.[1]?.toLowerCase();
2358
+ if (!target)
2359
+ return false;
2360
+ return !['this', 'that', 'it', 'thing', 'file', 'files', 'function', 'method', 'class', 'symbol', 'code', 'for', 'safe', 'safely', 'carefully', 'docs', 'doc', 'documentation', 'document', 'readme', 'changelog', 'examples', 'example', 'guide'].includes(target);
2361
+ }
2362
+ function hasPackageChangeTarget(intent) {
2363
+ const compactIntent = intent.trim().replace(/[?!\s]+$/g, '');
2364
+ const actionFirst = compactIntent.match(/\b(?:bump|upgrade|update)\s+(?:the\s+)?(?:(?:package|dependency)\s+)?(@?[A-Za-z0-9][\w.-]*(?:\/[A-Za-z0-9][\w.-]*)?)(?=\s|$)/i);
2365
+ const target = actionFirst?.[1]?.toLowerCase();
2366
+ if (!target)
2367
+ return false;
2368
+ return !['this', 'that', 'it', 'thing', 'file', 'files', 'function', 'method', 'class', 'symbol', 'code', 'for', 'docs', 'doc', 'documentation', 'document', 'readme', 'changelog', 'examples', 'example', 'guide'].includes(target);
2369
+ }
2370
+ function routeConfidence(score) {
2371
+ if (score >= 2)
2372
+ return 'high';
2373
+ if (score >= 1)
2374
+ return 'medium';
2375
+ return 'low';
2376
+ }
213
2377
  //# sourceMappingURL=intentRouter.js.map