@mrpatronz/nexusflow 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +80 -17
  2. package/dist/commands/ui.d.ts +1 -0
  3. package/dist/commands/ui.d.ts.map +1 -1
  4. package/dist/commands/ui.js +24 -13
  5. package/dist/commands/ui.js.map +1 -1
  6. package/dist/commands/ui.test.d.ts +2 -0
  7. package/dist/commands/ui.test.d.ts.map +1 -0
  8. package/dist/commands/ui.test.js +58 -0
  9. package/dist/commands/ui.test.js.map +1 -0
  10. package/dist/core/workspace-skills-workflow.integration.test.d.ts +2 -0
  11. package/dist/core/workspace-skills-workflow.integration.test.d.ts.map +1 -0
  12. package/dist/core/workspace-skills-workflow.integration.test.js +162 -0
  13. package/dist/core/workspace-skills-workflow.integration.test.js.map +1 -0
  14. package/dist/core/worktree.test.js +1 -1
  15. package/dist/core/worktree.test.js.map +1 -1
  16. package/dist/generators/index.d.ts.map +1 -1
  17. package/dist/generators/index.js +4 -8
  18. package/dist/generators/index.js.map +1 -1
  19. package/dist/generators/skills-generator.d.ts +2 -2
  20. package/dist/generators/skills-generator.d.ts.map +1 -1
  21. package/dist/generators/skills-generator.js +84 -89
  22. package/dist/generators/skills-generator.js.map +1 -1
  23. package/dist/generators/skills-generator.test.d.ts +2 -0
  24. package/dist/generators/skills-generator.test.d.ts.map +1 -0
  25. package/dist/generators/skills-generator.test.js +79 -0
  26. package/dist/generators/skills-generator.test.js.map +1 -0
  27. package/dist/gui/assets/AgentsPage-BtrTgIyU.js +3 -0
  28. package/dist/gui/assets/{DashboardPage-CUspfCap.js → DashboardPage-CeUkMdxc.js} +1 -1
  29. package/dist/gui/assets/{GettingStartedPage-BqtgARZW.js → GettingStartedPage-YwJXpsc6.js} +1 -1
  30. package/dist/gui/assets/ProjectsPage-D8kEHb-m.js +1 -0
  31. package/dist/gui/assets/ScaffoldRepoInline-ij7JLzmx.js +1 -0
  32. package/dist/gui/assets/SettingsPage-BQhAP4go.js +1 -0
  33. package/dist/gui/assets/SkillsPage-DJ-dXfcC.js +6 -0
  34. package/dist/gui/assets/StartWorkPage-DGWZzF4H.js +1 -0
  35. package/dist/gui/assets/{StrategiesPage-D0Y6A4Tw.js → StrategiesPage-CLOjDRbX.js} +2 -2
  36. package/dist/gui/assets/WorkspaceLauncher-CjNI4vqn.js +1 -0
  37. package/dist/gui/assets/WorkspacesPage-MDzm2JUh.js +6 -0
  38. package/dist/gui/assets/alert-dialog-DjwFcE6b.js +1 -0
  39. package/dist/gui/assets/bot-wH0FKlic.js +1 -0
  40. package/dist/gui/assets/chevron-down-DDmhvRLr.js +1 -0
  41. package/dist/gui/assets/code-xml-Chu1p81J.js +1 -0
  42. package/dist/gui/assets/index-CfQEnn52.js +52 -0
  43. package/dist/gui/assets/index-k7inKaui.css +2 -0
  44. package/dist/gui/assets/{spinner-DXt6mnJk.js → spinner-BCEaGbib.js} +1 -1
  45. package/dist/gui/assets/terminal-gK6BMl3E.js +1 -0
  46. package/dist/gui/assets/{textarea-BLU5HXmZ.js → textarea-D3-elOAa.js} +1 -1
  47. package/dist/gui/assets/{useAriaLabelledBy-CjNlctb9.js → useAriaLabelledBy-DmwmZIqb.js} +1 -1
  48. package/dist/gui/assets/useCompositeListItem-CZnlZ-eF.js +1 -0
  49. package/dist/gui/assets/useValueChanged-BfI2s7NX.js +1 -0
  50. package/dist/gui/assets/zap-FQrDnrgb.js +1 -0
  51. package/dist/gui/index.html +6 -4
  52. package/dist/resources/agents-catalog.d.ts +7 -0
  53. package/dist/resources/agents-catalog.d.ts.map +1 -0
  54. package/dist/resources/agents-catalog.js +141 -0
  55. package/dist/resources/agents-catalog.js.map +1 -0
  56. package/dist/resources/agents-catalog.test.d.ts +2 -0
  57. package/dist/resources/agents-catalog.test.d.ts.map +1 -0
  58. package/dist/resources/agents-catalog.test.js +50 -0
  59. package/dist/resources/agents-catalog.test.js.map +1 -0
  60. package/dist/resources/codex-agent.d.ts +6 -0
  61. package/dist/resources/codex-agent.d.ts.map +1 -0
  62. package/dist/resources/codex-agent.js +55 -0
  63. package/dist/resources/codex-agent.js.map +1 -0
  64. package/dist/resources/codex-agent.test.d.ts +2 -0
  65. package/dist/resources/codex-agent.test.d.ts.map +1 -0
  66. package/dist/resources/codex-agent.test.js +27 -0
  67. package/dist/resources/codex-agent.test.js.map +1 -0
  68. package/dist/resources/contracts.d.ts +116 -0
  69. package/dist/resources/contracts.d.ts.map +1 -0
  70. package/dist/resources/contracts.js +103 -0
  71. package/dist/resources/contracts.js.map +1 -0
  72. package/dist/resources/fs-safety.d.ts +11 -0
  73. package/dist/resources/fs-safety.d.ts.map +1 -0
  74. package/dist/resources/fs-safety.js +77 -0
  75. package/dist/resources/fs-safety.js.map +1 -0
  76. package/dist/resources/materializer.d.ts +17 -0
  77. package/dist/resources/materializer.d.ts.map +1 -0
  78. package/dist/resources/materializer.js +439 -0
  79. package/dist/resources/materializer.js.map +1 -0
  80. package/dist/resources/materializer.test.d.ts +2 -0
  81. package/dist/resources/materializer.test.d.ts.map +1 -0
  82. package/dist/resources/materializer.test.js +149 -0
  83. package/dist/resources/materializer.test.js.map +1 -0
  84. package/dist/resources/service.d.ts +8 -0
  85. package/dist/resources/service.d.ts.map +1 -0
  86. package/dist/resources/service.js +45 -0
  87. package/dist/resources/service.js.map +1 -0
  88. package/dist/resources/service.test.d.ts +2 -0
  89. package/dist/resources/service.test.d.ts.map +1 -0
  90. package/dist/resources/service.test.js +58 -0
  91. package/dist/resources/service.test.js.map +1 -0
  92. package/dist/server.d.ts +5 -0
  93. package/dist/server.d.ts.map +1 -1
  94. package/dist/server.js +310 -4
  95. package/dist/server.js.map +1 -1
  96. package/dist/server.test.js +365 -5
  97. package/dist/server.test.js.map +1 -1
  98. package/dist/types.d.ts +107 -0
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/resources.d.ts +2 -1
  101. package/dist/utils/resources.d.ts.map +1 -1
  102. package/dist/utils/resources.js +4 -3
  103. package/dist/utils/resources.js.map +1 -1
  104. package/dist/utils/session-finder.d.ts +6 -0
  105. package/dist/utils/session-finder.d.ts.map +1 -1
  106. package/dist/utils/session-finder.js +56 -0
  107. package/dist/utils/session-finder.js.map +1 -1
  108. package/dist/utils/session-finder.test.js +34 -1
  109. package/dist/utils/session-finder.test.js.map +1 -1
  110. package/dist/utils/skills-catalog.d.ts +77 -0
  111. package/dist/utils/skills-catalog.d.ts.map +1 -0
  112. package/dist/utils/skills-catalog.js +871 -0
  113. package/dist/utils/skills-catalog.js.map +1 -0
  114. package/dist/utils/skills-catalog.test.d.ts +2 -0
  115. package/dist/utils/skills-catalog.test.d.ts.map +1 -0
  116. package/dist/utils/skills-catalog.test.js +281 -0
  117. package/dist/utils/skills-catalog.test.js.map +1 -0
  118. package/dist/utils/workspace-launch.d.ts.map +1 -1
  119. package/dist/utils/workspace-launch.js +22 -1
  120. package/dist/utils/workspace-launch.js.map +1 -1
  121. package/dist/utils/workspace-launch.test.js +36 -2
  122. package/dist/utils/workspace-launch.test.js.map +1 -1
  123. package/package.json +4 -1
  124. package/dist/gui/assets/ProjectsPage-UBoQxI2G.js +0 -1
  125. package/dist/gui/assets/ScaffoldRepoInline-DqoourJe.js +0 -1
  126. package/dist/gui/assets/SettingsPage-BZl24tqD.js +0 -1
  127. package/dist/gui/assets/StartWorkPage-BGNFdBfj.js +0 -1
  128. package/dist/gui/assets/WorkspaceLauncher-Cxv0O_wt.js +0 -1
  129. package/dist/gui/assets/WorkspacesPage-Cvb1rOeh.js +0 -6
  130. package/dist/gui/assets/index-CllPiiFL.js +0 -52
  131. package/dist/gui/assets/index-DGCBhMAn.css +0 -2
  132. package/dist/gui/assets/useCompositeListItem-DWGFLKqA.js +0 -1
  133. /package/dist/{resources → static-resources}/workflows/plan-implement-review.md +0 -0
  134. /package/dist/{resources → static-resources}/workflows/research-verify.md +0 -0
  135. /package/dist/{resources → static-resources}/workflows/solo-developer.md +0 -0
