contextos-agents 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/.agents/adapters/aider/export.js +2 -2
  2. package/.agents/adapters/claude/export.js +53 -2
  3. package/.agents/adapters/drift-detector.js +6 -3
  4. package/.agents/adapters/pure-compiler.js +18 -6
  5. package/.agents/ctx.js +13 -8
  6. package/.agents/plugins.js +347 -26
  7. package/.agents/profiles.js +32 -11
  8. package/README.md +38 -3
  9. package/bin/commands/hook.js +50 -12
  10. package/bin/commands/scan.js +10 -3
  11. package/bin/index.js +165 -53
  12. package/bin/lib/git-snapshot.js +70 -43
  13. package/bin/lib/scan.js +108 -27
  14. package/bin/lib/ui.js +140 -0
  15. package/catalog/skills/adapters/EXAMPLES.md +19 -0
  16. package/catalog/skills/adapters/SKILL.md +101 -0
  17. package/catalog/skills/adapters/TROUBLESHOOTING.md +7 -0
  18. package/catalog/skills/adapters/VALIDATION.json +12 -0
  19. package/catalog/skills/adapters/skill.yaml +13 -0
  20. package/catalog/skills/api-design/EXAMPLES.md +91 -0
  21. package/catalog/skills/api-design/SKILL.md +63 -0
  22. package/catalog/skills/api-design/TROUBLESHOOTING.md +54 -0
  23. package/catalog/skills/api-design/VALIDATION.json +11 -0
  24. package/catalog/skills/api-design/skill.yaml +14 -0
  25. package/catalog/skills/architecture-diagrams/SKILL.md +108 -0
  26. package/catalog/skills/architecture-diagrams/VALIDATION.json +12 -0
  27. package/catalog/skills/architecture-diagrams/skill.yaml +9 -0
  28. package/catalog/skills/brutalist-design/EXAMPLES.md +59 -0
  29. package/catalog/skills/brutalist-design/SKILL.md +150 -0
  30. package/catalog/skills/brutalist-design/VALIDATION.json +12 -0
  31. package/catalog/skills/brutalist-design/skill.yaml +10 -0
  32. package/catalog/skills/ci-cd/EXAMPLES.md +79 -0
  33. package/catalog/skills/ci-cd/SKILL.md +69 -0
  34. package/catalog/skills/ci-cd/TROUBLESHOOTING.md +52 -0
  35. package/catalog/skills/ci-cd/VALIDATION.json +11 -0
  36. package/catalog/skills/ci-cd/skill.yaml +13 -0
  37. package/catalog/skills/database/EXAMPLES.md +74 -0
  38. package/catalog/skills/database/SKILL.md +101 -0
  39. package/catalog/skills/database/TROUBLESHOOTING.md +18 -0
  40. package/catalog/skills/database/VALIDATION.json +11 -0
  41. package/catalog/skills/database/skill.yaml +14 -0
  42. package/catalog/skills/ddd/EXAMPLES.md +42 -0
  43. package/catalog/skills/ddd/SKILL.md +247 -0
  44. package/catalog/skills/ddd/TROUBLESHOOTING.md +19 -0
  45. package/catalog/skills/ddd/VALIDATION.json +12 -0
  46. package/catalog/skills/ddd/skill.yaml +14 -0
  47. package/catalog/skills/decisions/EXAMPLES.md +35 -0
  48. package/catalog/skills/decisions/SKILL.md +90 -0
  49. package/catalog/skills/decisions/TROUBLESHOOTING.md +13 -0
  50. package/catalog/skills/decisions/VALIDATION.json +12 -0
  51. package/catalog/skills/decisions/skill.yaml +13 -0
  52. package/catalog/skills/docker/EXAMPLES.md +56 -0
  53. package/catalog/skills/docker/SKILL.md +169 -0
  54. package/catalog/skills/docker/TROUBLESHOOTING.md +18 -0
  55. package/catalog/skills/docker/VALIDATION.json +11 -0
  56. package/catalog/skills/docker/skill.yaml +13 -0
  57. package/catalog/skills/fastapi/EXAMPLES.md +36 -0
  58. package/catalog/skills/fastapi/SKILL.md +171 -0
  59. package/catalog/skills/fastapi/TROUBLESHOOTING.md +19 -0
  60. package/catalog/skills/fastapi/VALIDATION.json +12 -0
  61. package/catalog/skills/fastapi/skill.yaml +14 -0
  62. package/catalog/skills/generators/EXAMPLES.md +19 -0
  63. package/catalog/skills/generators/SKILL.md +110 -0
  64. package/catalog/skills/generators/TROUBLESHOOTING.md +7 -0
  65. package/catalog/skills/generators/VALIDATION.json +12 -0
  66. package/catalog/skills/generators/skill.yaml +22 -0
  67. package/catalog/skills/generators/templates/API.md +77 -0
  68. package/catalog/skills/generators/templates/ARCHITECTURE.md +70 -0
  69. package/catalog/skills/generators/templates/DATABASE.md +42 -0
  70. package/catalog/skills/generators/templates/DECISION.md +46 -0
  71. package/catalog/skills/generators/templates/PRD.md +67 -0
  72. package/catalog/skills/generators/templates/PROJECT_GRAPH.md +56 -0
  73. package/catalog/skills/generators/templates/ROADMAP.md +51 -0
  74. package/catalog/skills/generators/templates/TASKS.md +43 -0
  75. package/catalog/skills/generators/templates/UI.md +73 -0
  76. package/catalog/skills/graphify/EXAMPLES.md +73 -0
  77. package/catalog/skills/graphify/SKILL.md +130 -0
  78. package/catalog/skills/graphify/VALIDATION.json +12 -0
  79. package/catalog/skills/graphify/skill.yaml +13 -0
  80. package/catalog/skills/impeccable-design/EXAMPLES.md +26 -0
  81. package/catalog/skills/impeccable-design/SKILL.md +201 -0
  82. package/catalog/skills/impeccable-design/TROUBLESHOOTING.md +19 -0
  83. package/catalog/skills/impeccable-design/VALIDATION.json +12 -0
  84. package/catalog/skills/impeccable-design/skill.yaml +15 -0
  85. package/catalog/skills/interview-me/SKILL.md +97 -0
  86. package/catalog/skills/interview-me/VALIDATION.json +12 -0
  87. package/catalog/skills/interview-me/skill.yaml +9 -0
  88. package/catalog/skills/microservices/EXAMPLES.md +38 -0
  89. package/catalog/skills/microservices/SKILL.md +164 -0
  90. package/catalog/skills/microservices/TROUBLESHOOTING.md +19 -0
  91. package/catalog/skills/microservices/VALIDATION.json +12 -0
  92. package/catalog/skills/microservices/skill.yaml +14 -0
  93. package/catalog/skills/minimalist-design/EXAMPLES.md +58 -0
  94. package/catalog/skills/minimalist-design/SKILL.md +113 -0
  95. package/catalog/skills/minimalist-design/VALIDATION.json +12 -0
  96. package/catalog/skills/minimalist-design/skill.yaml +10 -0
  97. package/catalog/skills/nestjs/EXAMPLES.md +40 -0
  98. package/catalog/skills/nestjs/SKILL.md +139 -0
  99. package/catalog/skills/nestjs/TROUBLESHOOTING.md +19 -0
  100. package/catalog/skills/nestjs/VALIDATION.json +12 -0
  101. package/catalog/skills/nestjs/skill.yaml +14 -0
  102. package/catalog/skills/nextjs/EXAMPLES.md +40 -0
  103. package/catalog/skills/nextjs/SKILL.md +163 -0
  104. package/catalog/skills/nextjs/TROUBLESHOOTING.md +19 -0
  105. package/catalog/skills/nextjs/VALIDATION.json +12 -0
  106. package/catalog/skills/nextjs/skill.yaml +14 -0
  107. package/catalog/skills/node/EXAMPLES.md +80 -0
  108. package/catalog/skills/node/SKILL.md +128 -0
  109. package/catalog/skills/node/TROUBLESHOOTING.md +19 -0
  110. package/catalog/skills/node/VALIDATION.json +12 -0
  111. package/catalog/skills/node/skill.yaml +14 -0
  112. package/catalog/skills/performance/EXAMPLES.md +30 -0
  113. package/catalog/skills/performance/SKILL.md +75 -0
  114. package/catalog/skills/performance/TROUBLESHOOTING.md +19 -0
  115. package/catalog/skills/performance/VALIDATION.json +12 -0
  116. package/catalog/skills/performance/skill.yaml +14 -0
  117. package/catalog/skills/react/EXAMPLES.md +79 -0
  118. package/catalog/skills/react/SKILL.md +132 -0
  119. package/catalog/skills/react/TROUBLESHOOTING.md +19 -0
  120. package/catalog/skills/react/VALIDATION.json +12 -0
  121. package/catalog/skills/react/skill.yaml +14 -0
  122. package/catalog/skills/react-best-practices/SKILL.md +158 -0
  123. package/catalog/skills/react-best-practices/VALIDATION.json +12 -0
  124. package/catalog/skills/react-best-practices/skill.yaml +13 -0
  125. package/catalog/skills/redesign-audit/SKILL.md +117 -0
  126. package/catalog/skills/redesign-audit/VALIDATION.json +12 -0
  127. package/catalog/skills/redesign-audit/skill.yaml +9 -0
  128. package/catalog/skills/security-audit/EXAMPLES.md +79 -0
  129. package/catalog/skills/security-audit/SKILL.md +91 -0
  130. package/catalog/skills/security-audit/TROUBLESHOOTING.md +46 -0
  131. package/catalog/skills/security-audit/VALIDATION.json +11 -0
  132. package/catalog/skills/security-audit/skill.yaml +14 -0
  133. package/catalog/skills/soft-design/EXAMPLES.md +51 -0
  134. package/catalog/skills/soft-design/SKILL.md +108 -0
  135. package/catalog/skills/soft-design/VALIDATION.json +12 -0
  136. package/catalog/skills/soft-design/skill.yaml +10 -0
  137. package/catalog/skills/state-management/EXAMPLES.md +56 -0
  138. package/catalog/skills/state-management/SKILL.md +168 -0
  139. package/catalog/skills/state-management/TROUBLESHOOTING.md +18 -0
  140. package/catalog/skills/state-management/VALIDATION.json +11 -0
  141. package/catalog/skills/state-management/skill.yaml +14 -0
  142. package/catalog/skills/subagent-orchestrator/SKILL.md +117 -0
  143. package/catalog/skills/subagent-orchestrator/VALIDATION.json +12 -0
  144. package/catalog/skills/subagent-orchestrator/skill.yaml +9 -0
  145. package/catalog/skills/system-design/EXAMPLES.md +75 -0
  146. package/catalog/skills/system-design/SKILL.md +419 -0
  147. package/catalog/skills/system-design/TROUBLESHOOTING.md +19 -0
  148. package/catalog/skills/system-design/VALIDATION.json +12 -0
  149. package/catalog/skills/system-design/skill.yaml +14 -0
  150. package/catalog/skills/terraform/EXAMPLES.md +74 -0
  151. package/catalog/skills/terraform/SKILL.md +55 -0
  152. package/catalog/skills/terraform/TROUBLESHOOTING.md +53 -0
  153. package/catalog/skills/terraform/VALIDATION.json +11 -0
  154. package/catalog/skills/terraform/skill.yaml +14 -0
  155. package/catalog/skills/testing/EXAMPLES.md +122 -0
  156. package/catalog/skills/testing/SKILL.md +70 -0
  157. package/catalog/skills/testing/TROUBLESHOOTING.md +18 -0
  158. package/catalog/skills/testing/VALIDATION.json +11 -0
  159. package/catalog/skills/testing/skill.yaml +14 -0
  160. package/catalog/skills/typescript/EXAMPLES.md +64 -0
  161. package/catalog/skills/typescript/SKILL.md +112 -0
  162. package/catalog/skills/typescript/TROUBLESHOOTING.md +19 -0
  163. package/catalog/skills/typescript/VALIDATION.json +12 -0
  164. package/catalog/skills/typescript/skill.yaml +14 -0
  165. package/catalog/skills/ui-design/EXAMPLES.md +21 -0
  166. package/catalog/skills/ui-design/SKILL.md +124 -0
  167. package/catalog/skills/ui-design/TROUBLESHOOTING.md +19 -0
  168. package/catalog/skills/ui-design/VALIDATION.json +12 -0
  169. package/catalog/skills/ui-design/skill.yaml +16 -0
  170. package/catalog/skills/ui-ux-pro/EXAMPLES.md +62 -0
  171. package/catalog/skills/ui-ux-pro/SKILL.md +418 -0
  172. package/catalog/skills/ui-ux-pro/TROUBLESHOOTING.md +19 -0
  173. package/catalog/skills/ui-ux-pro/VALIDATION.json +12 -0
  174. package/catalog/skills/ui-ux-pro/skill.yaml +14 -0
  175. package/catalog/skills/ux-design/EXAMPLES.md +36 -0
  176. package/catalog/skills/ux-design/SKILL.md +116 -0
  177. package/catalog/skills/ux-design/TROUBLESHOOTING.md +19 -0
  178. package/catalog/skills/ux-design/VALIDATION.json +12 -0
  179. package/catalog/skills/ux-design/skill.yaml +16 -0
  180. package/catalog/skills/vercel-optimize/SKILL.md +83 -0
  181. package/catalog/skills/vercel-optimize/VALIDATION.json +12 -0
  182. package/catalog/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
  183. package/catalog/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
  184. package/catalog/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
  185. package/catalog/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
  186. package/catalog/skills/vercel-optimize/skill.yaml +15 -0
  187. package/catalog/skills/web-accessibility/EXAMPLES.md +39 -0
  188. package/catalog/skills/web-accessibility/SKILL.md +151 -0
  189. package/catalog/skills/web-accessibility/TROUBLESHOOTING.md +19 -0
  190. package/catalog/skills/web-accessibility/VALIDATION.json +12 -0
  191. package/catalog/skills/web-accessibility/skill.yaml +14 -0
  192. package/package.json +3 -2
