@produck/agent-toolkit 0.3.3 → 0.5.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.
package/README.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  Central CLI toolkit for organization-level AI execution workflows.
4
4
 
5
+ ## First-time bootstrap (downstream repositories)
6
+
7
+ For a new or existing downstream repository that has not yet applied the
8
+ organization baseline, run:
9
+
10
+ ```
11
+ npm create @produck/agent-toolkit@latest
12
+ ```
13
+
14
+ This command installs `@produck/create-agent-toolkit` and runs
15
+ `enforce-node-baseline` in the current directory. No prior installation is
16
+ required — npm handles the download automatically.
17
+
18
+ What it does (in order):
19
+
20
+ 1. Syncs organization AI instruction files into `.github/instructions/produck/`
21
+ 2. Runs preflight to verify required files and directories
22
+ 3. Syncs root `produck:format` script and initializes `.prettierrc`
23
+ 4. Syncs root `produck:lint` script, initializes/patches `eslint.config.mjs`,
24
+ and ensures `@produck/eslint-rules`
25
+ 5. Syncs root shared governance (`produck:baseline`,
26
+ `produck:coverage`, `produck:precommit-check`), initializes `.c8rc.json`,
27
+ and syncs shared pinned devDependencies
28
+ (`c8`, `husky`, `lerna`, `@produck/agent-toolkit`)
29
+ 6. Deploys the pinned `produck:coverage` script and `c8` devDependency to each workspace package, and enforces `scripts.test` (generates a default `test` script when missing).
30
+ **Note:** The `produck:coverage` script in subpackages is for local and AI development use only. It is NOT enforced by organization CI or `.c8rc.json`. Only the root workspace (monorepo root) is subject to org-level coverage enforcement and `.c8rc.json`.
31
+ 7. Deploys `.husky/pre-commit` and `.husky/commit-msg`
32
+
33
+ After running, add the persistent enforcement entry to the repository
34
+ `package.json`:
35
+
36
+ ```json
37
+ "produck:baseline": "npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd ."
38
+ ```
39
+
40
+ Then future enforcement runs via:
41
+
42
+ ```
43
+ npm run produck:baseline
44
+ ```
45
+
5
46
  ## Commands
6
47
 
7
48
  - agent-toolkit enforce-node-baseline
@@ -9,6 +50,9 @@ Central CLI toolkit for organization-level AI execution workflows.
9
50
  - agent-toolkit run-capture
10
51
  - agent-toolkit summarize-log
11
52
  - agent-toolkit sync-coverage-script
53
+ - agent-toolkit sync-prettier-config
54
+ - agent-toolkit sync-eslint-config
55
+ - agent-toolkit sync-workspace-config
12
56
  - agent-toolkit sync-husky-hooks
13
57
  - agent-toolkit validate-commit-msg
14
58
  - agent-toolkit sync-instructions
@@ -27,24 +71,62 @@ Equivalent explicit form:
27
71
  npm exec -- agent-toolkit enforce-node-baseline --cwd .
28
72
  ```
29
73
 
30
- Run preflight checks:
74
+ `enforce-node-baseline` runs seven steps in fixed order and stops at the first
75
+ failure:
76
+
77
+ 1. `sync-instructions` — distribute organization AI instruction files into
78
+ `.github/instructions/produck/`
79
+ 2. `preflight` — verify required files and directories exist
80
+ 3. `sync-prettier-config` — deploy organization format gate script
81
+ (`produck:format`) and initialize `.prettierrc`
82
+ 4. `sync-eslint-config` — deploy organization lint gate script
83
+ (`produck:lint`), initialize/patch `eslint.config.mjs`, and ensure
84
+ `@produck/eslint-rules` integration
85
+ 5. `sync-workspace-config` — deploy shared root governance scripts
86
+ (`produck:baseline`, `produck:coverage`, `produck:precommit-check`),
87
+ initialize root `.c8rc.json`, and sync shared pinned root devDependencies
88
+ (`c8`, `husky`, `lerna`, `@produck/agent-toolkit`)
89
+ 6. `sync-coverage-script` — deploy pinned `produck:coverage` script and `c8`
90
+ devDependency into each workspace package, and ensure each workspace
91
+ package has `scripts.test` (auto-generate a default value when missing)
92
+ 7. `sync-husky-hooks` — deploy `.husky/pre-commit` and `.husky/commit-msg`
93
+
94
+ Add to downstream repository root `package.json` for one-command enforcement:
95
+
96
+ ```json
97
+ "produck:baseline": "npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd ."
98
+ ```
99
+
100
+ Then run:
31
101
 
32
102
  ```
