@bleedingdev/modern-js-create 3.2.0-ultramodern.11 → 3.2.0-ultramodern.110

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 (95) hide show
  1. package/README.md +158 -35
  2. package/bin/run.js +0 -0
  3. package/dist/cjs/index.cjs +1040 -0
  4. package/dist/cjs/locale/en.cjs +97 -0
  5. package/dist/cjs/locale/index.cjs +50 -0
  6. package/dist/cjs/locale/zh.cjs +97 -0
  7. package/dist/cjs/ultramodern-checks/cli/i18n-check.cjs +73 -0
  8. package/dist/cjs/ultramodern-checks/cli/oxlint.cjs +174 -0
  9. package/dist/cjs/ultramodern-checks/cli/workspace-source-check.cjs +179 -0
  10. package/dist/cjs/ultramodern-checks/index.cjs +58 -0
  11. package/dist/cjs/ultramodern-checks/oxlint-plugin.cjs +354 -0
  12. package/dist/cjs/ultramodern-package-source.cjs +133 -0
  13. package/dist/cjs/ultramodern-workspace.cjs +5616 -0
  14. package/dist/esm/index.js +1002 -0
  15. package/dist/esm/locale/en.js +59 -0
  16. package/dist/esm/locale/index.js +9 -0
  17. package/dist/esm/locale/zh.js +59 -0
  18. package/dist/esm/ultramodern-checks/cli/i18n-check.js +26 -0
  19. package/dist/esm/ultramodern-checks/cli/oxlint.js +118 -0
  20. package/dist/esm/ultramodern-checks/cli/workspace-source-check.js +124 -0
  21. package/dist/esm/ultramodern-checks/index.js +3 -0
  22. package/dist/esm/ultramodern-checks/oxlint-plugin.js +316 -0
  23. package/dist/esm/ultramodern-package-source.js +61 -0
  24. package/dist/esm/ultramodern-workspace.js +5554 -0
  25. package/dist/esm-node/index.js +1003 -0
  26. package/dist/esm-node/locale/en.js +60 -0
  27. package/dist/esm-node/locale/index.js +10 -0
  28. package/dist/esm-node/locale/zh.js +60 -0
  29. package/dist/esm-node/ultramodern-checks/cli/i18n-check.js +27 -0
  30. package/dist/esm-node/ultramodern-checks/cli/oxlint.js +119 -0
  31. package/dist/esm-node/ultramodern-checks/cli/workspace-source-check.js +125 -0
  32. package/dist/esm-node/ultramodern-checks/index.js +4 -0
  33. package/dist/esm-node/ultramodern-checks/oxlint-plugin.js +317 -0
  34. package/dist/esm-node/ultramodern-package-source.js +62 -0
  35. package/dist/esm-node/ultramodern-workspace.js +5555 -0
  36. package/dist/types/locale/en.d.ts +3 -0
  37. package/dist/types/locale/index.d.ts +117 -2
  38. package/dist/types/locale/zh.d.ts +3 -0
  39. package/dist/types/ultramodern-checks/cli/i18n-check.d.ts +9 -0
  40. package/dist/types/ultramodern-checks/cli/oxlint.d.ts +22 -0
  41. package/dist/types/ultramodern-checks/cli/workspace-source-check.d.ts +8 -0
  42. package/dist/types/ultramodern-checks/index.d.ts +3 -0
  43. package/dist/types/ultramodern-checks/oxlint-plugin.d.ts +63 -0
  44. package/dist/types/ultramodern-package-source.d.ts +28 -0
  45. package/dist/types/ultramodern-workspace.d.ts +12 -2
  46. package/package.json +52 -11
  47. package/template/.codex/hooks.json +16 -0
  48. package/template/.github/renovate.json +53 -0
  49. package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
  50. package/template/.mise.toml.handlebars +2 -0
  51. package/template/AGENTS.md +9 -6
  52. package/template/README.md +66 -34
  53. package/template/api/effect/index.ts.handlebars +20 -9
  54. package/template/api/lambda/hello.ts.handlebars +5 -5
  55. package/template/config/favicon.svg +5 -0
  56. package/template/config/public/assets/ultramodern-logo.svg +6 -0
  57. package/template/config/public/locales/cs/translation.json +44 -0
  58. package/template/config/public/locales/en/translation.json +44 -0
  59. package/template/lefthook.yml +10 -0
  60. package/template/modern.config.ts.handlebars +35 -3
  61. package/template/oxfmt.config.ts +8 -1
  62. package/template/oxlint.config.ts +8 -1
  63. package/template/package.json.handlebars +36 -30
  64. package/template/pnpm-workspace.yaml +34 -0
  65. package/template/rstest.config.mts +5 -0
  66. package/template/scripts/bootstrap-agent-skills.mjs +148 -15
  67. package/template/scripts/check-i18n-strings.mjs +3 -0
  68. package/template/scripts/validate-ultramodern.mjs.handlebars +494 -3
  69. package/template/src/modern-app-env.d.ts +2 -0
  70. package/template/src/modern.runtime.ts.handlebars +17 -1
  71. package/template/src/routes/[lang]/page.tsx.handlebars +209 -0
  72. package/template/src/routes/index.css.handlebars +192 -55
  73. package/template/src/routes/layout.tsx.handlebars +2 -1
  74. package/template/tailwind.config.ts.handlebars +1 -1
  75. package/template/tests/tsconfig.json +7 -0
  76. package/template/tests/ultramodern.contract.test.ts.handlebars +160 -0
  77. package/template/tsconfig.json +2 -1
  78. package/template-workspace/.agents/agent-reference-repos.json +24 -0
  79. package/template-workspace/.agents/skills-lock.json +19 -0
  80. package/template-workspace/.codex/hooks.json +16 -0
  81. package/template-workspace/.github/renovate.json +29 -0
  82. package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
  83. package/template-workspace/.gitignore.handlebars +5 -0
  84. package/template-workspace/.mise.toml.handlebars +2 -0
  85. package/template-workspace/AGENTS.md +36 -5
  86. package/template-workspace/README.md.handlebars +70 -11
  87. package/template-workspace/lefthook.yml +10 -0
  88. package/template-workspace/oxfmt.config.ts +1 -0
  89. package/template-workspace/oxlint.config.ts +1 -0
  90. package/template-workspace/pnpm-workspace.yaml +31 -8
  91. package/template-workspace/scripts/bootstrap-agent-skills.mjs +190 -21
  92. package/template-workspace/scripts/setup-agent-reference-repos.mjs +370 -0
  93. package/dist/index.js +0 -2474
  94. package/template/src/routes/page.tsx.handlebars +0 -136
  95. package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -405
