contextos-agents 2.1.0 → 2.1.1
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/adapters/aider/export.js +2 -2
- package/.agents/adapters/claude/export.js +53 -2
- package/.agents/adapters/drift-detector.js +6 -3
- package/.agents/adapters/pure-compiler.js +18 -6
- package/.agents/ctx.js +4 -4
- package/.agents/plugins.js +100 -4
- package/.agents/profiles.js +32 -11
- package/README.md +2 -2
- package/bin/commands/hook.js +50 -12
- package/bin/commands/scan.js +10 -3
- package/bin/index.js +8 -2
- package/bin/lib/git-snapshot.js +70 -43
- package/bin/lib/scan.js +108 -27
- package/catalog/skills/adapters/EXAMPLES.md +19 -0
- package/catalog/skills/adapters/SKILL.md +101 -0
- package/catalog/skills/adapters/TROUBLESHOOTING.md +7 -0
- package/catalog/skills/adapters/VALIDATION.json +12 -0
- package/catalog/skills/adapters/skill.yaml +13 -0
- package/catalog/skills/api-design/EXAMPLES.md +91 -0
- package/catalog/skills/api-design/SKILL.md +63 -0
- package/catalog/skills/api-design/TROUBLESHOOTING.md +54 -0
- package/catalog/skills/api-design/VALIDATION.json +11 -0
- package/catalog/skills/api-design/skill.yaml +14 -0
- package/catalog/skills/architecture-diagrams/SKILL.md +108 -0
- package/catalog/skills/architecture-diagrams/VALIDATION.json +12 -0
- package/catalog/skills/architecture-diagrams/skill.yaml +9 -0
- package/catalog/skills/brutalist-design/EXAMPLES.md +59 -0
- package/catalog/skills/brutalist-design/SKILL.md +150 -0
- package/catalog/skills/brutalist-design/VALIDATION.json +12 -0
- package/catalog/skills/brutalist-design/skill.yaml +10 -0
- package/catalog/skills/ci-cd/EXAMPLES.md +79 -0
- package/catalog/skills/ci-cd/SKILL.md +69 -0
- package/catalog/skills/ci-cd/TROUBLESHOOTING.md +52 -0
- package/catalog/skills/ci-cd/VALIDATION.json +11 -0
- package/catalog/skills/ci-cd/skill.yaml +13 -0
- package/catalog/skills/database/EXAMPLES.md +74 -0
- package/catalog/skills/database/SKILL.md +101 -0
- package/catalog/skills/database/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/database/VALIDATION.json +11 -0
- package/catalog/skills/database/skill.yaml +14 -0
- package/catalog/skills/ddd/EXAMPLES.md +42 -0
- package/catalog/skills/ddd/SKILL.md +247 -0
- package/catalog/skills/ddd/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ddd/VALIDATION.json +12 -0
- package/catalog/skills/ddd/skill.yaml +14 -0
- package/catalog/skills/decisions/EXAMPLES.md +35 -0
- package/catalog/skills/decisions/SKILL.md +90 -0
- package/catalog/skills/decisions/TROUBLESHOOTING.md +13 -0
- package/catalog/skills/decisions/VALIDATION.json +12 -0
- package/catalog/skills/decisions/skill.yaml +13 -0
- package/catalog/skills/docker/EXAMPLES.md +56 -0
- package/catalog/skills/docker/SKILL.md +169 -0
- package/catalog/skills/docker/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/docker/VALIDATION.json +11 -0
- package/catalog/skills/docker/skill.yaml +13 -0
- package/catalog/skills/fastapi/EXAMPLES.md +36 -0
- package/catalog/skills/fastapi/SKILL.md +171 -0
- package/catalog/skills/fastapi/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/fastapi/VALIDATION.json +12 -0
- package/catalog/skills/fastapi/skill.yaml +14 -0
- package/catalog/skills/generators/EXAMPLES.md +19 -0
- package/catalog/skills/generators/SKILL.md +110 -0
- package/catalog/skills/generators/TROUBLESHOOTING.md +7 -0
- package/catalog/skills/generators/VALIDATION.json +12 -0
- package/catalog/skills/generators/skill.yaml +22 -0
- package/catalog/skills/generators/templates/API.md +77 -0
- package/catalog/skills/generators/templates/ARCHITECTURE.md +70 -0
- package/catalog/skills/generators/templates/DATABASE.md +42 -0
- package/catalog/skills/generators/templates/DECISION.md +46 -0
- package/catalog/skills/generators/templates/PRD.md +67 -0
- package/catalog/skills/generators/templates/PROJECT_GRAPH.md +56 -0
- package/catalog/skills/generators/templates/ROADMAP.md +51 -0
- package/catalog/skills/generators/templates/TASKS.md +43 -0
- package/catalog/skills/generators/templates/UI.md +73 -0
- package/catalog/skills/graphify/EXAMPLES.md +73 -0
- package/catalog/skills/graphify/SKILL.md +130 -0
- package/catalog/skills/graphify/VALIDATION.json +12 -0
- package/catalog/skills/graphify/skill.yaml +13 -0
- package/catalog/skills/impeccable-design/EXAMPLES.md +26 -0
- package/catalog/skills/impeccable-design/SKILL.md +201 -0
- package/catalog/skills/impeccable-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/impeccable-design/VALIDATION.json +12 -0
- package/catalog/skills/impeccable-design/skill.yaml +15 -0
- package/catalog/skills/interview-me/SKILL.md +97 -0
- package/catalog/skills/interview-me/VALIDATION.json +12 -0
- package/catalog/skills/interview-me/skill.yaml +9 -0
- package/catalog/skills/microservices/EXAMPLES.md +38 -0
- package/catalog/skills/microservices/SKILL.md +164 -0
- package/catalog/skills/microservices/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/microservices/VALIDATION.json +12 -0
- package/catalog/skills/microservices/skill.yaml +14 -0
- package/catalog/skills/minimalist-design/EXAMPLES.md +58 -0
- package/catalog/skills/minimalist-design/SKILL.md +113 -0
- package/catalog/skills/minimalist-design/VALIDATION.json +12 -0
- package/catalog/skills/minimalist-design/skill.yaml +10 -0
- package/catalog/skills/nestjs/EXAMPLES.md +40 -0
- package/catalog/skills/nestjs/SKILL.md +139 -0
- package/catalog/skills/nestjs/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/nestjs/VALIDATION.json +12 -0
- package/catalog/skills/nestjs/skill.yaml +14 -0
- package/catalog/skills/nextjs/EXAMPLES.md +40 -0
- package/catalog/skills/nextjs/SKILL.md +163 -0
- package/catalog/skills/nextjs/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/nextjs/VALIDATION.json +12 -0
- package/catalog/skills/nextjs/skill.yaml +14 -0
- package/catalog/skills/node/EXAMPLES.md +80 -0
- package/catalog/skills/node/SKILL.md +128 -0
- package/catalog/skills/node/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/node/VALIDATION.json +12 -0
- package/catalog/skills/node/skill.yaml +14 -0
- package/catalog/skills/performance/EXAMPLES.md +30 -0
- package/catalog/skills/performance/SKILL.md +75 -0
- package/catalog/skills/performance/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/performance/VALIDATION.json +12 -0
- package/catalog/skills/performance/skill.yaml +14 -0
- package/catalog/skills/react/EXAMPLES.md +79 -0
- package/catalog/skills/react/SKILL.md +132 -0
- package/catalog/skills/react/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/react/VALIDATION.json +12 -0
- package/catalog/skills/react/skill.yaml +14 -0
- package/catalog/skills/react-best-practices/SKILL.md +158 -0
- package/catalog/skills/react-best-practices/VALIDATION.json +12 -0
- package/catalog/skills/react-best-practices/skill.yaml +13 -0
- package/catalog/skills/redesign-audit/SKILL.md +117 -0
- package/catalog/skills/redesign-audit/VALIDATION.json +12 -0
- package/catalog/skills/redesign-audit/skill.yaml +9 -0
- package/catalog/skills/security-audit/EXAMPLES.md +79 -0
- package/catalog/skills/security-audit/SKILL.md +91 -0
- package/catalog/skills/security-audit/TROUBLESHOOTING.md +46 -0
- package/catalog/skills/security-audit/VALIDATION.json +11 -0
- package/catalog/skills/security-audit/skill.yaml +14 -0
- package/catalog/skills/soft-design/EXAMPLES.md +51 -0
- package/catalog/skills/soft-design/SKILL.md +108 -0
- package/catalog/skills/soft-design/VALIDATION.json +12 -0
- package/catalog/skills/soft-design/skill.yaml +10 -0
- package/catalog/skills/state-management/EXAMPLES.md +56 -0
- package/catalog/skills/state-management/SKILL.md +168 -0
- package/catalog/skills/state-management/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/state-management/VALIDATION.json +11 -0
- package/catalog/skills/state-management/skill.yaml +14 -0
- package/catalog/skills/subagent-orchestrator/SKILL.md +117 -0
- package/catalog/skills/subagent-orchestrator/VALIDATION.json +12 -0
- package/catalog/skills/subagent-orchestrator/skill.yaml +9 -0
- package/catalog/skills/system-design/EXAMPLES.md +75 -0
- package/catalog/skills/system-design/SKILL.md +419 -0
- package/catalog/skills/system-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/system-design/VALIDATION.json +12 -0
- package/catalog/skills/system-design/skill.yaml +14 -0
- package/catalog/skills/terraform/EXAMPLES.md +74 -0
- package/catalog/skills/terraform/SKILL.md +55 -0
- package/catalog/skills/terraform/TROUBLESHOOTING.md +53 -0
- package/catalog/skills/terraform/VALIDATION.json +11 -0
- package/catalog/skills/terraform/skill.yaml +14 -0
- package/catalog/skills/testing/EXAMPLES.md +122 -0
- package/catalog/skills/testing/SKILL.md +70 -0
- package/catalog/skills/testing/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/testing/VALIDATION.json +11 -0
- package/catalog/skills/testing/skill.yaml +14 -0
- package/catalog/skills/typescript/EXAMPLES.md +64 -0
- package/catalog/skills/typescript/SKILL.md +112 -0
- package/catalog/skills/typescript/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/typescript/VALIDATION.json +12 -0
- package/catalog/skills/typescript/skill.yaml +14 -0
- package/catalog/skills/ui-design/EXAMPLES.md +21 -0
- package/catalog/skills/ui-design/SKILL.md +124 -0
- package/catalog/skills/ui-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ui-design/VALIDATION.json +12 -0
- package/catalog/skills/ui-design/skill.yaml +16 -0
- package/catalog/skills/ui-ux-pro/EXAMPLES.md +62 -0
- package/catalog/skills/ui-ux-pro/SKILL.md +418 -0
- package/catalog/skills/ui-ux-pro/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ui-ux-pro/VALIDATION.json +12 -0
- package/catalog/skills/ui-ux-pro/skill.yaml +14 -0
- package/catalog/skills/ux-design/EXAMPLES.md +36 -0
- package/catalog/skills/ux-design/SKILL.md +116 -0
- package/catalog/skills/ux-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ux-design/VALIDATION.json +12 -0
- package/catalog/skills/ux-design/skill.yaml +16 -0
- package/catalog/skills/vercel-optimize/SKILL.md +83 -0
- package/catalog/skills/vercel-optimize/VALIDATION.json +12 -0
- package/catalog/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/catalog/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/catalog/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/catalog/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/catalog/skills/vercel-optimize/skill.yaml +15 -0
- package/catalog/skills/web-accessibility/EXAMPLES.md +39 -0
- package/catalog/skills/web-accessibility/SKILL.md +151 -0
- package/catalog/skills/web-accessibility/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/web-accessibility/VALIDATION.json +12 -0
- package/catalog/skills/web-accessibility/skill.yaml +14 -0
- package/package.json +3 -2
package/bin/lib/git-snapshot.js
CHANGED
|
@@ -61,7 +61,7 @@ function getStagedFiles(cwd = process.cwd()) {
|
|
|
61
61
|
try {
|
|
62
62
|
const output = execFileSync(
|
|
63
63
|
'git',
|
|
64
|
-
['diff', '--cached', '-z', '--name-status'],
|
|
64
|
+
['-c', 'core.quotepath=false', 'diff', '--cached', '-z', '--name-status', '--no-ext-diff'],
|
|
65
65
|
{ cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
66
66
|
);
|
|
67
67
|
|
|
@@ -104,78 +104,105 @@ function getStagedFiles(cwd = process.cwd()) {
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Reads the content of a file directly from the Git staged index.
|
|
107
|
+
* Fails closed with an error if the staged blob cannot be read.
|
|
107
108
|
*
|
|
108
109
|
* @param {string} relativePath - Path relative to repo root
|
|
109
110
|
* @param {string} [cwd=process.cwd()] - Working directory
|
|
110
|
-
* @returns {Buffer
|
|
111
|
+
* @returns {Buffer} Staged file buffer
|
|
111
112
|
*/
|
|
112
113
|
function getStagedBlob(relativePath, cwd = process.cwd()) {
|
|
114
|
+
const posixPath = relativePath.replace(/\\/g, '/');
|
|
113
115
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
const buf = execFileSync(
|
|
117
|
+
'git',
|
|
118
|
+
['--literal-pathspecs', 'show', `:${posixPath}`],
|
|
119
|
+
{
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
122
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
123
|
+
}
|
|
124
|
+
);
|
|
120
125
|
return buf;
|
|
121
126
|
} catch (err) {
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
const errMsg = err.stderr ? err.stderr.toString('utf8').trim() : err.message;
|
|
128
|
+
throw new Error(`Failed to read staged blob for "${posixPath}": ${errMsg}`);
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
/**
|
|
128
133
|
* Extracts added lines from git diff --cached -U0 for each staged file.
|
|
129
134
|
* Returns only lines that were added (+), excluding diff headers and deleted lines (-).
|
|
135
|
+
* Uses --literal-pathspecs, --no-ext-diff, and --no-textconv for strict path isolation.
|
|
130
136
|
*
|
|
131
137
|
* @param {string} [cwd=process.cwd()] - Working directory
|
|
138
|
+
* @param {string[]} [files=null] - Optional list of staged file paths. If omitted, discovered via getStagedFiles.
|
|
132
139
|
* @returns {Map<string, Array<{ line: number, content: string }>>} Added lines by file
|
|
133
140
|
*/
|
|
134
|
-
function getStagedAddedLines(cwd = process.cwd()) {
|
|
141
|
+
function getStagedAddedLines(cwd = process.cwd(), files = null) {
|
|
135
142
|
const addedLinesByFile = new Map();
|
|
136
143
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
if (!diffOutput) return addedLinesByFile;
|
|
146
|
-
|
|
147
|
-
const lines = diffOutput.split(/\r?\n/);
|
|
148
|
-
let currentFile = null;
|
|
149
|
-
let currentLineNum = 0;
|
|
144
|
+
let targetFiles = files;
|
|
145
|
+
if (!targetFiles) {
|
|
146
|
+
const staged = getStagedFiles(cwd);
|
|
147
|
+
targetFiles = staged.filter(e => e.status !== 'D').map(e => e.path);
|
|
148
|
+
}
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
150
|
+
for (const filePath of targetFiles) {
|
|
151
|
+
try {
|
|
152
|
+
const posixPath = filePath.replace(/\\/g, '/');
|
|
153
|
+
const diffOutput = execFileSync(
|
|
154
|
+
'git',
|
|
155
|
+
[
|
|
156
|
+
'--literal-pathspecs',
|
|
157
|
+
'-c', 'core.quotepath=false',
|
|
158
|
+
'diff',
|
|
159
|
+
'--cached',
|
|
160
|
+
'-U0',
|
|
161
|
+
'--no-ext-diff',
|
|
162
|
+
'--no-textconv',
|
|
163
|
+
'--',
|
|
164
|
+
posixPath,
|
|
165
|
+
],
|
|
166
|
+
{
|
|
167
|
+
cwd,
|
|
168
|
+
encoding: 'utf8',
|
|
169
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
170
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
162
171
|
}
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!diffOutput) continue;
|
|
175
|
+
|
|
176
|
+
const lines = diffOutput.split(/\r?\n/);
|
|
177
|
+
let currentLineNum = 0;
|
|
178
|
+
const fileAddedLines = [];
|
|
179
|
+
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (line.startsWith('@@ ')) {
|
|
182
|
+
// Parse @@ -a,b +c,d @@ to get current target line number
|
|
183
|
+
const match = line.match(/\+([0-9]+)(?:,([0-9]+))?/);
|
|
184
|
+
if (match) {
|
|
185
|
+
currentLineNum = parseInt(match[1], 10);
|
|
186
|
+
}
|
|
187
|
+
} else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
165
188
|
const content = line.slice(1);
|
|
166
|
-
|
|
189
|
+
fileAddedLines.push({
|
|
167
190
|
line: currentLineNum,
|
|
168
191
|
content,
|
|
169
192
|
});
|
|
193
|
+
currentLineNum++;
|
|
170
194
|
}
|
|
171
|
-
currentLineNum++;
|
|
172
195
|
}
|
|
173
|
-
}
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
197
|
+
if (fileAddedLines.length > 0) {
|
|
198
|
+
addedLinesByFile.set(filePath, fileAddedLines);
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
throw new Error(`Failed to extract staged diff for "${filePath}": ${err.message}`);
|
|
202
|
+
}
|
|
178
203
|
}
|
|
204
|
+
|
|
205
|
+
return addedLinesByFile;
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
module.exports = {
|
package/bin/lib/scan.js
CHANGED
|
@@ -127,18 +127,25 @@ const PLACEHOLDER_PATTERNS = [
|
|
|
127
127
|
},
|
|
128
128
|
];
|
|
129
129
|
|
|
130
|
-
// Exempt path
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
// Exempt path segments and exact files (e.g. test fixtures, test files)
|
|
131
|
+
const EXEMPT_SEGMENTS = new Set([
|
|
132
|
+
'tests',
|
|
133
|
+
'.git',
|
|
134
134
|
'node_modules',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
const EXEMPT_EXACT_PATHS = new Set([
|
|
138
|
+
'scripts/check-secrets.js',
|
|
139
|
+
'bin/lib/scan.js',
|
|
140
|
+
]);
|
|
138
141
|
|
|
139
142
|
function isExempt(filePath) {
|
|
140
|
-
const norm =
|
|
141
|
-
|
|
143
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
144
|
+
if (EXEMPT_EXACT_PATHS.has(norm)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const segments = norm.split('/');
|
|
148
|
+
return segments.some(seg => EXEMPT_SEGMENTS.has(seg));
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
function redact(str) {
|
|
@@ -147,7 +154,7 @@ function redact(str) {
|
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
/**
|
|
150
|
-
* Checks if a relative path matches a glob pattern or prefix.
|
|
157
|
+
* Checks if a relative path matches a glob pattern or prefix with strict segment boundaries.
|
|
151
158
|
*/
|
|
152
159
|
function matchesScope(filePath, pattern) {
|
|
153
160
|
const normFile = filePath.replace(/\\/g, '/').toLowerCase();
|
|
@@ -155,15 +162,16 @@ function matchesScope(filePath, pattern) {
|
|
|
155
162
|
|
|
156
163
|
if (normPattern.endsWith('/**')) {
|
|
157
164
|
const prefix = normPattern.slice(0, -3);
|
|
158
|
-
return normFile.startsWith(prefix);
|
|
165
|
+
return normFile === prefix || normFile.startsWith(prefix + '/');
|
|
159
166
|
}
|
|
160
167
|
if (normPattern.endsWith('/*')) {
|
|
161
168
|
const prefix = normPattern.slice(0, -2);
|
|
169
|
+
if (!normFile.startsWith(prefix + '/')) return false;
|
|
162
170
|
const rest = normFile.slice(prefix.length + 1);
|
|
163
|
-
return
|
|
171
|
+
return !rest.includes('/');
|
|
164
172
|
}
|
|
165
173
|
if (normPattern.includes('*')) {
|
|
166
|
-
const reg = new RegExp('^' + normPattern.replace(/\./g, '\\.').replace(/\*/g, '
|
|
174
|
+
const reg = new RegExp('^' + normPattern.replace(/\./g, '\\.').replace(/\*/g, '[^/]*') + '$');
|
|
167
175
|
return reg.test(normFile);
|
|
168
176
|
}
|
|
169
177
|
return normFile === normPattern || normFile.startsWith(normPattern + '/');
|
|
@@ -191,17 +199,26 @@ function runScan(options = {}) {
|
|
|
191
199
|
|
|
192
200
|
const checkSecrets = options.secrets !== false;
|
|
193
201
|
const checkPlaceholders = Boolean(options.placeholders);
|
|
194
|
-
const scopeFile = options.scope || null;
|
|
195
202
|
const enforce = Boolean(options.enforce);
|
|
196
203
|
|
|
204
|
+
let targetScopeFile = options.scope || null;
|
|
205
|
+
let scopeStatus = 'not_configured';
|
|
206
|
+
|
|
207
|
+
if (!targetScopeFile) {
|
|
208
|
+
const defaultScope = path.join(gitRoot, '.agents', 'task-scope.json');
|
|
209
|
+
if (fs.existsSync(defaultScope)) {
|
|
210
|
+
targetScopeFile = '.agents/task-scope.json';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
197
214
|
let allowedScopePatterns = null;
|
|
198
|
-
if (
|
|
199
|
-
const resolvedScopePath = path.resolve(gitRoot,
|
|
215
|
+
if (targetScopeFile) {
|
|
216
|
+
const resolvedScopePath = path.resolve(gitRoot, targetScopeFile);
|
|
200
217
|
if (!fs.existsSync(resolvedScopePath)) {
|
|
201
218
|
return {
|
|
202
219
|
ok: false,
|
|
203
220
|
code: 2,
|
|
204
|
-
error: `Scope file not found: ${
|
|
221
|
+
error: `Scope file not found: ${targetScopeFile}`,
|
|
205
222
|
findings: [],
|
|
206
223
|
stats: { filesScanned: 0, violations: 0 },
|
|
207
224
|
};
|
|
@@ -218,6 +235,7 @@ function runScan(options = {}) {
|
|
|
218
235
|
}
|
|
219
236
|
}
|
|
220
237
|
allowedScopePatterns = list;
|
|
238
|
+
scopeStatus = `active (${targetScopeFile})`;
|
|
221
239
|
} catch (err) {
|
|
222
240
|
return {
|
|
223
241
|
ok: false,
|
|
@@ -243,17 +261,28 @@ function runScan(options = {}) {
|
|
|
243
261
|
}
|
|
244
262
|
|
|
245
263
|
const findings = [];
|
|
246
|
-
|
|
264
|
+
let addedLinesMap = new Map();
|
|
265
|
+
if (checkPlaceholders) {
|
|
266
|
+
try {
|
|
267
|
+
const activePaths = stagedEntries.filter(e => e.status !== 'D').map(e => e.path);
|
|
268
|
+
addedLinesMap = getStagedAddedLines(gitRoot, activePaths);
|
|
269
|
+
} catch (err) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
code: 2,
|
|
273
|
+
error: `Failed to inspect staged added lines: ${err.message}`,
|
|
274
|
+
findings: [],
|
|
275
|
+
stats: { filesScanned: stagedEntries.length, violations: 0 },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
247
279
|
|
|
248
280
|
for (const entry of stagedEntries) {
|
|
249
|
-
// Skip deleted files from content and blocked filename checks
|
|
250
|
-
if (entry.status === 'D') continue;
|
|
251
|
-
|
|
252
281
|
const relPath = entry.path;
|
|
253
282
|
const baseName = path.basename(relPath).toLowerCase();
|
|
254
283
|
const extName = path.extname(relPath).toLowerCase();
|
|
255
284
|
|
|
256
|
-
// 1. Scope Containment Check
|
|
285
|
+
// 1. Scope Containment Check (checked for ALL staged actions: added, modified, deleted, renamed)
|
|
257
286
|
if (allowedScopePatterns) {
|
|
258
287
|
const inScope = allowedScopePatterns.some(p => matchesScope(relPath, p));
|
|
259
288
|
if (!inScope) {
|
|
@@ -265,8 +294,23 @@ function runScan(options = {}) {
|
|
|
265
294
|
details: `Staged file "${relPath}" is outside allowed task scope`,
|
|
266
295
|
});
|
|
267
296
|
}
|
|
297
|
+
if (entry.oldPath) {
|
|
298
|
+
const oldInScope = allowedScopePatterns.some(p => matchesScope(entry.oldPath, p));
|
|
299
|
+
if (!oldInScope) {
|
|
300
|
+
findings.push({
|
|
301
|
+
ruleId: 'SCOPE-001',
|
|
302
|
+
file: entry.oldPath,
|
|
303
|
+
type: 'Scope Violation',
|
|
304
|
+
severity: 'error',
|
|
305
|
+
details: `Source file of rename "${entry.oldPath}" is outside allowed task scope`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
268
309
|
}
|
|
269
310
|
|
|
311
|
+
// Skip deleted files from content and blocked filename checks
|
|
312
|
+
if (entry.status === 'D') continue;
|
|
313
|
+
|
|
270
314
|
// 2. Blocked Exact Names Check
|
|
271
315
|
if (checkSecrets && BLOCKED_EXACT_NAMES.has(baseName)) {
|
|
272
316
|
findings.push({
|
|
@@ -294,12 +338,43 @@ function runScan(options = {}) {
|
|
|
294
338
|
// Skip content scan for exempt paths
|
|
295
339
|
if (isExempt(relPath)) continue;
|
|
296
340
|
|
|
297
|
-
// Read blob from index
|
|
298
|
-
|
|
299
|
-
|
|
341
|
+
// Read blob from index (fails closed with code 2 on read errors in any mode)
|
|
342
|
+
let blob;
|
|
343
|
+
try {
|
|
344
|
+
blob = getStagedBlob(relPath, gitRoot);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
code: 2,
|
|
349
|
+
error: `Failed to read staged blob for "${relPath}": ${err.message}`,
|
|
350
|
+
findings,
|
|
351
|
+
stats: { filesScanned: stagedEntries.length, violations: findings.length },
|
|
352
|
+
};
|
|
353
|
+
}
|
|
300
354
|
|
|
301
|
-
|
|
302
|
-
|
|
355
|
+
if (!blob) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
code: 2,
|
|
359
|
+
error: `Failed to read staged blob for "${relPath}": file unreadable in git index`,
|
|
360
|
+
findings,
|
|
361
|
+
stats: { filesScanned: stagedEntries.length, violations: findings.length },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Large files (> 2MB): fail-closed in enforce mode
|
|
366
|
+
if (blob.length > 2 * 1024 * 1024) {
|
|
367
|
+
if (enforce) {
|
|
368
|
+
findings.push({
|
|
369
|
+
ruleId: 'SCAN-SKIP-001',
|
|
370
|
+
file: relPath,
|
|
371
|
+
type: 'Skipped Large File',
|
|
372
|
+
severity: 'error',
|
|
373
|
+
details: `Staged file "${relPath}" exceeds 2MB limit (${(blob.length / (1024 * 1024)).toFixed(2)}MB) and cannot be verified for secrets in enforce mode.`,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
303
378
|
|
|
304
379
|
const content = blob.toString('utf8');
|
|
305
380
|
const lines = content.split(/\r?\n/);
|
|
@@ -352,13 +427,19 @@ function runScan(options = {}) {
|
|
|
352
427
|
|
|
353
428
|
const hasViolations = findings.length > 0;
|
|
354
429
|
// If enforce is true, violations return code 1. If enforce is false, warnings return code 0.
|
|
430
|
+
// Incomplete/failed inspection returns code 2 in all modes.
|
|
355
431
|
const exitCode = hasViolations && enforce ? 1 : 0;
|
|
356
432
|
|
|
357
433
|
return {
|
|
358
434
|
ok: exitCode === 0,
|
|
359
435
|
code: exitCode,
|
|
436
|
+
message: exitCode === 0 ? 'Verification completed cleanly, no blocking violations' : 'Blocking violations detected in staged index',
|
|
360
437
|
gitRoot,
|
|
361
438
|
enforce,
|
|
439
|
+
scope: {
|
|
440
|
+
file: targetScopeFile,
|
|
441
|
+
status: scopeStatus,
|
|
442
|
+
},
|
|
362
443
|
findings,
|
|
363
444
|
stats: {
|
|
364
445
|
stagedFilesCount: stagedEntries.length,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# adapters Examples - Anti-patterns vs ContextOS Standard
|
|
2
|
+
|
|
3
|
+
## Example 1: Multi-Agent Configuration
|
|
4
|
+
|
|
5
|
+
### Anti-pattern: Manually Syncing 6 Different Rule Files
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Editing .cursorrules, then forgetting to update CLAUDE.md, then editing copilot-instructions.md.
|
|
9
|
+
Rules diverge across teammates using different IDEs.
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Best practice: ContextOS Standard (Single Source of Truth)
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Edit skills once in .agents/core/skills/
|
|
16
|
+
# Compile to all agents with one command:
|
|
17
|
+
node .agents/ctx.js export all
|
|
18
|
+
# Automatically updates .cursorrules, CLAUDE.md, copilot-instructions.md, .aider, .zed
|
|
19
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: adapters
|
|
3
|
+
description: >
|
|
4
|
+
Generates agent-specific configuration files from the unified ContextOS format.
|
|
5
|
+
One internal system, multiple output formats for Claude, Gemini, Copilot,
|
|
6
|
+
Cursor, Aider, Continue, OpenHands, and others.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# agent-adapters
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Unified cross-agent configuration engine. Translates single ContextOS source rules into optimized native formats for Claude Code (CLAUDE.md), Gemini (.agents/skills), Cursor (.cursorrules, .cursor/rules/*.mdc), GitHub Copilot, Zed, Aider, and Continue.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
Activate when configuring, synchronizing, or exporting agent rules and skills across multiple IDEs and AI programming assistants.
|
|
18
|
+
|
|
19
|
+
## Rules & Patterns
|
|
20
|
+
|
|
21
|
+
ContextOS is agent-agnostic. This skill generates the right config format for any AI agent.
|
|
22
|
+
|
|
23
|
+
## Supported Agents
|
|
24
|
+
|
|
25
|
+
| Agent | Config Target | Format |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| **Gemini / Antigravity** | `.agents/generated/gemini/skills/` | Native modular skills |
|
|
28
|
+
| **Claude Code** | `.agents/generated/claude/skills/` | Aggregated markdown skills |
|
|
29
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md` | Single instructions file with on-demand skill references |
|
|
30
|
+
| **Cursor** | `.cursor/rules/*.mdc` + `.cursorrules` | MDC modular glob rules + fallback |
|
|
31
|
+
| **Aider** | `.aider.conf.yml` + `CONVENTIONS.md` | YAML config + conventions markdown |
|
|
32
|
+
| **Zed IDE** | `.zed/rules.md` + `.zed/prompts/*.md` | Native rules and prompt templates |
|
|
33
|
+
|
|
34
|
+
## Generation Commands
|
|
35
|
+
|
|
36
|
+
- `contextos export <agent>` (or `node .agents/ctx.js export <agent>`) - Generate config for a specific agent (`gemini`, `claude`, `cursor`, `copilot`, `aider`, `zed`).
|
|
37
|
+
- `contextos export all` - Generate configs for all supported agents in a single transaction.
|
|
38
|
+
|
|
39
|
+
## Adapter Logic
|
|
40
|
+
|
|
41
|
+
### For Claude Code (`CLAUDE.md`)
|
|
42
|
+
|
|
43
|
+
Compile into a single markdown file:
|
|
44
|
+
|
|
45
|
+
1. Project overview from `docs/PRD.md` (summary only)
|
|
46
|
+
2. Architecture summary from `docs/ARCHITECTURE.md`
|
|
47
|
+
3. Coding rules from loaded skills
|
|
48
|
+
4. Active Decision Records
|
|
49
|
+
5. Current tasks from `docs/TASKS.md`
|
|
50
|
+
|
|
51
|
+
### For Gemini (`.agents/AGENTS.md`)
|
|
52
|
+
|
|
53
|
+
Already native format. Just ensure:
|
|
54
|
+
|
|
55
|
+
1. `AGENTS.md` references the skill directory
|
|
56
|
+
2. Skills have proper SKILL.md with frontmatter
|
|
57
|
+
3. Context Manager rules are in AGENTS.md
|
|
58
|
+
|
|
59
|
+
### For Cursor (`.cursorrules`)
|
|
60
|
+
|
|
61
|
+
Compile into a flat text file:
|
|
62
|
+
|
|
63
|
+
1. Project context (condensed)
|
|
64
|
+
2. Coding style rules
|
|
65
|
+
3. Framework-specific instructions
|
|
66
|
+
4. Anti-patterns to avoid
|
|
67
|
+
|
|
68
|
+
### For Aider (`.aider.conf.yml`)
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
read:
|
|
72
|
+
- docs/ARCHITECTURE.md
|
|
73
|
+
- docs/API.md
|
|
74
|
+
- docs/PROJECT_GRAPH.md
|
|
75
|
+
conventions:
|
|
76
|
+
- {{coding rules from skills}}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Sync Rules
|
|
80
|
+
|
|
81
|
+
- Adapters read from the canonical ContextOS documents
|
|
82
|
+
- Never edit adapter output files directly
|
|
83
|
+
- Re-run `contextos export all` after any skill or rule change
|
|
84
|
+
- Each adapter file includes a header: `# Generated by ContextOS - do not edit directly`
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
## Code Examples
|
|
88
|
+
|
|
89
|
+
See `EXAMPLES.md` for detailed code examples.
|
|
90
|
+
|
|
91
|
+
## Validation Checklist
|
|
92
|
+
|
|
93
|
+
What to verify during the review phase before completing the task.
|
|
94
|
+
|
|
95
|
+
## Common Mistakes
|
|
96
|
+
|
|
97
|
+
Anti-patterns and things to explicitly avoid. See `TROUBLESHOOTING.md`.
|
|
98
|
+
|
|
99
|
+
## Integration Notes
|
|
100
|
+
|
|
101
|
+
How this skill interacts with other skills.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# adapters Troubleshooting & Common Mistakes
|
|
2
|
+
|
|
3
|
+
## 1. Overwriting Custom Configs
|
|
4
|
+
|
|
5
|
+
- **Symptom**: Custom non-ContextOS rules wiped out during export.
|
|
6
|
+
- **Root Cause**: Running export with force flags over unmanaged files.
|
|
7
|
+
- **Fix**: Keep custom project overrides in dedicated config files or use plugin skills.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
schemaVersion: 2
|
|
2
|
+
name: adapters
|
|
3
|
+
category: engineering
|
|
4
|
+
type: compiler
|
|
5
|
+
description: >
|
|
6
|
+
Generates agent-specific configuration files from unified ContextOS format.
|
|
7
|
+
Produces configs for Claude, Gemini, Copilot, Cursor, Aider, Continue, and Zed.
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
resources:
|
|
10
|
+
- EXAMPLES.md
|
|
11
|
+
- SKILL.md
|
|
12
|
+
- TROUBLESHOOTING.md
|
|
13
|
+
- VALIDATION.json
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# API Design - Examples & Best Practices
|
|
2
|
+
|
|
3
|
+
## Example 1: Contract-First Interface with Zod & TypeScript
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
// 1. Unified Contract Definition
|
|
9
|
+
export const CreateTaskSchema = z.object({
|
|
10
|
+
title: z.string().min(1, 'Title is required').max(120, 'Title exceeds max length'),
|
|
11
|
+
description: z.string().max(1000).optional(),
|
|
12
|
+
priority: z.enum(['low', 'medium', 'high']).default('medium')
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type CreateTaskInput = z.infer<typeof CreateTaskSchema>;
|
|
16
|
+
|
|
17
|
+
export interface TaskDTO {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string | null;
|
|
21
|
+
priority: 'low' | 'medium' | 'high';
|
|
22
|
+
createdAt: string; // ISO 8601 string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface APIErrorResponse {
|
|
26
|
+
error: {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
details?: Array<{ field: string; message: string }>;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 2. Safe Edge Controller
|
|
34
|
+
export async function handleCreateTask(
|
|
35
|
+
body: unknown,
|
|
36
|
+
createTaskService: (input: CreateTaskInput) => Promise<TaskDTO>
|
|
37
|
+
): Promise<{ status: number; data: TaskDTO | APIErrorResponse }> {
|
|
38
|
+
const result = CreateTaskSchema.safeParse(body);
|
|
39
|
+
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
return {
|
|
42
|
+
status: 422,
|
|
43
|
+
data: {
|
|
44
|
+
error: {
|
|
45
|
+
code: 'VALIDATION_FAILED',
|
|
46
|
+
message: 'Request payload failed schema validation',
|
|
47
|
+
details: result.error.errors.map(err => ({
|
|
48
|
+
field: err.path.join('.'),
|
|
49
|
+
message: err.message
|
|
50
|
+
}))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const task = await createTaskService(result.data);
|
|
57
|
+
return { status: 201, data: task };
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Example 2: Idempotent Mutation Handler
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import crypto from 'node:crypto';
|
|
67
|
+
|
|
68
|
+
interface IdempotencyStore {
|
|
69
|
+
get(key: string): Promise<{ status: number; body: unknown } | null>;
|
|
70
|
+
set(key: string, result: { status: number; body: unknown }, ttlSeconds: number): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function withIdempotency<T>(
|
|
74
|
+
key: string | undefined,
|
|
75
|
+
store: IdempotencyStore,
|
|
76
|
+
handler: () => Promise<{ status: number; body: T }>
|
|
77
|
+
): Promise<{ status: number; body: T }> {
|
|
78
|
+
if (!key) {
|
|
79
|
+
return handler();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cached = await store.get(key);
|
|
83
|
+
if (cached) {
|
|
84
|
+
return cached as { status: number; body: T };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const response = await handler();
|
|
88
|
+
await store.set(key, response, 86400); // 24-hour cache
|
|
89
|
+
return response;
|
|
90
|
+
}
|
|
91
|
+
```
|