@produck/agent-toolkit 0.9.2 → 0.11.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.
@@ -42,6 +42,14 @@ import {
42
42
  printSyncPublishHelp,
43
43
  runSyncPublish,
44
44
  } from './command/sync-publish/index.mjs';
45
+ import {
46
+ printSyncTypescriptHelp,
47
+ runSyncTypescript,
48
+ } from './command/sync-typescript/index.mjs';
49
+ import {
50
+ printSyncWorkspaceHelp,
51
+ runSyncWorkspace,
52
+ } from './command/sync-workspace/index.mjs';
45
53
  import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
46
54
  import {
47
55
  printValidateCommitMsgHelp,
@@ -101,6 +109,14 @@ const COMMANDS = {
101
109
  printHelp: printSyncPublishHelp,
102
110
  run: runSyncPublish,
103
111
  },
112
+ 'sync-typescript': {
113
+ printHelp: printSyncTypescriptHelp,
114
+ run: runSyncTypescript,
115
+ },
116
+ 'sync-workspace': {
117
+ printHelp: printSyncWorkspaceHelp,
118
+ run: runSyncWorkspace,
119
+ },
104
120
  };
105
121
 
106
122
  const DEFAULT_COMMAND = 'enforce-node-baseline';
@@ -49,6 +49,7 @@ const OUTPUT_LERNA_PATH = path.resolve(
49
49
  PACKAGE_ROOT,
50
50
  'publish-assets/lerna.json',
51
51
  );
52
+ const ROOT_PACKAGE_JSON_PATH = path.resolve(REPO_ROOT, 'package.json');
52
53
  const LEGACY_OUTPUT_PATH = path.resolve(
53
54
  PACKAGE_ROOT,
54
55
  'publish-assets/instructions/org.instructions.md',
@@ -80,6 +81,10 @@ function validateSourceFile(fileName, text) {
80
81
  }
81
82
  }
82
83
 
84
+ function resolveSemverExact(text) {
85
+ return text.replace(/^[\^~>=<]+\s*/, '').trim();
86
+ }
87
+
83
88
  function readAndValidateToolingBaseline() {
84
89
  if (!fs.existsSync(SOURCE_TOOLING_BASELINE_PATH)) {
85
90
  throw new Error(
@@ -98,25 +103,40 @@ function readAndValidateToolingBaseline() {
98
103
  );
99
104
  }
100
105
 
101
- const c8Version = baseline?.tools?.c8?.version;
102
- const lernaVersion = baseline?.tools?.lerna?.version;
103
- const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
104
-
105
106
  if (typeof baseline.schemaVersion !== 'number') {
106
107
  throw new Error(
107
108
  `Invalid tooling baseline schemaVersion in: ${SOURCE_TOOLING_BASELINE_PATH}`,
108
109
  );
109
110
  }
110
111
 
112
+ // Auto-resolve tool versions: only override version when set to "auto"
113
+ const rootPkg = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON_PATH, 'utf8'));
114
+ const devDeps = rootPkg.devDependencies || {};
115
+
116
+ for (const [toolName, entry] of Object.entries(baseline.tools)) {
117
+ if (entry.version === 'auto') {
118
+ const depVersion = devDeps[toolName];
119
+ if (typeof depVersion === 'string' && depVersion.trim()) {
120
+ entry.version = resolveSemverExact(depVersion);
121
+ }
122
+ }
123
+ }
124
+
125
+ const c8Version = baseline?.tools?.c8?.version;
126
+ const lernaVersion = baseline?.tools?.lerna?.version;
127
+ const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
128
+
111
129
  if (typeof c8Version !== 'string' || c8Version.trim() === '') {
112
130
  throw new Error(
113
- `Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH}`,
131
+ `Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH} ` +
132
+ '(c8 must be listed in root package.json devDependencies)',
114
133
  );
115
134
  }
116
135
 
117
136
  if (typeof lernaVersion !== 'string' || lernaVersion.trim() === '') {
118
137
  throw new Error(
119
- `Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH}`,
138
+ `Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH} ` +
139
+ '(lerna must be listed in root package.json devDependencies)',
120
140
  );
121
141
  }