33
- npm exec -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
103
+ npm run produck:baseline
34
104
  ```
35
105
 
36
- Run one-shot mandatory baseline steps for downstream monorepo (1 -> 2 -> 3):
106
+ Dry-run to preview changes without writing files:
37
107
 
38
108
  ```
39
- npm exec -- agent-toolkit enforce-node-baseline --cwd .
109
+ npm exec -- agent-toolkit enforce-node-baseline --cwd . --dry-run
110
+ ```
111
+
112
+ Check-only mode to validate without writing:
113
+
114
+ ```
115
+ npm exec -- agent-toolkit enforce-node-baseline --cwd . --check
40
116
  ```
41
117
 
42
- Validate monorepo root workspace package.json baseline:
118
+ Validate monorepo root `package.json` scripts and workspace structure:
43
119
 
44
120
  ```
45
121
  npm exec -- agent-toolkit preflight --cwd . --check-workspace-package-json package.json
46
122
  ```
47
123
 
124
+ Run preflight with required-file and directory guards:
125
+
126
+ ```
127
+ npm exec -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
128
+ ```
129
+
48
130
  Capture long output safely:
49
131
 
50
132
  ```
@@ -57,22 +139,59 @@ Summarize captured output:
57
139
  npm exec -- agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
58
140
  ```
59
141
 
60
- Deploy organization coverage script and pinned local c8 devDependency to
61
- workspace packages:
142
+ Deploy organization coverage script and pinned local c8 devDependency to workspace packages:
62
143
 
63
144
  ```
64
145
  npm exec -- agent-toolkit sync-coverage-script --cwd .
65
146
  ```
66
147
 
148
+ This command also enforces `scripts.test` in each workspace package.
149
+ If missing, it generates:
150
+
151
+ ```
152
+ node -e "console.log('No tests configured')"
153
+ ```
154
+
155
+ **Note:** The `produck:coverage` script in subpackages is for local and AI development use only. It is NOT enforced by organization CI or `.c8rc.json`. Only the root workspace (monorepo root) is subject to org-level coverage enforcement and `.c8rc.json`.
156
+
157
+ Deploy organization format config and script baseline to repository root:
158
+
159
+ ```
160
+ npm exec -- agent-toolkit sync-prettier-config --cwd .
161
+ ```
162
+
163
+ This command manages `scripts.produck:format` and `.prettierrc` only.
164
+
165
+ Deploy organization lint config and script baseline to repository root:
166
+
167
+ ```
168
+ npm exec -- agent-toolkit sync-eslint-config --cwd .
169
+ ```
170
+
171
+ This command manages `scripts.produck:lint`, `eslint.config.mjs`, and
172
+ `devDependencies.@produck/eslint-rules`. If `eslint.config.mjs` exists without
173
+ `@produck/eslint-rules`, it appends Produck integration to the exported config
174
+ array.
175
+
176
+ Deploy organization root shared scripts and shared pinned dependencies to
177
+ repository root:
178
+
179
+ ```
180
+ npm exec -- agent-toolkit sync-workspace-config --cwd .
181
+ ```
182
+
183
+ This command manages `scripts.produck:baseline`,
184
+ `scripts.produck:coverage`, `scripts.produck:precommit-check`, initializes
185
+ `.c8rc.json`, and pins `c8`, `husky`, `lerna`, and `@produck/agent-toolkit`
186
+ in root `devDependencies`.
187
+
67
188
  Deploy organization local anti-drift husky hooks to repository root:
68
189
 
69
190
  ```
70
191
  npm exec -- agent-toolkit sync-husky-hooks --cwd .