@@ -0,0 +1,439 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import fse from 'fs-extra';
5
+ import { acquireLock, createMutationQueue } from '../core/locks.js';
6
+ import { parseSkillMarkdown, serializeSkillMarkdown } from '../utils/skills-catalog.js';
7
+ import { serializeCodexAgentToml } from './codex-agent.js';
8
+ import { formatValidationError, codexAgentNameSchema, resourceIdSchema, resourceLockSchema, skillFrontmatterSchema, } from './contracts.js';
9
+ import { assertNoLinkedPathComponents, assertPathIsNotLink, assertPathWithin, atomicWriteJson, } from './fs-safety.js';
10
+ const LOCK_FILE = path.join('.nexusflow', 'resources.lock.json');
11
+ const runMaterialization = createMutationQueue();
12
+ const PORTABLE_SKILL_ASSISTANTS = new Set(['codex', 'antigravity', 'cursor', 'copilot']);
13
+ const ALLOWED_SKILL_ROOT_ENTRIES = new Set(['SKILL.md', 'scripts', 'references', 'assets', 'agents']);
14
+ export class ResourceConflictError extends Error {
15
+ conflicts;
16
+ constructor(conflicts) {
17
+ super(`Resource materialization conflicts:\n- ${conflicts.join('\n- ')}`);
18
+ this.name = 'ResourceConflictError';
19
+ this.conflicts = conflicts;
20
+ }
21
+ }
22
+ function toPosix(relativePath) {
23
+ return relativePath.split(path.sep).join('/');
24
+ }
25
+ function hashBytes(bytes) {
26
+ return createHash('sha256').update(bytes).digest('hex');
27
+ }
28
+ function errorCode(error) {
29
+ return typeof error === 'object' && error !== null && 'code' in error
30
+ ? String(error.code)
31
+ : undefined;
32
+ }
33
+ async function hashFile(filePath) {
34
+ return hashBytes(await fs.readFile(filePath));
35
+ }
36
+ function portableMode(mode) {
37
+ return process.platform === 'win32' ? undefined : mode & 0o777;
38
+ }
39
+ async function fileMatchesOutput(filePath, output) {
40
+ if ((await hashFile(filePath)) !== output.hash)
41
+ return false;
42
+ if (output.mode === undefined)
43
+ return true;
44
+ return portableMode((await fs.stat(filePath)).mode) === output.mode;
45
+ }
46
+ function normalizeManagedPath(relativePath) {
47
+ if (path.isAbsolute(relativePath))
48
+ throw new Error(`Managed path must be relative: ${relativePath}`);
49
+ const normalized = path.normalize(relativePath);
50
+ if (normalized === '..' || normalized.startsWith(`..${path.sep}`)) {
51
+ throw new Error(`Managed path escapes the workspace: ${relativePath}`);
52
+ }
53
+ return toPosix(normalized);
54
+ }
55
+ async function walkSkillFiles(skill) {
56
+ if (!skill.sourcePath) {
57
+ const metadata = {
58
+ name: skill.id,
59
+ description: skill.description,
60
+ metadata: {
61
+ nexusflow: {
62
+ title: skill.title || skill.name,
63
+ category: skill.category,
64
+ tags: skill.tags || [],
65
+ },
66
+ },
67
+ };
68
+ return [{
69
+ relativePath: 'SKILL.md',
70
+ bytes: Buffer.from(serializeSkillMarkdown(metadata, skill.content), 'utf-8'),
71
+ mode: process.platform === 'win32' ? undefined : 0o644,
72
+ }];
73
+ }
74
+ const sourceRoot = path.resolve(skill.sourcePath);
75
+ await assertPathIsNotLink(sourceRoot);
76
+ const entries = await fs.readdir(sourceRoot, { withFileTypes: true });
77
+ for (const entry of entries) {
78
+ if (!ALLOWED_SKILL_ROOT_ENTRIES.has(entry.name)) {
79
+ throw new Error(`Unsupported top-level entry in skill "${skill.id}": ${entry.name}`);
80
+ }
81
+ }
82
+ const files = [];
83
+ async function walk(directory) {
84
+ await assertNoLinkedPathComponents(sourceRoot, directory);
85
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
86
+ if (entry.isSymbolicLink()) {
87
+ throw new Error(`Linked files are not allowed in managed skills: ${entry.name}`);
88
+ }
89
+ const absolute = path.join(directory, entry.name);
90
+ if (entry.isDirectory()) {
91
+ await walk(absolute);
92
+ }
93
+ else if (entry.isFile()) {
94
+ const relativePath = toPosix(path.relative(sourceRoot, absolute));
95
+ let bytes = await fs.readFile(absolute);
96
+ if (relativePath === 'SKILL.md') {
97
+ const parsed = parseSkillMarkdown(bytes.toString('utf-8'));
98
+ const validated = skillFrontmatterSchema.safeParse(parsed.metadata);
99
+ if (!validated.success) {
100
+ throw new Error(`Invalid SKILL.md for "${skill.id}": ${formatValidationError(validated.error)}`);
101
+ }
102
+ const portableMetadata = {
103
+ name: validated.data.name,
104
+ description: validated.data.description,
105
+ };
106
+ if (validated.data.license)
107
+ portableMetadata.license = validated.data.license;
108
+ if (validated.data.compatibility)
109
+ portableMetadata.compatibility = validated.data.compatibility;
110
+ if (validated.data.metadata)
111
+ portableMetadata.metadata = validated.data.metadata;
112
+ if (validated.data['allowed-tools'])
113
+ portableMetadata['allowed-tools'] = validated.data['allowed-tools'];
114
+ bytes = Buffer.from(serializeSkillMarkdown(portableMetadata, parsed.content), 'utf-8');
115
+ }
116
+ files.push({ relativePath, bytes, mode: portableMode((await fs.stat(absolute)).mode) });
117
+ }
118
+ }
119
+ }
120
+ await walk(sourceRoot);
121
+ if (!files.some((file) => file.relativePath === 'SKILL.md')) {
122
+ throw new Error(`Skill "${skill.id}" is missing SKILL.md.`);
123
+ }
124
+ return files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
125
+ }
126
+ async function buildDesiredFiles(assistants, skills, agents) {
127
+ const desired = new Map();
128
+ for (const skill of skills) {
129
+ const skillId = resourceIdSchema.safeParse(skill.id);
130
+ if (!skillId.success || skillId.data !== skill.name) {
131
+ throw new Error(skillId.success
132
+ ? `Skill id and name must match: ${skill.id}`
133
+ : `Invalid skill identity: ${formatValidationError(skillId.error)}`);
134
+ }
135
+ const sourceFiles = await walkSkillFiles(skill);
136
+ const roots = new Map();
137
+ if (assistants.some((assistant) => PORTABLE_SKILL_ASSISTANTS.has(assistant))) {
138
+ roots.set(path.join('.agents', 'skills', skill.id), 'agent-skill-v1');
139
+ }
140
+ if (assistants.includes('claude')) {
141
+ roots.set(path.join('.claude', 'skills', skill.id), 'claude-skill-v1');
142
+ }
143
+ for (const [root, adapter] of roots) {
144
+ for (const sourceFile of sourceFiles) {
145
+ const outputPath = normalizeManagedPath(path.join(root, sourceFile.relativePath));
146
+ const item = {
147
+ kind: 'skill',
148
+ resourceId: skill.id,
149
+ adapter,
150
+ path: outputPath,
151
+ hash: hashBytes(sourceFile.bytes),
152
+ mode: sourceFile.mode,
153
+ bytes: sourceFile.bytes,
154
+ resourceRoot: normalizeManagedPath(root),
155
+ };
156
+ const existing = desired.get(outputPath);
157
+ if (existing && existing.hash !== item.hash) {
158
+ throw new Error(`Multiple resources produce different content for ${outputPath}.`);
159
+ }
160
+ desired.set(outputPath, item);
161
+ }
162
+ }
163
+ }
164
+ if (assistants.includes('codex')) {
165
+ for (const agent of agents) {
166
+ const agentId = codexAgentNameSchema.safeParse(agent.id);
167
+ if (!agentId.success || agentId.data !== agent.name) {
168
+ throw new Error(agentId.success
169
+ ? `Agent id and name must match: ${agent.id}`
170
+ : `Invalid agent identity: ${formatValidationError(agentId.error)}`);
171
+ }
172
+ const bytes = Buffer.from(serializeCodexAgentToml(agent), 'utf-8');
173
+ const outputPath = normalizeManagedPath(path.join('.codex', 'agents', `${agent.id}.toml`));
174
+ desired.set(outputPath, {
175
+ kind: 'codex-agent',
176
+ resourceId: agent.id,
177
+ adapter: 'codex-agent-v1',
178
+ path: outputPath,
179
+ hash: hashBytes(bytes),
180
+ mode: process.platform === 'win32' ? undefined : 0o644,
181
+ bytes,
182
+ resourceRoot: outputPath,
183
+ });
184
+ }
185
+ }
186
+ return [...desired.values()].sort((a, b) => a.path.localeCompare(b.path));
187
+ }
188
+ async function loadLock(workspacePath) {
189
+ const lockPath = path.join(workspacePath, LOCK_FILE);
190
+ if (!(await fse.pathExists(lockPath)))
191
+ return { schemaVersion: 1, outputs: [] };
192
+ const parsed = resourceLockSchema.safeParse(JSON.parse(await fs.readFile(lockPath, 'utf-8')));
193
+ if (!parsed.success)
194
+ throw new Error(`Invalid workspace resource ownership lock: ${parsed.error.message}`);
195
+ for (const output of parsed.data.outputs) {
196
+ const normalizedPath = normalizeManagedPath(output.path);
197
+ const expectedRoot = managedResourceRoot(output);
198
+ const isValidPath = output.kind === 'codex-agent'
199
+ ? normalizedPath === expectedRoot
200
+ : normalizedPath.startsWith(`${expectedRoot}/`) && normalizedPath.length > expectedRoot.length + 1;
201
+ if (!isValidPath) {
202
+ throw new Error(`Invalid workspace resource ownership path: ${output.path}`);
203
+ }
204
+ }
205
+ return parsed.data;
206
+ }
207
+ function managedResourceRoot(output) {
208
+ if (output.kind === 'skill' && output.adapter === 'agent-skill-v1') {
209
+ return `.agents/skills/${output.resourceId}`;
210
+ }
211
+ if (output.kind === 'skill' && output.adapter === 'claude-skill-v1') {
212
+ return `.claude/skills/${output.resourceId}`;
213
+ }
214
+ if (output.kind === 'codex-agent' && output.adapter === 'codex-agent-v1') {
215
+ return `.codex/agents/${output.resourceId}.toml`;
216
+ }
217
+ throw new Error(`Invalid resource kind/adapter combination for ${output.path}.`);
218
+ }
219
+ function managedOutputDirectories(output) {
220
+ if (output.kind !== 'skill')
221
+ return [];
222
+ const root = managedResourceRoot(output);
223
+ const directories = [];
224
+ let current = path.posix.dirname(normalizeManagedPath(output.path));
225
+ while (current === root || current.startsWith(`${root}/`)) {
226
+ directories.push(current);
227
+ if (current === root)
228
+ break;
229
+ current = path.posix.dirname(current);
230
+ }
231
+ return directories;
232
+ }
233
+ async function listExistingTree(root) {
234
+ const files = [];
235
+ const directories = [];
236
+ const stat = await fs.lstat(root);
237
+ if (stat.isSymbolicLink())
238
+ throw new Error(`Linked managed resource roots are not allowed: ${root}`);
239
+ if (stat.isFile())
240
+ return { files: [root], directories };
241
+ if (!stat.isDirectory())
242
+ throw new Error(`Unsupported managed resource entry: ${root}`);
243
+ directories.push(root);
244
+ async function walk(directory) {
245
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
246
+ const absolute = path.join(directory, entry.name);
247
+ if (entry.isSymbolicLink()) {
248
+ throw new Error(`Linked files are not allowed in managed resource roots: ${absolute}`);
249
+ }
250
+ if (entry.isDirectory()) {
251
+ directories.push(absolute);
252
+ await walk(absolute);
253
+ }
254
+ else if (entry.isFile())
255
+ files.push(absolute);
256
+ else
257
+ throw new Error(`Unsupported managed resource entry: ${absolute}`);
258
+ }
259
+ }
260
+ await walk(root);
261
+ return { files, directories };
262
+ }
263
+ function oldOutputOwnsRoot(oldOutputs, root) {
264
+ const prefix = `${root.replaceAll('\\', '/')}/`;
265
+ return [...oldOutputs.keys()].some((item) => item === root || item.startsWith(prefix));
266
+ }
267
+ async function preflight(workspacePath, oldLock, desiredFiles) {
268
+ const conflicts = [];
269
+ const oldOutputs = new Map(oldLock.outputs.map((output) => [normalizeManagedPath(output.path), output]));
270
+ const oldOwnedDirectories = new Set(oldLock.outputs.flatMap(managedOutputDirectories));
271
+ for (const root of new Set(oldLock.outputs.map(managedResourceRoot))) {
272
+ const absoluteRoot = assertPathWithin(workspacePath, path.join(workspacePath, root));
273
+ await assertNoLinkedPathComponents(workspacePath, absoluteRoot);
274
+ if (!(await fse.pathExists(absoluteRoot)))
275
+ continue;
276
+ const existingTree = await listExistingTree(absoluteRoot);
277
+ for (const existingFile of existingTree.files) {
278
+ const relativePath = normalizeManagedPath(path.relative(workspacePath, existingFile));
279
+ if (!oldOutputs.has(relativePath)) {
280
+ conflicts.push(`${relativePath} exists inside a NexusFlow-managed resource but is not owned by NexusFlow`);
281
+ }
282
+ }
283
+ for (const existingDirectory of existingTree.directories) {
284
+ const relativePath = normalizeManagedPath(path.relative(workspacePath, existingDirectory));
285
+ if (!oldOwnedDirectories.has(relativePath)) {
286
+ conflicts.push(`${relativePath} exists inside a NexusFlow-managed resource but is not owned by NexusFlow`);
287
+ }
288
+ }
289
+ }
290
+ for (const root of new Set(desiredFiles.map((file) => file.resourceRoot))) {
291
+ const absoluteRoot = assertPathWithin(workspacePath, path.join(workspacePath, root));
292
+ await assertNoLinkedPathComponents(workspacePath, absoluteRoot);
293
+ if ((await fse.pathExists(absoluteRoot)) && !oldOutputOwnsRoot(oldOutputs, root)) {
294
+ conflicts.push(`${root} already exists and is not owned by NexusFlow`);
295
+ }
296
+ }
297
+ for (const desired of desiredFiles) {
298
+ const absolute = assertPathWithin(workspacePath, path.join(workspacePath, desired.path));
299
+ await assertNoLinkedPathComponents(workspacePath, absolute);
300
+ if (!(await fse.pathExists(absolute)))
301
+ continue;
302
+ const old = oldOutputs.get(desired.path);
303
+ if (!old) {
304
+ conflicts.push(`${desired.path} already exists and is not owned by NexusFlow`);
305
+ continue;
306
+ }
307
+ if (!(await fileMatchesOutput(absolute, old))) {
308
+ conflicts.push(`${desired.path} was modified after NexusFlow installed it`);
309
+ }
310
+ }
311
+ const desiredPaths = new Set(desiredFiles.map((file) => file.path));
312
+ for (const old of oldLock.outputs) {
313
+ const oldPath = normalizeManagedPath(old.path);
314
+ if (desiredPaths.has(oldPath))
315
+ continue;
316
+ const absolute = assertPathWithin(workspacePath, path.join(workspacePath, oldPath));
317
+ await assertNoLinkedPathComponents(workspacePath, absolute);
318
+ if ((await fse.pathExists(absolute)) && !(await fileMatchesOutput(absolute, old))) {
319
+ conflicts.push(`${oldPath} was modified and cannot be removed safely`);
320
+ }
321
+ }
322
+ if (conflicts.length)
323
+ throw new ResourceConflictError([...new Set(conflicts)]);
324
+ }
325
+ export async function reconcileWorkspaceResources(workspacePath, assistants, skills, agents, options = {}) {
326
+ return runMaterialization(async () => {
327
+ const canonicalWorkspace = await fs.realpath(workspacePath);
328
+ const nexusflowDir = path.join(canonicalWorkspace, '.nexusflow');
329
+ if (await fse.pathExists(nexusflowDir)) {
330
+ await assertNoLinkedPathComponents(canonicalWorkspace, nexusflowDir);
331
+ }
332
+ else {
333
+ await fse.ensureDir(nexusflowDir);
334
+ }
335
+ await assertPathIsNotLink(nexusflowDir);
336
+ const release = await acquireLock(path.join(nexusflowDir, 'resource-materialization.lock'), {
337
+ staleMs: 120_000,
338
+ timeoutMs: 30_000,
339
+ timeoutMessage: 'Timed out waiting for workspace resource materialization.',
340
+ });
341
+ try {
342
+ const oldLock = await loadLock(canonicalWorkspace);
343
+ const desiredFiles = await buildDesiredFiles(assistants, skills, agents);
344
+ await preflight(canonicalWorkspace, oldLock, desiredFiles);
345
+ const oldOutputs = new Map(oldLock.outputs.map((output) => [normalizeManagedPath(output.path), output]));
346
+ const desiredPaths = new Set(desiredFiles.map((file) => file.path));
347
+ const operationRoot = path.join(nexusflowDir, `resource-staging-${randomUUID()}`);
348
+ const stagedRoot = path.join(operationRoot, 'staged');
349
+ const backupRoot = path.join(operationRoot, 'backup');
350
+ await fse.ensureDir(stagedRoot);
351
+ const writes = [];
352
+ const unchanged = [];
353
+ for (const desired of desiredFiles) {
354
+ const absolute = path.join(canonicalWorkspace, desired.path);
355
+ if ((await fse.pathExists(absolute)) && (await fileMatchesOutput(absolute, desired))) {
356
+ unchanged.push(desired.path);
357
+ continue;
358
+ }
359
+ const staged = path.join(stagedRoot, desired.path);
360
+ await fse.ensureDir(path.dirname(staged));
361
+ await fs.writeFile(staged, desired.bytes);
362
+ if (desired.mode !== undefined)
363
+ await fs.chmod(staged, desired.mode);
364
+ writes.push(desired);
365
+ }
366
+ const removals = oldLock.outputs.filter((old) => !desiredPaths.has(normalizeManagedPath(old.path)));
367
+ const committed = [];
368
+ const rename = options.rename ?? fs.rename;
369
+ try {
370
+ for (const desired of writes) {
371
+ const target = path.join(canonicalWorkspace, desired.path);
372
+ await assertNoLinkedPathComponents(canonicalWorkspace, path.dirname(target));
373
+ await fse.ensureDir(path.dirname(target));
374
+ await assertNoLinkedPathComponents(canonicalWorkspace, path.dirname(target));
375
+ let backup;
376
+ if (await fse.pathExists(target)) {
377
+ backup = path.join(backupRoot, desired.path);
378
+ await fse.ensureDir(path.dirname(backup));
379
+ await rename(target, backup);
380
+ }
381
+ committed.push({ target, backup, wasRemoval: false });
382
+ await rename(path.join(stagedRoot, desired.path), target);
383
+ }
384
+ for (const removal of removals) {
385
+ const target = path.join(canonicalWorkspace, normalizeManagedPath(removal.path));
386
+ if (!(await fse.pathExists(target)))
387
+ continue;
388
+ const backup = path.join(backupRoot, normalizeManagedPath(removal.path));
389
+ await fse.ensureDir(path.dirname(backup));
390
+ await rename(target, backup);
391
+ committed.push({ target, backup, wasRemoval: true });
392
+ }
393
+ const removableDirectories = new Set(removals.flatMap(managedOutputDirectories));
394
+ for (const directory of [...removableDirectories].sort((a, b) => b.length - a.length)) {
395
+ const absolute = assertPathWithin(canonicalWorkspace, path.join(canonicalWorkspace, directory));
396
+ try {
397
+ await fs.rmdir(absolute);
398
+ }
399
+ catch (error) {
400
+ if (!['ENOENT', 'ENOTEMPTY', 'EEXIST'].includes(errorCode(error) ?? ''))
401
+ throw error;
402
+ }
403
+ }
404
+ const newLock = {
405
+ schemaVersion: 1,
406
+ outputs: desiredFiles.map(({ bytes: _bytes, resourceRoot: _root, ...output }) => output),
407
+ };
408
+ await atomicWriteJson(path.join(canonicalWorkspace, LOCK_FILE), newLock);
409
+ }
410
+ catch (error) {
411
+ for (const item of committed.reverse()) {
412
+ if (!item.wasRemoval && (await fse.pathExists(item.target))) {
413
+ await fse.remove(item.target).catch(() => { });
414
+ }
415
+ if (item.backup && (await fse.pathExists(item.backup))) {
416
+ await fse.ensureDir(path.dirname(item.target));
417
+ await rename(item.backup, item.target).catch(() => { });
418
+ }
419
+ }
420
+ throw error;
421
+ }
422
+ finally {
423
+ await fse.remove(operationRoot).catch(() => { });
424
+ }
425
+ const installed = writes.filter((file) => !oldOutputs.has(file.path)).map((file) => file.path);
426
+ const updated = writes.filter((file) => oldOutputs.has(file.path)).map((file) => file.path);
427
+ return {
428
+ installed,
429
+ updated,
430
+ removed: removals.map((output) => normalizeManagedPath(output.path)),
431
+ unchanged,
432
+ };
433
+ }
434
+ finally {
435
+ await release();
436
+ }
437
+ });
438
+ }
439
+ //# sourceMappingURL=materializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materializer.js","sourceRoot":"","sources":["../../src/resources/materializer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=materializer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materializer.test.d.ts","sourceRoot":"","sources":["../../src/resources/materializer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,149 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import * as fs from 'node:fs/promises';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import fse from 'fs-extra';
6
+ import { reconcileWorkspaceResources, ResourceConflictError } from './materializer.js';
7
+ const skill = {
8
+ id: 'portable-skill',
9
+ name: 'portable-skill',
10
+ title: 'Portable Skill',
11
+ category: 'general',
12
+ description: 'Use when testing portable skill materialization.',
13
+ content: '# Portable skill\n\nFollow the test instructions.',
14
+ custom: false,
15
+ };
16
+ const agent = {
17
+ id: 'code_reviewer',
18
+ name: 'code_reviewer',
19
+ category: 'general',
20
+ description: 'Use when reviewing a fixed diff.',
21
+ developerInstructions: 'Review correctness and security.',
22
+ sandboxMode: 'read-only',
23
+ custom: true,
24
+ };
25
+ describe('workspace resource materializer', () => {
26
+ let workspace;
27
+ beforeEach(async () => {
28
+ workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'nexusflow-materializer-'));
29
+ });
30
+ afterEach(async () => {
31
+ await fse.remove(workspace);
32
+ });
33
+ it('installs portable skills and Codex agents idempotently', async () => {
34
+ const first = await reconcileWorkspaceResources(workspace, ['codex', 'claude', 'cursor', 'copilot'], [skill], [agent]);
35
+ expect(first.installed).toContain('.agents/skills/portable-skill/SKILL.md');
36
+ expect(first.installed).toContain('.claude/skills/portable-skill/SKILL.md');
37
+ expect(first.installed).toContain('.codex/agents/code_reviewer.toml');
38
+ expect(await fse.pathExists(path.join(workspace, '.codex', 'skills'))).toBe(false);
39
+ expect(await fs.readFile(path.join(workspace, '.codex', 'agents', 'code_reviewer.toml'), 'utf-8')).toContain('developer_instructions');
40
+ const second = await reconcileWorkspaceResources(workspace, ['codex', 'claude', 'cursor', 'copilot'], [skill], [agent]);
41
+ expect(second.installed).toEqual([]);
42
+ expect(second.updated).toEqual([]);
43
+ expect(second.unchanged).toHaveLength(3);
44
+ });
45
+ it('removes only unchanged owned packages and permits reassignment', async () => {
46
+ await reconcileWorkspaceResources(workspace, ['codex'], [skill], [agent]);
47
+ const result = await reconcileWorkspaceResources(workspace, ['codex'], [], []);
48
+ expect(result.removed).toEqual(expect.arrayContaining([
49
+ '.agents/skills/portable-skill/SKILL.md',
50
+ '.codex/agents/code_reviewer.toml',
51
+ ]));
52
+ expect(await fse.pathExists(path.join(workspace, '.agents', 'skills', 'portable-skill', 'SKILL.md'))).toBe(false);
53
+ expect(await fse.pathExists(path.join(workspace, '.agents', 'skills', 'portable-skill'))).toBe(false);
54
+ expect(await fse.pathExists(path.join(workspace, '.codex', 'agents', 'code_reviewer.toml'))).toBe(false);
55
+ const reassigned = await reconcileWorkspaceResources(workspace, ['codex'], [skill], [agent]);
56
+ expect(reassigned.installed).toEqual(expect.arrayContaining([
57
+ '.agents/skills/portable-skill/SKILL.md',
58
+ '.codex/agents/code_reviewer.toml',
59
+ ]));
60
+ });
61
+ it('preserves modified managed files and reports drift before mutating anything', async () => {
62
+ await reconcileWorkspaceResources(workspace, ['codex'], [skill], [agent]);
63
+ const skillPath = path.join(workspace, '.agents', 'skills', 'portable-skill', 'SKILL.md');
64
+ const agentPath = path.join(workspace, '.codex', 'agents', 'code_reviewer.toml');
65
+ await fs.writeFile(skillPath, 'user edit', 'utf-8');
66
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [], [])).rejects.toBeInstanceOf(ResourceConflictError);
67
+ expect(await fs.readFile(skillPath, 'utf-8')).toBe('user edit');
68
+ expect(await fse.pathExists(agentPath)).toBe(true);
69
+ });
70
+ it('restores the old target and lock when installing a staged replacement fails', async () => {
71
+ await reconcileWorkspaceResources(workspace, ['codex'], [skill], []);
72
+ const target = path.join(workspace, '.agents', 'skills', 'portable-skill', 'SKILL.md');
73
+ const lockPath = path.join(workspace, '.nexusflow', 'resources.lock.json');
74
+ const beforeTarget = await fs.readFile(target, 'utf-8');
75
+ const beforeLock = await fs.readFile(lockPath, 'utf-8');
76
+ let renameCall = 0;
77
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [{ ...skill, content: '# Changed\n\nNew instructions.' }], [], {
78
+ rename: async (source, destination) => {
79
+ renameCall += 1;
80
+ if (renameCall === 2)
81
+ throw Object.assign(new Error('injected staged rename failure'), { code: 'EACCES' });
82
+ await fs.rename(source, destination);
83
+ },
84
+ })).rejects.toThrow(/injected staged rename failure/i);
85
+ expect(await fs.readFile(target, 'utf-8')).toBe(beforeTarget);
86
+ expect(await fs.readFile(lockPath, 'utf-8')).toBe(beforeLock);
87
+ });
88
+ it('rejects unmanaged files added inside an owned skill package', async () => {
89
+ await reconcileWorkspaceResources(workspace, ['codex'], [skill], []);
90
+ const extra = path.join(workspace, '.agents', 'skills', 'portable-skill', 'scripts', 'extra.sh');
91
+ await fse.ensureDir(path.dirname(extra));
92
+ await fs.writeFile(extra, 'echo unmanaged', 'utf-8');
93
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [skill], [])).rejects.toMatchObject({
94
+ conflicts: expect.arrayContaining([expect.stringContaining('is not owned by NexusFlow')]),
95
+ });
96
+ expect(await fs.readFile(extra, 'utf-8')).toBe('echo unmanaged');
97
+ });
98
+ it('rejects unmanaged collisions with zero target mutations', async () => {
99
+ const target = path.join(workspace, '.agents', 'skills', 'portable-skill');
100
+ await fse.ensureDir(target);
101
+ await fs.writeFile(path.join(target, 'README.md'), 'user owned', 'utf-8');
102
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [skill], [])).rejects.toBeInstanceOf(ResourceConflictError);
103
+ expect(await fs.readFile(path.join(target, 'README.md'), 'utf-8')).toBe('user owned');
104
+ expect(await fse.pathExists(path.join(target, 'SKILL.md'))).toBe(false);
105
+ expect(await fse.pathExists(path.join(workspace, '.nexusflow', 'resources.lock.json'))).toBe(false);
106
+ });
107
+ it('rejects invalid identities before writing targets', async () => {
108
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [{ ...skill, id: '../../escape', name: '../../escape' }], [])).rejects.toThrow(/invalid skill identity/i);
109
+ expect(await fse.pathExists(path.join(workspace, 'escape'))).toBe(false);
110
+ });
111
+ it('normalizes legacy NexusFlow frontmatter without dropping package files', async () => {
112
+ const source = path.join(workspace, 'source-skill');
113
+ await fse.ensureDir(path.join(source, 'assets'));
114
+ await fs.writeFile(path.join(source, 'SKILL.md'), '---\nname: portable-skill\ndescription: Use when testing.\ntitle: Legacy title\ncategory: general\ntags: [test]\nmetadata:\n owner: team\n---\n\n# Instructions\n', 'utf-8');
115
+ await fs.writeFile(path.join(source, 'assets', 'fixture.txt'), 'fixture', 'utf-8');
116
+ await reconcileWorkspaceResources(workspace, ['codex'], [{ ...skill, sourcePath: source }], []);
117
+ const installed = await fs.readFile(path.join(workspace, '.agents', 'skills', 'portable-skill', 'SKILL.md'), 'utf-8');
118
+ expect(installed).not.toContain('title: Legacy title');
119
+ expect(installed).not.toContain('category: general');
120
+ expect(installed).toContain('owner: team');
121
+ expect(await fs.readFile(path.join(workspace, '.agents', 'skills', 'portable-skill', 'assets', 'fixture.txt'), 'utf-8')).toBe('fixture');
122
+ });
123
+ it.skipIf(process.platform === 'win32')('preserves executable script modes and detects permission drift', async () => {
124
+ const source = path.join(workspace, 'executable-skill');
125
+ const sourceScript = path.join(source, 'scripts', 'check.sh');
126
+ await fse.ensureDir(path.dirname(sourceScript));
127
+ await fs.writeFile(path.join(source, 'SKILL.md'), '---\nname: portable-skill\ndescription: Use when checking executable scripts.\n---\n\n# Check\n', 'utf-8');
128
+ await fs.writeFile(sourceScript, '#!/bin/sh\necho ok\n', 'utf-8');
129
+ await fs.chmod(sourceScript, 0o755);
130
+ await reconcileWorkspaceResources(workspace, ['codex'], [{ ...skill, sourcePath: source }], []);
131
+ const targetScript = path.join(workspace, '.agents', 'skills', 'portable-skill', 'scripts', 'check.sh');
132
+ expect((await fs.stat(targetScript)).mode & 0o777).toBe(0o755);
133
+ await fs.chmod(targetScript, 0o644);
134
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [{ ...skill, sourcePath: source }], [])).rejects.toBeInstanceOf(ResourceConflictError);
135
+ });
136
+ it('never trusts a tampered ownership lock to delete an arbitrary workspace file', async () => {
137
+ await reconcileWorkspaceResources(workspace, ['codex'], [skill], []);
138
+ const victim = path.join(workspace, 'victim.txt');
139
+ await fs.writeFile(victim, 'keep me', 'utf-8');
140
+ const lockPath = path.join(workspace, '.nexusflow', 'resources.lock.json');
141
+ const lock = JSON.parse(await fs.readFile(lockPath, 'utf-8'));
142
+ lock.outputs[0].path = 'victim.txt';
143
+ lock.outputs[0].hash = '6cc7d5a6f9f85c209a8e18e5426de33f0db1df6d2a223c90a4a4e0e08e64b341';
144
+ await fs.writeFile(lockPath, `${JSON.stringify(lock, null, 2)}\n`, 'utf-8');
145
+ await expect(reconcileWorkspaceResources(workspace, ['codex'], [], [])).rejects.toThrow(/invalid workspace resource ownership path/i);
146
+ expect(await fs.readFile(victim, 'utf-8')).toBe('keep me');
147
+ });
148
+ });
149
+ //# sourceMappingURL=materializer.test.js.map