@@ -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|null} Staged file buffer or null if deleted/missing
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 posixPath = relativePath.replace(/\\/g, '/');
115
- const buf = execFileSync('git', ['show', `:${posixPath}`], {
116
- cwd,
117
- stdio: ['pipe', 'pipe', 'pipe'],
118
- maxBuffer: 10 * 1024 * 1024,
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
- // If file was deleted or unreadable in index, return null
123
- return null;
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
- try {
138
- const diffOutput = execFileSync('git', ['diff', '--cached', '-U0'], {
139
- cwd,
140
- encoding: 'utf8',
141
- stdio: ['pipe', 'pipe', 'pipe'],
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
- for (const line of lines) {
152
- if (line.startsWith('+++ b/')) {
153
- currentFile = line.slice(6).trim();
154
- if (!addedLinesByFile.has(currentFile)) {
155
- addedLinesByFile.set(currentFile, []);
156
- }
157
- } else if (line.startsWith('@@ ')) {
158
- // Parse @@ -a,b +c,d @@ to get current target line number
159
- const match = line.match(/\+([0-9]+)(?:,([0-9]+))?/);
160
- if (match) {
161
- currentLineNum = parseInt(match[1], 10);
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
- } else if (line.startsWith('+') && !line.startsWith('+++')) {
164
- if (currentFile) {
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
- addedLinesByFile.get(currentFile).push({
189
+ fileAddedLines.push({
167
190
  line: currentLineNum,
168
191
  content,
169
192
  });
193
+ currentLineNum++;
170
194
  }
171
- currentLineNum++;
172
195
  }
173
- }
174
196
 
175
- return addedLinesByFile;
176
- } catch {
177
- return addedLinesByFile;
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 substrings (e.g. test fixtures, test files, lockfiles)
131
- const EXEMPT_PATH_SUBSTRINGS = [
132
- path.join('tests', ''),
133
- path.join('.git', ''),
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
- 'check-secrets.js',
136
- 'scan.js',
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 = path.normalize(filePath);
141
- return EXEMPT_PATH_SUBSTRINGS.some(exempt => norm.includes(exempt));
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 normFile.startsWith(prefix) && !rest.includes('/');
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 (scopeFile) {
199
- const resolvedScopePath = path.resolve(gitRoot, scopeFile);
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: ${scopeFile}`,
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
- const addedLinesMap = checkPlaceholders ? getStagedAddedLines(gitRoot) : new Map();
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
- const blob = getStagedBlob(relPath, gitRoot);
299
- if (!blob) continue;
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
- // Skip large files (> 2MB)
302
- if (blob.length > 2 * 1024 * 1024) continue;
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,
package/bin/lib/ui.js ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * bin/lib/ui.js
3
+ * Terminal visual formatting & progress helpers for ContextOS.
4
+ * Zero external dependencies - uses standard Node.js ANSI and process streams.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const isTTY = Boolean(process.stdout && process.stdout.isTTY && !process.env.CI && process.env.NODE_ENV !== 'test');
10
+ const noColor = Boolean(process.env.NO_COLOR || (!isTTY && !process.env.FORCE_COLOR));
11
+
12
+ const c = {
13
+ cyan: (s) => noColor ? s : `\x1b[36m${s}\x1b[0m`,
14
+ green: (s) => noColor ? s : `\x1b[32m${s}\x1b[0m`,
15
+ yellow: (s) => noColor ? s : `\x1b[33m${s}\x1b[0m`,
16
+ red: (s) => noColor ? s : `\x1b[31m${s}\x1b[0m`,
17
+ blue: (s) => noColor ? s : `\x1b[34m${s}\x1b[0m`,
18
+ magenta: (s) => noColor ? s : `\x1b[35m${s}\x1b[0m`,
19
+ dim: (s) => noColor ? s : `\x1b[2m${s}\x1b[0m`,
20
+ bold: (s) => noColor ? s : `\x1b[1m${s}\x1b[0m`,
21
+ gray: (s) => noColor ? s : `\x1b[90m${s}\x1b[0m`,
22
+ };
23
+
24
+ /**
25
+ * Render a sleek ASCII brand banner with version.
26
+ */
27
+ function renderBanner(version) {
28
+ const title = `ContextOS v${version}`;
29
+ const subtitle = 'Deterministic Context & Policy Engine for Coding Agents';
30
+ const width = Math.max(title.length, subtitle.length) + 6;
31
+ const topBorder = ` ╭${'─'.repeat(width)}╮`;
32
+ const bottomBorder = ` ╰${'─'.repeat(width)}╯`;
33
+
34
+ console.log('\n' + c.cyan(topBorder));
35
+ console.log(` ${c.cyan('│')} ${c.bold(c.cyan('ContextOS'))} ${c.dim('v' + version)}${' '.repeat(width - title.length - 3)}${c.cyan('│')}`);
36
+ console.log(` ${c.cyan('│')} ${c.gray(subtitle)}${' '.repeat(width - subtitle.length - 3)}${c.cyan('│')}`);
37
+ console.log(c.cyan(bottomBorder) + '\n');
38
+ }
39
+
40
+ /**
41
+ * Format a step badge: e.g. [1/4]
42
+ */
43
+ function formatStepBadge(step, total) {
44
+ return c.cyan(`[${step}/${total}]`);
45
+ }
46
+
47
+ /**
48
+ * Render an installation step with title and detailed item.
49
+ */
50
+ function renderStep(step, total, title, detail = null, isSuccess = true) {
51
+ const badge = formatStepBadge(step, total);
52
+ console.log(` ${badge} ${c.bold(title)}`);
53
+ if (detail) {
54
+ const symbol = isSuccess ? c.green('✓') : c.yellow('•');
55
+ if (Array.isArray(detail)) {
56
+ if (detail.length > 0) {
57
+ console.log(` ${symbol} ${detail[0]}`);
58
+ for (let i = 1; i < detail.length; i++) {
59
+ console.log(` ${c.dim(detail[i])}`);
60
+ }
61
+ }
62
+ } else {
63
+ console.log(` ${symbol} ${detail}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Render an interactive or batch progress bar for skills/files.
70
+ */
71
+ function renderProgressBar(current, total, label = '', width = 26) {
72
+ const percent = total > 0 ? Math.min(100, Math.round((current / total) * 100)) : 100;
73
+ const filledCount = total > 0 ? Math.min(width, Math.round((current / total) * width)) : width;
74
+ const emptyCount = Math.max(0, width - filledCount);
75
+
76
+ const filledBar = '█'.repeat(filledCount);
77
+ const emptyBar = '░'.repeat(emptyCount);
78
+ const barStr = `${c.cyan(filledBar)}${c.dim(emptyBar)}`;
79
+ const progressText = `[${barStr}] ${String(percent).padStart(3)}% (${current}/${total})${label ? ' ' + c.gray(label) : ''}`;
80
+
81
+ if (isTTY) {
82
+ process.stdout.write(`\r ${progressText} `);
83
+ if (current >= total) {
84
+ process.stdout.write('\n');
85
+ }
86
+ } else {
87
+ // In CI or non-TTY test runners, log only at completion to avoid log bloat
88
+ if (current === total) {
89
+ console.log(` ${progressText}`);
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Render the final readiness card with next steps and commands.
96
+ */
97
+ function stripAnsi(str) {
98
+ return String(str).replace(/\x1b\[[0-9;]*m/g, '');
99
+ }
100
+
101
+ function renderCardLine(content, innerWidth = 62) {
102
+ const visible = stripAnsi(content);
103
+ const padding = Math.max(0, innerWidth - visible.length);
104
+ return ` ${c.green('│')} ${content}${' '.repeat(padding)}${c.green('│')}`;
105
+ }
106
+
107
+ /**
108
+ * Render the final readiness card with next steps and commands.
109
+ */
110
+ function renderSuccessCard(options = {}) {
111
+ const innerWidth = 62;
112
+ const topBorder = ` ╭${'─'.repeat(innerWidth + 2)}╮`;
113
+ const bottomBorder = ` ╰${'─'.repeat(innerWidth + 2)}╯`;
114
+
115
+ console.log('\n' + c.green(topBorder));
116
+ console.log(renderCardLine(c.bold(c.green('✓ Project ready for AI coding agents!')), innerWidth));
117
+ console.log(renderCardLine('', innerWidth));
118
+ console.log(renderCardLine(c.bold('Next steps:'), innerWidth));
119
+ console.log(renderCardLine(`1. Open project in your AI assistant (${c.cyan('Gemini')}, ${c.cyan('Cursor')}, ${c.cyan('Copilot')})`, innerWidth));
120
+ console.log(renderCardLine(` Rules are auto-loaded from ${c.cyan('.agents/AGENTS.md')}`, innerWidth));
121
+ console.log(renderCardLine('', innerWidth));
122
+ console.log(renderCardLine('2. Explore and install domain skills:', innerWidth));
123
+ console.log(renderCardLine(` ${c.cyan('contextos skill list --available')}`, innerWidth));
124
+ console.log(renderCardLine(` ${c.cyan('contextos skill add <name>')} (or ${c.cyan('--all')})`, innerWidth));
125
+ console.log(renderCardLine('', innerWidth));
126
+ console.log(renderCardLine('3. Inspect project health anytime:', innerWidth));
127
+ console.log(renderCardLine(` ${c.cyan('contextos doctor')}`, innerWidth));
128
+ console.log(c.green(bottomBorder) + '\n');
129
+ }
130
+
131
+ module.exports = {
132
+ isTTY,
133
+ noColor,
134
+ c,
135
+ renderBanner,
136
+ formatStepBadge,
137
+ renderStep,
138
+ renderProgressBar,
139
+ renderSuccessCard,
140
+ };
@@ -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,12 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "rules_followed": {
6
+ "type": "boolean"
7
+ }
8
+ },
9
+ "required": [
10
+ "rules_followed"
11
+ ]
12
+ }
@@ -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