contextos-agents 2.2.0 → 2.3.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.
Files changed (109) hide show
  1. package/.agents/AGENTS.md +53 -396
  2. package/.agents/adapters/aider/export.js +11 -16
  3. package/.agents/adapters/claude/export.js +13 -13
  4. package/.agents/adapters/copilot/export.js +29 -8
  5. package/.agents/adapters/cursor/export.js +9 -18
  6. package/.agents/adapters/gemini/export.js +11 -46
  7. package/.agents/adapters/pure-compiler.js +65 -42
  8. package/.agents/adapters/shared.js +35 -1
  9. package/.agents/adapters/zed/export.js +2 -2
  10. package/.agents/compiled/registry.v2.json +30 -18
  11. package/.agents/compiled/registry.v2.sha256 +1 -1
  12. package/.agents/compiler/manifest-compiler.js +5 -29
  13. package/.agents/core/skills/context-os/references/project-graph.md +3 -3
  14. package/.agents/core/skills/engineering-workflow/SKILL.md +11 -316
  15. package/.agents/core/skills/engineering-workflow/references/workflow.md +336 -0
  16. package/.agents/core/skills/engineering-workflow/skill.yaml +2 -4
  17. package/.agents/core/skills/gstack-roles/SKILL.md +11 -128
  18. package/.agents/core/skills/gstack-roles/references/roles.md +149 -0
  19. package/.agents/core/skills/gstack-roles/skill.yaml +2 -4
  20. package/.agents/core/skills/ponytail-mindset/SKILL.md +13 -165
  21. package/.agents/core/skills/ponytail-mindset/references/minimalism.md +186 -0
  22. package/.agents/core/skills/ponytail-mindset/skill.yaml +2 -5
  23. package/.agents/core/skills/security/skill.yaml +1 -0
  24. package/.agents/ctx.js +13 -13
  25. package/.agents/customization-dx.js +13 -9
  26. package/.agents/doctor.js +2 -2
  27. package/.agents/generated/claude/skills/context-manager/EXAMPLES.md +19 -0
  28. package/.agents/generated/claude/skills/context-manager/SKILL.md +0 -29
  29. package/.agents/generated/claude/skills/context-manager/TROUBLESHOOTING.md +7 -0
  30. package/.agents/generated/claude/skills/context-manager/VALIDATION.json +12 -0
  31. package/.agents/generated/claude/skills/context-manager/references/context-rules.md +59 -0
  32. package/.agents/generated/claude/skills/context-os/EXAMPLES.md +21 -0
  33. package/.agents/generated/claude/skills/context-os/SKILL.md +0 -31
  34. package/.agents/generated/claude/skills/context-os/TROUBLESHOOTING.md +7 -0
  35. package/.agents/generated/claude/skills/context-os/VALIDATION.json +12 -0
  36. package/.agents/generated/claude/skills/context-os/packs.yaml +59 -0
  37. package/.agents/generated/claude/skills/context-os/references/context-rules.md +68 -0
  38. package/.agents/generated/claude/skills/context-os/references/pipeline.md +119 -0
  39. package/.agents/generated/claude/skills/context-os/references/project-graph.md +103 -0
  40. package/.agents/generated/claude/skills/context-os/rules.yaml +135 -0
  41. package/.agents/generated/claude/skills/engineering-workflow/EXAMPLES.md +57 -0
  42. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +10 -391
  43. package/.agents/generated/claude/skills/engineering-workflow/TROUBLESHOOTING.md +19 -0
  44. package/.agents/generated/claude/skills/engineering-workflow/VALIDATION.json +12 -0
  45. package/.agents/generated/claude/skills/engineering-workflow/references/workflow.md +336 -0
  46. package/.agents/generated/claude/skills/gemini-precision/EXAMPLES.md +72 -0
  47. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +0 -100
  48. package/.agents/generated/claude/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
  49. package/.agents/generated/claude/skills/gemini-precision/VALIDATION.json +12 -0
  50. package/.agents/generated/claude/skills/gstack-roles/EXAMPLES.md +23 -0
  51. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +10 -164
  52. package/.agents/generated/claude/skills/gstack-roles/TROUBLESHOOTING.md +13 -0
  53. package/.agents/generated/claude/skills/gstack-roles/VALIDATION.json +12 -0
  54. package/.agents/generated/claude/skills/gstack-roles/references/roles.md +149 -0
  55. package/.agents/generated/claude/skills/ponytail-mindset/EXAMPLES.md +45 -0
  56. package/.agents/generated/claude/skills/ponytail-mindset/SKILL.md +12 -228
  57. package/.agents/generated/claude/skills/ponytail-mindset/TROUBLESHOOTING.md +19 -0
  58. package/.agents/generated/claude/skills/ponytail-mindset/VALIDATION.json +12 -0
  59. package/.agents/generated/claude/skills/ponytail-mindset/references/minimalism.md +186 -0
  60. package/.agents/generated/claude/skills/security/EXAMPLES.md +64 -0
  61. package/.agents/generated/claude/skills/security/SKILL.md +0 -86
  62. package/.agents/generated/claude/skills/security/TROUBLESHOOTING.md +19 -0
  63. package/.agents/generated/claude/skills/security/VALIDATION.json +12 -0
  64. package/.agents/generated/gemini/skills/context-manager/EXAMPLES.md +19 -0
  65. package/.agents/generated/gemini/skills/context-manager/SKILL.md +1 -33
  66. package/.agents/generated/gemini/skills/context-manager/TROUBLESHOOTING.md +7 -0
  67. package/.agents/generated/gemini/skills/context-manager/VALIDATION.json +12 -0
  68. package/.agents/generated/gemini/skills/context-manager/references/context-rules.md +59 -0
  69. package/.agents/generated/gemini/skills/context-os/EXAMPLES.md +21 -0
  70. package/.agents/generated/gemini/skills/context-os/SKILL.md +0 -35
  71. package/.agents/generated/gemini/skills/context-os/TROUBLESHOOTING.md +7 -0
  72. package/.agents/generated/gemini/skills/context-os/VALIDATION.json +12 -0
  73. package/.agents/generated/gemini/skills/context-os/packs.yaml +59 -0
  74. package/.agents/generated/gemini/skills/context-os/references/context-rules.md +68 -0
  75. package/.agents/generated/gemini/skills/context-os/references/pipeline.md +119 -0
  76. package/.agents/generated/gemini/skills/context-os/references/project-graph.md +103 -0
  77. package/.agents/generated/gemini/skills/context-os/rules.yaml +135 -0
  78. package/.agents/generated/gemini/skills/engineering-workflow/EXAMPLES.md +57 -0
  79. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +11 -396
  80. package/.agents/generated/gemini/skills/engineering-workflow/TROUBLESHOOTING.md +19 -0
  81. package/.agents/generated/gemini/skills/engineering-workflow/VALIDATION.json +12 -0
  82. package/.agents/generated/gemini/skills/engineering-workflow/references/workflow.md +336 -0
  83. package/.agents/generated/gemini/skills/gemini-precision/EXAMPLES.md +72 -0
  84. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +0 -104
  85. package/.agents/generated/gemini/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
  86. package/.agents/generated/gemini/skills/gemini-precision/VALIDATION.json +12 -0
  87. package/.agents/generated/gemini/skills/gstack-roles/EXAMPLES.md +23 -0
  88. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +11 -169
  89. package/.agents/generated/gemini/skills/gstack-roles/TROUBLESHOOTING.md +13 -0
  90. package/.agents/generated/gemini/skills/gstack-roles/VALIDATION.json +12 -0
  91. package/.agents/generated/gemini/skills/gstack-roles/references/roles.md +149 -0
  92. package/.agents/generated/gemini/skills/ponytail-mindset/EXAMPLES.md +45 -0
  93. package/.agents/generated/gemini/skills/ponytail-mindset/SKILL.md +13 -233
  94. package/.agents/generated/gemini/skills/ponytail-mindset/TROUBLESHOOTING.md +19 -0
  95. package/.agents/generated/gemini/skills/ponytail-mindset/VALIDATION.json +12 -0
  96. package/.agents/generated/gemini/skills/ponytail-mindset/references/minimalism.md +186 -0
  97. package/.agents/generated/gemini/skills/security/EXAMPLES.md +64 -0
  98. package/.agents/generated/gemini/skills/security/SKILL.md +2 -92
  99. package/.agents/generated/gemini/skills/security/TROUBLESHOOTING.md +19 -0
  100. package/.agents/generated/gemini/skills/security/VALIDATION.json +12 -0
  101. package/.agents/plugins.js +24 -5
  102. package/.agents/resolver/canonical-resolver.js +43 -7
  103. package/.agents/resolver/resolve-args.js +31 -0
  104. package/.agents/stats.js +8 -11
  105. package/.agents/workspace/workspace-graph.js +16 -6
  106. package/README.md +48 -18
  107. package/bin/index.js +1 -1
  108. package/bin/lib/ui.js +2 -2
  109. package/package.json +89 -86