122
142
 
@@ -129,6 +149,7 @@ function readAndValidateToolingBaseline() {
129
149
  );
130
150
  }
131
151
 
152
+ // Dynamically inject @produck/eslint-rules version from its own package.json
132
153
  const eslintRulesPkgPath = path.resolve(
133
154
  PACKAGE_ROOT,
134
155
  '../eslint-rules/package.json',
@@ -146,6 +167,7 @@ function readAndValidateToolingBaseline() {
146
167
  };
147
168
  }
148
169
  }
170
+
149
171
  return `${JSON.stringify(baseline, null, 2)}\n`;
150
172
  }
151
173
 
@@ -168,6 +168,16 @@ export function runEnforceNodeBaseline(options) {
168
168
  syncEslintConfigArgs.push('--dry-run');
169
169
  }
170
170
 
171
+ const syncWorkspaceArgs = ['sync-workspace', '--cwd', cwd];
172
+ for (const workspacePath of workspaces) {
173
+ syncWorkspaceArgs.push('--workspace', workspacePath);
174
+ }
175
+ if (check) {
176
+ syncWorkspaceArgs.push('--check');
177
+ } else if (dryRun) {
178
+ syncWorkspaceArgs.push('--dry-run');
179
+ }
180
+
171
181
  const plan = [
172
182
  { name: 'preflight', args: preflightArgs },
173
183
  { name: 'sync-instructions', args: syncInstructionsArgs },
@@ -177,6 +187,7 @@ export function runEnforceNodeBaseline(options) {
177
187
  { name: 'sync-install', args: syncInstallArgs },
178
188
  { name: 'sync-git', args: syncGitArgs },
179
189
  { name: 'sync-coverage', args: syncCoverageArgs },
190
+ { name: 'sync-workspace', args: syncWorkspaceArgs },
180
191
  { name: 'sync-publish', args: syncPublishArgs },
181
192
  ];
182
193
 
@@ -5,13 +5,15 @@ agent-toolkit commands:
5
5
  summarize-log
6
6
  sync-coverage
7
7
  sync-editorconfig
8
- sync-git
9
8
  sync-format
9
+ sync-git
10
10
  sync-install
11
+ sync-instructions
11
12
  sync-lint
12
13
  sync-publish
14
+ sync-typescript
15
+ sync-workspace
13
16
  validate-commit-msg
14
- sync-instructions
15
17
 
16
18
  Default:
17
19
  agent-toolkit
@@ -1,30 +1,15 @@
1
1
  Usage:
2
2
  agent-toolkit sync-coverage [--cwd <dir>]
3
- [--workspace <path>] ... [--check] [--dry-run] [--json <file>]
3
+ [--check] [--dry-run] [--json <file>]
4
4
 
5
5
  Behavior:
6
- - Adds or updates root scripts.produck:coverage, root .c8rc.json, and root devDependencies.c8.
7
- - Adds or updates scripts.produck:coverage and devDependencies.c8 in each workspace package.json.
8
- - Enforces scripts.test in each workspace package.json.
9
- - If scripts.test is missing, generates:
10
- node -e "console.log('No tests configured')"
11
- - The produck:coverage script in subpackages is for local and AI development use only.
12
- - Subpackage produck:coverage is NOT enforced by organization CI or .c8rc.json.
13
- - Only the root workspace (monorepo root) is subject to org-level coverage enforcement and .c8rc.json.
6
+ - Adds or updates root scripts.produck:coverage, root .c8rc.json,
7
+ and root devDependencies.c8.
8
+ - Only the root workspace (monorepo root) is subject to org-level
9
+ coverage enforcement and .c8rc.json.
14
10
  - Organization-reserved script key is scripts.produck:coverage
15
- - Target script is rendered from organization tooling baseline file
16
- (lookup order):
17
- 1) .github/distribution/produck/tooling-version-baseline.json
18
- 2) publish-assets/instructions/produck/tooling-version-baseline.json
19
- - Baseline template:
20
- c8 --reporter=lcov --reporter=html --reporter=text-summary npm test
11
+ - c8 version is resolved from organization tooling baseline file
21
12
 
22
13
  Rules:
