contextos-agents 1.7.0 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +16 -50
- package/.agents/adapters/aider/export.js +117 -99
- package/.agents/adapters/claude/export.js +68 -26
- package/.agents/adapters/copilot/export.js +90 -53
- package/.agents/adapters/cursor/export.js +100 -104
- package/.agents/adapters/drift-detector.js +196 -0
- package/.agents/adapters/gemini/export.js +76 -45
- package/.agents/adapters/pure-compiler.js +443 -0
- package/.agents/adapters/zed/export.js +104 -96
- package/.agents/compiled/registry.v2.json +504 -0
- package/.agents/compiled/registry.v2.sha256 +1 -0
- package/.agents/compiler/manifest-compiler.js +963 -0
- package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
- package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
- package/.agents/compiler/vendor/yaml.js +139 -0
- package/.agents/core/profiles/init.yaml +25 -0
- package/.agents/core/skills/context-manager/skill.yaml +3 -5
- package/.agents/core/skills/context-os/SKILL.md +3 -6
- package/.agents/core/skills/context-os/skill.yaml +3 -8
- package/.agents/core/skills/engineering-workflow/skill.yaml +1 -7
- package/.agents/core/skills/gemini-precision/skill.yaml +1 -6
- package/.agents/core/skills/gstack-roles/skill.yaml +3 -6
- package/.agents/core/skills/ponytail-mindset/skill.yaml +1 -7
- package/.agents/core/skills/security/skill.yaml +15 -3
- package/.agents/ctx.js +537 -112
- package/.agents/customization-dx.js +282 -0
- package/.agents/doctor.js +855 -66
- package/.agents/filesystem/index.js +71 -0
- package/.agents/filesystem/journaled-transaction.js +451 -0
- package/.agents/filesystem/lockfile-v2.js +275 -0
- package/.agents/filesystem/platform-hardening.js +222 -0
- package/.agents/filesystem/project-lock.js +218 -0
- package/.agents/filesystem/safe-path.js +256 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
- package/.agents/plugins/contextos/plugin.json +1 -1
- package/.agents/plugins.js +278 -64
- package/.agents/profiles.js +486 -51
- package/.agents/resolver/canonical-resolver.js +1348 -0
- package/.agents/resolver.js +50 -534
- package/.agents/rules/rule-catalog.js +525 -0
- package/.agents/schemas/attestation.review.v1.json +111 -0
- package/.agents/schemas/attestation.verification.v1.json +85 -0
- package/.agents/schemas/lockfile.v2.schema.json +134 -0
- package/.agents/schemas/profile.v2.schema.json +114 -0
- package/.agents/schemas/runtime.thread.v1.json +192 -0
- package/.agents/schemas/skill.manifest.v2.json +177 -0
- package/.agents/schemas/verification.spec.v1.json +39 -0
- package/.agents/schemas/workspace.graph.schema.json +106 -0
- package/.agents/skills-index.json +6 -166
- package/.agents/stats.js +9 -9
- package/.agents/transaction-core/event-store.js +288 -0
- package/.agents/transaction-core/idempotency.js +129 -0
- package/.agents/transaction-core/ipc-lock.js +311 -0
- package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
- package/.agents/validate.js +44 -1
- package/.agents/watch.js +354 -102
- package/.agents/workspace/workspace-graph.js +778 -0
- package/README.md +59 -387
- package/benchmarks/v2/analysis/statistics.js +140 -0
- package/benchmarks/v2/analysis/stats.js +69 -0
- package/benchmarks/v2/arms/arm-definitions.js +79 -0
- package/benchmarks/v2/dataset.schema.json +34 -0
- package/benchmarks/v2/evaluators/index.js +25 -0
- package/benchmarks/v2/evaluators/verified-success.js +116 -0
- package/benchmarks/v2/harness/runner.js +88 -0
- package/benchmarks/v2/pilot-tasks.json +392 -0
- package/bin/commands/recover.js +88 -0
- package/bin/commands/update.js +80 -17
- package/bin/commands.js +62 -25
- package/bin/index.js +138 -81
- package/bin/lib/lockfile.js +5 -3
- package/bin/lib/safe-writer.js +34 -3
- package/package.json +87 -72
- package/registry.json +2 -2
- package/registry.v2.schema.json +86 -0
- package/.agents/core/profiles/backend.yaml +0 -47
- package/.agents/core/profiles/enterprise.yaml +0 -46
- package/.agents/core/profiles/frontend.yaml +0 -46
- package/.agents/core/profiles/hackathon.yaml +0 -45
- package/.agents/core/profiles/mvp.yaml +0 -44
- package/.agents/core/profiles/startup.yaml +0 -48
- package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
- package/.agents/core/skills/adapters/SKILL.md +0 -105
- package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/adapters/VALIDATION.json +0 -12
- package/.agents/core/skills/adapters/skill.yaml +0 -16
- package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
- package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
- package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
- package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
- package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
- package/.agents/core/skills/database/EXAMPLES.md +0 -74
- package/.agents/core/skills/database/SKILL.md +0 -101
- package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/database/VALIDATION.json +0 -11
- package/.agents/core/skills/database/skill.yaml +0 -31
- package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
- package/.agents/core/skills/ddd/SKILL.md +0 -247
- package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ddd/VALIDATION.json +0 -12
- package/.agents/core/skills/ddd/ddd.md +0 -178
- package/.agents/core/skills/ddd/skill.yaml +0 -17
- package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
- package/.agents/core/skills/decisions/SKILL.md +0 -90
- package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
- package/.agents/core/skills/decisions/VALIDATION.json +0 -12
- package/.agents/core/skills/decisions/skill.yaml +0 -16
- package/.agents/core/skills/docker/EXAMPLES.md +0 -56
- package/.agents/core/skills/docker/SKILL.md +0 -63
- package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/docker/VALIDATION.json +0 -11
- package/.agents/core/skills/docker/skill.yaml +0 -29
- package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
- package/.agents/core/skills/fastapi/SKILL.md +0 -148
- package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
- package/.agents/core/skills/fastapi/fastapi.md +0 -112
- package/.agents/core/skills/fastapi/skill.yaml +0 -17
- package/.agents/core/skills/generators/EXAMPLES.md +0 -19
- package/.agents/core/skills/generators/SKILL.md +0 -112
- package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/generators/VALIDATION.json +0 -12
- package/.agents/core/skills/generators/skill.yaml +0 -25
- package/.agents/core/skills/generators/templates/API.md +0 -77
- package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
- package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
- package/.agents/core/skills/generators/templates/DECISION.md +0 -46
- package/.agents/core/skills/generators/templates/PRD.md +0 -67
- package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
- package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
- package/.agents/core/skills/generators/templates/TASKS.md +0 -43
- package/.agents/core/skills/generators/templates/UI.md +0 -73
- package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
- package/.agents/core/skills/graphify/SKILL.md +0 -130
- package/.agents/core/skills/graphify/VALIDATION.json +0 -12
- package/.agents/core/skills/graphify/skill.yaml +0 -18
- package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
- package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
- package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
- package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
- package/.agents/core/skills/interview-me/SKILL.md +0 -97
- package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
- package/.agents/core/skills/interview-me/skill.yaml +0 -12
- package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
- package/.agents/core/skills/microservices/SKILL.md +0 -164
- package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/microservices/VALIDATION.json +0 -12
- package/.agents/core/skills/microservices/microservices.md +0 -119
- package/.agents/core/skills/microservices/skill.yaml +0 -17
- package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
- package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
- package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nestjs/SKILL.md +0 -139
- package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nestjs/nestjs.md +0 -103
- package/.agents/core/skills/nestjs/skill.yaml +0 -17
- package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nextjs/SKILL.md +0 -163
- package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nextjs/nextjs.md +0 -67
- package/.agents/core/skills/nextjs/skill.yaml +0 -17
- package/.agents/core/skills/node/EXAMPLES.md +0 -80
- package/.agents/core/skills/node/SKILL.md +0 -128
- package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/node/VALIDATION.json +0 -12
- package/.agents/core/skills/node/node.md +0 -87
- package/.agents/core/skills/node/skill.yaml +0 -17
- package/.agents/core/skills/performance/EXAMPLES.md +0 -30
- package/.agents/core/skills/performance/SKILL.md +0 -75
- package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/performance/VALIDATION.json +0 -12
- package/.agents/core/skills/performance/performance.md +0 -52
- package/.agents/core/skills/performance/skill.yaml +0 -17
- package/.agents/core/skills/react/EXAMPLES.md +0 -79
- package/.agents/core/skills/react/SKILL.md +0 -132
- package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/react/VALIDATION.json +0 -12
- package/.agents/core/skills/react/react.md +0 -93
- package/.agents/core/skills/react/skill.yaml +0 -17
- package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
- package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
- package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
- package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
- package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
- package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
- package/.agents/core/skills/soft-design/SKILL.md +0 -108
- package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
- package/.agents/core/skills/soft-design/skill.yaml +0 -12
- package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
- package/.agents/core/skills/state-management/SKILL.md +0 -48
- package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/state-management/VALIDATION.json +0 -11
- package/.agents/core/skills/state-management/skill.yaml +0 -28
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
- package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
- package/.agents/core/skills/system-design/SKILL.md +0 -419
- package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/system-design/VALIDATION.json +0 -12
- package/.agents/core/skills/system-design/skill.yaml +0 -20
- package/.agents/core/skills/system-design/system-design.md +0 -112
- package/.agents/core/skills/testing/EXAMPLES.md +0 -71
- package/.agents/core/skills/testing/SKILL.md +0 -70
- package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/testing/VALIDATION.json +0 -11
- package/.agents/core/skills/testing/skill.yaml +0 -32
- package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
- package/.agents/core/skills/typescript/SKILL.md +0 -112
- package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/typescript/VALIDATION.json +0 -12
- package/.agents/core/skills/typescript/skill.yaml +0 -17
- package/.agents/core/skills/typescript/typescript.md +0 -71
- package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
- package/.agents/core/skills/ui-design/SKILL.md +0 -124
- package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-design/skill.yaml +0 -17
- package/.agents/core/skills/ui-design/ui.md +0 -88
- package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
- package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
- package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
- package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
- package/.agents/core/skills/ux-design/SKILL.md +0 -116
- package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ux-design/skill.yaml +0 -17
- package/.agents/core/skills/ux-design/ux.md +0 -80
- package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
- package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
- package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
- package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
- package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
- package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
- package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
- package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
- package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
- package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
- package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
- package/.agents/generated/claude/skills/database/SKILL.md +0 -191
- package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
- package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
- package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
- package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
- package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
- package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
- package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
- package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
- package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
- package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
- package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
- package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
- package/.agents/generated/claude/skills/node/SKILL.md +0 -224
- package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
- package/.agents/generated/claude/skills/react/SKILL.md +0 -227
- package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
- package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
- package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
- package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
- package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
- package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
- package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
- package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
- package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
- package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
- package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
- package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
- package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
- package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
- package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
- package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
- package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
- package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
- package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
- package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
- package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
- package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
- package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
- package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
- package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
- package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
- package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
- package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
- package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
- package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
- package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
- package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
- package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
- package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
- package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
- package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
- package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
- package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
- package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
- package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
- package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
- package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
- package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* gate-investigations.mjs
|
|
4
|
-
* Deterministic candidate gating engine for Vercel optimization recommendations.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node scripts/gate-investigations.mjs <signals-file> [--threshold <n>] [--out <file>] [--help]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
|
-
import { resolve } from 'node:path';
|
|
12
|
-
|
|
13
|
-
function printHelp() {
|
|
14
|
-
console.log(`
|
|
15
|
-
gate-investigations.mjs — Optimization Candidate Gating Engine
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
node scripts/gate-investigations.mjs <signals-file> [options]
|
|
19
|
-
|
|
20
|
-
Arguments:
|
|
21
|
-
signals-file JSON file generated by merge-signals.mjs (or collect-signals.mjs)
|
|
22
|
-
|
|
23
|
-
Options:
|
|
24
|
-
--threshold <n> Invocation volume threshold for candidate evaluation (default: 5000)
|
|
25
|
-
--out <file> Output destination file (default: stdout)
|
|
26
|
-
--help, -h Show this help message
|
|
27
|
-
`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const args = process.argv.slice(2);
|
|
31
|
-
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
32
|
-
printHelp();
|
|
33
|
-
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let signalsFile = null;
|
|
37
|
-
let threshold = 5000;
|
|
38
|
-
let outFile = null;
|
|
39
|
-
|
|
40
|
-
for (let i = 0; i < args.length; i++) {
|
|
41
|
-
const arg = args[i];
|
|
42
|
-
if (arg === '--threshold' && i + 1 < args.length) {
|
|
43
|
-
threshold = parseInt(args[++i], 10) || 5000;
|
|
44
|
-
} else if (arg === '--out' && i + 1 < args.length) {
|
|
45
|
-
outFile = args[++i];
|
|
46
|
-
} else if (!arg.startsWith('-') && !signalsFile) {
|
|
47
|
-
signalsFile = arg;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!signalsFile || !existsSync(signalsFile)) {
|
|
52
|
-
console.error(`Error: Signals file not found: ${signalsFile}`);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
let data = {};
|
|
57
|
-
try {
|
|
58
|
-
data = JSON.parse(readFileSync(resolve(process.cwd(), signalsFile), 'utf8'));
|
|
59
|
-
} catch (e) {
|
|
60
|
-
console.error(`Error reading signals file: ${e.message}`);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const candidates = [];
|
|
65
|
-
const routes = data.routes || [];
|
|
66
|
-
|
|
67
|
-
for (const r of routes) {
|
|
68
|
-
if (r.invocations < threshold && r.p95DurationMs < 1000) {
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 1. Caching Opportunity Gate
|
|
73
|
-
if (r.method === 'GET' && r.invocations >= threshold && (r.cacheable || r.currentCacheControl === 'no-store' || !r.currentCacheControl)) {
|
|
74
|
-
candidates.push({
|
|
75
|
-
id: `cache-opp-${r.route.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
76
|
-
scope: 'code',
|
|
77
|
-
category: 'caching',
|
|
78
|
-
route: r.route,
|
|
79
|
-
file: r.file || null,
|
|
80
|
-
priority: r.invocations > 100000 ? 'high' : 'medium',
|
|
81
|
-
metric: {
|
|
82
|
-
invocations: r.invocations,
|
|
83
|
-
p95DurationMs: r.p95DurationMs,
|
|
84
|
-
currentCacheControl: r.currentCacheControl
|
|
85
|
-
},
|
|
86
|
-
reason: `High volume GET route (${r.invocations.toLocaleString()} invocations) lacking edge/CDN cache headers.`
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 2. High Latency / Cold Start Gate
|
|
91
|
-
if (r.p95DurationMs >= 1000 || r.coldStartRate >= 0.1) {
|
|
92
|
-
candidates.push({
|
|
93
|
-
id: `latency-coldstart-${r.route.replace(/[^a-zA-Z0-9]/g, '-')}`,
|
|
94
|
-
scope: 'code',
|
|
95
|
-
category: 'compute-duration',
|
|
96
|
-
route: r.route,
|
|
97
|
-
file: r.file || null,
|
|
98
|
-
priority: r.p95DurationMs > 2000 ? 'high' : 'medium',
|
|
99
|
-
metric: {
|
|
100
|
-
p95DurationMs: r.p95DurationMs,
|
|
101
|
-
coldStartRate: r.coldStartRate,
|
|
102
|
-
invocations: r.invocations
|
|
103
|
-
},
|
|
104
|
-
reason: `P95 execution latency (${r.p95DurationMs}ms) or cold start rate (${Math.round(r.coldStartRate * 100)}%) warrants runtime or bundling optimization.`
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 3. Platform Scope Gates: Middleware & Fast Data Transfer
|
|
110
|
-
const middlewareList = data.middleware || [];
|
|
111
|
-
for (const mw of middlewareList) {
|
|
112
|
-
if (!mw.hasMatcher) {
|
|
113
|
-
candidates.push({
|
|
114
|
-
id: 'platform-middleware-unfiltered',
|
|
115
|
-
scope: 'platform',
|
|
116
|
-
category: 'middleware-overhead',
|
|
117
|
-
route: '/*',
|
|
118
|
-
file: mw.file,
|
|
119
|
-
priority: 'high',
|
|
120
|
-
metric: {
|
|
121
|
-
hasMatcher: false,
|
|
122
|
-
runtime: mw.runtime
|
|
123
|
-
},
|
|
124
|
-
reason: 'Middleware runs globally across all asset/data requests without a path matcher config.'
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const gatedResult = {
|
|
130
|
-
timestamp: new Date().toISOString(),
|
|
131
|
-
projectId: data.projectId || 'unknown',
|
|
132
|
-
gateThreshold: threshold,
|
|
133
|
-
totalCandidates: candidates.length,
|
|
134
|
-
candidates
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const outputJson = JSON.stringify(gatedResult, null, 2);
|
|
138
|
-
if (outFile) {
|
|
139
|
-
writeFileSync(resolve(process.cwd(), outFile), outputJson, 'utf8');
|
|
140
|
-
} else {
|
|
141
|
-
console.log(outputJson);
|
|
142
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* merge-signals.mjs
|
|
4
|
-
* Merges Vercel telemetry production metrics with codebase route scan outputs.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node scripts/merge-signals.mjs <signals-file> <codebase-file> [--out <file>] [--help]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
|
-
import { resolve } from 'node:path';
|
|
12
|
-
|
|
13
|
-
function printHelp() {
|
|
14
|
-
console.log(`
|
|
15
|
-
merge-signals.mjs — Correlates Telemetry Metrics with Codebase Routes
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
node scripts/merge-signals.mjs <signals-file> <codebase-file> [options]
|
|
19
|
-
|
|
20
|
-
Arguments:
|
|
21
|
-
signals-file JSON file generated by collect-signals.mjs
|
|
22
|
-
codebase-file JSON file generated by scan-codebase.mjs
|
|
23
|
-
|
|
24
|
-
Options:
|
|
25
|
-
--out <file> Output destination file (default: stdout)
|
|
26
|
-
--help, -h Show this help message
|
|
27
|
-
`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const args = process.argv.slice(2);
|
|
31
|
-
if (args.includes('--help') || args.includes('-h') || args.length < 2) {
|
|
32
|
-
printHelp();
|
|
33
|
-
process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const positional = [];
|
|
37
|
-
let outFile = null;
|
|
38
|
-
|
|
39
|
-
for (let i = 0; i < args.length; i++) {
|
|
40
|
-
const arg = args[i];
|
|
41
|
-
if (arg === '--out' && i + 1 < args.length) {
|
|
42
|
-
outFile = args[++i];
|
|
43
|
-
} else if (!arg.startsWith('-')) {
|
|
44
|
-
positional.push(arg);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (positional.length < 2) {
|
|
49
|
-
console.error('Error: Both <signals-file> and <codebase-file> are required.');
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const [signalsPath, codebasePath] = positional;
|
|
54
|
-
if (!existsSync(signalsPath)) {
|
|
55
|
-
console.error(`Error: File not found: ${signalsPath}`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
if (!existsSync(codebasePath)) {
|
|
59
|
-
console.error(`Error: File not found: ${codebasePath}`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let signalsData = {};
|
|
64
|
-
let codebaseData = {};
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
signalsData = JSON.parse(readFileSync(resolve(process.cwd(), signalsPath), 'utf8'));
|
|
68
|
-
} catch (e) {
|
|
69
|
-
console.error(`Error parsing signals JSON: ${e.message}`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
codebaseData = JSON.parse(readFileSync(resolve(process.cwd(), codebasePath), 'utf8'));
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error(`Error parsing codebase JSON: ${e.message}`);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const telemetryRoutes = signalsData.routes || [];
|
|
81
|
-
const codeRoutes = codebaseData.routes || [];
|
|
82
|
-
|
|
83
|
-
// Correlate metrics with source files
|
|
84
|
-
const mergedRoutes = [];
|
|
85
|
-
|
|
86
|
-
for (const tr of telemetryRoutes) {
|
|
87
|
-
// Find matching route in codebase
|
|
88
|
-
const match = codeRoutes.find(cr => {
|
|
89
|
-
return cr.route === tr.route || cr.route === tr.route + '/' || tr.route === cr.route + '/';
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
mergedRoutes.push({
|
|
93
|
-
...tr,
|
|
94
|
-
file: match ? match.file : null,
|
|
95
|
-
frameworkConfig: match ? {
|
|
96
|
-
type: match.type,
|
|
97
|
-
dynamic: match.dynamic,
|
|
98
|
-
revalidate: match.revalidate,
|
|
99
|
-
runtime: match.runtime
|
|
100
|
-
} : null
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Add codebase routes that had no telemetry records
|
|
105
|
-
for (const cr of codeRoutes) {
|
|
106
|
-
const existing = mergedRoutes.some(mr => mr.file === cr.file || mr.route === cr.route);
|
|
107
|
-
if (!existing) {
|
|
108
|
-
mergedRoutes.push({
|
|
109
|
-
route: cr.route,
|
|
110
|
-
method: 'GET',
|
|
111
|
-
invocations: 0,
|
|
112
|
-
p95DurationMs: 0,
|
|
113
|
-
avgDurationMs: 0,
|
|
114
|
-
coldStartRate: 0,
|
|
115
|
-
errorRate: 0,
|
|
116
|
-
cacheable: cr.dynamic === 'force-static' || cr.revalidate != null,
|
|
117
|
-
file: cr.file,
|
|
118
|
-
frameworkConfig: {
|
|
119
|
-
type: cr.type,
|
|
120
|
-
dynamic: cr.dynamic,
|
|
121
|
-
revalidate: cr.revalidate,
|
|
122
|
-
runtime: cr.runtime
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const merged = {
|
|
129
|
-
timestamp: new Date().toISOString(),
|
|
130
|
-
projectId: signalsData.projectId || 'unknown',
|
|
131
|
-
windowDays: signalsData.windowDays || 14,
|
|
132
|
-
framework: codebaseData.framework || 'unknown',
|
|
133
|
-
metrics: signalsData.metrics || {},
|
|
134
|
-
middleware: codebaseData.middleware || [],
|
|
135
|
-
routes: mergedRoutes
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const outputJson = JSON.stringify(merged, null, 2);
|
|
139
|
-
if (outFile) {
|
|
140
|
-
writeFileSync(resolve(process.cwd(), outFile), outputJson, 'utf8');
|
|
141
|
-
} else {
|
|
142
|
-
console.log(outputJson);
|
|
143
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* scan-codebase.mjs
|
|
4
|
-
* Scans repository routes, middleware, and rendering directives for Vercel optimization.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node scripts/scan-codebase.mjs <repo-root> [--out <file>] [--help]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readdirSync, statSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
|
-
import { resolve, join, relative, basename } from 'node:path';
|
|
12
|
-
|
|
13
|
-
function printHelp() {
|
|
14
|
-
console.log(`
|
|
15
|
-
scan-codebase.mjs — Vercel Codebase Route & Config Scanner
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
node scripts/scan-codebase.mjs <repo-root> [options]
|
|
19
|
-
|
|
20
|
-
Arguments:
|
|
21
|
-
repo-root Path to the repository root directory to scan
|
|
22
|
-
|
|
23
|
-
Options:
|
|
24
|
-
--out <file> Output file path (default: stdout)
|
|
25
|
-
--help, -h Show this help message
|
|
26
|
-
`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const args = process.argv.slice(2);
|
|
30
|
-
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
31
|
-
printHelp();
|
|
32
|
-
process.exit(0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let repoRoot = null;
|
|
36
|
-
let outFile = null;
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < args.length; i++) {
|
|
39
|
-
const arg = args[i];
|
|
40
|
-
if (arg === '--out' && i + 1 < args.length) {
|
|
41
|
-
outFile = args[++i];
|
|
42
|
-
} else if (!arg.startsWith('-') && !repoRoot) {
|
|
43
|
-
repoRoot = resolve(process.cwd(), arg);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!repoRoot || !existsSync(repoRoot)) {
|
|
48
|
-
console.error(`Error: Repository path not found: ${repoRoot}`);
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Detect framework
|
|
53
|
-
let framework = 'unknown';
|
|
54
|
-
const pkgPath = join(repoRoot, 'package.json');
|
|
55
|
-
if (existsSync(pkgPath)) {
|
|
56
|
-
try {
|
|
57
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
58
|
-
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
59
|
-
if (deps.next) framework = 'nextjs';
|
|
60
|
-
else if (deps['@sveltejs/kit']) framework = 'sveltekit';
|
|
61
|
-
else if (deps.nuxt) framework = 'nuxt';
|
|
62
|
-
else if (deps.astro) framework = 'astro';
|
|
63
|
-
else if (deps.react) framework = 'react';
|
|
64
|
-
} catch {
|
|
65
|
-
// Ignore error
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const routes = [];
|
|
70
|
-
const middleware = [];
|
|
71
|
-
|
|
72
|
-
function walk(dir) {
|
|
73
|
-
let entries;
|
|
74
|
-
try {
|
|
75
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
76
|
-
} catch {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
for (const entry of entries) {
|
|
80
|
-
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.next' || entry.name === 'dist') {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const fullPath = join(dir, entry.name);
|
|
84
|
-
if (entry.isDirectory()) {
|
|
85
|
-
walk(fullPath);
|
|
86
|
-
} else if (entry.isFile()) {
|
|
87
|
-
const relPath = relative(repoRoot, fullPath).replace(/\\/g, '/');
|
|
88
|
-
const filename = basename(entry.name);
|
|
89
|
-
|
|
90
|
-
// Check middleware
|
|
91
|
-
if (/^middleware\.(ts|js|mjs)$/.test(filename) || /^src\/middleware\.(ts|js|mjs)$/.test(relPath)) {
|
|
92
|
-
try {
|
|
93
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
94
|
-
middleware.push({
|
|
95
|
-
file: relPath,
|
|
96
|
-
hasMatcher: content.includes('matcher:'),
|
|
97
|
-
runtime: content.includes("runtime = 'edge'") ? 'edge' : 'nodejs'
|
|
98
|
-
});
|
|
99
|
-
} catch {}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Check App Router route handlers
|
|
103
|
-
if (/^(route|page)\.(ts|tsx|js|jsx)$/.test(filename) && (relPath.includes('app/') || relPath.includes('src/app/'))) {
|
|
104
|
-
try {
|
|
105
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
106
|
-
const isRoute = filename.startsWith('route');
|
|
107
|
-
const isDynamic = content.includes("dynamic = 'force-dynamic'") || content.includes('export const dynamic = "force-dynamic"');
|
|
108
|
-
const isStatic = content.includes("dynamic = 'force-static'") || content.includes('export const dynamic = "force-static"');
|
|
109
|
-
const hasRevalidate = /revalidate\s*=\s*(\d+)/.test(content);
|
|
110
|
-
const revalidateVal = hasRevalidate ? parseInt(content.match(/revalidate\s*=\s*(\d+)/)[1], 10) : null;
|
|
111
|
-
const runtime = content.includes("runtime = 'edge'") ? 'edge' : 'nodejs';
|
|
112
|
-
|
|
113
|
-
// Extract route pattern
|
|
114
|
-
let routeUrl = relPath
|
|
115
|
-
.replace(/^(src\/)?app/, '')
|
|
116
|
-
.replace(/\/(route|page)\.(ts|tsx|js|jsx)$/, '');
|
|
117
|
-
if (!routeUrl.startsWith('/')) routeUrl = '/' + routeUrl;
|
|
118
|
-
if (routeUrl === '') routeUrl = '/';
|
|
119
|
-
|
|
120
|
-
routes.push({
|
|
121
|
-
type: isRoute ? 'api-route' : 'page-route',
|
|
122
|
-
route: routeUrl,
|
|
123
|
-
file: relPath,
|
|
124
|
-
dynamic: isDynamic ? 'force-dynamic' : (isStatic ? 'force-static' : 'auto'),
|
|
125
|
-
revalidate: revalidateVal,
|
|
126
|
-
runtime
|
|
127
|
-
});
|
|
128
|
-
} catch {}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check Pages Router API handlers
|
|
132
|
-
if ((relPath.includes('pages/api/') || relPath.includes('src/pages/api/')) && /\.(ts|js)$/.test(filename)) {
|
|
133
|
-
try {
|
|
134
|
-
const content = readFileSync(fullPath, 'utf8');
|
|
135
|
-
let routeUrl = relPath
|
|
136
|
-
.replace(/^(src\/)?pages/, '')
|
|
137
|
-
.replace(/\.(ts|js)$/, '');
|
|
138
|
-
if (routeUrl.endsWith('/index')) routeUrl = routeUrl.slice(0, -6);
|
|
139
|
-
|
|
140
|
-
routes.push({
|
|
141
|
-
type: 'api-route',
|
|
142
|
-
route: routeUrl,
|
|
143
|
-
file: relPath,
|
|
144
|
-
dynamic: 'pages-api',
|
|
145
|
-
runtime: content.includes("runtime = 'edge'") ? 'edge' : 'nodejs'
|
|
146
|
-
});
|
|
147
|
-
} catch {}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
walk(repoRoot);
|
|
154
|
-
|
|
155
|
-
const result = {
|
|
156
|
-
timestamp: new Date().toISOString(),
|
|
157
|
-
repoRoot: relative(process.cwd(), repoRoot).replace(/\\/g, '/') || '.',
|
|
158
|
-
framework,
|
|
159
|
-
summary: {
|
|
160
|
-
totalRoutes: routes.length,
|
|
161
|
-
apiRoutes: routes.filter(r => r.type === 'api-route').length,
|
|
162
|
-
pageRoutes: routes.filter(r => r.type === 'page-route').length,
|
|
163
|
-
middlewareCount: middleware.length
|
|
164
|
-
},
|
|
165
|
-
middleware,
|
|
166
|
-
routes
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const outputJson = JSON.stringify(result, null, 2);
|
|
170
|
-
if (outFile) {
|
|
171
|
-
writeFileSync(resolve(process.cwd(), outFile), outputJson, 'utf8');
|
|
172
|
-
} else {
|
|
173
|
-
console.log(outputJson);
|
|
174
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
name: vercel-optimize
|
|
2
|
-
type: runtime
|
|
3
|
-
description: Observability-first Vercel performance and cost reduction optimization skill.
|
|
4
|
-
tags:
|
|
5
|
-
- vercel
|
|
6
|
-
- nextjs
|
|
7
|
-
- performance
|
|
8
|
-
- cost
|
|
9
|
-
version: 1.2.0
|
|
10
|
-
requires:
|
|
11
|
-
- nextjs
|
|
12
|
-
resources:
|
|
13
|
-
- SKILL.md
|
|
14
|
-
- VALIDATION.json
|
|
15
|
-
- scripts/collect-signals.mjs
|
|
16
|
-
- scripts/gate-investigations.mjs
|
|
17
|
-
- scripts/merge-signals.mjs
|
|
18
|
-
- scripts/scan-codebase.mjs
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# web-accessibility Examples — Anti-patterns vs ContextOS Standard
|
|
2
|
-
|
|
3
|
-
## Example 1: Semantic Buttons vs Clickable Divs
|
|
4
|
-
|
|
5
|
-
### Anti-pattern: Clickable Div
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
// BAD: Cannot be focused with Tab, does not respond to Enter or Space, silent to screen readers
|
|
9
|
-
<div className="button" onClick={handleSubmit}>Submit</div>
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
### Best practice: ContextOS Standard (Semantic Button Element)
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
// GOOD: Keyboard focusable, native Enter/Space handling, properly announced by assistive tech
|
|
16
|
-
<button type="button" onClick={handleSubmit} className="btn btn-primary">
|
|
17
|
-
Submit
|
|
18
|
-
</button>
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Example 2: Icon-only Buttons
|
|
24
|
-
|
|
25
|
-
### Anti-pattern: Unlabelled Icon Button
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
// BAD: Screen reader announces "button", user has zero idea what it does
|
|
29
|
-
<button onClick={onClose}><XIcon /></button>
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Best practice: ContextOS Standard (Accessible Label)
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
// GOOD: Explicit aria-label and hidden decorative icon
|
|
36
|
-
<button type="button" onClick={onClose} aria-label="Close modal window">
|
|
37
|
-
<XIcon aria-hidden="true" />
|
|
38
|
-
</button>
|
|
39
|
-
```
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: Web Accessibility
|
|
3
|
-
description: >
|
|
4
|
-
ContextOS skill for Web Accessibility (WCAG 2.1 AA/AAA), ARIA dialog patterns, keyboard focus traps, and Web Interface Guidelines.
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Web Accessibility & Interface Guidelines
|
|
8
|
-
|
|
9
|
-
## Overview
|
|
10
|
-
|
|
11
|
-
Enforces universal accessibility compliance (WCAG 2.1 AA / AAA), rigorous semantic markup, keyboard navigability with focus traps, screen reader live regions, and Web Interface Guidelines standards.
|
|
12
|
-
|
|
13
|
-
## When to Use
|
|
14
|
-
|
|
15
|
-
Activate whenever building, styling, or reviewing user interfaces, forms, modals, menus, navigation drawers, custom interactive widgets, or media elements.
|
|
16
|
-
|
|
17
|
-
## Negative Constraints (What NOT to Do)
|
|
18
|
-
|
|
19
|
-
1. **NEVER use `outline: none` without a custom `:focus-visible` replacement**: Keyboard users must always have a distinct, high-contrast visual focus ring.
|
|
20
|
-
2. **NEVER use non-semantic elements (`<div onClick>`) for interactive triggers**: Always use native `<button>` or `<a href>`.
|
|
21
|
-
3. **NEVER create modals or dialogs without keyboard focus traps**: Focus must remain trapped inside open dialogs during Tab / Shift-Tab navigation and restore to trigger on close.
|
|
22
|
-
4. **NEVER rely exclusively on color to indicate state or errors**: Always pair colors with text labels, icons, or ARIA attributes (`aria-invalid="true"`).
|
|
23
|
-
5. **NEVER trap screen readers with missing form labels or error associations**: Every input must link to `<label htmlFor="id">` and errors via `aria-describedby`.
|
|
24
|
-
6. **NEVER play animations without honoring `prefers-reduced-motion`**: Respect user OS motion reduction preferences.
|
|
25
|
-
|
|
26
|
-
## Rules & Patterns
|
|
27
|
-
|
|
28
|
-
### 1. Focus Visible & High-Contrast Focus Rings
|
|
29
|
-
|
|
30
|
-
```css
|
|
31
|
-
button:focus-visible,
|
|
32
|
-
a:focus-visible,
|
|
33
|
-
input:focus-visible {
|
|
34
|
-
outline: 2px solid #6366f1;
|
|
35
|
-
outline-offset: 2px;
|
|
36
|
-
border-radius: 4px;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
button:focus:not(:focus-visible) {
|
|
40
|
-
outline: none;
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### 2. Accessible Modal & Focus Trap Contract
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
import React, { useEffect, useRef } from 'react';
|
|
48
|
-
import { createPortal } from 'react-dom';
|
|
49
|
-
|
|
50
|
-
interface ModalProps {
|
|
51
|
-
isOpen: boolean;
|
|
52
|
-
onClose: () => void;
|
|
53
|
-
titleId: string;
|
|
54
|
-
children: React.ReactNode;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function AccessibleModal({ isOpen, onClose, titleId, children }: ModalProps) {
|
|
58
|
-
const dialogRef = useRef<HTMLDivElement>(null);
|
|
59
|
-
const triggerRef = useRef<HTMLElement | null>(null);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (!isOpen) return;
|
|
63
|
-
triggerRef.current = document.activeElement as HTMLElement;
|
|
64
|
-
|
|
65
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
66
|
-
if (e.key === 'Escape') {
|
|
67
|
-
e.preventDefault();
|
|
68
|
-
onClose();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (e.key === 'Tab') {
|
|
72
|
-
const focusables = dialogRef.current?.querySelectorAll<HTMLElement>(
|
|
73
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
74
|
-
) || [];
|
|
75
|
-
if (!focusables.length) return;
|
|
76
|
-
|
|
77
|
-
const first = focusables[0];
|
|
78
|
-
const last = focusables[focusables.length - 1];
|
|
79
|
-
|
|
80
|
-
if (e.shiftKey && document.activeElement === first) {
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
last.focus();
|
|
83
|
-
} else if (!e.shiftKey && document.activeElement === last) {
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
first.focus();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
91
|
-
return () => {
|
|
92
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
93
|
-
triggerRef.current?.focus();
|
|
94
|
-
};
|
|
95
|
-
}, [isOpen, onClose]);
|
|
96
|
-
|
|
97
|
-
if (!isOpen) return null;
|
|
98
|
-
|
|
99
|
-
return createPortal(
|
|
100
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
|
|
101
|
-
<div
|
|
102
|
-
ref={dialogRef}
|
|
103
|
-
role="dialog"
|
|
104
|
-
aria-modal="true"
|
|
105
|
-
aria-labelledby={titleId}
|
|
106
|
-
className="w-full max-w-lg rounded-xl bg-background p-6 shadow-2xl border"
|
|
107
|
-
>
|
|
108
|
-
{children}
|
|
109
|
-
</div>
|
|
110
|
-
</div>,
|
|
111
|
-
document.body
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 3. Accessible Forms & Error Association
|
|
117
|
-
|
|
118
|
-
```tsx
|
|
119
|
-
export function EmailInput({ error, ...props }: { error?: string } & React.InputHTMLAttributes<HTMLInputElement>) {
|
|
120
|
-
const inputId = 'user-email';
|
|
121
|
-
const errorId = 'user-email-error';
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div className="flex flex-col gap-1.5">
|
|
125
|
-
<label htmlFor={inputId} className="text-sm font-medium">
|
|
126
|
-
Email Address <span aria-hidden="true" className="text-destructive">*</span>
|
|
127
|
-
</label>
|
|
128
|
-
<input
|
|
129
|
-
id={inputId}
|
|
130
|
-
type="email"
|
|
131
|
-
autoComplete="email"
|
|
132
|
-
required
|
|
133
|
-
aria-invalid={Boolean(error)}
|
|
134
|
-
aria-describedby={error ? errorId : undefined}
|
|
135
|
-
className="rounded-md border p-2 text-sm focus-visible:ring-2"
|
|
136
|
-
{...props}
|
|
137
|
-
/>
|
|
138
|
-
{error && (
|
|
139
|
-
<p id={errorId} role="alert" className="text-xs text-destructive">
|
|
140
|
-
{error}
|
|
141
|
-
</p>
|
|
142
|
-
)}
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Code Examples
|
|
149
|
-
|
|
150
|
-
See `EXAMPLES.md` for detailed dialog, menu, and form examples.
|
|
151
|
-
|
|
152
|
-
## Validation Checklist
|
|
153
|
-
|
|
154
|
-
- [ ] All interactive elements are fully operable via Keyboard (`Tab`, `Enter`, `Space`, `Escape`).
|
|
155
|
-
- [ ] Visual `:focus-visible` styling is distinct and high contrast (≥ 3:1).
|
|
156
|
-
- [ ] Modals use `role="dialog"`, `aria-modal="true"`, focus trap, and restore focus on close.
|
|
157
|
-
- [ ] Text contrast ratios satisfy WCAG AA (≥ 4.5:1 for normal text, ≥ 3:1 for large text).
|
|
158
|
-
- [ ] Forms pair inputs with `<label htmlFor>`, valid `autocomplete` tokens, and `aria-invalid`.
|
|
159
|
-
- [ ] Non-text media contains descriptive `alt` attributes or `aria-hidden="true"` for decorative icons.
|
|
160
|
-
|
|
161
|
-
## Common Mistakes
|
|
162
|
-
|
|
163
|
-
- Hiding outline focus indicators globally without `:focus-visible` fallback.
|
|
164
|
-
- Forgetting to trap focus in modal dialogs or not returning focus to trigger when modal closes.
|
|
165
|
-
- Missing `aria-expanded` attributes on disclosure buttons and dropdown toggles.
|
|
166
|
-
|
|
167
|
-
## Integration Notes
|
|
168
|
-
|
|
169
|
-
- Pairs with `ui-ux-pro` and `impeccable-design` for visual contrast and component standards.
|
|
170
|
-
- Pairs with `react` and `nextjs` for accessible dialogs and focus restoration across route transitions.
|