@@ -5,7 +5,7 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const { collectSkillDirectories, extractYamlField, stripFrontmatter } = require('../shared.js');
8
+ const { getSkillEntrypointPath, collectSkillDirectories, extractYamlField, stripFrontmatter } = require('../shared.js');
9
9
  const { registerAdapter, applyArtifacts, createProvenanceHeader } = require('../pure-compiler.js');
10
10
 
11
11
  const GENERATOR_ID = 'copilot@2';
@@ -19,9 +19,9 @@ function describe() {
19
19
  };
20
20
  }
21
21
 
22
- function buildSkillSection(skillDir) {
22
+ function buildSkillSection(skillDir, context) {
23
23
  const skillName = path.basename(skillDir);
24
- const skillMdPath = path.join(skillDir, 'SKILL.md');
24
+ const skillMdPath = getSkillEntrypointPath(skillDir);
25
25
  const yamlPath = path.join(skillDir, 'skill.yaml');
26
26
 
27
27
  let title = skillName;
@@ -43,7 +43,8 @@ function buildSkillSection(skillDir) {
43
43
  }
44
44
 
45
45
  const descLine = description ? `> ${description.replace(/\r?\n+/g, ' ').trim()}\n` : '';
46
- const skillRef = `*Source: \`.agents/skills/${skillName}/SKILL.md\` (Read on demand)*\n`;
46
+ const sourcePath = path.relative(context?.projectRoot || '.', skillMdPath).split(path.sep).join('/');
47
+ const skillRef = `*Source: \`${sourcePath}\` (Read on demand)*\n`;
47
48
  return `\n### ${title}\n${descLine}${skillRef}`;
48
49
  }
49
50
 
@@ -62,7 +63,7 @@ function render(context) {
62
63
  `# GitHub Copilot Instructions — ContextOS\n\n` +
63
64
  `## Project Rules\n\n` +
64
65
  `### Step 0 — Identify Before Acting\n\n` +
65
- `Before writing a single line of code or plan, state:\n` +
66
+ `When useful for substantial work, identify:\n` +
66
67
  `\`\`\`\n1. WHAT DOMAIN? → Frontend / Backend / Architecture / Full-Stack / DevOps\n2. WHAT PHASE? → Define / Plan / Build / Verify / Review / Ship\n3. WHAT ROLE? → Declare specialist role for this phase\n\`\`\`\n\n` +
67
68
  `> **Anti-Spam Invariant**: Declare this strictly once at the start of a task or phase. Never repeat before intermediate tool calls or step updates.\n\n` +
68
69
  `### Non-Negotiable Rules\n\n` +
@@ -89,22 +90,42 @@ function render(context) {
89
90
  const rawTags = extractYamlField(yaml, 'tags');
90
91
  if (rawTags) tags = rawTags.replace(/[\[\]]/g, '');
91
92
  }
92
- const relPath = `.agents/core/skills/${skillName}/SKILL.md`;
93
+ const relPath = path.relative(context?.projectRoot || '.', getSkillEntrypointPath(skill)).replace(/\\/g, '/');
93
94
  lines.push(`| \`${tags}\` | **${skillName}** | \`${relPath}\` |\n`);
94
95
  }
95
96
  lines.push(`\n---\n`);
96
97
 
97
98
  for (const skill of skills) {
98
- const section = buildSkillSection(skill);
99
+ const section = buildSkillSection(skill, context);
99
100
  if (section) {
100
101
  lines.push(section);
101
102
  }
102
103
  }
103
104
 
105
+ const target = path.join(context?.projectRoot || '.', '.github/copilot-instructions.md');
106
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : '';
107
+ const start = '<!-- CONTEXTOS:START -->';
108
+ const end = '<!-- CONTEXTOS:END -->';
109
+ const block = `${start}\n${lines.join('').replace(/\r\n/g, '\n')}${end}`;
110
+ let content;
111
+ if (existing.includes(start) || existing.includes(end)) {
112
+ if (existing.split(start).length !== 2 || existing.split(end).length !== 2 || existing.indexOf(end) < existing.indexOf(start)) {
113
+ throw new Error('Malformed ContextOS block in Copilot instructions; refusing to overwrite.');
114
+ }
115
+ content = existing.slice(0, existing.indexOf(start)) + block + existing.slice(existing.indexOf(end) + end.length);
116
+ } else {
117
+ const { LockfileV2Manager, computeExactHash } = require('../../filesystem/index.js');
118
+ const managed = new LockfileV2Manager(context?.projectRoot || '.').read()?.managedFiles?.['.github/copilot-instructions.md'];
119
+ const legacyManaged = managed?.generator === GENERATOR_ID && managed.exactSha256 === computeExactHash(Buffer.from(existing));
120
+ content = legacyManaged ? block + '\n' : existing + (existing && !existing.endsWith('\n') ? '\n' : '') + (existing ? '\n' : '') + block + '\n';
121
+ }
122
+
104
123
  return [
105
124
  {
106
125
  path: '.github/copilot-instructions.md',
107
- content: lines.join('').replace(/\r\n/g, '\n'),
126
+ content,
127
+ preservesUserContent: true,
128
+ expectedBeforeHash: fs.existsSync(target) ? require('../../filesystem/index.js').computeExactHash(Buffer.from(existing)) : 'NONE',
108
129
  mediaType: 'text/markdown',
109
130
  kind: 'generated-adapter',
110
131
  generator: GENERATOR_ID,
@@ -5,7 +5,7 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const { AGENTS_MD_PATH, collectSkillDirectories, extractYamlField, stripFrontmatter, readMeaningfulMarkdown } = require('../shared.js');
8
+ const { getSkillEntrypointPath, AGENTS_MD_PATH, collectSkillDirectories, extractYamlField, stripFrontmatter } = require('../shared.js');
9
9
  const { registerAdapter, applyArtifacts, createProvenanceHeader } = require('../pure-compiler.js');
10
10
 
11
11
  const GENERATOR_ID = 'cursor@2';
@@ -20,11 +20,6 @@ function describe() {
20
20
  }
21
21
 
22
22
  function getSkillGlobsAndFlags(skillName, skillDir = null) {
23
- const ALWAYS_APPLY_SKILLS = ['engineering-workflow', 'gstack-roles', 'ponytail-mindset', 'gemini-precision'];
24
- if (ALWAYS_APPLY_SKILLS.includes(skillName)) {
25
- return { globs: '', alwaysApply: true };
26
- }
27
-
28
23
  // 1. Dynamic extraction from skill.yaml if available
29
24
  if (skillDir) {
30
25
  const yamlPath = path.join(skillDir, 'skill.yaml');
@@ -78,7 +73,7 @@ function getSkillGlobsAndFlags(skillName, skillDir = null) {
78
73
 
79
74
  function buildSkillSection(skillDir) {
80
75
  const skillName = path.basename(skillDir);
81
- const skillMdPath = path.join(skillDir, 'SKILL.md');
76
+ const skillMdPath = getSkillEntrypointPath(skillDir);
82
77
  const yamlPath = path.join(skillDir, 'skill.yaml');
83
78
 
84
79
  let title = skillName;
@@ -92,7 +87,7 @@ function buildSkillSection(skillDir) {
92
87
  if (!fs.existsSync(skillMdPath)) return null;
93
88
 
94
89
  const raw = fs.readFileSync(skillMdPath, 'utf8');
95
- const body = stripFrontmatter(raw);
90
+ const body = stripFrontmatter(raw).trimEnd();
96
91
 
97
92
  if (!description) {
98
93
  const firstLine = body.split(/\r?\n/).map(l => l.trim()).find(l => l && !l.startsWith('#'));
@@ -103,9 +98,9 @@ function buildSkillSection(skillDir) {
103
98
  return `\n## Skill: ${title}\n${descLine}*Rule file: \`.cursor/rules/${skillName}.mdc\` (load on demand)*\n`;
104
99
  }
105
100
 
106
- function generateCursorMdc(skillDir) {
101
+ function generateCursorMdc(skillDir, context) {
107
102
  const skillName = path.basename(skillDir);
108
- const skillMdPath = path.join(skillDir, 'SKILL.md');
103
+ const skillMdPath = getSkillEntrypointPath(skillDir);
109
104
  const yamlPath = path.join(skillDir, 'skill.yaml');
110
105
 
111
106
  if (!fs.existsSync(skillMdPath)) return null;
@@ -120,13 +115,7 @@ function generateCursorMdc(skillDir) {
120
115
  }
121
116
 
122
117
  let raw = fs.readFileSync(skillMdPath, 'utf8');
123
- const examples = readMeaningfulMarkdown(path.join(skillDir, 'EXAMPLES.md'));
124
- if (examples) raw += '\n\n' + examples;
125
-
126
- const troubleshooting = readMeaningfulMarkdown(path.join(skillDir, 'TROUBLESHOOTING.md'));
127
- if (troubleshooting) raw += '\n\n' + troubleshooting;
128
-
129
- const body = stripFrontmatter(raw);
118
+ const body = stripFrontmatter(raw).trimEnd();
130
119
  const { globs, alwaysApply } = getSkillGlobsAndFlags(skillName, skillDir);
131
120
 
132
121
  let cleanDescription = description.replace(/\r?\n+/g, ' ').replace(/"/g, "'").trim();
@@ -141,6 +130,8 @@ alwaysApply: ${alwaysApply}
141
130
 
142
131
  # Skill: ${title}
143
132
 
133
+ Resolve supporting resource paths relative to \`${path.relative(context?.projectRoot || process.cwd(), skillDir).replace(/\\/g, '/')}\`. Read examples and references only when needed.
134
+
144
135
  ${body}
145
136
  `;
146
137
 
@@ -189,7 +180,7 @@ ${agentsMd}
189
180
 
190
181
  const skills = collectSkillDirectories(context?.profile, context?.projectRoot);
191
182
  for (const skill of skills) {
192
- const mdc = generateCursorMdc(skill);
183
+ const mdc = generateCursorMdc(skill, context);
193
184
  if (mdc) {
194
185
  const fullMdc = mdc.mdcContent.replace(/\r\n/g, '\n');
195
186
  artifacts.push({
@@ -5,7 +5,7 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const { collectSkillDirectories, readMeaningfulMarkdown, extractYamlField } = require('../shared.js');
8
+ const { getSkillEntrypointPath, readSkillManifest, collectSkillDirectories, renderSkillResources } = require('../shared.js');
9
9
  const { registerAdapter, applyArtifacts } = require('../pure-compiler.js');
10
10
 
11
11
  const GENERATOR_ID = 'gemini@2';
@@ -21,61 +21,23 @@ function describe() {
21
21
 
22
22
  function renderGeminiSkill(skillDir, context) {
23
23
  const skillName = path.basename(skillDir);
24
- const yamlPath = path.join(skillDir, 'skill.yaml');
25
- const existingSkillMdPath = path.join(skillDir, 'SKILL.md');
24
+ const manifest = readSkillManifest(skillDir);
25
+ const existingSkillMdPath = getSkillEntrypointPath(skillDir);
26
26
 
27
- if (!fs.existsSync(existingSkillMdPath) && !fs.existsSync(yamlPath)) {
27
+ if (!fs.existsSync(existingSkillMdPath)) {
28
28
  return [];
29
29
  }
30
30
 
31
- let name = skillName;
32
- let description = null;
33
-
34
- if (fs.existsSync(yamlPath)) {
35
- const yamlText = fs.readFileSync(yamlPath, 'utf8');
36
- name = extractYamlField(yamlText, 'name') || skillName;
37
- description = extractYamlField(yamlText, 'description');
38
- if (!description) {
39
- const descRegex = new RegExp(`^description:\\s*>\\s*\\n\\s*([^\\n]+)`, 'm');
40
- const descMatch = yamlText.match(descRegex);
41
- if (descMatch) description = descMatch[1].trim();
42
- }
43
- }
44
- description = description || `ContextOS skill for ${name}`;
45
-
46
- const files = fs.readdirSync(skillDir).sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }));
47
- const mdFiles = files.filter(f => f.endsWith('.md'));
48
-
49
- const primaryMd = mdFiles.includes('SKILL.md')
50
- ? 'SKILL.md'
51
- : (mdFiles.find(f => f === `${skillName}.md`) || mdFiles[0]);
52
-
53
- let mergedContent = '';
54
- if (primaryMd) {
55
- let primaryText = fs.readFileSync(path.join(skillDir, primaryMd), 'utf8');
56
- primaryText = primaryText.replace(/^---[\s\S]*?---\r?\n/, '');
57
- mergedContent += primaryText;
58
- }
59
-
60
- const otherTopicFiles = mdFiles.filter(f => f !== primaryMd && f !== 'EXAMPLES.md' && f !== 'TROUBLESHOOTING.md');
61
- const orderedExtras = [...otherTopicFiles];
62
- if (mdFiles.includes('EXAMPLES.md') && primaryMd !== 'EXAMPLES.md') orderedExtras.push('EXAMPLES.md');
63
- if (mdFiles.includes('TROUBLESHOOTING.md') && primaryMd !== 'TROUBLESHOOTING.md') orderedExtras.push('TROUBLESHOOTING.md');
64
-
65
- for (const mdFile of orderedExtras) {
66
- if (mergedContent.includes(`<!-- Source: ${mdFile} -->`)) continue;
67
- const extraContent = readMeaningfulMarkdown(path.join(skillDir, mdFile));
68
- if (extraContent) {
69
- mergedContent += `\n\n<!-- Source: ${mdFile} -->\n\n` + extraContent;
70
- }
71
- }
31
+ const name = skillName;
32
+ const description = manifest?.description || `ContextOS skill for ${name}`;
33
+ const mergedContent = fs.readFileSync(existingSkillMdPath, 'utf8').replace(/^---[\s\S]*?---\r?\n/, '');
72
34
 
73
35
  const outputContent = `---
74
36
  name: ${name}
75
37
  description: >
76
38
  ${description}
77
39
  ---
78
- ${mergedContent.trimStart()}
40
+ ${mergedContent.trim()}
79
41
  `.replace(/\r\n/g, '\n');
80
42
 
81
43
  const artifacts = [
@@ -99,6 +61,9 @@ ${mergedContent.trimStart()}
99
61
  },
100
62
  ];
101
63
 
64
+ artifacts.push(...renderSkillResources(skillDir, [
65
+ `.agents/generated/gemini/skills/${skillName}`, `.agents/skills/${skillName}`,
66
+ ], { kind: 'generated-adapter', generator: GENERATOR_ID, sourceSkillIds: [skillName], inputsHash: context?.sourceGraphHash || 'none' }));
102
67
  return artifacts;
103
68
  }
104
69
 
@@ -107,8 +107,8 @@ function loadCompilerContext(projectRoot, options = {}) {
107
107
  // 3. Workspace Graph
108
108
  let workspace = null;
109
109
  try {
110
- const { buildWorkspaceGraph } = require('../workspace/workspace-graph.js');
111
- workspace = buildWorkspaceGraph(absRoot);
110
+ const { WorkspaceGraphBuilder } = require('../workspace/workspace-graph.js');
111
+ workspace = new WorkspaceGraphBuilder({ rootDir: absRoot }).build();
112
112
  } catch {
113
113
  workspace = null;
114
114
  }
@@ -256,6 +256,11 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
256
256
  });
257
257
 
258
258
  let hasModifications = false;
259
+ const legacyLockPath = path.join(absRoot, '.agents', 'contextos.lock.json');
260
+ let legacyLock = null;
261
+ if (fs.existsSync(legacyLockPath)) {
262
+ try { legacyLock = JSON.parse(fs.readFileSync(legacyLockPath, 'utf8')); } catch {}
263
+ }
259
264
 
260
265
  for (const art of artifacts) {
261
266
  const exactSha256 = computeExactHash(art.content);
@@ -272,15 +277,27 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
272
277
  }
273
278
 
274
279
  const absPath = path.resolve(absRoot, art.path);
280
+ let diskHash = 'NONE';
275
281
  if (fs.existsSync(absPath)) {
276
282
  const diskBuf = fs.readFileSync(absPath);
277
- if (computeExactHash(diskBuf) === exactSha256) {
283
+ diskHash = computeExactHash(diskBuf);
284
+ if (diskHash === exactSha256) {
278
285
  needsWrite = false;
279
286
  }
287
+ if (needsWrite && !art.preservesUserContent) {
288
+ const legacyRecord = legacyLock?.managedFiles?.[art.path] || legacyLock?.files?.[art.path];
289
+ const legacyOwned = legacyRecord?.managed && legacyRecord.sha256 === computeSemanticHash(diskBuf).replace('sha256:', '');
290
+ const textEquivalent = /^text\/|yaml/.test(art.mediaType || '') && existingRecord?.semanticTextSha256 === computeSemanticHash(diskBuf);
291
+ if ((!existingRecord && !legacyOwned) || (existingRecord && diskHash !== existingRecord.exactSha256 && !textEquivalent)) {
292
+ const err = new Error(`Refusing to overwrite user-owned or modified file '${art.path}'. Move custom rules into a project override or restore the managed artifact before exporting.`);
293
+ err.code = 'CTX_ADAPTER_OWNERSHIP_CONFLICT';
294
+ throw err;
295
+ }
296
+ }
280
297
  }
281
298
 
282
299
  if (needsWrite) {
283
- tx.stageWrite(art.path, art.content, { mode: art.mode || 420 });
300
+ tx.stageWrite(art.path, art.content, { mode: art.mode || 420, expectedBeforeHash: art.expectedBeforeHash || diskHash });
284
301
  hasModifications = true;
285
302
  }
286
303
 
@@ -310,13 +327,21 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
310
327
  } catch {}
311
328
  }
312
329
 
330
+ const prunedPaths = new Set();
313
331
  // 1. Prune stale files recorded in Lockfile v2
314
332
  if (lockfileData && lockfileData.managedFiles) {
315
333
  for (const [recordedPath, meta] of Object.entries(lockfileData.managedFiles)) {
316
334
  if (activeGenerators.has(meta.generator) && !activePaths.has(recordedPath)) {
317
335
  const absRecPath = path.resolve(absRoot, recordedPath);
318
336
  if (fs.existsSync(absRecPath)) {
319
- tx.stageDelete(recordedPath);
337
+ const beforeHash = computeExactHash(fs.readFileSync(absRecPath));
338
+ if (beforeHash !== meta.exactSha256) {
339
+ const err = new Error(`Refusing to prune modified managed file '${recordedPath}'.`);
340
+ err.code = 'CTX_ADAPTER_OWNERSHIP_CONFLICT';
341
+ throw err;
342
+ }
343
+ tx.stageDelete(recordedPath, { expectedBeforeHash: beforeHash });
344
+ prunedPaths.add(recordedPath);
320
345
  hasModifications = true;
321
346
  }
322
347
  lockfileManager.removeManagedFile(lockfileData, recordedPath);
@@ -332,21 +357,14 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
332
357
  for (const file of fs.readdirSync(cursorRulesDir)) {
333
358
  if (file.endsWith('.mdc')) {
334
359
  const relPath = toPosix(path.relative(absRoot, path.join(cursorRulesDir, file)));
335
- if (!activePaths.has(relPath)) {
360
+ if (!activePaths.has(relPath) && !prunedPaths.has(relPath)) {
336
361
  const isManagedInV1 = Boolean(v1Data?.files?.[relPath]?.managed || v1Data?.managedFiles?.[relPath]?.managed);
337
362
  const isManagedInV2 = Boolean(lockfileData?.managedFiles?.[relPath]);
338
- let hasContextosMarker = false;
339
- try {
340
- const content = fs.readFileSync(path.join(cursorRulesDir, file), 'utf8');
341
- hasContextosMarker =
342
- content.includes('ContextOS rules for') ||
343
- content.includes('<!-- Auto-generated by: node .agents/ctx.js export cursor -->') ||
344
- content.includes('ContextOS — Project Operating System Rules') ||
345
- content.includes('contextos:generator');
346
- } catch {}
347
-
348
- if (isManagedInV1 || isManagedInV2 || hasContextosMarker) {
349
- tx.stageDelete(relPath);
363
+ if (isManagedInV1 || isManagedInV2) {
364
+ const beforeHash = computeExactHash(fs.readFileSync(path.join(absRoot, relPath)));
365
+ const expected = lockfileData?.managedFiles?.[relPath]?.exactSha256 || `sha256:${(v1Data?.files?.[relPath] || v1Data?.managedFiles?.[relPath])?.sha256}`;
366
+ if (beforeHash !== expected && computeSemanticHash(fs.readFileSync(path.join(absRoot, relPath))) !== expected) throw new Error(`Refusing to prune modified legacy artifact '${relPath}'.`);
367
+ tx.stageDelete(relPath, { expectedBeforeHash: beforeHash });
350
368
  hasModifications = true;
351
369
  if (v1Data?.files?.[relPath]) { delete v1Data.files[relPath]; hasModifications = true; }
352
370
  if (v1Data?.managedFiles?.[relPath]) { delete v1Data.managedFiles[relPath]; hasModifications = true; }
@@ -365,21 +383,14 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
365
383
  for (const file of fs.readdirSync(zedPromptsDir)) {
366
384
  if (file.endsWith('.md')) {
367
385
  const relPath = toPosix(path.relative(absRoot, path.join(zedPromptsDir, file)));
368
- if (!activePaths.has(relPath)) {
386
+ if (!activePaths.has(relPath) && !prunedPaths.has(relPath)) {
369
387
  const isManagedInV1 = Boolean(v1Data?.files?.[relPath]?.managed || v1Data?.managedFiles?.[relPath]?.managed);
370
388
  const isManagedInV2 = Boolean(lockfileData?.managedFiles?.[relPath]);
371
- let hasContextosMarker = false;
372
- try {
373
- const content = fs.readFileSync(path.join(zedPromptsDir, file), 'utf8');
374
- hasContextosMarker =
375
- content.includes('<!-- Auto-generated by: node .agents/ctx.js export zed -->') ||
376
- content.includes('# ContextOS —') ||
377
- content.includes('# ContextOS Master Prompt') ||
378
- content.includes('contextos:generator');
379
- } catch {}
380
-
381
- if (isManagedInV1 || isManagedInV2 || hasContextosMarker) {
382
- tx.stageDelete(relPath);
389
+ if (isManagedInV1 || isManagedInV2) {
390
+ const beforeHash = computeExactHash(fs.readFileSync(path.join(absRoot, relPath)));
391
+ const expected = lockfileData?.managedFiles?.[relPath]?.exactSha256 || `sha256:${(v1Data?.files?.[relPath] || v1Data?.managedFiles?.[relPath])?.sha256}`;
392
+ if (beforeHash !== expected && computeSemanticHash(fs.readFileSync(path.join(absRoot, relPath))) !== expected) throw new Error(`Refusing to prune modified legacy artifact '${relPath}'.`);
393
+ tx.stageDelete(relPath, { expectedBeforeHash: beforeHash });
383
394
  hasModifications = true;
384
395
  if (v1Data?.files?.[relPath]) { delete v1Data.files[relPath]; hasModifications = true; }
385
396
  if (v1Data?.managedFiles?.[relPath]) { delete v1Data.managedFiles[relPath]; hasModifications = true; }
@@ -392,10 +403,9 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
392
403
  }
393
404
 
394
405
  // Clean up empty/orphan skill directories in generated paths
395
- const cleanupDirs = [
396
- path.join(absRoot, '.agents', 'generated', 'gemini', 'skills'),
397
- path.join(absRoot, '.agents', 'generated', 'claude', 'skills'),
398
- ];
406
+ const cleanupDirs = ['gemini', 'claude']
407
+ .filter(name => activeGenerators.has(`${name}@2`))
408
+ .map(name => path.join(absRoot, '.agents', 'generated', name, 'skills'));
399
409
  for (const parentDir of cleanupDirs) {
400
410
  if (fs.existsSync(parentDir)) {
401
411
  for (const entry of fs.readdirSync(parentDir)) {
@@ -403,13 +413,26 @@ function applyArtifacts(projectRoot, artifacts, options = {}) {
403
413
  if (fs.statSync(entryPath).isDirectory()) {
404
414
  const relMd = toPosix(path.relative(absRoot, path.join(entryPath, 'SKILL.md')));
405
415
  if (!activePaths.has(relMd)) {
406
- // Delete orphaned files inside the directory, if any. The generator writes SKILL.md and optional references.
407
- for (const child of fs.readdirSync(entryPath)) {
408
- tx.stageDelete(toPosix(path.relative(absRoot, path.join(entryPath, child))));
409
- hasModifications = true;
410
- }
411
- const relDir = toPosix(path.relative(absRoot, entryPath));
412
- tx.stageDeleteDir(relDir);
416
+ const pruneDirectory = (dir) => {
417
+ for (const child of fs.readdirSync(dir, { withFileTypes: true })) {
418
+ const absChild = path.join(dir, child.name);
419
+ const relChild = toPosix(path.relative(absRoot, absChild));
420
+ if (child.isDirectory()) { pruneDirectory(absChild); continue; }
421
+ if (prunedPaths.has(relChild)) continue;
422
+ const v1Record = v1Data?.files?.[relChild] || v1Data?.managedFiles?.[relChild];
423
+ if (!v1Record?.managed) {
424
+ const err = new Error(`Refusing to prune untracked file '${relChild}' from an orphan skill.`);
425
+ err.code = 'CTX_ADAPTER_OWNERSHIP_CONFLICT';
426
+ throw err;
427
+ }
428
+ const before = fs.readFileSync(absChild);
429
+ const beforeHash = computeExactHash(before);
430
+ if (`sha256:${v1Record.sha256}` !== beforeHash && `sha256:${v1Record.sha256}` !== computeSemanticHash(before)) throw new Error(`Refusing to prune modified legacy resource '${relChild}'.`);
431
+ tx.stageDelete(relChild, { expectedBeforeHash: beforeHash });
432
+ }
433
+ tx.stageDeleteDir(toPosix(path.relative(absRoot, dir)));
434
+ };
435
+ pruneDirectory(entryPath);
413
436
  hasModifications = true;
414
437
  }
415
438
  }
@@ -115,6 +115,38 @@ function readMeaningfulMarkdown(filePath) {
115
115
  return content;
116
116
  }
117
117
 
118
+ /** Read the same manifest precedence and entrypoint used by the compiler. */
119
+ function readSkillManifest(skillDir) {
120
+ const manifestPath = ['skill.v2.yaml', 'skill.yaml'].map(name => path.join(skillDir, name)).find(file => fs.existsSync(file));
121
+ return manifestPath ? require('../compiler/vendor/yaml.js').parse(fs.readFileSync(manifestPath, 'utf8')) : {};
122
+ }
123
+
124
+ function getSkillEntrypointPath(skillDir) {
125
+ const manifest = readSkillManifest(skillDir);
126
+ return require('../filesystem/index.js').resolveManagedPath(skillDir, manifest?.entrypoint || 'SKILL.md').resolvedPath;
127
+ }
128
+
129
+ /** Copy declared resources beside exported entrypoints without inlining them. */
130
+ function renderSkillResources(skillDir, destinations, metadata) {
131
+ const manifest = readSkillManifest(skillDir);
132
+ const resources = manifest?.resources || [];
133
+ const { resolveManagedPath } = require('../filesystem/index.js');
134
+ const artifacts = [];
135
+ const seen = new Set();
136
+ for (const resource of resources) {
137
+ const rel = typeof resource === 'string' ? resource : resource.path;
138
+ if (rel === 'SKILL.md' || rel === manifest?.entrypoint || seen.has(rel)) continue;
139
+ seen.add(rel);
140
+ const { resolvedPath, relativePosixPath } = resolveManagedPath(skillDir, rel);
141
+ if (!fs.statSync(resolvedPath).isFile()) throw new Error(`Skill resource must be a file: ${rel}`);
142
+ for (const destination of destinations) {
143
+ const mediaType = /\.(md|txt|js|ts|py)$/i.test(rel) ? 'text/plain' : /\.ya?ml$/i.test(rel) ? 'application/yaml' : /\.json$/i.test(rel) ? 'text/plain' : 'application/octet-stream';
144
+ artifacts.push({ ...metadata, path: `${destination}/${relativePosixPath}`, content: fs.readFileSync(resolvedPath), mediaType });
145
+ }
146
+ }
147
+ return artifacts;
148
+ }
149
+
118
150
  module.exports = {
119
151
  AGENTS_DIR,
120
152
  AGENTS_MD_PATH,
@@ -124,5 +156,7 @@ module.exports = {
124
156
  readMeaningfulMarkdown,
125
157
  resetDirectory,
126
158
  stripFrontmatter,
159
+ renderSkillResources,
160
+ readSkillManifest,
161
+ getSkillEntrypointPath,
127
162
  };
128
-
@@ -5,7 +5,7 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const { AGENTS_MD_PATH, collectSkillDirectories, extractYamlField, stripFrontmatter } = require('../shared.js');
8
+ const { getSkillEntrypointPath, AGENTS_MD_PATH, collectSkillDirectories, extractYamlField, stripFrontmatter } = require('../shared.js');
9
9
  const { registerAdapter, applyArtifacts, createProvenanceHeader } = require('../pure-compiler.js');
10
10
 
11
11
  const GENERATOR_ID = 'zed@2';
@@ -21,7 +21,7 @@ function describe() {
21
21
 
22
22
  function buildSkillSection(skillDir) {
23
23
  const skillName = path.basename(skillDir);
24
- const skillMdPath = path.join(skillDir, 'SKILL.md');
24
+ const skillMdPath = getSkillEntrypointPath(skillDir);
25
25
  const yamlPath = path.join(skillDir, 'skill.yaml');
26
26
 
27
27
  let title = skillName;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "compilerVersion": "2.0.0",
4
- "sourceGraphHash": "sha256:938c5051454535087e03f26af35c434cf194f414d2ec8044b4ae754e8524cf37",
4
+ "sourceGraphHash": "sha256:efcac8692b9208232aa09a065009438cd7b151e7f4d19366d75b699645cddf94",
5
5
  "skills": {
6
6
  "context-manager": {
7
7
  "id": "context-manager",
@@ -67,7 +67,7 @@
67
67
  "type": "compiler",
68
68
  "entrypoint": "SKILL.md",
69
69
  "entrypointHash": "sha256:ab14bcd603573c975a69cb95b5ef8a305de8eb888c81c20c994121313f017239",
70
- "resourceTreeHash": "sha256:48568f01d27fa53e2e01a6a63afd60eb2d43cabdc9eb2cfe6f02a2b67d0f53e1",
70
+ "resourceTreeHash": "sha256:1f9422adff6b9e69772dfeff2e1c9c9f8615b5fae56f8b60d2d7bef4a5c1c7fb",
71
71
  "estimatedTokens": 1365,
72
72
  "source": ".agents/core/skills/context-os/skill.yaml",
73
73
  "signals": {
@@ -133,13 +133,13 @@
133
133
  "id": "engineering-workflow",
134
134
  "displayName": "engineering-workflow",
135
135
  "version": "1.0.0",
136
- "description": "Senior engineering workflow skill inspired by Addy Osmani's agent-skills. Enforces the full development lifecycle: spec → plan → build → test → review → ship. AI must never write code before a spec and plan are approved.\n",
136
+ "description": "Scope implementation work, verify behavior, and report evidence using a proportional lifecycle.",
137
137
  "category": "general",
138
138
  "type": "instruction-only",
139
139
  "entrypoint": "SKILL.md",
140
- "entrypointHash": "sha256:88b606d086f63c5e293630ad84ff524d2542f2953fc1a5a451bd77352073b0ad",
141
- "resourceTreeHash": "sha256:e78a054d8f54a72550cf10d551a82f56015aedb3f16a14021e654574bad5804f",
142
- "estimatedTokens": 3083,
140
+ "entrypointHash": "sha256:e729b3bc21cecef2a39b64f0b301715974697600524629fa05609fd2d9175b48",
141
+ "resourceTreeHash": "sha256:6371d58615c6b5ba47c1523f6f99c106dcd9d99c5abc30338b4a3504271a23f1",
142
+ "estimatedTokens": 418,
143
143
  "source": ".agents/core/skills/engineering-workflow/skill.yaml",
144
144
  "signals": {
145
145
  "aliases": [
@@ -156,7 +156,7 @@
156
156
  },
157
157
  "context": {
158
158
  "priority": 50,
159
- "estimatedTokens": 3083
159
+ "estimatedTokens": 418
160
160
  },
161
161
  "resources": [
162
162
  {
@@ -174,6 +174,10 @@
174
174
  {
175
175
  "path": "VALIDATION.json",
176
176
  "mode": "on-demand"
177
+ },
178
+ {
179
+ "path": "references/workflow.md",
180
+ "mode": "on-demand"
177
181
  }
178
182
  ],
179
183
  "deprecated": false,
@@ -235,13 +239,13 @@
235
239
  "id": "gstack-roles",
236
240
  "displayName": "gstack-roles",
237
241
  "version": "1.0.0",
238
- "description": "Role-based AI specialist system inspired by Garry Tan's gstack. Defines 23 specialist roles (CEO, Eng Manager, Designer, QA, Security etc.) and teaches the AI to adopt the correct role before each task phase.\n",
242
+ "description": "Compatibility alias for engineering-workflow with optional specialist review perspectives.",
239
243
  "category": "general",
240
244
  "type": "instruction-only",
241
245
  "entrypoint": "SKILL.md",
242
- "entrypointHash": "sha256:09334359de594f9c3e667ae0660db9a5c977c583b25ccf7de71fe25b7134454e",
243
- "resourceTreeHash": "sha256:0bcfb511ef61a6782e3394c0779e5f5eed1833aff6e24c7749957df209c6185a",
244
- "estimatedTokens": 1791,
246
+ "entrypointHash": "sha256:baeb5f187b8ea491f4854e6287c29d5c58b436c327e8df46c04651e6deb43513",
247
+ "resourceTreeHash": "sha256:d720b3727681e63f5212e97687b9ad32326685f22493214edc8750a3f504347a",
248
+ "estimatedTokens": 343,
245
249
  "source": ".agents/core/skills/gstack-roles/skill.yaml",
246
250
  "signals": {
247
251
  "aliases": [
@@ -258,7 +262,7 @@
258
262
  },
259
263
  "context": {
260
264
  "priority": 50,
261
- "estimatedTokens": 1791
265
+ "estimatedTokens": 343
262
266
  },
263
267
  "resources": [
264
268
  {
@@ -276,6 +280,10 @@
276
280
  {
277
281
  "path": "VALIDATION.json",
278
282
  "mode": "on-demand"
283
+ },
284
+ {
285
+ "path": "references/roles.md",
286
+ "mode": "on-demand"
279
287
  }
280
288
  ],
281
289
  "deprecated": true,
@@ -286,13 +294,13 @@
286
294
  "id": "ponytail-mindset",
287
295
  "displayName": "ponytail-mindset",
288
296
  "version": "1.0.0",
289
- "description": "Minimalist coding mindset based on DietrichGebert/ponytail. Teaches the AI to write only what is strictly necessary. Uses a 7-rung ladder: YAGNI → reuse → stdlib → platform → deps → one-liner → minimum. Minimizes unnecessary boilerplate and over-engineering while keeping all safety, validation and security guards.\n",
297
+ "description": "Choose a minimal maintainable implementation for substantive Build tasks while preserving safety and verification.",
290
298
  "category": "general",
291
299
  "type": "instruction-only",
292
300
  "entrypoint": "SKILL.md",
293
- "entrypointHash": "sha256:122a01d74edfebcfe18e163242a038d61c943f3320b582723eb1a185b6ee53d6",
294
- "resourceTreeHash": "sha256:a181af2582fd80a893c52db6a59ceb038c52dbb310fffd99f0f8f7a55a568990",
295
- "estimatedTokens": 1812,
301
+ "entrypointHash": "sha256:837795e41d09af94040fb9ae318126c6ff3a24bf35b43bbf4cb2888a5067a950",
302
+ "resourceTreeHash": "sha256:3a4d757373ad00ae321c25d0774d35d0f8c9f25b1e95d58303ffb68d0128fd59",
303
+ "estimatedTokens": 413,
296
304
  "source": ".agents/core/skills/ponytail-mindset/skill.yaml",
297
305
  "signals": {
298
306
  "aliases": [
@@ -309,7 +317,7 @@
309
317
  },
310
318
  "context": {
311
319
  "priority": 50,
312
- "estimatedTokens": 1812
320
+ "estimatedTokens": 413
313
321
  },
314
322
  "resources": [
315
323
  {
@@ -327,6 +335,10 @@
327
335
  {
328
336
  "path": "VALIDATION.json",
329
337
  "mode": "on-demand"
338
+ },
339
+ {
340
+ "path": "references/minimalism.md",
341
+ "mode": "on-demand"
330
342
  }
331
343
  ],
332
344
  "deprecated": false,
@@ -337,7 +349,7 @@
337
349
  "id": "security",
338
350
  "displayName": "Application Security",
339
351
  "version": "1.0.0",
340
- "description": "ContextOS skill for Application Security",
352
+ "description": "Protect authentication, authorization, sensitive data, untrusted input, external integrations, and agent tool execution.",
341
353
  "category": "backend",
342
354
  "type": "instruction-only",
343
355
  "entrypoint": "SKILL.md",
@@ -1 +1 @@
1
- sha256:938c5051454535087e03f26af35c434cf194f414d2ec8044b4ae754e8524cf37 registry.v2.json
1
+ sha256:efcac8692b9208232aa09a065009438cd7b151e7f4d19366d75b699645cddf94 registry.v2.json