contextos-agents 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +5 -0
- package/.agents/adapters/aider/export.js +4 -2
- package/.agents/adapters/copilot/export.js +4 -2
- package/.agents/adapters/cursor/export.js +48 -12
- package/.agents/adapters/zed/export.js +53 -14
- package/.agents/core/profiles/mvp.yaml +1 -0
- package/.agents/core/skills/adapters/skill.yaml +6 -0
- package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
- package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +7 -0
- package/.agents/core/skills/context-os/skill.yaml +11 -0
- package/.agents/core/skills/database/skill.yaml +6 -0
- package/.agents/core/skills/ddd/skill.yaml +7 -0
- package/.agents/core/skills/decisions/skill.yaml +6 -0
- package/.agents/core/skills/docker/skill.yaml +6 -0
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
- package/.agents/core/skills/fastapi/skill.yaml +7 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
- package/.agents/core/skills/generators/skill.yaml +15 -0
- package/.agents/core/skills/graphify/SKILL.md +2 -2
- package/.agents/core/skills/graphify/skill.yaml +6 -1
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
- package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
- package/.agents/core/skills/interview-me/skill.yaml +4 -0
- package/.agents/core/skills/microservices/skill.yaml +7 -0
- package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
- package/.agents/core/skills/nestjs/skill.yaml +7 -0
- package/.agents/core/skills/nextjs/skill.yaml +7 -0
- package/.agents/core/skills/node/skill.yaml +7 -0
- package/.agents/core/skills/performance/skill.yaml +7 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
- package/.agents/core/skills/react/skill.yaml +7 -0
- package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
- package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
- package/.agents/core/skills/security/skill.yaml +7 -0
- package/.agents/core/skills/soft-design/skill.yaml +4 -0
- package/.agents/core/skills/state-management/skill.yaml +6 -0
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
- package/.agents/core/skills/system-design/skill.yaml +7 -0
- package/.agents/core/skills/testing/skill.yaml +6 -0
- package/.agents/core/skills/typescript/skill.yaml +7 -0
- package/.agents/core/skills/ui-design/skill.yaml +7 -0
- package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
- package/.agents/core/skills/ux-design/skill.yaml +7 -0
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
- package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
- package/.agents/ctx.js +44 -0
- package/.agents/doctor.js +193 -4
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +180 -20
- package/.agents/profiles.js +26 -0
- package/.agents/resolver.js +150 -30
- package/.agents/stats.js +22 -9
- package/.agents/validate.js +152 -13
- package/README.md +77 -47
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +262 -0
- package/bin/commands.js +305 -0
- package/bin/index.js +275 -119
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +251 -0
- package/bin/lib/safe-writer.js +259 -0
- package/package.json +6 -9
- package/registry.json +13 -5
- package/registry.schema.json +3 -1
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189386
- package/benchmarks/gemini-issues.js +0 -533
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
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,4 +1,5 @@
|
|
|
1
1
|
name: vercel-optimize
|
|
2
|
+
type: runtime
|
|
2
3
|
description: Observability-first Vercel performance and cost reduction optimization skill.
|
|
3
4
|
tags:
|
|
4
5
|
- vercel
|
|
@@ -8,3 +9,10 @@ tags:
|
|
|
8
9
|
version: 1.2.0
|
|
9
10
|
requires:
|
|
10
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,6 +1,7 @@
|
|
|
1
1
|
id: web-accessibility
|
|
2
2
|
name: Web Accessibility
|
|
3
3
|
category: frontend
|
|
4
|
+
type: instruction-only
|
|
4
5
|
tags: [frontend, a11y, wcag, aria, accessibility]
|
|
5
6
|
requires: []
|
|
6
7
|
optional: [react, nextjs]
|
|
@@ -8,3 +9,9 @@ conflicts: []
|
|
|
8
9
|
weight: 7
|
|
9
10
|
documents:
|
|
10
11
|
- accessibility.md
|
|
12
|
+
resources:
|
|
13
|
+
- EXAMPLES.md
|
|
14
|
+
- SKILL.md
|
|
15
|
+
- TROUBLESHOOTING.md
|
|
16
|
+
- VALIDATION.json
|
|
17
|
+
- accessibility.md
|
package/.agents/ctx.js
CHANGED
|
@@ -46,6 +46,10 @@ function printHelp() {
|
|
|
46
46
|
console.log(' audit Alias for validate (check skills)');
|
|
47
47
|
console.log(' validate Validate skill sources, frontmatter, deps & sync');
|
|
48
48
|
console.log(' clean-worktrees Clean up lingering .swarm-worktrees and swarm/* branches');
|
|
49
|
+
console.log(' doctor Run project diagnostic health check');
|
|
50
|
+
console.log(' stats Display token context savings report');
|
|
51
|
+
console.log(' watch Start continuous file watcher and auto-sync daemon');
|
|
52
|
+
console.log(' init Show initialization guide');
|
|
49
53
|
console.log(' install-skill <ref> Alias for skill add (install a plugin)');
|
|
50
54
|
console.log(' skill add <ref> Install a plugin skill (GitHub or npm)');
|
|
51
55
|
console.log(' skill remove <name> Uninstall a plugin skill');
|
|
@@ -363,6 +367,46 @@ if (command === 'export') {
|
|
|
363
367
|
|
|
364
368
|
console.log(`✓ Cleaned up ${dirCount} worktree directory(ies) and ${branchCount} swarm branch(es).`);
|
|
365
369
|
|
|
370
|
+
// ── doctor ────────────────────────────────────────────────────────────────────
|
|
371
|
+
} else if (command === 'doctor') {
|
|
372
|
+
const doctorModule = require('./doctor.js');
|
|
373
|
+
const res = doctorModule.runDoctor(process.cwd(), { json: args.includes('--json') });
|
|
374
|
+
if (res && res.ok === false) {
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── status ────────────────────────────────────────────────────────────────────
|
|
379
|
+
} else if (command === 'status') {
|
|
380
|
+
const commands = require(path.join(__dirname, '..', 'bin', 'commands.js'));
|
|
381
|
+
const status = commands.getStatus(process.cwd());
|
|
382
|
+
if (args.includes('--json')) {
|
|
383
|
+
console.log(JSON.stringify(status, null, 2));
|
|
384
|
+
} else {
|
|
385
|
+
console.log(commands.formatStatusText(status));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── stats ─────────────────────────────────────────────────────────────────────
|
|
389
|
+
} else if (command === 'stats') {
|
|
390
|
+
const statsModule = require('./stats.js');
|
|
391
|
+
statsModule.runStats(process.cwd());
|
|
392
|
+
|
|
393
|
+
// ── watch ─────────────────────────────────────────────────────────────────────
|
|
394
|
+
} else if (command === 'watch') {
|
|
395
|
+
const watchModule = require('./watch.js');
|
|
396
|
+
watchModule.runWatch(process.cwd());
|
|
397
|
+
|
|
398
|
+
// ── init ──────────────────────────────────────────────────────────────────────
|
|
399
|
+
} else if (command === 'init') {
|
|
400
|
+
console.log('\nContextOS — Project Initialization Guide\n');
|
|
401
|
+
console.log('To set up ContextOS in your project:');
|
|
402
|
+
console.log(' npx contextos Install .agents/ with auto-detected profile');
|
|
403
|
+
console.log(' npx contextos --minimal Install with minimal core skills');
|
|
404
|
+
console.log(' npx contextos --profile <name> Install with specific profile (mvp, startup, enterprise, etc.)');
|
|
405
|
+
console.log(' npx contextos --with-mcp Install with MCP execution server enabled');
|
|
406
|
+
console.log('\nAfter setup, verify your installation:');
|
|
407
|
+
console.log(' node .agents/ctx.js doctor');
|
|
408
|
+
console.log(' node .agents/ctx.js stats\n');
|
|
409
|
+
|
|
366
410
|
// ── unknown ───────────────────────────────────────────────────────────────────
|
|
367
411
|
} else {
|
|
368
412
|
console.error(`Unknown command: ${command}`);
|
package/.agents/doctor.js
CHANGED
|
@@ -112,6 +112,100 @@ function checkCompiledAdapters(projectDir) {
|
|
|
112
112
|
return compiled;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function checkLockfileIntegrity(projectDir) {
|
|
116
|
+
const lockPath = path.join(projectDir, '.agents', 'contextos.lock.json');
|
|
117
|
+
if (!fs.existsSync(lockPath)) {
|
|
118
|
+
return { ok: true, exists: false, isError: false, message: 'none (not generated yet)' };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const raw = fs.readFileSync(lockPath, 'utf8');
|
|
122
|
+
const data = JSON.parse(raw);
|
|
123
|
+
if (!data || typeof data !== 'object') {
|
|
124
|
+
return { ok: false, exists: true, isError: true, error: 'Lockfile JSON root is not an object' };
|
|
125
|
+
}
|
|
126
|
+
if (!data.schemaVersion || typeof data.schemaVersion !== 'number') {
|
|
127
|
+
return { ok: false, exists: true, isError: true, error: 'Lockfile missing valid schemaVersion' };
|
|
128
|
+
}
|
|
129
|
+
if (!data.managedFiles || typeof data.managedFiles !== 'object') {
|
|
130
|
+
return { ok: false, exists: true, isError: true, error: 'Lockfile missing managedFiles map' };
|
|
131
|
+
}
|
|
132
|
+
const count = Object.keys(data.managedFiles).length;
|
|
133
|
+
return { ok: true, exists: true, isError: false, message: `valid (v${data.schemaVersion}, ${count} managed files)` };
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return { ok: false, exists: true, isError: true, error: `Lockfile is corrupted JSON: ${err.message}` };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function checkAdapterIntegrity(projectDir) {
|
|
140
|
+
const errors = [];
|
|
141
|
+
const filesToCheck = [
|
|
142
|
+
path.join(projectDir, '.cursorrules'),
|
|
143
|
+
path.join(projectDir, '.github', 'copilot-instructions.md'),
|
|
144
|
+
path.join(projectDir, '.aider.conf.yml'),
|
|
145
|
+
path.join(projectDir, '.zed', 'rules.md'),
|
|
146
|
+
];
|
|
147
|
+
for (const f of filesToCheck) {
|
|
148
|
+
if (fs.existsSync(f)) {
|
|
149
|
+
try {
|
|
150
|
+
const stat = fs.statSync(f);
|
|
151
|
+
if (stat.size === 0) {
|
|
152
|
+
errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is empty (0 bytes)`);
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
errors.push(`Adapter file ${path.relative(projectDir, f).replace(/\\/g, '/')} is unreadable`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const cursorRulesDir = path.join(projectDir, '.cursor', 'rules');
|
|
161
|
+
if (fs.existsSync(cursorRulesDir)) {
|
|
162
|
+
try {
|
|
163
|
+
const files = fs.readdirSync(cursorRulesDir);
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
const full = path.join(cursorRulesDir, file);
|
|
166
|
+
if (fs.statSync(full).size === 0) {
|
|
167
|
+
errors.push(`Cursor rule ${file} is empty (0 bytes)`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { ok: errors.length === 0, errors };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function checkSymlinks(projectDir) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
const targetDir = path.join(projectDir, '.agents');
|
|
179
|
+
if (!fs.existsSync(targetDir)) return { ok: true, errors: [] };
|
|
180
|
+
|
|
181
|
+
function scan(current) {
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = fs.readdirSync(current);
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const name of entries) {
|
|
189
|
+
const full = path.join(current, name);
|
|
190
|
+
try {
|
|
191
|
+
const lstat = fs.lstatSync(full);
|
|
192
|
+
if (lstat.isSymbolicLink()) {
|
|
193
|
+
if (!fs.existsSync(full)) {
|
|
194
|
+
errors.push(`Broken symlink: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
|
|
195
|
+
}
|
|
196
|
+
} else if (lstat.isDirectory()) {
|
|
197
|
+
scan(full);
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
errors.push(`Cannot stat file: ${path.relative(projectDir, full).replace(/\\/g, '/')}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
scan(targetDir);
|
|
206
|
+
return { ok: errors.length === 0, errors };
|
|
207
|
+
}
|
|
208
|
+
|
|
115
209
|
function inspectSkills(projectDir, activeProfile) {
|
|
116
210
|
const skillsDir = path.join(projectDir, '.agents', 'core', 'skills');
|
|
117
211
|
if (!fs.existsSync(skillsDir)) return { total: 0, active: 0, excluded: 0, byDomain: { frontend: [], backend: [], cross: [] } };
|
|
@@ -139,18 +233,47 @@ function inspectSkills(projectDir, activeProfile) {
|
|
|
139
233
|
};
|
|
140
234
|
}
|
|
141
235
|
|
|
142
|
-
function runDoctor(projectDir = process.cwd()) {
|
|
236
|
+
function runDoctor(projectDir = process.cwd(), options = {}) {
|
|
143
237
|
const version = (() => {
|
|
144
238
|
try {
|
|
145
239
|
return require('../package.json').version;
|
|
146
240
|
} catch {
|
|
147
|
-
return '1.
|
|
241
|
+
return '1.7.0';
|
|
148
242
|
}
|
|
149
243
|
})();
|
|
150
244
|
|
|
245
|
+
const isJson = Boolean(options.json);
|
|
246
|
+
const errors = [];
|
|
247
|
+
const warnings = [];
|
|
248
|
+
|
|
151
249
|
const agentsDir = path.join(projectDir, '.agents');
|
|
152
250
|
const hasAgents = fs.existsSync(agentsDir);
|
|
251
|
+
if (!hasAgents) {
|
|
252
|
+
errors.push('.agents/ directory missing (run: npx contextos-agents init)');
|
|
253
|
+
}
|
|
254
|
+
|
|
153
255
|
const nodeCheck = checkNodeVersion();
|
|
256
|
+
if (!nodeCheck.ok) {
|
|
257
|
+
errors.push(nodeCheck.message);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const lockfileCheck = checkLockfileIntegrity(projectDir);
|
|
261
|
+
if (!lockfileCheck.ok) {
|
|
262
|
+
errors.push(lockfileCheck.error);
|
|
263
|
+
} else if (!lockfileCheck.exists && hasAgents) {
|
|
264
|
+
warnings.push('Lockfile missing (.agents/contextos.lock.json)');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const adapterCheck = checkAdapterIntegrity(projectDir);
|
|
268
|
+
if (!adapterCheck.ok) {
|
|
269
|
+
errors.push(...adapterCheck.errors);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const symlinkCheck = checkSymlinks(projectDir);
|
|
273
|
+
if (!symlinkCheck.ok) {
|
|
274
|
+
errors.push(...symlinkCheck.errors);
|
|
275
|
+
}
|
|
276
|
+
|
|
154
277
|
const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
|
|
155
278
|
const skillsInfo = inspectSkills(projectDir, activeProfile);
|
|
156
279
|
const compiledAdapters = checkCompiledAdapters(projectDir);
|
|
@@ -162,6 +285,34 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
162
285
|
const secretCheck = checkSecretScanner(projectDir);
|
|
163
286
|
const stack = profiles.detectStack(projectDir);
|
|
164
287
|
|
|
288
|
+
const ok = errors.length === 0;
|
|
289
|
+
|
|
290
|
+
if (isJson) {
|
|
291
|
+
const report = {
|
|
292
|
+
ok,
|
|
293
|
+
version,
|
|
294
|
+
errors,
|
|
295
|
+
warnings,
|
|
296
|
+
hasAgents,
|
|
297
|
+
nodeOk: nodeCheck.ok,
|
|
298
|
+
activeProfile,
|
|
299
|
+
skillsInfo,
|
|
300
|
+
compiledAdapters,
|
|
301
|
+
hasMcp,
|
|
302
|
+
hookCheck,
|
|
303
|
+
secretCheck,
|
|
304
|
+
lockfileCheck,
|
|
305
|
+
adapterCheck,
|
|
306
|
+
symlinkCheck,
|
|
307
|
+
stack,
|
|
308
|
+
};
|
|
309
|
+
console.log(JSON.stringify(report, null, 2));
|
|
310
|
+
if (options.exitOnError !== false && !ok && require.main === module) {
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
return report;
|
|
314
|
+
}
|
|
315
|
+
|
|
165
316
|
console.log('\n┌─────────────────────────────────────────────────────────────┐');
|
|
166
317
|
console.log(`│ ContextOS Doctor — Project Health Check v${version.padEnd(16)}│`);
|
|
167
318
|
console.log('├─────────────────────────────────────────────────────────────┤');
|
|
@@ -178,6 +329,15 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
178
329
|
const nodeIcon = nodeCheck.ok ? '✓' : '✗';
|
|
179
330
|
console.log(`│ ${nodeIcon} ${nodeCheck.message.padEnd(58)}│`);
|
|
180
331
|
|
|
332
|
+
// Lockfile
|
|
333
|
+
if (lockfileCheck.exists) {
|
|
334
|
+
const lockIcon = lockfileCheck.ok ? '✓' : '✗';
|
|
335
|
+
const lockStr = `Lockfile: ${lockfileCheck.ok ? lockfileCheck.message : lockfileCheck.error}`;
|
|
336
|
+
console.log(`│ ${lockIcon} ${lockStr.slice(0, 58).padEnd(58)}│`);
|
|
337
|
+
} else {
|
|
338
|
+
console.log('│ • Lockfile: not present (optional for dev repository) │');
|
|
339
|
+
}
|
|
340
|
+
|
|
181
341
|
// Active profile
|
|
182
342
|
if (activeProfile) {
|
|
183
343
|
const profStr = `Active profile: ${activeProfile.name || activeProfile.profile}`;
|
|
@@ -199,6 +359,11 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
199
359
|
console.log('│ • Adapters compiled: none (run: contextos export gemini) │');
|
|
200
360
|
}
|
|
201
361
|
|
|
362
|
+
// Symlinks
|
|
363
|
+
const symIcon = symlinkCheck.ok ? '✓' : '✗';
|
|
364
|
+
const symStr = symlinkCheck.ok ? 'Symlinks: verified (0 broken)' : `Broken symlinks: ${symlinkCheck.errors.length}`;
|
|
365
|
+
console.log(`│ ${symIcon} ${symStr.padEnd(58)}│`);
|
|
366
|
+
|
|
202
367
|
// MCP
|
|
203
368
|
if (hasMcp) {
|
|
204
369
|
console.log('│ ✓ MCP server: installed (.agents/mcp/server.mjs) │');
|
|
@@ -242,7 +407,18 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
242
407
|
console.log('│ │');
|
|
243
408
|
console.log('└─────────────────────────────────────────────────────────────┘\n');
|
|
244
409
|
|
|
245
|
-
|
|
410
|
+
if (errors.length > 0) {
|
|
411
|
+
console.error(`[ERROR] Diagnostic check failed with ${errors.length} error(s):`);
|
|
412
|
+
for (const err of errors) {
|
|
413
|
+
console.error(` - ${err}`);
|
|
414
|
+
}
|
|
415
|
+
console.error('');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const result = {
|
|
419
|
+
ok,
|
|
420
|
+
errors,
|
|
421
|
+
warnings,
|
|
246
422
|
hasAgents,
|
|
247
423
|
nodeOk: nodeCheck.ok,
|
|
248
424
|
activeProfile,
|
|
@@ -251,12 +427,22 @@ function runDoctor(projectDir = process.cwd()) {
|
|
|
251
427
|
hasMcp,
|
|
252
428
|
hookCheck,
|
|
253
429
|
secretCheck,
|
|
430
|
+
lockfileCheck,
|
|
431
|
+
adapterCheck,
|
|
432
|
+
symlinkCheck,
|
|
254
433
|
stack,
|
|
255
434
|
};
|
|
435
|
+
|
|
436
|
+
if (options.exitOnError !== false && !ok && require.main === module) {
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return result;
|
|
256
441
|
}
|
|
257
442
|
|
|
258
443
|
if (require.main === module) {
|
|
259
|
-
runDoctor();
|
|
444
|
+
const res = runDoctor();
|
|
445
|
+
if (!res.ok) process.exit(1);
|
|
260
446
|
}
|
|
261
447
|
|
|
262
448
|
module.exports = {
|
|
@@ -265,5 +451,8 @@ module.exports = {
|
|
|
265
451
|
checkPreCommitHook,
|
|
266
452
|
checkSecretScanner,
|
|
267
453
|
checkCompiledAdapters,
|
|
454
|
+
checkLockfileIntegrity,
|
|
455
|
+
checkAdapterIntegrity,
|
|
456
|
+
checkSymlinks,
|
|
268
457
|
inspectSkills,
|
|
269
458
|
};
|
|
@@ -33,7 +33,7 @@ Inspired by [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
**IRON RULE**: In interactive development, no phase can be skipped and no code is written before `/plan` is approved.
|
|
36
|
-
**Direct Build Exception**: When the prompt/caller explicitly requests a standalone implementation,
|
|
36
|
+
**Direct Build & Fast-Track Exception**: When the prompt/caller explicitly requests a standalone implementation, declares `[PHASE: Build]`, or requests routine operational/maintenance tasks (git operations, version bumps, typo fixes, small config tweaks, diagnostic checks), proceed directly to execution without conversational approval pauses.
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|