contextos-agents 1.6.1 → 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.
Files changed (86) hide show
  1. package/.agents/AGENTS.md +5 -0
  2. package/.agents/adapters/aider/export.js +4 -2
  3. package/.agents/adapters/copilot/export.js +4 -2
  4. package/.agents/adapters/cursor/export.js +48 -12
  5. package/.agents/adapters/zed/export.js +53 -14
  6. package/.agents/core/skills/adapters/skill.yaml +6 -0
  7. package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
  8. package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
  9. package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
  10. package/.agents/core/skills/context-manager/skill.yaml +7 -0
  11. package/.agents/core/skills/context-os/skill.yaml +11 -0
  12. package/.agents/core/skills/database/skill.yaml +6 -0
  13. package/.agents/core/skills/ddd/skill.yaml +7 -0
  14. package/.agents/core/skills/decisions/skill.yaml +6 -0
  15. package/.agents/core/skills/docker/skill.yaml +6 -0
  16. package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
  17. package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
  18. package/.agents/core/skills/fastapi/skill.yaml +7 -0
  19. package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
  20. package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
  21. package/.agents/core/skills/generators/skill.yaml +15 -0
  22. package/.agents/core/skills/graphify/SKILL.md +2 -2
  23. package/.agents/core/skills/graphify/skill.yaml +6 -1
  24. package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
  25. package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
  26. package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
  27. package/.agents/core/skills/interview-me/skill.yaml +4 -0
  28. package/.agents/core/skills/microservices/skill.yaml +7 -0
  29. package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
  30. package/.agents/core/skills/nestjs/skill.yaml +7 -0
  31. package/.agents/core/skills/nextjs/skill.yaml +7 -0
  32. package/.agents/core/skills/node/skill.yaml +7 -0
  33. package/.agents/core/skills/performance/skill.yaml +7 -0
  34. package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
  35. package/.agents/core/skills/react/skill.yaml +7 -0
  36. package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
  37. package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
  38. package/.agents/core/skills/security/skill.yaml +7 -0
  39. package/.agents/core/skills/soft-design/skill.yaml +4 -0
  40. package/.agents/core/skills/state-management/skill.yaml +6 -0
  41. package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
  42. package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
  43. package/.agents/core/skills/system-design/skill.yaml +7 -0
  44. package/.agents/core/skills/testing/skill.yaml +6 -0
  45. package/.agents/core/skills/typescript/skill.yaml +7 -0
  46. package/.agents/core/skills/ui-design/skill.yaml +7 -0
  47. package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
  48. package/.agents/core/skills/ux-design/skill.yaml +7 -0
  49. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
  50. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
  51. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
  52. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
  53. package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
  54. package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
  55. package/.agents/ctx.js +14 -1
  56. package/.agents/doctor.js +193 -4
  57. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
  58. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
  59. package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
  60. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
  61. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
  62. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
  63. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
  64. package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
  65. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
  66. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
  67. package/.agents/plugins/contextos/hooks.json +25 -0
  68. package/.agents/plugins/contextos/plugin.json +19 -0
  69. package/.agents/plugins.js +180 -20
  70. package/.agents/profiles.js +26 -0
  71. package/.agents/resolver.js +150 -30
  72. package/.agents/stats.js +22 -9
  73. package/.agents/validate.js +99 -13
  74. package/bin/commands/uninstall.js +207 -0
  75. package/bin/commands/update.js +262 -0
  76. package/bin/commands.js +305 -0
  77. package/bin/index.js +203 -83
  78. package/bin/lib/detector.js +106 -0
  79. package/bin/lib/lockfile.js +251 -0
  80. package/bin/lib/safe-writer.js +259 -0
  81. package/package.json +5 -6
  82. package/registry.json +13 -5
  83. package/registry.schema.json +3 -1
  84. package/.agents/mcp/runtime.py +0 -470
  85. package/.agents/mcp/server.mjs +0 -189271
  86. 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
@@ -370,7 +370,20 @@ if (command === 'export') {
370
370
  // ── doctor ────────────────────────────────────────────────────────────────────
371
371
  } else if (command === 'doctor') {
372
372
  const doctorModule = require('./doctor.js');
373
- doctorModule.runDoctor(process.cwd());
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
+ }
374
387
 
375
388
  // ── stats ─────────────────────────────────────────────────────────────────────
376
389
  } else if (command === 'stats') {
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.5.0';
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
- return {
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, or declares `[PHASE: Build]`, execute the BUILD phase directly and deliver the complete, self-contained production code without conversational pauses.
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
 
@@ -82,6 +82,10 @@ Activate whenever:
82
82
  - State what was inspected or verified from the code.
83
83
  - State the architectural decision made and the immediate next action.
84
84
  - Keep narration crisp and actionable without excessive verbosity.
85
+ - **Zero-Spam Constraint**:
86
+ - ❌ **Forbidden**: Starting every intermediate step, tool call, or status update with domain/phase/role tags (e.g. `[DOMAIN: ...] [PHASE: ...] [ROLE: ...]`).
87
+ - ✅ **Mandatory**: Declare role and phase strictly once at the start of a phase. Intermediate step updates must be clean, natural language sentences describing technical actions directly.
88
+ - Do not narrate routine micro-inspections (single line reads or basic greps). Announce only meaningful task phases and decisions.
85
89
 
86
90
  ---
87
91
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- **Graphify** is a codebase mapping and context optimization engine. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify leverages local **Tree-sitter** AST parsing to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`).
5
+ **Graphify** is an instruction-only codebase mapping and context optimization guide. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify instructs agents on how to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`) using external companion analyzers (such as the TypeScript AST analyzer in `contextos-mcp` or the external `graphifyy` CLI), keeping the core package 100% zero-dependency without bundled native Tree-sitter binaries.
6
6
 
7
7
  This skill instructs agents how to build, query, and maintain codebase graphs to navigate complex architectures with near-zero token overhead.
8
8
 
@@ -18,12 +18,14 @@ Inspired by [Garry Tan's gstack](https://github.com/garrytan/gstack) — shippin
18
18
 
19
19
  ## Role Identification Protocol
20
20
 
21
- At the start of each task, declare your role:
21
+ At the start of each task or major phase switch, declare your role:
22
22
 
23
23
  ```
24
24
  [ROLE: <Role Name>] — <One-line description of your mandate for this task>
25
25
  ```
26
26
 
27
+ > **Anti-Spam Invariant**: Declare this role **strictly once per phase**. Never prefix intermediate tool calls, file operations, or step updates with role tags.
28
+
27
29
  Then execute ONLY within the constraints of that role.
28
30
 
29
31
  ---