71
192
  ```
72
193
 
73
- This command pins root local hook dependencies (`husky` and
74
- `@produck/agent-toolkit`) and syncs `.husky/pre-commit` and
75
- `.husky/commit-msg`.
194
+ This command syncs only `.husky/pre-commit` and `.husky/commit-msg`.
76
195
 
77
196
  Validate commit message format:
78
197
 
@@ -121,11 +240,18 @@ Organization-only instruction source (not published):
121
240
 
122
241
  ## Local verification
123
242
 
243
+ `verify` is an optional package-level health check and is not a commit gate.
244
+ Use repository style gates first, then run package checks when needed.
245
+
124
246
  From repository root:
125
247
 
126
248
  ```bash
127
- npm --workspace @produck/agent-toolkit run verify
249
+ npm run format:check
250
+ npm run lint
251
+ npm --workspace @produck/agent-toolkit run test
128
252
  npm --workspace @produck/agent-toolkit run pack:check
253
+ # optional health check
254
+ npm --workspace @produck/agent-toolkit run verify
129
255
  ```
130
256
 
131
257
  ## Publishing
@@ -156,13 +282,14 @@ Repository includes manual workflow:
156
282
 
157
283
  Workflow behavior:
158
284
 
159
- - Always runs verify and pack:check for `@produck/agent-toolkit`.
285
+ - Runs test and pack:check for `@produck/agent-toolkit`.
160
286
  - Does not publish to npm.
161
287
  - Used as release gate before workspace-level publish.
162
288
 
163
289
  Release policy:
164
290
 
165
- - Default organization usage is @latest.
291
+ - Central package is installed locally in downstream repositories at a fixed
292
+ version managed by `agent-toolkit sync-husky-hooks`.
166
293
  - Run format:check and test first, then workspace `publish:dry-run` before
167
294
  `publish`.
168
295
  - Keep rollback option by republishing previous stable version if needed.
@@ -16,6 +16,18 @@ import {
16
16
  printSyncInstructionsHelp,
17
17
  runSyncInstructions,
18
18
  } from './command/sync-instructions/index.mjs';
19
+ import {
20
+ printSyncPrettierConfigHelp,
21
+ runSyncPrettierConfig,
22
+ } from './command/sync-prettier-config/index.mjs';
23
+ import {
24
+ printSyncEslintConfigHelp,
25
+ runSyncEslintConfig,
26
+ } from './command/sync-eslint-config/index.mjs';
27
+ import {
28
+ printSyncWorkspaceConfigHelp,
29
+ runSyncWorkspaceConfig,
30
+ } from './command/sync-workspace-config/index.mjs';
19
31
  import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
20
32
  import {
21
33
  printValidateCommitMsgHelp,
@@ -43,6 +55,18 @@ const COMMANDS = {
43
55
  printHelp: printSyncCoverageScriptHelp,
44
56
  run: runSyncCoverageScript,
45
57
  },
58
+ 'sync-prettier-config': {
59
+ printHelp: printSyncPrettierConfigHelp,
60
+ run: runSyncPrettierConfig,
61
+ },
62
+ 'sync-eslint-config': {
63
+ printHelp: printSyncEslintConfigHelp,
64
+ run: runSyncEslintConfig,
65
+ },
66
+ 'sync-workspace-config': {
67
+ printHelp: printSyncWorkspaceConfigHelp,
68
+ run: runSyncWorkspaceConfig,
69
+ },
46
70
  'sync-husky-hooks': {
47
71
  printHelp: printSyncHuskyHooksHelp,
48
72
  run: runSyncHuskyHooks,
@@ -7,13 +7,16 @@ Behavior:
7
7
  - Runs mandatory baseline flow in fixed order:
8
8
  1) sync-instructions
9
9
  2) preflight
10
- 3) sync-coverage-script
11
- 4) sync-husky-hooks
10
+ 3) sync-prettier-config
11
+ 4) sync-eslint-config
12
+ 5) sync-workspace-config
13
+ 6) sync-coverage-script
14
+ 7) sync-husky-hooks
12
15
  - Stops at first failed step and exits non-zero
13
16
  - Prints one combined JSON report for all executed steps
14
17
 
15
18
  Rules:
16
- - --check runs non-mutating validation mode for step 1, step 3, and step 4
17
- - --dry-run runs non-mutating preview mode for step 1, step 3, and step 4
19
+ - --check runs non-mutating validation mode for step 1, step 3, step 4, step 5, step 6, and step 7
20
+ - --dry-run runs non-mutating preview mode for step 1, step 3, step 4, step 5, step 6, and step 7
18
21
  - --check takes precedence over --dry-run
19
- - --workspace filters coverage sync targets in step 3
22
+ - --workspace filters coverage sync targets in step 6
@@ -98,15 +98,10 @@ export function runEnforceNodeBaseline(options) {
98
98
  syncInstructionsArgs.push('--dry-run');
99
99
  }
100
100
 
101
- const preflightArgs = [
102
- 'preflight',
103
- '--cwd',
104
- cwd,
105
- '--require',
106
- 'package.json',
107
- '--check-workspace-package-json',
108
- 'package.json',
109
- ];
101
+ const preflightArgs = ['preflight', '--cwd', cwd, '--require', 'package.json'];
102
+ if (mode !== 'sync') {
103
+ preflightArgs.push('--check-workspace-package-json', 'package.json');
104
+ }
110
105
 
111
106
  const syncCoverageArgs = ['sync-coverage-script', '--cwd', cwd];
112
107
  for (const workspacePath of workspaces) {
@@ -125,9 +120,33 @@ export function runEnforceNodeBaseline(options) {
125
120
  syncHuskyArgs.push('--dry-run');
126
121
  }
127
122
 
123
+ const syncWorkspaceConfigArgs = ['sync-workspace-config', '--cwd', cwd];
124
+ if (check) {
125
+ syncWorkspaceConfigArgs.push('--check');
126
+ } else if (dryRun) {
127
+ syncWorkspaceConfigArgs.push('--dry-run');
128
+ }
129
+
130
+ const syncPrettierConfigArgs = ['sync-prettier-config', '--cwd', cwd];
131
+ if (check) {
132
+ syncPrettierConfigArgs.push('--check');
133
+ } else if (dryRun) {
134
+ syncPrettierConfigArgs.push('--dry-run');
135
+ }
136
+
137
+ const syncEslintConfigArgs = ['sync-eslint-config', '--cwd', cwd];
138
+ if (check) {
139
+ syncEslintConfigArgs.push('--check');
140
+ } else if (dryRun) {
141
+ syncEslintConfigArgs.push('--dry-run');
142
+ }
143
+
128
144
  const plan = [
129
145
  { name: 'sync-instructions', args: syncInstructionsArgs },
130
146
  { name: 'preflight', args: preflightArgs },
147
+ { name: 'sync-prettier-config', args: syncPrettierConfigArgs },
148
+ { name: 'sync-eslint-config', args: syncEslintConfigArgs },
149
+ { name: 'sync-workspace-config', args: syncWorkspaceConfigArgs },
131
150
  { name: 'sync-coverage-script', args: syncCoverageArgs },
132
151
  { name: 'sync-husky-hooks', args: syncHuskyArgs },
133
152
  ];
@@ -4,6 +4,9 @@ agent-toolkit commands:
4
4
  run-capture
5
5
  summarize-log
6
6
  sync-coverage-script
7
+ sync-prettier-config
8
+ sync-eslint-config
9
+ sync-workspace-config
7
10
  sync-husky-hooks
8
11
  validate-commit-msg
9
12
  sync-instructions
@@ -4,10 +4,10 @@ import { fileURLToPath } from 'node:url';
4
4
 
5
5
  import { getSingle, hasFlag } from '../shared/args.mjs';
6
6
  import { printTextResource } from '../shared/text-resource.mjs';
7
+ import { validateWorkspaceShape } from '../shared/workspace-validation.mjs';
7
8
 
8
9
  const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
9
10
  const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
10
- const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
11
11
  const REQUIRED_WORKSPACE_FIELDS = ['private', 'workspaces', 'scripts'];
12
12
  const REQUIRED_WORKSPACE_SCRIPTS = ['deps:install', 'test', 'produck:coverage', 'lint'];
13
13
 
@@ -43,40 +43,12 @@ function validateWorkspacePackageJson(cwd, checkPath) {
43
43
  return check;
44
44
  }
45
45
 
46
- check.missingFields = REQUIRED_WORKSPACE_FIELDS.filter((field) => {
47
- return !(field in json);
48
- });
49
-
50
- if (typeof json.scripts !== 'object' || json.scripts === null || Array.isArray(json.scripts)) {
51
- check.scriptTypeValid = false;
52
- check.ok = false;
53
- } else {
54
- check.missingScripts = REQUIRED_WORKSPACE_SCRIPTS.filter((scriptName) => {
55
- return !(scriptName in json.scripts);
56
- });
57
- }
58
-
59
- const workspaceList = Array.isArray(json.workspaces)
60
- ? json.workspaces.map((entry) => String(entry))
61
- : [];
62
- if (!Array.isArray(json.workspaces)) {
63
- check.wildcardWorkspaces = ['<non-array-workspaces>'];
64
- check.ok = false;
65
- } else {
66
- check.wildcardWorkspaces = workspaceList.filter((entry) => GLOB_TOKEN_PATTERN.test(entry));
67
- }
68
-
69
- if (json.private !== true) {
70
- check.ok = false;
71
- }
72
-
73
- if (check.missingFields.length > 0 || check.missingScripts.length > 0) {
74
- check.ok = false;
75
- }
76
-
77
- if (check.wildcardWorkspaces.length > 0) {
78
- check.ok = false;
79
- }
46
+ const shape = validateWorkspaceShape(json, REQUIRED_WORKSPACE_FIELDS, REQUIRED_WORKSPACE_SCRIPTS);
47
+ check.missingFields = shape.missingFields;
48
+ check.scriptTypeValid = shape.scriptTypeValid;
49
+ check.missingScripts = shape.missingScripts;
50
+ check.wildcardWorkspaces = shape.wildcardWorkspaces;
51
+ check.ok = shape.ok;
80
52
 
81
53
  return check;
82
54
  }
@@ -0,0 +1,63 @@
1
+ const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
2
+
3
+ export function toObjectRecord(value) {
4
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
5
+ return { ...value };
6
+ }
7
+
8
+ return {};
9
+ }
10
+
11
+ export function findMissingKeys(record, requiredKeys) {
12
+ return requiredKeys.filter((key) => {
13
+ return !(key in record);
14
+ });
15
+ }
16
+
17
+ export function validateWorkspaceShape(pkg, requiredFields, requiredScripts) {
18
+ const missingFields = findMissingKeys(pkg, requiredFields);
19
+ const scripts = toObjectRecord(pkg.scripts);
20
+ const scriptTypeValid =
21
+ typeof pkg.scripts === 'object' && pkg.scripts !== null && !Array.isArray(pkg.scripts);
22
+ const missingScripts = scriptTypeValid ? findMissingKeys(scripts, requiredScripts) : [];
23
+
24
+ const workspacesIsArray = Array.isArray(pkg.workspaces);
25
+ const wildcardWorkspaces = workspacesIsArray
26
+ ? pkg.workspaces.map((entry) => String(entry)).filter((entry) => GLOB_TOKEN_PATTERN.test(entry))
27
+ : ['<non-array-workspaces>'];
28
+
29
+ const privateIsTrue = pkg.private === true;
30
+ const ok =
31
+ missingFields.length === 0 &&
32
+ scriptTypeValid &&
33
+ missingScripts.length === 0 &&
34
+ workspacesIsArray &&
35
+ wildcardWorkspaces.length === 0 &&
36
+ privateIsTrue;
37
+
38
+ return {
39
+ ok,
40
+ missingFields,
41
+ scriptTypeValid,
42
+ missingScripts,
43
+ wildcardWorkspaces,
44
+ };
45
+ }
46
+
47
+ export function validateRequiredExactEntries(currentRecord, requiredRecord) {
48
+ const mismatches = {};
49
+
50
+ for (const [name, expectedValue] of Object.entries(requiredRecord)) {
51
+ if (currentRecord[name] !== expectedValue) {
52
+ mismatches[name] = {
53
+ expected: expectedValue,
54
+ actual: name in currentRecord ? currentRecord[name] : null,
55
+ };
56
+ }
57
+ }
58
+
59
+ return {
60
+ ok: Object.keys(mismatches).length === 0,
61
+ mismatches,
62
+ };
63
+ }
@@ -3,10 +3,14 @@ Usage:
3
3
  [--workspace <path>] ... [--check] [--dry-run] [--json <file>]
4
4
 
5
5
  Behavior:
6
- - Applies organization-required coverage script to workspace package.json files
6
+ - Adds or updates scripts.produck:coverage and devDependencies.c8 in each workspace package.json.
7
+ - Enforces scripts.test in each workspace package.json.
8
+ - If scripts.test is missing, generates:
9
+ node -e "console.log('No tests configured')"
10
+ - The produck:coverage script in subpackages is for local and AI development use only.
11
+ - Subpackage produck:coverage is NOT enforced by organization CI or .c8rc.json.
12
+ - Only the root workspace (monorepo root) is subject to org-level coverage enforcement and .c8rc.json.
7
13
  - Organization-reserved script key is scripts.produck:coverage
8
- - Applies organization-required pinned local c8 devDependency to workspace
9
- package.json files
10
14
  - Target script is rendered from organization tooling baseline file
11
15
  (lookup order):
12
16
  1) .github/distribution/produck/tooling-version-baseline.json
@@ -17,6 +21,9 @@ Behavior:
17
21
  Rules:
18
22
  - When --workspace is omitted, root package.json workspaces are used
19
23
  - Root workspaces must be explicit paths (no glob tokens)
24
+ - Workspace package.json files must include scripts.test
20
25
  - Workspace package.json files must pin devDependencies.c8 to baseline version
26
+ - Subpackage produck:coverage is not a CI or org gate; it is for local/dev use only
27
+ - Only the root workspace is enforced by org CI and .c8rc.json
21
28
  - --check validates without writing and exits non-zero on mismatch
22
29
  - --dry-run prints planned changes without writing
@@ -15,6 +15,8 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
15
15
  ];
16
16
  const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
17
17
  const REQUIRED_COVERAGE_SCRIPT_KEY = 'produck:coverage';
18
+ const REQUIRED_TEST_SCRIPT_KEY = 'test';
19
+ const DEFAULT_TEST_SCRIPT_VALUE = 'node -e "console.log(\'No tests configured\')"';
18
20
 
19
21
  export function printSyncCoverageScriptHelp() {
20
22
  printTextResource(HELP_FILE);
@@ -132,10 +134,14 @@ function reconcileCoverageScript(
132
134
  validJson: false,
133
135
  previousCoverage: null,
134
136
  coverageScript: null,
137
+ previousTestScript: null,
138
+ testScript: null,
135
139
  previousC8DevDependency: null,
136
140
  c8DevDependency: null,
137
141
  matchesRequiredCoverageBefore: false,
138
142
  matchesRequiredCoverageAfter: false,
143
+ hasRequiredTestScriptBefore: false,
144
+ hasRequiredTestScriptAfter: false,
139
145
  matchesRequiredC8DevDependencyBefore: false,
140
146
  matchesRequiredC8DevDependencyAfter: false,
141
147
  updated: false,
@@ -172,18 +178,30 @@ function reconcileCoverageScript(
172
178
  typeof scripts[REQUIRED_COVERAGE_SCRIPT_KEY] === 'string'
173
179
  ? scripts[REQUIRED_COVERAGE_SCRIPT_KEY]
174
180
  : null;
181
+ const previousTestScript =
182
+ typeof scripts[REQUIRED_TEST_SCRIPT_KEY] === 'string' &&
183
+ scripts[REQUIRED_TEST_SCRIPT_KEY].trim() !== ''
184
+ ? scripts[REQUIRED_TEST_SCRIPT_KEY]
185
+ : null;
175
186
  const previousC8DevDependency =
176
187
  typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null;
177
188
  result.previousCoverage = previousCoverage;
189
+ result.previousTestScript = previousTestScript;
178
190
  result.previousC8DevDependency = previousC8DevDependency;
179
191
  result.matchesRequiredCoverageBefore = previousCoverage === requiredCoverageScript;
192
+ result.hasRequiredTestScriptBefore = previousTestScript !== null;
180
193
  result.matchesRequiredC8DevDependencyBefore = previousC8DevDependency === requiredC8Version;
181
194
 
182
195
  if (
183
- (!result.matchesRequiredCoverageBefore || !result.matchesRequiredC8DevDependencyBefore) &&
196
+ (!result.matchesRequiredCoverageBefore ||
197
+ !result.hasRequiredTestScriptBefore ||
198
+ !result.matchesRequiredC8DevDependencyBefore) &&
184
199
  mode === 'sync'
185
200
  ) {
186
201
  scripts[REQUIRED_COVERAGE_SCRIPT_KEY] = requiredCoverageScript;
202
+ if (!result.hasRequiredTestScriptBefore) {
203
+ scripts[REQUIRED_TEST_SCRIPT_KEY] = DEFAULT_TEST_SCRIPT_VALUE;
204
+ }
187
205
  devDependencies.c8 = requiredC8Version;
188
206
  pkg.scripts = scripts;
189
207
  pkg.devDependencies = devDependencies;
@@ -195,12 +213,18 @@ function reconcileCoverageScript(
195
213
  mode === 'sync' && !result.matchesRequiredCoverageBefore
196
214
  ? requiredCoverageScript
197
215
  : previousCoverage;
216
+ result.testScript =
217
+ mode === 'sync' && !result.hasRequiredTestScriptBefore
218
+ ? DEFAULT_TEST_SCRIPT_VALUE
219
+ : previousTestScript;
198
220
  result.c8DevDependency =
199
221
  mode === 'sync' && !result.matchesRequiredC8DevDependencyBefore
200
222
  ? requiredC8Version
201
223
  : previousC8DevDependency;
202
224
 
203
225
  result.matchesRequiredCoverageAfter = result.updated || result.matchesRequiredCoverageBefore;
226
+ result.hasRequiredTestScriptAfter =
227
+ (mode === 'sync' && !result.hasRequiredTestScriptBefore) || result.hasRequiredTestScriptBefore;
204
228
  result.matchesRequiredC8DevDependencyAfter =
205
229
  result.updated || result.matchesRequiredC8DevDependencyBefore;
206
230
  return result;
@@ -232,6 +256,7 @@ export function runSyncCoverageScript(options) {
232
256
  c8Version: toolingBaseline.tools.c8.version,
233
257
  },
234
258
  requiredCoverageScript,
259
+ requiredTestScript: DEFAULT_TEST_SCRIPT_VALUE,
235
260
  requiredC8DevDependency: requiredC8Version,
236
261
  workspaces: workspacePaths,
237
262
  results: [],
@@ -256,7 +281,9 @@ export function runSyncCoverageScript(options) {
256
281
 
257
282
  if (
258
283
  mode === 'check' &&
259
- (!item.matchesRequiredCoverageAfter || !item.matchesRequiredC8DevDependencyAfter)
284
+ (!item.matchesRequiredCoverageAfter ||
285
+ !item.hasRequiredTestScriptAfter ||
286
+ !item.matchesRequiredC8DevDependencyAfter)
260
287
  ) {
261
288
  report.ok = false;
262
289
  }
@@ -0,0 +1,18 @@
1
+ Usage:
2
+ agent-toolkit sync-eslint-config [--cwd <dir>] [--check] [--dry-run]
3
+ [--json <file>]
4
+
5
+ Behavior:
6
+ - Applies organization-required root lint script:
7
+ - scripts.produck:lint = npm exec -- eslint --fix . --max-warnings=0 && npm run lint --if-present
8
+ - Applies organization-required root ESLint config file:
9
+ - eslint.config.mjs
10
+ - If eslint.config.mjs exists and does not use @produck/eslint-rules,
11
+ append Produck integration at the tail of export default array
12
+ - Applies organization-required root managed devDependency:
13
+ - devDependencies.@produck/eslint-rules = <latest-version-resolved-at-runtime>
14
+
15
+ Rules:
16
+ - --check validates without writing and exits non-zero on mismatch
17
+ - --dry-run prints planned changes without writing
18
+ - --check takes precedence over --dry-run