@@ -8,7 +8,7 @@ const lockPath = path.join(root, '.agents/skills-lock.json');
8
8
  const checkOnly = process.argv.includes('--check');
9
9
  const force = process.argv.includes('--force');
10
10
 
11
- const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
11
+ const readJson = filePath => JSON.parse(fs.readFileSync(filePath, 'utf-8'));
12
12
 
13
13
  const run = (command, args, options = {}) =>
14
14
  execFileSync(command, args, {
@@ -17,16 +17,138 @@ const run = (command, args, options = {}) =>
17
17
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
18
18
  });
19
19
 
20
+ const commandExists = command => {
21
+ try {
22
+ run(command, ['--version'], { stdio: 'ignore' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const runShell = script =>
30
+ run('sh', ['-lc', script], {
31
+ stdio: 'inherit',
32
+ });
33
+
34
+ const installGit = () => {
35
+ if (commandExists('git')) {
36
+ return;
37
+ }
38
+
39
+ if (commandExists('brew')) {
40
+ run('brew', ['install', 'git'], { stdio: 'inherit' });
41
+ } else if (process.platform === 'linux' && commandExists('apt-get')) {
42
+ const sudo =
43
+ typeof process.getuid === 'function' && process.getuid() === 0
44
+ ? ''
45
+ : 'sudo ';
46
+ runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
47
+ } else if (process.platform === 'linux' && commandExists('dnf')) {
48
+ const sudo =
49
+ typeof process.getuid === 'function' && process.getuid() === 0
50
+ ? ''
51
+ : 'sudo ';
52
+ runShell(`${sudo}dnf install -y git`);
53
+ } else if (process.platform === 'linux' && commandExists('yum')) {
54
+ const sudo =
55
+ typeof process.getuid === 'function' && process.getuid() === 0
56
+ ? ''
57
+ : 'sudo ';
58
+ runShell(`${sudo}yum install -y git`);
59
+ } else if (process.platform === 'linux' && commandExists('apk')) {
60
+ runShell('apk add --no-cache git');
61
+ }
62
+
63
+ if (!commandExists('git')) {
64
+ throw new Error(
65
+ 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
66
+ );
67
+ }
68
+ };
69
+
70
+ const isInsideGitWorkTree = () => {
71
+ try {
72
+ return run('git', ['rev-parse', '--is-inside-work-tree']).trim() === 'true';
73
+ } catch {
74
+ return false;
75
+ }
76
+ };
77
+
78
+ const initializeGitRepository = () => {
79
+ if (isInsideGitWorkTree()) {
80
+ return;
81
+ }
82
+
83
+ try {
84
+ run('git', ['init', '-b', 'main'], { stdio: 'inherit' });
85
+ } catch {
86
+ run('git', ['init'], { stdio: 'inherit' });
87
+ run('git', ['branch', '-M', 'main'], { stdio: 'inherit' });
88
+ }
89
+ };
90
+
91
+ const installLefthook = () => {
92
+ try {
93
+ run('lefthook', ['install'], { stdio: 'inherit' });
94
+ } catch (error) {
95
+ console.warn(`Unable to install lefthook hooks: ${error.message}`);
96
+ }
97
+ };
98
+
99
+ const removeTree = dir =>
100
+ fs.rmSync(dir, {
101
+ force: true,
102
+ maxRetries: 5,
103
+ recursive: true,
104
+ retryDelay: 100,
105
+ });
106
+
20
107
  const cloneSource = (source, targetDir) => {
108
+ if (source.commit) {
109
+ run('git', ['init', targetDir]);
110
+ run('git', ['remote', 'add', 'origin', source.repository], {
111
+ cwd: targetDir,
112
+ });
113
+ run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
114
+ cwd: targetDir,
115
+ });
116
+ run(
117
+ 'git',
118
+ [
119
+ '-c',
120
+ 'advice.detachedHead=false',
121
+ 'checkout',
122
+ '--detach',
123
+ '--quiet',
124
+ 'FETCH_HEAD',
125
+ ],
126
+ { cwd: targetDir },
127
+ );
128
+ return;
129
+ }
130
+
21
131
  const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
22
132
  try {
23
- run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
24
- stdio: 'inherit',
25
- });
133
+ run('gh', [
134
+ 'repo',
135
+ 'clone',
136
+ repo,
137
+ targetDir,
138
+ '--',
139
+ '--depth',
140
+ '1',
141
+ '--quiet',
142
+ ]);
26
143
  } catch {
27
- run('git', ['clone', '--depth', '1', source.repository, targetDir], {
28
- stdio: 'inherit',
29
- });
144
+ run('git', [
145
+ 'clone',
146
+ '--depth',
147
+ '1',
148
+ '--quiet',
149
+ source.repository,
150
+ targetDir,
151
+ ]);
30
152
  }
31
153
  };