23
- - When --workspace is omitted, root package.json workspaces are used
24
- - Root workspaces must be explicit paths (no glob tokens)
25
- - Workspace package.json files must include scripts.test
26
- - Workspace package.json files must pin devDependencies.c8 to baseline version
27
- - Subpackage produck:coverage is not a CI or org gate; it is for local/dev use only
28
- - Only the root workspace is enforced by org CI and .c8rc.json
29
14
  - --check validates without writing and exits non-zero on mismatch
30
15
  - --dry-run prints planned changes without writing
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
- import { getMulti, getSingle, hasFlag } from '../shared/args.mjs';
5
+ import { getSingle, hasFlag } from '../shared/args.mjs';
6
6
  import { printTextResource } from '../shared/text-resource.mjs';
7
7
 
8
8
  const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
@@ -19,7 +19,6 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
19
19
  'publish-assets/instructions/produck/tooling-version-baseline.json',
20
20
  ),
21
21
  ];
22
- const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
23
22
  const REQUIRED_ROOT_COVERAGE_SCRIPT_KEY = 'produck:coverage';
24
23
  const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE = [
25
24
  'c8',
@@ -28,10 +27,6 @@ const REQUIRED_ROOT_COVERAGE_SCRIPT_VALUE = [
28
27
  '--workspaces',
29
28
  '--if-present',
30
29
  ].join(' ');
31
- const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
32
- const REQUIRED_TEST_SCRIPT_KEY = 'test';
33
- const DEFAULT_TEST_SCRIPT_VALUE =
34
- 'node -e "console.log(\'No tests configured\')"';
35
30
  const REQUIRED_C8_CONFIG_FILE = '.c8rc.json';
36
31
  const REQUIRED_C8_CONFIG_TEMPLATE_FILE = path.resolve(
37
32
  COMMAND_DIR,
@@ -51,6 +46,35 @@ function parseJsonFile(filePath, label) {
51
46
  }
52
47
  }
53
48
 
49
+ function resolveSemverExact(text) {
50
+ return text.replace(/^[\^~>=<]+\s*/, '').trim();
51
+ }
52
+
53
+ function resolveToolVersionFromDevDeps(baseline, toolName) {
54
+ // If baseline has a concrete version (not "auto"), use it directly.
55
+ // This is the case when reading the published publish-assets baseline.
56
+ const baselineVersion = String(
57
+ baseline?.tools?.[toolName]?.version || '',
58
+ ).trim();
59
+ if (baselineVersion && baselineVersion !== 'auto') {
60
+ return baselineVersion;
61
+ }
62
+
63
+ // Fall back to resolving from local root package.json devDependencies.
64
+ // This covers source baseline with version="auto" during local dev.
65
+ const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
66
+ const pkgJsonPath = path.resolve(repoRoot, 'package.json');
67
+ if (fs.existsSync(pkgJsonPath)) {
68
+ const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
69
+ const dep = pkg?.devDependencies?.[toolName];
70
+ if (typeof dep === 'string' && dep.trim()) {
71
+ return resolveSemverExact(dep);
72
+ }
73
+ }
74
+
75
+ return '';
76
+ }
77
+
54
78
  function loadToolingBaseline() {
55
79
  const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
56
80
  (candidatePath) => {
@@ -76,7 +100,7 @@ function loadToolingBaseline() {
76
100
  process.exit(2);
77
101
  }
78
102
 
79
- const c8Version = baseline?.tools?.c8?.version;
103
+ const c8Version = resolveToolVersionFromDevDeps(baseline, 'c8');
80
104
  if (typeof c8Version !== 'string' || c8Version.trim() === '') {
81
105
  console.error(
82
106
  `Tooling baseline tools.c8.version must be a non-empty string: ${toolingBaselinePath}`,
@@ -98,12 +122,6 @@ function loadToolingBaseline() {
98
122
  };
99
123
  }
100
124
 
101
- function buildRequiredCoverageScript(baseline) {
102
- const c8Version = String(baseline.tools.c8.version);
103
- const coverageTemplate = String(baseline.coverage.scriptTemplate);
104
- return coverageTemplate.replace(/\{c8\.version\}/g, c8Version);
105
- }
106
-
107
125
  function buildRequiredC8DevDependency(baseline) {
108
126
  return String(baseline.tools.c8.version);
109
127
  }
@@ -131,37 +149,6 @@ function loadRequiredC8ConfigContent() {
131
149
  return `${JSON.stringify(template, null, 2)}\n`;
132
150
  }
133
151
 
134
- function resolveWorkspacePaths(cwd, options) {
135
- const manual = getMulti(options, '--workspace');
136
- if (manual.length > 0) {
137
- return manual;
138
- }
139
-
140
- const rootPackageJsonPath = path.resolve(cwd, 'package.json');
141
- const rootPackageJson = parseJsonFile(
142
- rootPackageJsonPath,
143
- 'Root package.json',
144
- );
145
- if (!Array.isArray(rootPackageJson.workspaces)) {
146
- return [];
147
- }
148
-
149
- const workspaces = rootPackageJson.workspaces.map((entry) => String(entry));
150
- if (workspaces.length === 0) {
151
- return [];
152
- }
153
-
154
- const hasGlob = workspaces.some((entry) => GLOB_TOKEN_PATTERN.test(entry));
155
- if (hasGlob) {
156
- console.error(
157
- 'Root package.json `workspaces` must use explicit paths without glob tokens',
158
- );
159
- process.exit(2);
160
- }
161
-
162
- return workspaces;
163
- }
164
-
165
152
  function syncRootCoverage(
166
153
  cwd,
167
154
  mode,
@@ -245,130 +232,6 @@ function syncRootCoverage(
245
232
  };
246
233
  }
247
234
 
248
- function reconcileCoverageScript(
249
- cwd,
250
- workspacePath,
251
- mode,
252
- requiredCoverageScript,
253
- requiredC8Version,
254
- ) {
255
- const packageDir = path.resolve(cwd, workspacePath);
256
- const packageJsonPath = path.resolve(packageDir, 'package.json');
257
-
258
- const result = {
259
- workspacePath,
260
- packageDir,
261
- packageJsonPath,
262
- exists: false,
263
- validJson: false,
264
- previousCoverage: null,
265
- coverageScript: null,
266
- previousTestScript: null,
267
- testScript: null,
268
- previousC8DevDependency: null,
269
- c8DevDependency: null,
270
- matchesRequiredCoverageBefore: false,
271
- matchesRequiredCoverageAfter: false,
272
- hasRequiredTestScriptBefore: false,
273
- hasRequiredTestScriptAfter: false,
274
- matchesRequiredC8DevDependencyBefore: false,
275
- matchesRequiredC8DevDependencyAfter: false,
276
- updated: false,
277
- error: '',
278
- };
279
-
280
- if (!fs.existsSync(packageJsonPath)) {
281
- result.error = `Workspace package.json does not exist: ${workspacePath}`;
282
- return result;
283
- }
284
- result.exists = true;
285
-
286
- let pkg;
287
- try {
288
- pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
289
- result.validJson = true;
290
- } catch {
291
- result.error = `Workspace package.json is not valid JSON: ${workspacePath}`;
292
- return result;
293
- }
294
-
295
- const scripts =
296
- pkg.scripts &&
297
- typeof pkg.scripts === 'object' &&
298
- !Array.isArray(pkg.scripts)
299
- ? { ...pkg.scripts }
300
- : {};
301
- const devDependencies =
302
- pkg.devDependencies &&
303
- typeof pkg.devDependencies === 'object' &&
304
- !Array.isArray(pkg.devDependencies)
305
- ? { ...pkg.devDependencies }
306
- : {};
307
-
308
- const previousCoverage =
309
- typeof scripts[REQUIRED_COVERAGE_SCRIPT_KEY] === 'string'
310
- ? scripts[REQUIRED_COVERAGE_SCRIPT_KEY]
311
- : null;
312
- const previousTestScript =
313
- typeof scripts[REQUIRED_TEST_SCRIPT_KEY] === 'string' &&
314
- scripts[REQUIRED_TEST_SCRIPT_KEY].trim() !== ''
315
- ? scripts[REQUIRED_TEST_SCRIPT_KEY]
316
- : null;
317
- const previousC8DevDependency =
318
- typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null;
319
- result.previousCoverage = previousCoverage;
320
- result.previousTestScript = previousTestScript;
321
- result.previousC8DevDependency = previousC8DevDependency;
322
- result.matchesRequiredCoverageBefore =
323
- previousCoverage === requiredCoverageScript;
324
- result.hasRequiredTestScriptBefore = previousTestScript !== null;
325
- result.matchesRequiredC8DevDependencyBefore =
326
- previousC8DevDependency === requiredC8Version;
327
-
328
- if (
329
- (!result.matchesRequiredCoverageBefore ||
330
- !result.hasRequiredTestScriptBefore ||
331
- !result.matchesRequiredC8DevDependencyBefore) &&
332
- mode === 'sync'
333
- ) {
334
- scripts[REQUIRED_COVERAGE_SCRIPT_KEY] = requiredCoverageScript;
335
- if (!result.hasRequiredTestScriptBefore) {
336
- scripts[REQUIRED_TEST_SCRIPT_KEY] = DEFAULT_TEST_SCRIPT_VALUE;
337
- }
338
- devDependencies.c8 = requiredC8Version;
339
- pkg.scripts = scripts;
340
- pkg.devDependencies = devDependencies;
341
- fs.writeFileSync(
342
- packageJsonPath,
343
- `${JSON.stringify(pkg, null, 2)}\n`,
344
- 'utf8',
345
- );
346
- result.updated = true;
347
- }
348
-
349
- result.coverageScript =
350
- mode === 'sync' && !result.matchesRequiredCoverageBefore
351
- ? requiredCoverageScript
352
- : previousCoverage;
353
- result.testScript =
354
- mode === 'sync' && !result.hasRequiredTestScriptBefore
355
- ? DEFAULT_TEST_SCRIPT_VALUE
356
- : previousTestScript;
357
- result.c8DevDependency =
358
- mode === 'sync' && !result.matchesRequiredC8DevDependencyBefore
359
- ? requiredC8Version
360
- : previousC8DevDependency;
361
-
362
- result.matchesRequiredCoverageAfter =
363
- result.updated || result.matchesRequiredCoverageBefore;
364
- result.hasRequiredTestScriptAfter =
365
- (mode === 'sync' && !result.hasRequiredTestScriptBefore) ||
366
- result.hasRequiredTestScriptBefore;
367
- result.matchesRequiredC8DevDependencyAfter =
368
- result.updated || result.matchesRequiredC8DevDependencyBefore;
369
- return result;
370
- }
371
-
372
235
  export function runSyncCoverage(options) {
373
236
  const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
374
237
  const check = hasFlag(options, '--check');
@@ -376,7 +239,6 @@ export function runSyncCoverage(options) {
376
239
  const jsonFile = getSingle(options, '--json', '');
377
240
  const { baseline: toolingBaseline, toolingBaselinePath } =
378
241
  loadToolingBaseline();
379
- const requiredCoverageScript = buildRequiredCoverageScript(toolingBaseline);
380
242
  const requiredC8Version = buildRequiredC8DevDependency(toolingBaseline);
381
243
  const requiredC8ConfigContent = loadRequiredC8ConfigContent();
382
244
 
@@ -392,7 +254,6 @@ export function runSyncCoverage(options) {
392
254
  requiredC8Version,
393
255
  requiredC8ConfigContent,
394
256
  );
395
- const workspacePaths = resolveWorkspacePaths(cwd, options);
396
257
 
397
258
  const report = {
398
259
  cwd,
@@ -402,12 +263,8 @@ export function runSyncCoverage(options) {
402
263
  schemaVersion: toolingBaseline.schemaVersion,
403
264
  c8Version: toolingBaseline.tools.c8.version,
404
265
  },
405
- requiredCoverageScript,
406
- requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
407
266
  requiredC8DevDependency: requiredC8Version,
408
267
  root,
409
- workspaces: workspacePaths,
410
- results: [],
411
268
  ok: true,
412
269
  };
413
270
 
@@ -420,32 +277,6 @@ export function runSyncCoverage(options) {
420
277
  report.ok = false;
421
278
  }
422
279
 
423
- for (const workspacePath of workspacePaths) {
424
- const effectiveMode = mode === 'sync' ? 'sync' : 'check';
425
- const item = reconcileCoverageScript(
426
- cwd,
427
- workspacePath,
428
- effectiveMode,
429
- requiredCoverageScript,
430
- requiredC8Version,
431
- );
432
- report.results.push(item);
433
-
434
- if (item.error) {
435
- report.ok = false;
436
- continue;
437
- }
438
-
439
- if (
440
- mode === 'check' &&
441
- (!item.matchesRequiredCoverageAfter ||
442
- !item.hasRequiredTestScriptAfter ||
443
- !item.matchesRequiredC8DevDependencyAfter)
444
- ) {
445
- report.ok = false;
446
- }
447
- }
448
-
449
280
  if (jsonFile) {
450
281
  const outPath = path.resolve(cwd, jsonFile);
451
282
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
@@ -1,15 +1,20 @@
1
1
  {
2
+ "branches": 99.5,
2
3
  "check-coverage": true,
3
4
  "exclude": [
4
5
  "**/node_modules/**",
5
6
  "**/coverage/**",
6
7
  "**/dist/**",
7
8
  "**/build/**",
8
- "**/out/**"
9
+ "**/out/**",
10
+ "**/test/**"
9
11
  ],
10
- "reporter": ["lcov", "html", "text-summary"],
11
- "branches": 99.5,
12
12
  "functions": 99.5,
13
- "statements": 99.5,
14
- "lines": 99.5
13
+ "lines": 99.5,
14
+ "reporter": [
15
+ "lcov",
16
+ "html",
17
+ "text-summary"
18
+ ],
19
+ "statements": 99.5
15
20
  }
@@ -99,6 +99,35 @@ function parseJsonFile(filePath, label) {
99
99
  }
100
100
  }
101
101
 
102
+ function resolveSemverExact(text) {
103
+ return text.replace(/^[\^~>=<]+\s*/, '').trim();
104
+ }
105
+
106
+ function resolveToolVersionFromDevDeps(baseline, toolName) {
107
+ // If baseline has a concrete version (not "auto"), use it directly.
108
+ // This is the case when reading the published publish-assets baseline.
109
+ const baselineVersion = String(
110
+ baseline?.tools?.[toolName]?.version || '',
111
+ ).trim();
112
+ if (baselineVersion && baselineVersion !== 'auto') {
113
+ return baselineVersion;
114
+ }
115
+
116
+ // Fall back to resolving from local root package.json devDependencies.
117
+ // This covers source baseline with version="auto" during local dev.
118
+ const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
119
+ const pkgJsonPath = path.resolve(repoRoot, 'package.json');
120
+ if (fs.existsSync(pkgJsonPath)) {
121
+ const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
122
+ const dep = pkg?.devDependencies?.[toolName];
123
+ if (typeof dep === 'string' && dep.trim()) {
124
+ return resolveSemverExact(dep);
125
+ }
126
+ }
127
+
128
+ return '';
129
+ }
130
+
102
131
  function loadToolingBaseline() {
103
132
  const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
104
133
  (candidatePath) => {
@@ -117,9 +146,7 @@ function loadToolingBaseline() {
117
146
  }
118
147
 
119
148
  const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
120
- const prettierVersion = String(
121
- baseline?.tools?.prettier?.version || '',
122
- ).trim();
149
+ const prettierVersion = resolveToolVersionFromDevDeps(baseline, 'prettier');
123
150
 
124
151
  if (!prettierVersion) {
125
152
  console.error(
@@ -116,6 +116,35 @@ function getRequiredToolkitDevDependency() {
116
116
  return version;
117
117
  }
118
118
 
119
+ function resolveSemverExact(text) {
120
+ return text.replace(/^[\^~>=<]+\s*/, '').trim();
121
+ }
122
+
123
+ function resolveToolVersionFromDevDeps(baseline, toolName) {
124
+ // If baseline has a concrete version (not "auto"), use it directly.
125
+ // This is the case when reading the published publish-assets baseline.
126
+ const baselineVersion = String(
127
+ baseline?.tools?.[toolName]?.version || '',
128
+ ).trim();
129
+ if (baselineVersion && baselineVersion !== 'auto') {
130
+ return baselineVersion;
131
+ }
132
+
133
+ // Fall back to resolving from local root package.json devDependencies.
134
+ // This covers source baseline with version="auto" during local dev.
135
+ const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
136
+ const pkgJsonPath = path.resolve(repoRoot, 'package.json');
137
+ if (fs.existsSync(pkgJsonPath)) {
138
+ const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
139
+ const dep = pkg?.devDependencies?.[toolName];
140
+ if (typeof dep === 'string' && dep.trim()) {
141
+ return resolveSemverExact(dep);
142
+ }
143
+ }
144
+
145
+ return '';
146
+ }
147
+
119
148
  function loadToolingBaseline() {
120
149
  const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
121
150
  (candidatePath) => {
@@ -134,8 +163,8 @@ function loadToolingBaseline() {
134
163
  }
135
164
 
136
165
  const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
137
- const huskyVersion = String(baseline?.tools?.husky?.version || '').trim();
138
- const lernaVersion = String(baseline?.tools?.lerna?.version || '').trim();
166
+ const huskyVersion = resolveToolVersionFromDevDeps(baseline, 'husky');
167
+ const lernaVersion = resolveToolVersionFromDevDeps(baseline, 'lerna');
139
168
 
140
169
  if (!huskyVersion || !lernaVersion) {
141
170
  console.error(
@@ -128,16 +128,38 @@ function getRequiredEslintDevDependencies() {
128
128
  eslintRulesVersion = v;
129
129
  }
130
130
 
131
+ function resolveSemverExact(text) {
132
+ return text.replace(/^[\^~>=<]+\s*/, '').trim();
133
+ }
134
+
135
+ function resolveToolVersionFromDevDeps(name) {
136
+ // If baseline has a concrete version (not "auto"), use it directly.
137
+ // This is the case when reading the published publish-assets baseline.
138
+ const entry = baseline?.tools?.[name];
139
+ const v = typeof entry?.version === 'string' ? entry.version.trim() : '';
140
+ if (v && v !== 'auto') {
141
+ return v;
142
+ }
143
+
144
+ // Fall back to resolving from local root package.json devDependencies.
145
+ // This covers source baseline with version="auto" during local dev.
146
+ const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
147
+ const pkgJsonPath = path.resolve(repoRoot, 'package.json');
148
+ if (fs.existsSync(pkgJsonPath)) {
149
+ const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
150
+ const dep = pkg?.devDependencies?.[name];
151
+ if (typeof dep === 'string' && dep.trim()) {
152
+ return resolveSemverExact(dep);
153
+ }
154
+ }
155
+
156
+ return '';
157
+ }
158
+
131
159
  const deps = { [ESLINT_RULES_PACKAGE_NAME]: eslintRulesVersion };
132
160
 
133
161
  for (const name of ESLINT_TOOLING_PACKAGE_NAMES) {
134
- const entry = baseline?.tools?.[name];
135
- const v =
136
- typeof entry?.version === 'string'
137
- ? entry.version.trim()
138
- : /* c8 ignore next */
139
- '';
140
- /* c8 ignore next 6 */
162
+ const v = resolveToolVersionFromDevDeps(name);
141
163
  if (!v) {
142
164
  console.error(
143
165
  `Tooling baseline tools["${name}"].version must be a non-empty string: ${toolingBaselinePath}`,
@@ -10,7 +10,7 @@ Behavior:
10
10
  - Sync mode applies organization-required root publish scripts:
11
11
  - scripts.produck:publish:check = npm run produck:install && npm run produck:format && npm run produck:lint && npm run produck:coverage
12
12
  - scripts.produck:publish = npm run produck:publish:check && npm run publish --
13
- when scripts.publish exists; otherwise it falls back to lerna publish
13
+ - If scripts.publish is missing, defaults to "lerna publish"
14
14
  - Enforces lerna.json command.version.commitHooks = false so publish/version commits skip git commit hooks
15
15
 
16
16
  Rules: