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
|
@@ -90,8 +90,8 @@ function render(context) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
aiderConfContent = yamlProv + '\n' + YAML.stringify(parsed);
|
|
93
|
-
} catch {
|
|
94
|
-
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(`Failed to parse existing .aider.conf.yml: ${err.message}. Aborting to protect existing configuration from data loss.`);
|
|
95
95
|
}
|
|
96
96
|
} else {
|
|
97
97
|
aiderConfContent = [
|
|
@@ -9,13 +9,15 @@ const { collectSkillDirectories, readMeaningfulMarkdown } = require('../shared.j
|
|
|
9
9
|
const { registerAdapter, applyArtifacts } = require('../pure-compiler.js');
|
|
10
10
|
|
|
11
11
|
const GENERATOR_ID = 'claude@2';
|
|
12
|
+
const CLAUDE_START_MARKER = '<!-- CONTEXTOS:START -->';
|
|
13
|
+
const CLAUDE_END_MARKER = '<!-- CONTEXTOS:END -->';
|
|
12
14
|
|
|
13
15
|
function describe() {
|
|
14
16
|
return {
|
|
15
17
|
name: 'claude',
|
|
16
18
|
version: '2.0.0',
|
|
17
|
-
description: 'Compiles clean markdown skills for Claude Code CLI',
|
|
18
|
-
targetPattern: '.agents/generated/claude/skills/**/SKILL.md',
|
|
19
|
+
description: 'Compiles clean markdown skills for Claude Code CLI and manages CLAUDE.md entrypoint',
|
|
20
|
+
targetPattern: '{CLAUDE.md,.agents/generated/claude/skills/**/SKILL.md}',
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -53,6 +55,50 @@ function renderClaudeSkill(skillDir, context) {
|
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
function renderClaudeRootIndex(skills, context) {
|
|
59
|
+
const projectRoot = context?.projectRoot || '.';
|
|
60
|
+
const claudePath = path.join(projectRoot, 'CLAUDE.md');
|
|
61
|
+
let existingContent = '';
|
|
62
|
+
if (fs.existsSync(claudePath)) {
|
|
63
|
+
try {
|
|
64
|
+
existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const skillEntries = skills.map(skillDir => {
|
|
69
|
+
const name = path.basename(skillDir);
|
|
70
|
+
return `- [${name}](.agents/generated/claude/skills/${name}/SKILL.md)`;
|
|
71
|
+
}).join('\n');
|
|
72
|
+
|
|
73
|
+
const managedBlock = `${CLAUDE_START_MARKER}
|
|
74
|
+
<!-- Do not edit this section directly. Synchronized by ContextOS. -->
|
|
75
|
+
# ContextOS Agent Governance
|
|
76
|
+
|
|
77
|
+
The following skills are managed by ContextOS:
|
|
78
|
+
${skillEntries}
|
|
79
|
+
${CLAUDE_END_MARKER}`;
|
|
80
|
+
|
|
81
|
+
let finalContent;
|
|
82
|
+
if (existingContent.includes(CLAUDE_START_MARKER)) {
|
|
83
|
+
const regex = new RegExp(`${CLAUDE_START_MARKER}[\\s\\S]*?${CLAUDE_END_MARKER}`);
|
|
84
|
+
finalContent = existingContent.replace(regex, managedBlock);
|
|
85
|
+
} else if (existingContent.trim()) {
|
|
86
|
+
finalContent = `${existingContent.trimEnd()}\n\n${managedBlock}\n`;
|
|
87
|
+
} else {
|
|
88
|
+
finalContent = `${managedBlock}\n`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
path: 'CLAUDE.md',
|
|
93
|
+
content: finalContent.replace(/\r\n/g, '\n'),
|
|
94
|
+
mediaType: 'text/markdown',
|
|
95
|
+
kind: 'generated-adapter',
|
|
96
|
+
generator: GENERATOR_ID,
|
|
97
|
+
sourceSkillIds: skills.map(s => path.basename(s)),
|
|
98
|
+
inputsHash: context?.sourceGraphHash || 'none',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
56
102
|
function render(context) {
|
|
57
103
|
const skills = collectSkillDirectories(context?.profile, context?.projectRoot);
|
|
58
104
|
const artifacts = [];
|
|
@@ -62,6 +108,11 @@ function render(context) {
|
|
|
62
108
|
if (art) artifacts.push(art);
|
|
63
109
|
}
|
|
64
110
|
|
|
111
|
+
if (skills.length > 0) {
|
|
112
|
+
const rootIndex = renderClaudeRootIndex(skills, context);
|
|
113
|
+
if (rootIndex) artifacts.push(rootIndex);
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
return artifacts;
|
|
66
117
|
}
|
|
67
118
|
|
|
@@ -170,10 +170,13 @@ function detectDrift(projectRoot, adapters = 'all', options = {}) {
|
|
|
170
170
|
} else {
|
|
171
171
|
const diskContent = fs.readFileSync(fullPath, 'utf8');
|
|
172
172
|
const diskSemanticHash = computeSemanticHash(diskContent);
|
|
173
|
+
const lockRecord = lockfile?.managedFiles?.[art.path];
|
|
173
174
|
|
|
174
|
-
// Check if disk matches projected render
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
// Check if disk matches projected render and recorded lockfile
|
|
176
|
+
const matchesProjection = diskSemanticHash === projectedSemanticHash;
|
|
177
|
+
const matchesLock = lockRecord ? diskSemanticHash === lockRecord.semanticTextSha256 : matchesProjection;
|
|
178
|
+
|
|
179
|
+
if (!matchesProjection || !matchesLock) {
|
|
177
180
|
if (lockRecord && lockRecord.semanticTextSha256 !== diskSemanticHash) {
|
|
178
181
|
findings[DRIFT_STATES.MODIFIED_MANAGED_OUTPUT].push({
|
|
179
182
|
path: art.path,
|
|
@@ -86,11 +86,22 @@ function loadCompilerContext(projectRoot, options = {}) {
|
|
|
86
86
|
|
|
87
87
|
// 2. Profile v2
|
|
88
88
|
let profile = null;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
const profiles = require('../profiles.js');
|
|
90
|
+
if (options.profile) {
|
|
91
|
+
try {
|
|
92
|
+
profile = profiles.getProfile(options.profile, absRoot);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(`Failed to load profile '${options.profile}': ${err.message}`);
|
|
95
|
+
}
|
|
96
|
+
if (!profile) {
|
|
97
|
+
throw new Error(`Profile '${options.profile}' was specified but does not exist.`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
try {
|
|
101
|
+
profile = profiles.getActiveProfile(absRoot);
|
|
102
|
+
} catch {
|
|
103
|
+
profile = null;
|
|
104
|
+
}
|
|
94
105
|
}
|
|
95
106
|
|
|
96
107
|
// 3. Workspace Graph
|
|
@@ -172,9 +183,10 @@ function listAdapters() {
|
|
|
172
183
|
*/
|
|
173
184
|
function renderAdapters(projectRoot, adapterNames = 'all', options = {}) {
|
|
174
185
|
const context = loadCompilerContext(projectRoot, options);
|
|
175
|
-
const
|
|
186
|
+
const rawTargets = adapterNames === 'all'
|
|
176
187
|
? listAdapters()
|
|
177
188
|
: (Array.isArray(adapterNames) ? adapterNames : [adapterNames]);
|
|
189
|
+
const targets = rawTargets.flatMap(a => (typeof a === 'string' && a.includes(',') ? a.split(',').map(s => s.trim()).filter(Boolean) : a));
|
|
178
190
|
|
|
179
191
|
const allArtifacts = [];
|
|
180
192
|
const pathToArtifact = new Map();
|
package/.agents/ctx.js
CHANGED
|
@@ -253,8 +253,8 @@ if (command === 'export') {
|
|
|
253
253
|
: (args.find(a => a.startsWith('--scope=')) || '').split('=')[1] || null;
|
|
254
254
|
|
|
255
255
|
if (subcommand === 'list') {
|
|
256
|
-
const list = profiles.listProfiles();
|
|
257
|
-
const active = profiles.getActiveProfile();
|
|
256
|
+
const list = profiles.listProfiles(process.cwd());
|
|
257
|
+
const active = profiles.getActiveProfile(process.cwd());
|
|
258
258
|
if (asJson) {
|
|
259
259
|
console.log(JSON.stringify({ active, profiles: list }, null, 2));
|
|
260
260
|
} else {
|
|
@@ -285,7 +285,7 @@ if (command === 'export') {
|
|
|
285
285
|
console.error('[ERROR] Usage: node ctx.js profile show <name> [--json]');
|
|
286
286
|
process.exit(1);
|
|
287
287
|
}
|
|
288
|
-
const profile = profiles.getProfile(name);
|
|
288
|
+
const profile = profiles.getProfile(name, process.cwd());
|
|
289
289
|
if (!profile) {
|
|
290
290
|
console.error(`[ERROR] Profile '${name}' not found.`);
|
|
291
291
|
process.exit(1);
|
|
@@ -306,7 +306,7 @@ if (command === 'export') {
|
|
|
306
306
|
process.exit(1);
|
|
307
307
|
}
|
|
308
308
|
try {
|
|
309
|
-
const explanation = profiles.explainProfile(name);
|
|
309
|
+
const explanation = profiles.explainProfile(name, process.cwd());
|
|
310
310
|
if (asJson) {
|
|
311
311
|
console.log(JSON.stringify(explanation, null, 2));
|
|
312
312
|
} else {
|
package/.agents/plugins.js
CHANGED
|
@@ -26,7 +26,7 @@ const https = require('https');
|
|
|
26
26
|
const crypto = require('crypto');
|
|
27
27
|
const { execFileSync } = require('child_process');
|
|
28
28
|
const { ProjectMutationLock } = require('./filesystem/project-lock.js');
|
|
29
|
-
const JournaledTransaction = require('./filesystem/journaled-transaction.js');
|
|
29
|
+
const { JournaledTransaction } = require('./filesystem/journaled-transaction.js');
|
|
30
30
|
const {
|
|
31
31
|
calculateTreeDigest,
|
|
32
32
|
validateArchiveEntry,
|
|
@@ -284,11 +284,49 @@ function isValidNpmPackage(name) {
|
|
|
284
284
|
return /^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+$/.test(name);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
function parseRef(ref) {
|
|
287
|
+
function parseRef(ref, projectRoot = null) {
|
|
288
288
|
if (typeof ref !== 'string' || !ref.trim()) {
|
|
289
289
|
throw new Error('A plugin reference is required');
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// 1. Built-in package catalog skills take strict precedence for catalog names.
|
|
293
|
+
// Consumer project local files can never spoof or hijack package catalog skills.
|
|
294
|
+
const pkgCatalogPath = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills', ref);
|
|
295
|
+
if (fs.existsSync(pkgCatalogPath) && fs.existsSync(path.join(pkgCatalogPath, 'SKILL.md'))) {
|
|
296
|
+
return {
|
|
297
|
+
type: 'catalog',
|
|
298
|
+
name: ref,
|
|
299
|
+
path: pkgCatalogPath,
|
|
300
|
+
isPinned: true,
|
|
301
|
+
raw: ref,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 2. Explicit local references (must start with 'local:', './', '../', or 'file:')
|
|
306
|
+
if (ref.startsWith('local:') || ref.startsWith('./') || ref.startsWith('../') || ref.startsWith('file:')) {
|
|
307
|
+
const rawTarget = ref.startsWith('local:')
|
|
308
|
+
? ref.slice(6)
|
|
309
|
+
: (ref.startsWith('file:') ? ref.slice(5) : ref);
|
|
310
|
+
|
|
311
|
+
const baseRoot = projectRoot || process.cwd();
|
|
312
|
+
let localDir = path.resolve(baseRoot, rawTarget);
|
|
313
|
+
if (!fs.existsSync(localDir)) {
|
|
314
|
+
localDir = path.join(baseRoot, 'catalog', 'skills', rawTarget);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (fs.existsSync(localDir) && fs.existsSync(path.join(localDir, 'SKILL.md'))) {
|
|
318
|
+
const skillName = path.basename(localDir);
|
|
319
|
+
return {
|
|
320
|
+
type: 'catalog',
|
|
321
|
+
name: skillName,
|
|
322
|
+
path: localDir,
|
|
323
|
+
isPinned: true,
|
|
324
|
+
raw: ref,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`Local skill not found at: '${ref}'`);
|
|
328
|
+
}
|
|
329
|
+
|
|
292
330
|
// Scoped packages contain a slash but are npm packages, not GitHub refs.
|
|
293
331
|
if (ref.startsWith('@')) {
|
|
294
332
|
if (!isValidNpmPackage(ref)) {
|
|
@@ -529,6 +567,62 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
|
|
|
529
567
|
return sha256;
|
|
530
568
|
}
|
|
531
569
|
|
|
570
|
+
// ── Catalog installer ─────────────────────────────────────────────────────────
|
|
571
|
+
function installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
|
|
572
|
+
const sourceDir = descriptor.path;
|
|
573
|
+
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
574
|
+
const relTargetDir = toPosix(path.relative(ROOT, targetDir));
|
|
575
|
+
|
|
576
|
+
if (dryRun) {
|
|
577
|
+
console.log(c.dim(` [DRY-RUN] Would copy catalog skill from: ${sourceDir}`));
|
|
578
|
+
return 'sha256:0000000000000000000000000000000000000000000000000000000000000000';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function copyRecursive(src, dest, relDest) {
|
|
582
|
+
if (!fs.existsSync(dest)) {
|
|
583
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
584
|
+
}
|
|
585
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
586
|
+
for (const entry of entries) {
|
|
587
|
+
const srcPath = path.join(src, entry.name);
|
|
588
|
+
const destPath = path.join(dest, entry.name);
|
|
589
|
+
const relPath = toPosix(path.join(relDest, entry.name));
|
|
590
|
+
|
|
591
|
+
if (entry.isDirectory()) {
|
|
592
|
+
copyRecursive(srcPath, destPath, relPath);
|
|
593
|
+
} else {
|
|
594
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
595
|
+
if (tx) {
|
|
596
|
+
tx.stageWrite(relPath, content);
|
|
597
|
+
} else {
|
|
598
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
copyRecursive(sourceDir, targetDir, relTargetDir);
|
|
605
|
+
|
|
606
|
+
const primarySkillMd = path.join(sourceDir, 'SKILL.md');
|
|
607
|
+
const sha256 = fs.existsSync(primarySkillMd) ? crypto.createHash('sha256').update(fs.readFileSync(primarySkillMd)).digest('hex') : null;
|
|
608
|
+
|
|
609
|
+
const sourceMeta = {
|
|
610
|
+
source: 'catalog',
|
|
611
|
+
name: skillName,
|
|
612
|
+
path: toPosix(path.relative(ROOT, sourceDir)),
|
|
613
|
+
sha256,
|
|
614
|
+
installedAt: new Date().toISOString(),
|
|
615
|
+
};
|
|
616
|
+
if (tx) {
|
|
617
|
+
tx.stageWrite(`${relTargetDir}/.source`, JSON.stringify(sourceMeta, null, 2) + '\n');
|
|
618
|
+
} else {
|
|
619
|
+
fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify(sourceMeta, null, 2) + '\n');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
console.log(c.green(` ✓ Installed from built-in catalog: ${skillName}`));
|
|
623
|
+
return sha256;
|
|
624
|
+
}
|
|
625
|
+
|
|
532
626
|
// ── Derive skill name from ref ────────────────────────────────────────────────
|
|
533
627
|
function deriveSkillName(ref) {
|
|
534
628
|
// username/repo/path/to/my-skill → my-skill
|
|
@@ -570,7 +664,7 @@ async function add(ref, options = {}) {
|
|
|
570
664
|
}
|
|
571
665
|
|
|
572
666
|
// Supply-chain source pinning check (Wave 6)
|
|
573
|
-
if (!dryRun) {
|
|
667
|
+
if (!dryRun && descriptor.type !== 'catalog') {
|
|
574
668
|
const pinning = validatePluginPinning(
|
|
575
669
|
{
|
|
576
670
|
type: descriptor.type,
|
|
@@ -637,7 +731,9 @@ async function add(ref, options = {}) {
|
|
|
637
731
|
lockToken = projectLock.acquire({ command: 'skill:add' });
|
|
638
732
|
|
|
639
733
|
let installedSha256 = null;
|
|
640
|
-
if (descriptor.type === '
|
|
734
|
+
if (descriptor.type === 'catalog') {
|
|
735
|
+
installedSha256 = installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
736
|
+
} else if (descriptor.type === 'github') {
|
|
641
737
|
installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
642
738
|
} else {
|
|
643
739
|
installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
package/.agents/profiles.js
CHANGED
|
@@ -298,14 +298,26 @@ function validateProfile(profile, registry = null) {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
|
-
* Lists all available project profiles defined in core/profiles.
|
|
301
|
+
* Lists all available project profiles defined in core/profiles and optionally consumer project.
|
|
302
|
+
* Searches built-in profiles in packageRoot, and if projectRoot is supplied, searches
|
|
303
|
+
* consumer project .agents/profiles, .agents/core/profiles, and profiles directories.
|
|
302
304
|
*
|
|
305
|
+
* @param {string|null} [projectRoot=null] - Optional consumer project root
|
|
303
306
|
* @returns {ProfileConfig[]} Array of profile configurations
|
|
304
307
|
*/
|
|
305
|
-
function listProfiles() {
|
|
308
|
+
function listProfiles(projectRoot = null) {
|
|
306
309
|
const profiles = [];
|
|
307
310
|
const searchDirs = [PROFILES_DIR, path.join(AGENTS_DIR, 'catalog', 'presets')];
|
|
308
311
|
|
|
312
|
+
if (projectRoot) {
|
|
313
|
+
const absConsumer = path.resolve(projectRoot);
|
|
314
|
+
searchDirs.push(
|
|
315
|
+
path.join(absConsumer, '.agents', 'profiles'),
|
|
316
|
+
path.join(absConsumer, '.agents', 'core', 'profiles'),
|
|
317
|
+
path.join(absConsumer, 'profiles')
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
309
321
|
for (const dir of searchDirs) {
|
|
310
322
|
if (!fs.existsSync(dir)) continue;
|
|
311
323
|
const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
@@ -314,22 +326,31 @@ function listProfiles() {
|
|
|
314
326
|
const parsed = parseYamlProfile(content);
|
|
315
327
|
if (!parsed.id) parsed.id = path.basename(file, path.extname(file));
|
|
316
328
|
if (!parsed.name) parsed.name = parsed.id;
|
|
317
|
-
|
|
329
|
+
|
|
330
|
+
// Consumer profiles can override or extend built-in profiles; deduplicate by id
|
|
331
|
+
const normId = parsed.id.toLowerCase();
|
|
332
|
+
const existingIdx = profiles.findIndex(p => p.id.toLowerCase() === normId);
|
|
333
|
+
if (existingIdx !== -1) {
|
|
334
|
+
profiles[existingIdx] = parsed;
|
|
335
|
+
} else {
|
|
336
|
+
profiles.push(parsed);
|
|
337
|
+
}
|
|
318
338
|
}
|
|
319
339
|
}
|
|
320
340
|
return profiles;
|
|
321
341
|
}
|
|
322
342
|
|
|
323
343
|
/**
|
|
324
|
-
* Finds a profile by its slug ID or title.
|
|
344
|
+
* Finds a profile by its slug ID or title across package and consumer roots.
|
|
325
345
|
*
|
|
326
346
|
* @param {string} name - Profile identifier or name
|
|
347
|
+
* @param {string|null} [projectRoot=null] - Optional consumer project root
|
|
327
348
|
* @returns {ProfileConfig|null} Found profile or null
|
|
328
349
|
*/
|
|
329
|
-
function getProfile(name) {
|
|
350
|
+
function getProfile(name, projectRoot = null) {
|
|
330
351
|
if (!name) return null;
|
|
331
352
|
const clean = name.toLowerCase().trim();
|
|
332
|
-
const all = listProfiles();
|
|
353
|
+
const all = listProfiles(projectRoot);
|
|
333
354
|
return all.find(p => p.id.toLowerCase() === clean || p.name.toLowerCase() === clean) || null;
|
|
334
355
|
}
|
|
335
356
|
|
|
@@ -398,7 +419,7 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
|
|
|
398
419
|
}
|
|
399
420
|
|
|
400
421
|
if (matchedProfileName) {
|
|
401
|
-
const overrideProf = getProfile(matchedProfileName);
|
|
422
|
+
const overrideProf = getProfile(matchedProfileName, projectDir);
|
|
402
423
|
if (overrideProf) {
|
|
403
424
|
return {
|
|
404
425
|
...overrideProf,
|
|
@@ -424,9 +445,9 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
|
|
|
424
445
|
* @returns {Object} Updated profile lock data
|
|
425
446
|
*/
|
|
426
447
|
function applyProfile(profileName, projectDir = process.cwd(), options = {}) {
|
|
427
|
-
const profile = getProfile(profileName);
|
|
448
|
+
const profile = getProfile(profileName, projectDir);
|
|
428
449
|
if (!profile) {
|
|
429
|
-
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
|
|
450
|
+
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
|
|
430
451
|
}
|
|
431
452
|
|
|
432
453
|
validateProfile(profile);
|
|
@@ -563,9 +584,9 @@ function removeActiveProfile(projectDir = process.cwd(), options = {}) {
|
|
|
563
584
|
* @returns {Object} Explanation structure
|
|
564
585
|
*/
|
|
565
586
|
function explainProfile(profileName, projectDir = process.cwd()) {
|
|
566
|
-
const profile = getProfile(profileName);
|
|
587
|
+
const profile = getProfile(profileName, projectDir);
|
|
567
588
|
if (!profile) {
|
|
568
|
-
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
|
|
589
|
+
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
|
|
569
590
|
}
|
|
570
591
|
|
|
571
592
|
return {
|
package/README.md
CHANGED
|
@@ -172,9 +172,9 @@ jobs:
|
|
|
172
172
|
runs-on: ubuntu-latest
|
|
173
173
|
steps:
|
|
174
174
|
- uses: actions/checkout@v4
|
|
175
|
-
- uses: kok-o/contextos-agents/.github/actions/contextos-gate@v2.
|
|
175
|
+
- uses: kok-o/contextos-agents/.github/actions/contextos-gate@v2.1.1
|
|
176
176
|
with:
|
|
177
|
-
version: '2.
|
|
177
|
+
version: '2.1.1' # Pinned version of contextos-agents runner
|
|
178
178
|
adapters: 'all' # Adapters to verify (or specific: 'cursor', 'claude')
|
|
179
179
|
working-directory: '.' # Project root directory
|
|
180
180
|
```
|
package/bin/commands/hook.js
CHANGED
|
@@ -17,7 +17,28 @@ const HOOK_MARKER_START = '# BEGIN CONTEXTOS HOOK';
|
|
|
17
17
|
const HOOK_MARKER_END = '# END CONTEXTOS HOOK';
|
|
18
18
|
|
|
19
19
|
const HOOK_PAYLOAD = `${HOOK_MARKER_START}
|
|
20
|
-
|
|
20
|
+
CTX_SCOPE_ARG=""
|
|
21
|
+
if [ -f ".agents/task-scope.json" ]; then
|
|
22
|
+
CTX_SCOPE_ARG="--scope .agents/task-scope.json"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [ -f "./node_modules/.bin/contextos" ]; then
|
|
26
|
+
./node_modules/.bin/contextos scan --staged --enforce --placeholders $CTX_SCOPE_ARG
|
|
27
|
+
CTX_STATUS=$?
|
|
28
|
+
elif [ -f "./node_modules/.bin/contextos-agents" ]; then
|
|
29
|
+
./node_modules/.bin/contextos-agents scan --staged --enforce --placeholders $CTX_SCOPE_ARG
|
|
30
|
+
CTX_STATUS=$?
|
|
31
|
+
elif [ -f "./bin/index.js" ] && [ -f "./package.json" ]; then
|
|
32
|
+
node ./bin/index.js scan --staged --enforce --placeholders $CTX_SCOPE_ARG
|
|
33
|
+
CTX_STATUS=$?
|
|
34
|
+
else
|
|
35
|
+
echo "[ERROR] ContextOS local runner not found. Install locally (npm i -D contextos-agents) before committing." >&2
|
|
36
|
+
exit 2
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [ $CTX_STATUS -ne 0 ]; then
|
|
40
|
+
exit $CTX_STATUS
|
|
41
|
+
fi
|
|
21
42
|
${HOOK_MARKER_END}`;
|
|
22
43
|
|
|
23
44
|
function installHook(cwd = process.cwd()) {
|
|
@@ -37,19 +58,36 @@ function installHook(cwd = process.cwd()) {
|
|
|
37
58
|
if (fs.existsSync(hookFile)) {
|
|
38
59
|
const existing = fs.readFileSync(hookFile, 'utf8');
|
|
39
60
|
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
// Validate shebang if present
|
|
62
|
+
const shebangMatch = existing.match(/^#!([^\r\n]+)/);
|
|
63
|
+
if (shebangMatch) {
|
|
64
|
+
const interpreter = shebangMatch[1].toLowerCase();
|
|
65
|
+
const isShell = /\b(sh|bash|zsh|dash)\b/.test(interpreter);
|
|
66
|
+
if (!isShell) {
|
|
67
|
+
console.error(`[ERROR] Pre-commit hook uses non-shell interpreter (${shebangMatch[0]}). ContextOS pre-commit hook requires POSIX shell.`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Strip any existing ContextOS block (e.g. legacy trailing block from v2.1.0)
|
|
73
|
+
const stripped = existing.includes(HOOK_MARKER_START)
|
|
74
|
+
? existing.replace(new RegExp(`\\n?${HOOK_MARKER_START}[\\s\\S]*?${HOOK_MARKER_END}\\n?`, 'g'), '').trim()
|
|
75
|
+
: existing.trim();
|
|
76
|
+
|
|
77
|
+
// Place HOOK_PAYLOAD right after shebang so it runs first and cannot be bypassed by user 'exit 0'
|
|
78
|
+
let updated;
|
|
79
|
+
const strippedShebangMatch = stripped.match(/^#!.*(?:\r?\n|$)/);
|
|
80
|
+
if (strippedShebangMatch) {
|
|
81
|
+
const shebang = strippedShebangMatch[0];
|
|
82
|
+
const rest = stripped.slice(shebang.length).trim();
|
|
83
|
+
const sep = rest ? '\n\n' : '\n';
|
|
84
|
+
updated = `${shebang.trimEnd()}\n\n${HOOK_PAYLOAD}${sep}${rest}`;
|
|
46
85
|
} else {
|
|
47
|
-
|
|
48
|
-
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
49
|
-
const updated = existing + separator + HOOK_PAYLOAD + '\n';
|
|
50
|
-
fs.writeFileSync(hookFile, updated, 'utf8');
|
|
51
|
-
console.log(`✓ ContextOS pre-commit hook appended to existing hook in ${hookFile}`);
|
|
86
|
+
updated = `#!/bin/sh\n\n${HOOK_PAYLOAD}${stripped ? `\n\n${stripped}` : ''}`;
|
|
52
87
|
}
|
|
88
|
+
|
|
89
|
+
fs.writeFileSync(hookFile, updated.trimEnd() + '\n', 'utf8');
|
|
90
|
+
console.log(`✓ ContextOS pre-commit hook installed in ${hookFile}`);
|
|
53
91
|
} else {
|
|
54
92
|
// Fresh hook installation with shebang
|
|
55
93
|
const fresh = `#!/bin/sh\n\n${HOOK_PAYLOAD}\n`;
|
package/bin/commands/scan.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
+
const path = require('path');
|
|
10
11
|
const { runScan } = require('../lib/scan.js');
|
|
11
12
|
|
|
12
|
-
function scanCommand(args, flags) {
|
|
13
|
+
function scanCommand(args, flags = {}) {
|
|
13
14
|
const isJson = flags.json || args.includes('--json');
|
|
14
15
|
const enforce = flags.enforce || args.includes('--enforce');
|
|
15
16
|
const checkPlaceholders = flags.placeholders || args.includes('--placeholders');
|
|
@@ -18,8 +19,13 @@ function scanCommand(args, flags) {
|
|
|
18
19
|
const scopeIdx = args.indexOf('--scope');
|
|
19
20
|
const scopeFile = scopeIdx !== -1 && args[scopeIdx + 1] ? args[scopeIdx + 1] : null;
|
|
20
21
|
|
|
22
|
+
const projectIdx = args.indexOf('--project');
|
|
23
|
+
const projectArg = projectIdx !== -1 && args[projectIdx + 1] ? args[projectIdx + 1] : null;
|
|
24
|
+
const targetProject = flags.project || projectArg;
|
|
25
|
+
const projectDir = targetProject ? path.resolve(targetProject) : process.cwd();
|
|
26
|
+
|
|
21
27
|
const result = runScan({
|
|
22
|
-
cwd:
|
|
28
|
+
cwd: projectDir,
|
|
23
29
|
staged: true,
|
|
24
30
|
secrets: checkSecrets,
|
|
25
31
|
placeholders: checkPlaceholders,
|
|
@@ -43,6 +49,7 @@ function scanCommand(args, flags) {
|
|
|
43
49
|
|
|
44
50
|
console.log(` Staged Files Scanned : ${result.stats.stagedFilesCount}`);
|
|
45
51
|
console.log(` Enforcement Mode : ${enforce ? 'STRICT (fails on findings)' : 'ADVISORY (warnings only)'}`);
|
|
52
|
+
console.log(` Scope Containment : ${result.scope.status.toUpperCase()}`);
|
|
46
53
|
console.log(` Violations Found : ${result.stats.violationsCount}\n`);
|
|
47
54
|
|
|
48
55
|
if (result.findings.length > 0) {
|
|
@@ -58,7 +65,7 @@ function scanCommand(args, flags) {
|
|
|
58
65
|
|
|
59
66
|
console.log('──────────────────────────────────────────────────────');
|
|
60
67
|
if (result.ok) {
|
|
61
|
-
console.log(' RESULT: PASSED (
|
|
68
|
+
console.log(' RESULT: PASSED (Verification completed cleanly, no blocking violations)');
|
|
62
69
|
} else {
|
|
63
70
|
console.log(' RESULT: FAILED (Commit blocked due to staged violations)');
|
|
64
71
|
}
|
package/bin/index.js
CHANGED
|
@@ -270,7 +270,10 @@ if (mainCommand === 'recover') {
|
|
|
270
270
|
// Gate command (deterministic quality gate verification)
|
|
271
271
|
if (mainCommand === 'gate') {
|
|
272
272
|
const { runGate, emitGitHubAnnotations, writeGitHubSummary } = require('./lib/gate.js');
|
|
273
|
-
|
|
273
|
+
let target = flags.target || (args[1] && !args[1].startsWith('-') ? args[1] : 'all');
|
|
274
|
+
if (typeof target === 'string' && target.includes(',')) {
|
|
275
|
+
target = target.split(',').map(s => s.trim()).filter(Boolean);
|
|
276
|
+
}
|
|
274
277
|
const result = runGate(flags.project, {
|
|
275
278
|
target,
|
|
276
279
|
profile: flags.profile,
|
|
@@ -377,7 +380,7 @@ if (mainCommand === 'hook') {
|
|
|
377
380
|
|
|
378
381
|
// Proxy commands to trusted package .agents/ctx.js targeting flags.project
|
|
379
382
|
const PROXY_COMMANDS = [
|
|
380
|
-
'profile', 'export', 'validate', 'resolve', 'skill', 'index',
|
|
383
|
+
'profile', 'export', 'validate', 'audit', 'resolve', 'skill', 'index',
|
|
381
384
|
'clean-worktrees', 'stats', 'watch', 'compile', 'explain',
|
|
382
385
|
'thread'
|
|
383
386
|
];
|
|
@@ -545,6 +548,9 @@ if (mainCommand === 'install-skill') {
|
|
|
545
548
|
console.error(' To use the MCP Bridge or execution runtime, install @contextos/mcp:');
|
|
546
549
|
console.error(' npm install @contextos/mcp --save-dev');
|
|
547
550
|
process.exit(1);
|
|
551
|
+
} else if (mainCommand && mainCommand !== 'init') {
|
|
552
|
+
console.error(`[ERROR] Unknown command: '${mainCommand}'. Run 'contextos --help' for available commands.`);
|
|
553
|
+
process.exit(1);
|
|
548
554
|
} else {
|
|
549
555
|
|
|
550
556
|
// ── Paths ─────────────────────────────────────────────────────────────────────
|