32
154
 
@@ -37,7 +159,9 @@ const resolveSkillDir = (sourceRoot, skillName) => {
37
159
  path.join(sourceRoot, 'skills', 'engineering', skillName),
38
160
  path.join(sourceRoot, 'skills', 'productivity', skillName),
39
161
  ];
40
- return candidates.find((candidate) => fs.existsSync(path.join(candidate, 'SKILL.md')));
162
+ return candidates.find(candidate =>
163
+ fs.existsSync(path.join(candidate, 'SKILL.md')),
164
+ );
41
165
  };
42
166
 
43
167
  if (!fs.existsSync(lockPath)) {
@@ -47,36 +171,79 @@ if (!fs.existsSync(lockPath)) {
47
171
 
48
172
  const lock = readJson(lockPath);
49
173
  const installDir = path.join(root, lock.installDir ?? '.agents/skills');
50
- const privateSources = (lock.sources ?? []).filter(
51
- (source) => source.install === 'clone-if-authorized',
174
+ const sources = lock.sources ?? [];
175
+ const requiredCloneSources = sources.filter(
176
+ source => source.install === 'clone',
177
+ );
178
+ const optionalCloneSources = sources.filter(
179
+ source => source.install === 'clone-if-authorized',
180
+ );
181
+ const requiredSkills = [
182
+ ...(lock.baseline ?? []),
183
+ ...requiredCloneSources.flatMap(source => source.baseline ?? []),
184
+ ].filter(
185
+ (skill, index, skills) =>
186
+ skills.findIndex(candidate => candidate.name === skill.name) === index,
52
187
  );
53
188
 
54
189
  if (checkOnly) {
55
- const missing = privateSources.flatMap((source) =>
190
+ const missingRequired = requiredSkills
191
+ .map(skill => skill.name)
192
+ .filter(
193
+ skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
194
+ );
195
+ const missingOptional = optionalCloneSources.flatMap(source =>
56
196
  (source.baseline ?? [])
57
- .map((skill) => skill.name)
58
- .filter((skillName) => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md'))),
197
+ .map(skill => skill.name)
198
+ .filter(
199
+ skillName =>
200
+ !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
201
+ ),
59
202
  );
60
- if (missing.length > 0) {
203
+
204
+ if (missingRequired.length > 0) {
205
+ console.error(
206
+ `Required agent skills not installed: ${missingRequired.join(', ')}. Run pnpm skills:install.`,
207
+ );
208
+ process.exit(1);
209
+ }
210
+
211
+ if (missingOptional.length > 0) {
61
212
  console.warn(
62
- `Private skills not installed: ${missing.join(', ')}. Run pnpm skills:install if you have access.`,
213
+ `Private skills not installed: ${missingOptional.join(', ')}. Run pnpm skills:install if you have access.`,
63
214
  );
64
215
  } else {
65
- console.log('Agent skills are installed.');
216
+ console.log('Required and private agent skills are installed.');
217
+ process.exit(0);
66
218
  }
219
+ console.log('Required agent skills are installed.');
67
220
  process.exit(0);
68
221
  }
69
222
 
70
223
  fs.mkdirSync(installDir, { recursive: true });
224
+ installGit();
225
+ initializeGitRepository();
71
226
 
72
- for (const source of privateSources) {
227
+ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
73
228
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
74
229
  try {
75
- cloneSource(source, tempDir);
230
+ try {
231
+ cloneSource(source, tempDir);
232
+ } catch (error) {
233
+ if (source.install === 'clone-if-authorized') {
234
+ console.warn(
235
+ `Skipping ${source.repository}; current developer may not have access.`,
236
+ );
237
+ continue;
238
+ }
239
+ throw error;
240
+ }
76
241
  for (const skill of source.baseline ?? []) {
77
242
  const sourceSkillDir = resolveSkillDir(tempDir, skill.name);
78
243
  if (!sourceSkillDir) {
79
- throw new Error(`Skill ${skill.name} not found in ${source.repository}`);
244
+ throw new Error(
245
+ `Skill ${skill.name} not found in ${source.repository}`,
246
+ );
80
247
  }
81
248
  const targetSkillDir = path.join(installDir, skill.name);
82
249
  if (fs.existsSync(targetSkillDir)) {
@@ -84,12 +251,14 @@ for (const source of privateSources) {
84
251
  console.log(`Skipping existing ${skill.name}`);
85
252
  continue;
86
253
  }
87
- fs.rmSync(targetSkillDir, { force: true, recursive: true });
254
+ removeTree(targetSkillDir);
88
255
  }
89
256
  fs.cpSync(sourceSkillDir, targetSkillDir, { recursive: true });
90
257
  console.log(`Installed ${skill.name}`);
91
258
  }
92
259
  } finally {
93
- fs.rmSync(tempDir, { force: true, recursive: true });
260
+ removeTree(tempDir);
94
261
  }
95
262
  }
263
+
264
+ installLefthook();
@@ -0,0 +1,370 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const root = process.cwd();
6
+ const args = new Set(process.argv.slice(2));
7
+ const checkOnly = args.has('--check');
8
+ const configPath = path.join(root, '.agents', 'agent-reference-repos.json');
9
+ const manifestPath = path.join(root, '.modernjs', 'agent-reference-repos.json');
10
+
11
+ const truthy = value => /^(1|true|yes|on)$/i.test(String(value ?? ''));
12
+ const falsy = value => /^(0|false|no|off)$/i.test(String(value ?? ''));
13
+
14
+ const skipRequested =
15
+ truthy(process.env.ULTRAMODERN_SKIP_AGENT_REPOS) ||
16
+ falsy(process.env.ULTRAMODERN_AGENT_REPOS);
17
+ const required = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REQUIRED);
18
+ const refresh = truthy(process.env.ULTRAMODERN_AGENT_REPOS_REFRESH);
19
+
20
+ const gitIdentityEnv = {
21
+ GIT_AUTHOR_NAME:
22
+ process.env.GIT_AUTHOR_NAME || 'UltraModern Agent Reference Setup',
23
+ GIT_AUTHOR_EMAIL:
24
+ process.env.GIT_AUTHOR_EMAIL || 'ultramodern-agent-refs@local',
25
+ GIT_COMMITTER_NAME:
26
+ process.env.GIT_COMMITTER_NAME || 'UltraModern Agent Reference Setup',
27
+ GIT_COMMITTER_EMAIL:
28
+ process.env.GIT_COMMITTER_EMAIL || 'ultramodern-agent-refs@local',
29
+ };
30
+
31
+ const log = message => console.log(`[agent-reference-repos] ${message}`);
32
+ const warn = message => console.warn(`[agent-reference-repos] ${message}`);
33
+
34
+ function fail(message) {
35
+ if (required || checkOnly) {
36
+ throw new Error(message);
37
+ }
38
+ warn(message);
39
+ }
40
+
41
+ function readJson(filePath) {
42
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
43
+ }
44
+
45
+ function run(command, commandArgs, options = {}) {
46
+ const result = spawnSync(command, commandArgs, {
47
+ cwd: options.cwd ?? root,
48
+ encoding: 'utf-8',
49
+ env: {
50
+ ...process.env,
51
+ ...gitIdentityEnv,
52
+ ...(options.env ?? {}),
53
+ },
54
+ stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
55
+ timeout: options.timeout ?? 120000,
56
+ });
57
+
58
+ if (result.error) {
59
+ throw result.error;
60
+ }
61
+ if (result.status !== 0) {
62
+ const stderr = result.stderr?.trim();
63
+ throw new Error(
64
+ `${command} ${commandArgs.join(' ')} failed${
65
+ stderr ? `: ${stderr}` : ''
66
+ }`,
67
+ );
68
+ }
69
+ return result.stdout?.trim() ?? '';
70
+ }
71
+
72
+ function assertSafeRepoPath(relativePath) {
73
+ if (
74
+ typeof relativePath !== 'string' ||
75
+ relativePath.length === 0 ||
76
+ path.isAbsolute(relativePath) ||
77
+ relativePath.split(/[\\/]+/).includes('..') ||
78
+ !relativePath.startsWith('repos/')
79
+ ) {
80
+ throw new Error(`Unsafe reference repository path: ${relativePath}`);
81
+ }
82
+ }
83
+
84
+ function hasGit() {
85
+ const result = spawnSync('git', ['--version'], {
86
+ encoding: 'utf-8',
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ });
89
+ return result.status === 0;
90
+ }
91
+
92
+ function hasGitSubtree() {
93
+ const result = spawnSync('git', ['subtree', '-h'], {
94
+ encoding: 'utf-8',
95
+ stdio: ['ignore', 'pipe', 'pipe'],
96
+ });
97
+ return (
98
+ (result.status === 0 || result.status === 129) &&
99
+ result.stdout.includes('usage: git subtree')
100
+ );
101
+ }
102
+
103
+ function isGitWorkTree() {
104
+ const result = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
105
+ cwd: root,
106
+ encoding: 'utf-8',
107
+ stdio: ['ignore', 'pipe', 'pipe'],
108
+ });
109
+ return result.status === 0 && result.stdout.trim() === 'true';
110
+ }
111
+
112
+ function hasCommits() {
113
+ const result = spawnSync('git', ['rev-parse', '--verify', 'HEAD'], {
114
+ cwd: root,
115
+ encoding: 'utf-8',
116
+ stdio: ['ignore', 'pipe', 'pipe'],
117
+ });
118
+ return result.status === 0;
119
+ }
120
+
121
+ function porcelainStatus() {
122
+ return run('git', ['status', '--porcelain'], { timeout: 30000 });
123
+ }
124
+
125
+ function commitInstallerChanges(message) {
126
+ run('git', ['commit', '--no-verify', '-m', message], {
127
+ timeout: 120000,
128
+ });
129
+ }
130
+
131
+ function ensureGitRepository() {
132
+ if (!isGitWorkTree()) {
133
+ if (checkOnly) {
134
+ fail('workspace is not a git repository');
135
+ return false;
136
+ }
137
+ log('initializing git repository for agent reference subtrees');
138
+ run('git', ['init'], { timeout: 30000 });
139
+ }
140
+
141
+ if (!hasCommits()) {
142
+ if (checkOnly) {
143
+ fail('workspace has no initial git commit');
144
+ return false;
145
+ }
146
+ log('creating initial workspace commit before adding reference subtrees');
147
+ run('git', ['add', '-A'], { timeout: 30000 });
148
+ commitInstallerChanges('Initialize UltraModern workspace');
149
+ return true;
150
+ }
151
+
152
+ const status = porcelainStatus();
153
+ if (status) {
154
+ fail(
155
+ 'workspace has uncommitted changes; commit or stash them before installing reference subtrees',
156
+ );
157
+ return false;
158
+ }
159
+
160
+ return true;
161
+ }
162
+
163
+ function remoteCommit(repo) {
164
+ let output = run('git', ['ls-remote', repo.url, `refs/heads/${repo.ref}`], {
165
+ timeout: 120000,
166
+ });
167
+ if (!output) {
168
+ output = run('git', ['ls-remote', repo.url, repo.ref], {
169
+ timeout: 120000,
170
+ });
171
+ }
172
+ const [commit] = output.split(/\s+/);
173
+ if (!/^[a-f0-9]{40}$/i.test(commit ?? '')) {
174
+ throw new Error(`Could not resolve ${repo.url}#${repo.ref}`);
175
+ }
176
+ return commit;
177
+ }
178
+
179
+ function subtreeCommitExists(repo) {
180
+ const result = spawnSync(
181
+ 'git',
182
+ [
183
+ 'log',
184
+ '--grep',
185
+ `git-subtree-dir: ${repo.path}`,
186
+ '--format=%H',
187
+ '-n',
188
+ '1',
189
+ ],
190
+ {
191
+ cwd: root,
192
+ encoding: 'utf-8',
193
+ stdio: ['ignore', 'pipe', 'pipe'],
194
+ },
195
+ );
196
+ return result.status === 0 && result.stdout.trim().length > 0;
197
+ }
198
+
199
+ function installedManifestEntry(repo) {
200
+ if (!fs.existsSync(manifestPath)) {
201
+ return undefined;
202
+ }
203
+ try {
204
+ const manifest = readJson(manifestPath);
205
+ return manifest.repositories?.find(entry => entry.id === repo.id);
206
+ } catch {
207
+ return undefined;
208
+ }
209
+ }
210
+
211
+ function assertSubtreePresent(repo) {
212
+ assertSafeRepoPath(repo.path);
213
+ const targetPath = path.join(root, repo.path);
214
+ if (!fs.existsSync(targetPath)) {
215
+ fail(`${repo.path} is missing`);
216
+ return undefined;
217
+ }
218
+ if (!subtreeCommitExists(repo)) {
219
+ fail(`${repo.path} is present but has no git-subtree commit evidence`);
220
+ return undefined;
221
+ }
222
+ return (
223
+ installedManifestEntry(repo) ?? {
224
+ id: repo.id,
225
+ name: repo.name,
226
+ url: repo.url,
227
+ ref: repo.ref,
228
+ path: repo.path,
229
+ readOnly: repo.readOnly !== false,
230
+ status: 'present',
231
+ strategy: 'git-subtree-squash',
232
+ }
233
+ );
234
+ }
235
+
236
+ function addSubtree(repo) {
237
+ assertSafeRepoPath(repo.path);
238
+ const targetPath = path.join(root, repo.path);
239
+ const existing = fs.existsSync(targetPath);
240
+
241
+ if (existing && !refresh) {
242
+ return assertSubtreePresent(repo);
243
+ }
244
+
245
+ if (existing && refresh) {
246
+ fail(
247
+ `${repo.path} already exists; refresh for subtree references is intentionally manual`,
248
+ );
249
+ return undefined;
250
+ }
251
+
252
+ if (checkOnly) {
253
+ fail(`${repo.path} is missing`);
254
+ return undefined;
255
+ }
256
+
257
+ const commit = remoteCommit(repo);
258
+ log(`adding ${repo.name} as git subtree at ${repo.path} (${commit})`);
259
+ run('git', ['fetch', '--depth', '1', repo.url, repo.ref], {
260
+ timeout: 300000,
261
+ });
262
+ run(
263
+ 'git',
264
+ [
265
+ 'subtree',
266
+ 'add',
267
+ '--prefix',
268
+ repo.path,
269
+ 'FETCH_HEAD',
270
+ '--squash',
271
+ '-m',
272
+ `Add ${repo.name} agent reference repo`,
273
+ ],
274
+ { timeout: 600000 },
275
+ );
276
+
277
+ return {
278
+ schemaVersion: 1,
279
+ id: repo.id,
280
+ name: repo.name,
281
+ url: repo.url,
282
+ ref: repo.ref,
283
+ commit,
284
+ path: repo.path,
285
+ readOnly: repo.readOnly !== false,
286
+ strategy: 'git-subtree-squash',
287
+ status: 'installed',
288
+ installedAt: new Date().toISOString(),
289
+ };
290
+ }
291
+
292
+ function writeManifest(entries) {
293
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
294
+ fs.writeFileSync(
295
+ manifestPath,
296
+ `${JSON.stringify(
297
+ {
298
+ schemaVersion: 1,
299
+ generatedAt: new Date().toISOString(),
300
+ strategy: 'git-subtree-squash',
301
+ installDir: 'repos',
302
+ repositories: entries,
303
+ },
304
+ null,
305
+ 2,
306
+ )}\n`,
307
+ );
308
+ }
309
+
310
+ function commitManifestIfChanged() {
311
+ const status = run('git', ['status', '--porcelain', '--', manifestPath], {
312
+ timeout: 30000,
313
+ });
314
+ if (!status) {
315
+ return;
316
+ }
317
+ run('git', ['add', manifestPath], { timeout: 30000 });
318
+ commitInstallerChanges('Record agent reference repo manifest');
319
+ }
320
+
321
+ function main() {
322
+ if (!fs.existsSync(configPath)) {
323
+ fail('Missing .agents/agent-reference-repos.json');
324
+ return;
325
+ }
326
+
327
+ const config = readJson(configPath);
328
+ const enabled = config.defaultEnabled !== false && !skipRequested;
329
+
330
+ if (!enabled) {
331
+ log('setup skipped; set ULTRAMODERN_SKIP_AGENT_REPOS=0 to enable it again');
332
+ return;
333
+ }
334
+
335
+ if (!hasGit()) {
336
+ fail('git is required to install agent reference repositories');
337
+ return;
338
+ }
339
+ if (!hasGitSubtree()) {
340
+ fail('git subtree is required to install agent reference repositories');
341
+ return;
342
+ }
343
+ if (!ensureGitRepository()) {
344
+ return;
345
+ }
346
+
347
+ const entries = [];
348
+ for (const repo of config.repositories ?? []) {
349
+ const result = checkOnly ? assertSubtreePresent(repo) : addSubtree(repo);
350
+ if (result) {
351
+ entries.push(result);
352
+ }
353
+ }
354
+
355
+ if (!checkOnly) {
356
+ writeManifest(entries);
357
+ commitManifestIfChanged();
358
+ }
359
+ }
360
+
361
+ try {
362
+ main();
363
+ } catch (error) {
364
+ if (required || checkOnly) {
365
+ console.error(`[agent-reference-repos] ${error.message}`);
366
+ process.exitCode = 1;
367
+ } else {
368
+ warn(error.message);
369
+ }
370
+ }