contextos-agents 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/AGENTS.md +5 -0
- package/.agents/adapters/aider/export.js +4 -2
- package/.agents/adapters/copilot/export.js +4 -2
- package/.agents/adapters/cursor/export.js +48 -12
- package/.agents/adapters/zed/export.js +53 -14
- package/.agents/core/skills/adapters/skill.yaml +6 -0
- package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
- package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +7 -0
- package/.agents/core/skills/context-os/skill.yaml +11 -0
- package/.agents/core/skills/database/skill.yaml +6 -0
- package/.agents/core/skills/ddd/skill.yaml +7 -0
- package/.agents/core/skills/decisions/skill.yaml +6 -0
- package/.agents/core/skills/docker/skill.yaml +6 -0
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
- package/.agents/core/skills/fastapi/skill.yaml +7 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
- package/.agents/core/skills/generators/skill.yaml +15 -0
- package/.agents/core/skills/graphify/SKILL.md +2 -2
- package/.agents/core/skills/graphify/skill.yaml +6 -1
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
- package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
- package/.agents/core/skills/interview-me/skill.yaml +4 -0
- package/.agents/core/skills/microservices/skill.yaml +7 -0
- package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
- package/.agents/core/skills/nestjs/skill.yaml +7 -0
- package/.agents/core/skills/nextjs/skill.yaml +7 -0
- package/.agents/core/skills/node/skill.yaml +7 -0
- package/.agents/core/skills/performance/skill.yaml +7 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
- package/.agents/core/skills/react/skill.yaml +7 -0
- package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
- package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
- package/.agents/core/skills/security/skill.yaml +7 -0
- package/.agents/core/skills/soft-design/skill.yaml +4 -0
- package/.agents/core/skills/state-management/skill.yaml +6 -0
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
- package/.agents/core/skills/system-design/skill.yaml +7 -0
- package/.agents/core/skills/testing/skill.yaml +6 -0
- package/.agents/core/skills/typescript/skill.yaml +7 -0
- package/.agents/core/skills/ui-design/skill.yaml +7 -0
- package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
- package/.agents/core/skills/ux-design/skill.yaml +7 -0
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
- package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
- package/.agents/ctx.js +14 -1
- package/.agents/doctor.js +193 -4
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +180 -20
- package/.agents/profiles.js +26 -0
- package/.agents/resolver.js +150 -30
- package/.agents/stats.js +22 -9
- package/.agents/validate.js +99 -13
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +262 -0
- package/bin/commands.js +305 -0
- package/bin/index.js +203 -83
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +251 -0
- package/bin/lib/safe-writer.js +259 -0
- package/package.json +5 -6
- package/registry.json +13 -5
- package/registry.schema.json +3 -1
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189271
- package/benchmarks/gemini-issues.js +0 -533
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/commands/uninstall.js
|
|
3
|
+
* ContextOS CLI — Safe Uninstall Command
|
|
4
|
+
*
|
|
5
|
+
* Removes ContextOS artifacts safely:
|
|
6
|
+
* - Only deletes clean managed files (content matches installation hash)
|
|
7
|
+
* - Never deletes user-modified managed files (USER_MODIFIED_PRESERVED)
|
|
8
|
+
* - Never deletes user-created custom skills or unmanaged files
|
|
9
|
+
* - Preserves .agents/ directory if user files remain
|
|
10
|
+
* - Removes .agents/ cleanly if no user files remain
|
|
11
|
+
* - Supports --dry-run
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const {
|
|
17
|
+
loadLockfile,
|
|
18
|
+
migrateExistingInstallation,
|
|
19
|
+
} = require('../lib/lockfile.js');
|
|
20
|
+
const {
|
|
21
|
+
planFileDelete,
|
|
22
|
+
executeFileDelete,
|
|
23
|
+
} = require('../lib/safe-writer.js');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Recursively count all files remaining in a directory (excluding specified skip names).
|
|
27
|
+
*/
|
|
28
|
+
function countFiles(dir, skipNames = new Set()) {
|
|
29
|
+
let count = 0;
|
|
30
|
+
if (!fs.existsSync(dir)) return 0;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
if (skipNames.has(entry.name)) continue;
|
|
35
|
+
const full = path.join(dir, entry.name);
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
count += countFiles(full, skipNames);
|
|
38
|
+
} else if (entry.isFile()) {
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
return count;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Plan uninstallation for a target project.
|
|
50
|
+
*/
|
|
51
|
+
function planUninstall(projectDir, options = {}) {
|
|
52
|
+
const targetAgentsDir = path.join(projectDir, '.agents');
|
|
53
|
+
if (!fs.existsSync(targetAgentsDir)) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
error: 'NO_AGENTS_DIR',
|
|
57
|
+
message: 'No .agents/ directory found in the target project. Nothing to uninstall.',
|
|
58
|
+
plans: [],
|
|
59
|
+
summary: { delete: 0, skipModified: 0, skipUnmanaged: 0, missing: 0 },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sourceAgentsDir = options.sourceAgentsDir || path.join(__dirname, '..', '..', '.agents');
|
|
64
|
+
|
|
65
|
+
// Load existing lockfile or build migration plan
|
|
66
|
+
let lockData = loadLockfile(projectDir);
|
|
67
|
+
if (!lockData) {
|
|
68
|
+
lockData = migrateExistingInstallation(projectDir, sourceAgentsDir, {
|
|
69
|
+
dryRun: true,
|
|
70
|
+
inMemory: true,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const plans = [];
|
|
75
|
+
if (lockData && lockData.managedFiles) {
|
|
76
|
+
for (const relPath of Object.keys(lockData.managedFiles)) {
|
|
77
|
+
const plan = planFileDelete({
|
|
78
|
+
projectDir,
|
|
79
|
+
relPath,
|
|
80
|
+
lockData,
|
|
81
|
+
});
|
|
82
|
+
plans.push(plan);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Count unmanaged files in .agents that are not in lockData
|
|
87
|
+
const managedSet = new Set(Object.keys(lockData?.managedFiles || {}));
|
|
88
|
+
let unmanagedFilesCount = 0;
|
|
89
|
+
|
|
90
|
+
function findUnmanaged(currentDir) {
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const full = path.join(currentDir, entry.name);
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
findUnmanaged(full);
|
|
102
|
+
} else if (entry.isFile()) {
|
|
103
|
+
if (entry.name === 'contextos.lock.json') continue;
|
|
104
|
+
const rel = path.relative(projectDir, full).replace(/\\/g, '/');
|
|
105
|
+
if (!managedSet.has(rel)) {
|
|
106
|
+
unmanagedFilesCount++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
findUnmanaged(targetAgentsDir);
|
|
113
|
+
|
|
114
|
+
const summary = {
|
|
115
|
+
delete: plans.filter(p => p.action === 'DELETE').length,
|
|
116
|
+
skipModified: plans.filter(p => p.action === 'SKIP' && p.reason === 'USER_MODIFIED_PRESERVED').length,
|
|
117
|
+
missing: plans.filter(p => p.action === 'SKIP' && p.reason === 'ALREADY_MISSING').length,
|
|
118
|
+
skipUnmanaged: unmanagedFilesCount,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
plans,
|
|
124
|
+
summary,
|
|
125
|
+
lockData,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Run safe uninstall against a target project.
|
|
131
|
+
*/
|
|
132
|
+
function runUninstall(projectDir = process.cwd(), options = {}) {
|
|
133
|
+
const dryRun = Boolean(options.dryRun);
|
|
134
|
+
|
|
135
|
+
console.log(`\nContextOS Safe Uninstall ${dryRun ? '[DRY-RUN]' : ''}`);
|
|
136
|
+
console.log(`Target: ${projectDir}`);
|
|
137
|
+
|
|
138
|
+
const planned = planUninstall(projectDir, options);
|
|
139
|
+
if (!planned.ok) {
|
|
140
|
+
console.error(`\n[ERROR] ${planned.message}`);
|
|
141
|
+
return planned;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { lockData, plans, summary } = planned;
|
|
145
|
+
|
|
146
|
+
console.log('\nPlanned Actions:');
|
|
147
|
+
for (const item of plans) {
|
|
148
|
+
if (item.action === 'DELETE') {
|
|
149
|
+
console.log(` - DELETE ${item.relPath}`);
|
|
150
|
+
} else if (item.reason === 'USER_MODIFIED_PRESERVED') {
|
|
151
|
+
console.log(` = PRESERVE ${item.relPath} (modified by user — will NOT delete)`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (summary.skipUnmanaged > 0) {
|
|
156
|
+
console.log(` = PRESERVE ${summary.skipUnmanaged} unmanaged user file(s)/skill(s) in .agents/`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('\nSummary:');
|
|
160
|
+
console.log(` To delete: ${summary.delete}`);
|
|
161
|
+
console.log(` Preserved: ${summary.skipModified} (user-modified) + ${summary.skipUnmanaged} (user-created)`);
|
|
162
|
+
|
|
163
|
+
if (dryRun) {
|
|
164
|
+
console.log('\n[DRY-RUN] No files were deleted.');
|
|
165
|
+
return { ...planned, executed: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Execute deletions
|
|
169
|
+
let deletedCount = 0;
|
|
170
|
+
for (const item of plans) {
|
|
171
|
+
if (item.action === 'DELETE') {
|
|
172
|
+
const res = executeFileDelete(item, lockData, { dryRun: false });
|
|
173
|
+
if (res.executed) deletedCount++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const targetAgentsDir = path.join(projectDir, '.agents');
|
|
178
|
+
const lockfilePath = path.join(targetAgentsDir, 'contextos.lock.json');
|
|
179
|
+
|
|
180
|
+
// Count remaining files in .agents (excluding lockfile)
|
|
181
|
+
const remainingFiles = countFiles(targetAgentsDir, new Set(['contextos.lock.json']));
|
|
182
|
+
|
|
183
|
+
if (remainingFiles === 0) {
|
|
184
|
+
// Completely clean, remove lockfile and .agents directory
|
|
185
|
+
try {
|
|
186
|
+
if (fs.existsSync(lockfilePath)) fs.unlinkSync(lockfilePath);
|
|
187
|
+
fs.rmSync(targetAgentsDir, { recursive: true, force: true });
|
|
188
|
+
console.log('\n[OK] ContextOS completely uninstalled. .agents/ removed.');
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.warn(`[WARN] Could not remove .agents directory: ${err.message}`);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
// User files or modified files remain — remove lockfile or keep remaining?
|
|
194
|
+
try {
|
|
195
|
+
if (fs.existsSync(lockfilePath)) fs.unlinkSync(lockfilePath);
|
|
196
|
+
} catch {}
|
|
197
|
+
console.log(`\n[OK] Clean ContextOS artifacts uninstalled (${deletedCount} removed).`);
|
|
198
|
+
console.log(`[INFO] .agents/ preserved because ${remainingFiles} user-modified or custom file(s) remain.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { ...planned, executed: true, deletedCount, remainingFiles };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
planUninstall,
|
|
206
|
+
runUninstall,
|
|
207
|
+
};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/commands/update.js
|
|
3
|
+
* ContextOS CLI — Safe Update Command
|
|
4
|
+
*
|
|
5
|
+
* Updates ContextOS installation non-destructively:
|
|
6
|
+
* - Scans distribution package against target project .agents directory
|
|
7
|
+
* - Plans all mutations: CREATE, UPDATE, CONFLICT, SKIP, DELETE
|
|
8
|
+
* - Never overwrites user-customized files (generates .contextos.new conflicts)
|
|
9
|
+
* - Never touches or deletes unmanaged user files or custom skills
|
|
10
|
+
* - Supports --dry-run to preview changes without modifying filesystem
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const {
|
|
16
|
+
loadLockfile,
|
|
17
|
+
saveLockfile,
|
|
18
|
+
migrateExistingInstallation,
|
|
19
|
+
createLockfileData,
|
|
20
|
+
} = require('../lib/lockfile.js');
|
|
21
|
+
const {
|
|
22
|
+
planFileWrite,
|
|
23
|
+
executeFileWrite,
|
|
24
|
+
planFileDelete,
|
|
25
|
+
executeFileDelete,
|
|
26
|
+
} = require('../lib/safe-writer.js');
|
|
27
|
+
|
|
28
|
+
const pkg = require('../../package.json');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Collect all distributable files from the source .agents directory.
|
|
32
|
+
*/
|
|
33
|
+
function collectDistributionFiles(sourceAgentsDir, options = {}) {
|
|
34
|
+
const files = [];
|
|
35
|
+
const excludedSkills = new Set(options.excludedSkills || []);
|
|
36
|
+
|
|
37
|
+
function walk(currentDir) {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const full = path.join(currentDir, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
// Skip .git or temp dirs if any
|
|
49
|
+
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
50
|
+
|
|
51
|
+
// Skip excluded skills by profile
|
|
52
|
+
const relToAgents = path.relative(sourceAgentsDir, full).replace(/\\/g, '/');
|
|
53
|
+
const coreMatch = relToAgents.match(/^core\/skills\/([^/]+)$/);
|
|
54
|
+
if (coreMatch && excludedSkills.has(coreMatch[1])) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const genMatch = relToAgents.match(/^generated\/[^/]+\/skills\/([^/]+)$/);
|
|
58
|
+
if (genMatch && excludedSkills.has(genMatch[1])) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
walk(full);
|
|
63
|
+
} else if (entry.isFile()) {
|
|
64
|
+
// Do not distribute lockfile itself or backup files
|
|
65
|
+
if (entry.name === 'contextos.lock.json' || entry.name.endsWith('.bak')) continue;
|
|
66
|
+
|
|
67
|
+
const relToAgents = path.relative(sourceAgentsDir, full).replace(/\\/g, '/');
|
|
68
|
+
files.push({
|
|
69
|
+
relPath: `.agents/${relToAgents}`,
|
|
70
|
+
fullSourcePath: full,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
walk(sourceAgentsDir);
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Plan the update operations for a project.
|
|
82
|
+
*/
|
|
83
|
+
function planUpdate(projectDir, sourceAgentsDir, options = {}) {
|
|
84
|
+
const targetAgentsDir = path.join(projectDir, '.agents');
|
|
85
|
+
if (!fs.existsSync(targetAgentsDir)) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: 'NO_AGENTS_DIR',
|
|
89
|
+
message: 'No .agents/ directory found in the target project. Run `contextos` to install first.',
|
|
90
|
+
plans: [],
|
|
91
|
+
summary: { create: 0, update: 0, conflict: 0, skip: 0, delete: 0 },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load existing lockfile or safely migrate
|
|
96
|
+
let lockData = loadLockfile(projectDir);
|
|
97
|
+
let migrated = false;
|
|
98
|
+
if (!lockData) {
|
|
99
|
+
lockData = migrateExistingInstallation(projectDir, sourceAgentsDir, {
|
|
100
|
+
dryRun: Boolean(options.dryRun),
|
|
101
|
+
version: pkg.version,
|
|
102
|
+
});
|
|
103
|
+
migrated = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Determine active profile exclusions if available
|
|
107
|
+
let excludedSkills = [];
|
|
108
|
+
try {
|
|
109
|
+
const profilesPath = path.join(projectDir, '.agents', 'profiles.js');
|
|
110
|
+
if (fs.existsSync(profilesPath)) {
|
|
111
|
+
const profilesModule = require(profilesPath);
|
|
112
|
+
const active = profilesModule.getActiveProfile(projectDir);
|
|
113
|
+
if (active && Array.isArray(active.exclude_skills)) {
|
|
114
|
+
excludedSkills = active.exclude_skills;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
|
|
119
|
+
const distFiles = collectDistributionFiles(sourceAgentsDir, { excludedSkills });
|
|
120
|
+
const distRelPaths = new Set(distFiles.map(f => f.relPath));
|
|
121
|
+
|
|
122
|
+
const plans = [];
|
|
123
|
+
|
|
124
|
+
// 1. Plan writes for distribution files
|
|
125
|
+
for (const item of distFiles) {
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(item.fullSourcePath, 'utf8');
|
|
128
|
+
const plan = planFileWrite({
|
|
129
|
+
projectDir,
|
|
130
|
+
relPath: item.relPath,
|
|
131
|
+
content,
|
|
132
|
+
lockData,
|
|
133
|
+
});
|
|
134
|
+
plans.push(plan);
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 2. Plan deletions for stale managed files that no longer exist in distribution
|
|
139
|
+
if (lockData && lockData.managedFiles) {
|
|
140
|
+
for (const managedRel of Object.keys(lockData.managedFiles)) {
|
|
141
|
+
if (managedRel.startsWith('.agents/') && !distRelPaths.has(managedRel)) {
|
|
142
|
+
const delPlan = planFileDelete({
|
|
143
|
+
projectDir,
|
|
144
|
+
relPath: managedRel,
|
|
145
|
+
lockData,
|
|
146
|
+
});
|
|
147
|
+
plans.push(delPlan);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const summary = {
|
|
153
|
+
create: plans.filter(p => p.action === 'CREATE').length,
|
|
154
|
+
update: plans.filter(p => p.action === 'UPDATE').length,
|
|
155
|
+
conflict: plans.filter(p => p.action === 'CONFLICT').length,
|
|
156
|
+
skip: plans.filter(p => p.action === 'SKIP').length,
|
|
157
|
+
delete: plans.filter(p => p.action === 'DELETE').length,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
lockData,
|
|
163
|
+
migrated,
|
|
164
|
+
plans,
|
|
165
|
+
summary,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run the update command against a target project.
|
|
171
|
+
*/
|
|
172
|
+
function runUpdate(projectDir = process.cwd(), options = {}) {
|
|
173
|
+
const sourceAgentsDir = options.sourceAgentsDir || path.join(__dirname, '..', '..', '.agents');
|
|
174
|
+
const dryRun = Boolean(options.dryRun);
|
|
175
|
+
|
|
176
|
+
console.log(`\nContextOS Safe Update ${dryRun ? '[DRY-RUN]' : ''}`);
|
|
177
|
+
console.log(`Target: ${projectDir}`);
|
|
178
|
+
|
|
179
|
+
const planned = planUpdate(projectDir, sourceAgentsDir, options);
|
|
180
|
+
if (!planned.ok) {
|
|
181
|
+
console.error(`\n[ERROR] ${planned.message}`);
|
|
182
|
+
return planned;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { lockData, plans, summary } = planned;
|
|
186
|
+
|
|
187
|
+
// Print classified plan preview
|
|
188
|
+
console.log('\nPlanned Actions:');
|
|
189
|
+
for (const item of plans) {
|
|
190
|
+
if (item.action === 'CREATE') {
|
|
191
|
+
console.log(` + CREATE ${item.relPath}`);
|
|
192
|
+
} else if (item.action === 'UPDATE') {
|
|
193
|
+
console.log(` ~ UPDATE ${item.relPath}`);
|
|
194
|
+
} else if (item.action === 'CONFLICT') {
|
|
195
|
+
console.log(` ! CONFLICT ${item.relPath} → ${item.conflictPath || item.relPath + '.contextos.new'} (customized by user)`);
|
|
196
|
+
} else if (item.action === 'DELETE') {
|
|
197
|
+
console.log(` - DELETE ${item.relPath} (stale managed file)`);
|
|
198
|
+
} else if (item.action === 'SKIP' && item.reason === 'UNMANAGED_USER_FILE') {
|
|
199
|
+
console.log(` = PRESERVE ${item.relPath} (unmanaged user file)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log('\nSummary:');
|
|
204
|
+
console.log(` Created: ${summary.create}`);
|
|
205
|
+
console.log(` Updated: ${summary.update}`);
|
|
206
|
+
console.log(` Conflicts: ${summary.conflict} (written as .contextos.new)`);
|
|
207
|
+
console.log(` Deleted: ${summary.delete}`);
|
|
208
|
+
console.log(` Skipped: ${summary.skip}`);
|
|
209
|
+
|
|
210
|
+
if (dryRun) {
|
|
211
|
+
console.log('\n[DRY-RUN] No changes were written to disk.');
|
|
212
|
+
return { ...planned, executed: false };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Execute plans
|
|
216
|
+
let executedCount = 0;
|
|
217
|
+
for (const item of plans) {
|
|
218
|
+
if (item.action === 'CREATE' || item.action === 'UPDATE' || item.action === 'CONFLICT') {
|
|
219
|
+
executeFileWrite(item, lockData, { dryRun: false });
|
|
220
|
+
if (item.action !== 'SKIP') executedCount++;
|
|
221
|
+
} else if (item.action === 'DELETE') {
|
|
222
|
+
executeFileDelete(item, lockData, { dryRun: false });
|
|
223
|
+
executedCount++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Update lockfile metadata
|
|
228
|
+
lockData.version = options.packageVersion || pkg.version;
|
|
229
|
+
lockData.updatedAt = new Date().toISOString();
|
|
230
|
+
saveLockfile(projectDir, lockData);
|
|
231
|
+
|
|
232
|
+
console.log(`\n[OK] ContextOS successfully updated to v${lockData.version}!`);
|
|
233
|
+
if (summary.conflict > 0) {
|
|
234
|
+
console.warn(`[WARN] ${summary.conflict} customized file(s) had upstream changes.`);
|
|
235
|
+
console.warn(' Inspect the .contextos.new file(s) to merge upstream updates.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Re-compile if ctx.js exists and not skipped
|
|
239
|
+
if (!options.skipCompile) {
|
|
240
|
+
const ctxPath = path.join(projectDir, '.agents', 'ctx.js');
|
|
241
|
+
if (fs.existsSync(ctxPath)) {
|
|
242
|
+
try {
|
|
243
|
+
const { execFileSync } = require('child_process');
|
|
244
|
+
console.log('\nRecompiling skills...');
|
|
245
|
+
execFileSync(process.execPath, [ctxPath, 'export', 'gemini'], {
|
|
246
|
+
cwd: projectDir,
|
|
247
|
+
stdio: 'inherit',
|
|
248
|
+
});
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.warn(`[WARN] Post-update compilation failed: ${err.message}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { ...planned, executed: true, executedCount };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = {
|
|
259
|
+
planUpdate,
|
|
260
|
+
runUpdate,
|
|
261
|
+
collectDistributionFiles,
|
|
262
|
+
};
|