@bleedingdev/modern-js-create 3.2.0-ultramodern.8 → 3.2.0-ultramodern.80

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