@produck/agent-toolkit 0.2.1 → 0.3.3

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
@@ -4,42 +4,86 @@ Central CLI toolkit for organization-level AI execution workflows.
4
4
 
5
5
  ## Commands
6
6
 
7
+ - agent-toolkit enforce-node-baseline
7
8
  - agent-toolkit preflight
8
9
  - agent-toolkit run-capture
9
10
  - agent-toolkit summarize-log
11
+ - agent-toolkit sync-coverage-script
12
+ - agent-toolkit sync-husky-hooks
10
13
  - agent-toolkit validate-commit-msg
11
14
  - agent-toolkit sync-instructions
12
15
 
13
16
  ## Examples
14
17
 
18
+ Run default mandatory baseline flow in downstream repository root:
19
+
20
+ ```
21
+ npm exec -- agent-toolkit
22
+ ```
23
+
24
+ Equivalent explicit form:
25
+
26
+ ```
27
+ npm exec -- agent-toolkit enforce-node-baseline --cwd .
28
+ ```
29
+
15
30
  Run preflight checks:
16
31
 
17
32
  ```
18
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
33
+ npm exec -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
34
+ ```
35
+
36
+ Run one-shot mandatory baseline steps for downstream monorepo (1 -> 2 -> 3):
37
+
38
+ ```
39
+ npm exec -- agent-toolkit enforce-node-baseline --cwd .
40
+ ```
41
+
42
+ Validate monorepo root workspace package.json baseline:
43
+
44
+ ```
45
+ npm exec -- agent-toolkit preflight --cwd . --check-workspace-package-json package.json
19
46
  ```
20
47
 
21
48
  Capture long output safely:
22
49
 
23
50
  ```
24
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit run-capture --cwd . --cmd "npm run test" --out logs/test.log
51
+ npm exec -- agent-toolkit run-capture --cwd . --cmd "npm run test" --out logs/test.log
25
52
  ```
26
53
 
27
54
  Summarize captured output:
28
55
 
29
56
  ```
30
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
57
+ npm exec -- agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
58
+ ```
59
+
60
+ Deploy organization coverage script and pinned local c8 devDependency to
61
+ workspace packages:
62
+
63
+ ```
64
+ npm exec -- agent-toolkit sync-coverage-script --cwd .
65
+ ```
66
+
67
+ Deploy organization local anti-drift husky hooks to repository root:
68
+
69
+ ```
70
+ npm exec -- agent-toolkit sync-husky-hooks --cwd .
31
71
  ```
32
72
 
73
+ This command pins root local hook dependencies (`husky` and
74
+ `@produck/agent-toolkit`) and syncs `.husky/pre-commit` and
75
+ `.husky/commit-msg`.
76
+
33
77
  Validate commit message format:
34
78
 
35
79
  ```
36
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit validate-commit-msg --file .git/COMMIT_EDITMSG
80
+ npm exec -- agent-toolkit validate-commit-msg --file .git/COMMIT_EDITMSG
37
81
  ```
38
82
 
39
83
  Manual per-repository instruction distribution (write .github/instructions/produck/\*.instructions.md):
40
84
 
41
85
  ```
42
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit sync-instructions --cwd .
86
+ npm exec -- agent-toolkit sync-instructions --cwd .
43
87
  ```
44
88
 
45
89
  Legacy repository bootstrap behavior:
@@ -52,7 +96,7 @@ Legacy repository bootstrap behavior:
52
96
  Use organization source directory instead of built-in assets:
53
97
 
54
98
  ```
55
- npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit sync-instructions --cwd . --source path/to/org/.github/distribution/produck --force --prune
99
+ npm exec -- agent-toolkit sync-instructions --cwd . --source path/to/org/.github/distribution/produck --force --prune
56
100
  ```
57
101
 
58
102
  Built-in command-local resource locations (for review and updates):
@@ -1,8 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { printMainHelp } from './command/main/index.mjs';
3
+ import {
4
+ printEnforceNodeBaselineHelp,
5
+ runEnforceNodeBaseline,
6
+ } from './command/enforce-node-baseline/index.mjs';
3
7
  import { printPreflightHelp, runPreflight } from './command/preflight/index.mjs';
4
8
  import { printRunCaptureHelp, runCapture } from './command/run-capture/index.mjs';
5
9
  import { printSummarizeHelp, runSummarize } from './command/summarize-log/index.mjs';
10
+ import {
11
+ printSyncCoverageScriptHelp,
12
+ runSyncCoverageScript,
13
+ } from './command/sync-coverage-script/index.mjs';
14
+ import { printSyncHuskyHooksHelp, runSyncHuskyHooks } from './command/sync-husky-hooks/index.mjs';
6
15
  import {
7
16
  printSyncInstructionsHelp,
8
17
  runSyncInstructions,
@@ -14,6 +23,10 @@ import {
14
23
  } from './command/validate-commit-msg/index.mjs';
15
24
 
16
25
  const COMMANDS = {
26
+ 'enforce-node-baseline': {
27
+ printHelp: printEnforceNodeBaselineHelp,
28
+ run: runEnforceNodeBaseline,
29
+ },
17
30
  preflight: {
18
31
  printHelp: printPreflightHelp,
19
32
  run: runPreflight,
@@ -26,6 +39,14 @@ const COMMANDS = {
26
39
  printHelp: printSummarizeHelp,
27
40
  run: runSummarize,
28
41
  },
42
+ 'sync-coverage-script': {
43
+ printHelp: printSyncCoverageScriptHelp,
44
+ run: runSyncCoverageScript,
45
+ },
46
+ 'sync-husky-hooks': {
47
+ printHelp: printSyncHuskyHooksHelp,
48
+ run: runSyncHuskyHooks,
49
+ },
29
50
  'validate-commit-msg': {
30
51
  printHelp: printValidateCommitMsgHelp,
31
52
  run: runValidateCommitMsg,
@@ -36,6 +57,8 @@ const COMMANDS = {
36
57
  },
37
58
  };
38
59
 
60
+ const DEFAULT_COMMAND = 'enforce-node-baseline';
61
+
39
62
  function printCommandHelp(command) {
40
63
  const entry = COMMANDS[command];
41
64
  if (!entry) {
@@ -50,17 +73,19 @@ function main() {
50
73
  const command = parsed.positional[0] || '';
51
74
  const options = parsed.options;
52
75
 
53
- if (!command || command === '--help' || command === '-h') {
76
+ if (command === '--help' || command === '-h' || (!command && hasFlag(options, '--help'))) {
54
77
  printMainHelp();
55
78
  process.exit(0);
56
79
  }
57
80
 
81
+ const effectiveCommand = command || DEFAULT_COMMAND;
82
+
58
83
  if (hasFlag(options, '--help') || hasFlag(options, '-h')) {
59
- printCommandHelp(command);
84
+ printCommandHelp(effectiveCommand);
60
85
  process.exit(0);
61
86
  }
62
87
 
63
- const entry = COMMANDS[command];
88
+ const entry = COMMANDS[effectiveCommand];
64
89
  if (!entry) {
65
90
  console.error(`Unknown command: ${command}`);
66
91
  printMainHelp();
@@ -8,6 +8,8 @@ const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '..');
8
8
  const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
9
9
  const SOURCE_DIR = path.resolve(REPO_ROOT, '.github/distribution/produck');
10
10
  const OUTPUT_DIR = path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck');
11
+ const SOURCE_TOOLING_BASELINE_PATH = path.resolve(SOURCE_DIR, 'tooling-version-baseline.json');
12
+ const OUTPUT_TOOLING_BASELINE_PATH = path.resolve(OUTPUT_DIR, 'tooling-version-baseline.json');
11
13
  const LEGACY_OUTPUT_PATH = path.resolve(
12
14
  PACKAGE_ROOT,
13
15
  'publish-assets/instructions/org.instructions.md',
@@ -39,6 +41,47 @@ function validateSourceFile(fileName, text) {
39
41
  }
40
42
  }
41
43
 
44
+ function readAndValidateToolingBaseline() {
45
+ if (!fs.existsSync(SOURCE_TOOLING_BASELINE_PATH)) {
46
+ throw new Error(`Missing tooling baseline source file: ${SOURCE_TOOLING_BASELINE_PATH}`);
47
+ }
48
+
49
+ let baseline;
50
+ try {
51
+ baseline = JSON.parse(fs.readFileSync(SOURCE_TOOLING_BASELINE_PATH, 'utf8'));
52
+ } catch {
53
+ throw new Error(`Invalid tooling baseline JSON: ${SOURCE_TOOLING_BASELINE_PATH}`);
54
+ }
55
+
56
+ const c8Version = baseline?.tools?.c8?.version;
57
+ const lernaVersion = baseline?.tools?.lerna?.version;
58
+ const coverageScriptTemplate = baseline?.coverage?.scriptTemplate;
59
+
60
+ if (typeof baseline.schemaVersion !== 'number') {
61
+ throw new Error(`Invalid tooling baseline schemaVersion in: ${SOURCE_TOOLING_BASELINE_PATH}`);
62
+ }
63
+
64
+ if (typeof c8Version !== 'string' || c8Version.trim() === '') {
65
+ throw new Error(`Invalid tools.c8.version in: ${SOURCE_TOOLING_BASELINE_PATH}`);
66
+ }
67
+
68
+ if (typeof lernaVersion !== 'string' || lernaVersion.trim() === '') {
69
+ throw new Error(`Invalid tools.lerna.version in: ${SOURCE_TOOLING_BASELINE_PATH}`);
70
+ }
71
+
72
+ if (typeof coverageScriptTemplate !== 'string' || coverageScriptTemplate.trim() === '') {
73
+ throw new Error(`Invalid coverage.scriptTemplate in: ${SOURCE_TOOLING_BASELINE_PATH}`);
74
+ }
75
+
76
+ if (!coverageScriptTemplate.includes('{c8.version}')) {
77
+ throw new Error(
78
+ `coverage.scriptTemplate must include {c8.version} in: ${SOURCE_TOOLING_BASELINE_PATH}`,
79
+ );
80
+ }
81
+
82
+ return `${JSON.stringify(baseline, null, 2)}\n`;
83
+ }
84
+
42
85
  function readSourceEntries() {
43
86
  if (!fs.existsSync(SOURCE_DIR)) {
44
87
  throw new Error(`Missing source directory: ${SOURCE_DIR}`);
@@ -94,6 +137,12 @@ function run() {
94
137
  process.stdout.write(`Generated ${outPath} from ${entry.sourcePath}\n`);
95
138
  }
96
139
 
140
+ const toolingBaselineText = readAndValidateToolingBaseline();
141
+ fs.writeFileSync(OUTPUT_TOOLING_BASELINE_PATH, toolingBaselineText, 'utf8');
142
+ process.stdout.write(
143
+ `Generated ${OUTPUT_TOOLING_BASELINE_PATH} from ${SOURCE_TOOLING_BASELINE_PATH}\n`,
144
+ );
145
+
97
146
  cleanStaleManagedFiles(expectedNames);
98
147
 
99
148
  if (fs.existsSync(LEGACY_OUTPUT_PATH)) {
@@ -0,0 +1,19 @@
1
+ Usage:
2
+ agent-toolkit enforce-node-baseline [--cwd <dir>] [--source <file-or-dir>]
3
+ [--force] [--prune] [--workspace <path>] ... [--check] [--dry-run]
4
+ [--json <file>]
5
+
6
+ Behavior:
7
+ - Runs mandatory baseline flow in fixed order:
8
+ 1) sync-instructions
9
+ 2) preflight
10
+ 3) sync-coverage-script
11
+ 4) sync-husky-hooks
12
+ - Stops at first failed step and exits non-zero
13
+ - Prints one combined JSON report for all executed steps
14
+
15
+ 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
18
+ - --check takes precedence over --dry-run
19
+ - --workspace filters coverage sync targets in step 3
@@ -0,0 +1,155 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import { getMulti, getSingle, hasFlag } from '../shared/args.mjs';
7
+ import { printTextResource } from '../shared/text-resource.mjs';
8
+
9
+ const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
10
+ const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
11
+ const TOOLKIT_BIN = path.resolve(COMMAND_DIR, '../../agent-toolkit.mjs');
12
+
13
+ export function printEnforceNodeBaselineHelp() {
14
+ printTextResource(HELP_FILE);
15
+ }
16
+
17
+ function parseJsonOrNull(text) {
18
+ const trimmed = text.trim();
19
+ if (!trimmed) {
20
+ return null;
21
+ }
22
+
23
+ try {
24
+ return JSON.parse(trimmed);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function runToolkitSubcommand(cwd, args) {
31
+ const result = spawnSync(process.execPath, [TOOLKIT_BIN, ...args], {
32
+ cwd,
33
+ encoding: 'utf8',
34
+ });
35
+
36
+ const stdout = String(result.stdout || '');
37
+ const stderr = String(result.stderr || '');
38
+ const status = typeof result.status === 'number' ? result.status : 1;
39
+
40
+ return {
41
+ args,
42
+ status,
43
+ ok: status === 0,
44
+ report: parseJsonOrNull(stdout),
45
+ stdout,
46
+ stderr,
47
+ };
48
+ }
49
+
50
+ function buildStepReport(name, stepResult) {
51
+ const hasParsedReport = Boolean(stepResult.report);
52
+
53
+ return {
54
+ name,
55
+ args: stepResult.args,
56
+ status: stepResult.status,
57
+ ok: stepResult.ok,
58
+ report: stepResult.report,
59
+ stdout: hasParsedReport ? '' : stepResult.stdout,
60
+ stderr: stepResult.stderr,
61
+ };
62
+ }
63
+
64
+ export function runEnforceNodeBaseline(options) {
65
+ const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
66
+ const source = getSingle(options, '--source', '');
67
+ const force = hasFlag(options, '--force');
68
+ const prune = hasFlag(options, '--prune');
69
+ const check = hasFlag(options, '--check');
70
+ const dryRun = hasFlag(options, '--dry-run') && !check;
71
+ const jsonFile = getSingle(options, '--json', '');
72
+ const workspaces = getMulti(options, '--workspace');
73
+
74
+ if (!fs.existsSync(cwd)) {
75
+ console.error(`CWD does not exist: ${cwd}`);
76
+ process.exit(2);
77
+ }
78
+
79
+ const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
80
+ const report = {
81
+ cwd,
82
+ mode,
83
+ ok: true,
84
+ steps: [],
85
+ };
86
+
87
+ const syncInstructionsArgs = ['sync-instructions', '--cwd', cwd];
88
+ if (source) {
89
+ syncInstructionsArgs.push('--source', source);
90
+ }
91
+ if (force) {
92
+ syncInstructionsArgs.push('--force');
93
+ }
94
+ if (prune) {
95
+ syncInstructionsArgs.push('--prune');
96
+ }
97
+ if (mode !== 'sync') {
98
+ syncInstructionsArgs.push('--dry-run');
99
+ }
100
+
101
+ const preflightArgs = [
102
+ 'preflight',
103
+ '--cwd',
104
+ cwd,
105
+ '--require',
106
+ 'package.json',
107
+ '--check-workspace-package-json',
108
+ 'package.json',
109
+ ];
110
+
111
+ const syncCoverageArgs = ['sync-coverage-script', '--cwd', cwd];
112
+ for (const workspacePath of workspaces) {
113
+ syncCoverageArgs.push('--workspace', workspacePath);
114
+ }
115
+ if (check) {
116
+ syncCoverageArgs.push('--check');
117
+ } else if (dryRun) {
118
+ syncCoverageArgs.push('--dry-run');
119
+ }
120
+
121
+ const syncHuskyArgs = ['sync-husky-hooks', '--cwd', cwd];
122
+ if (check) {
123
+ syncHuskyArgs.push('--check');
124
+ } else if (dryRun) {
125
+ syncHuskyArgs.push('--dry-run');
126
+ }
127
+
128
+ const plan = [
129
+ { name: 'sync-instructions', args: syncInstructionsArgs },
130
+ { name: 'preflight', args: preflightArgs },
131
+ { name: 'sync-coverage-script', args: syncCoverageArgs },
132
+ { name: 'sync-husky-hooks', args: syncHuskyArgs },
133
+ ];
134
+
135
+ for (const step of plan) {
136
+ const stepResult = runToolkitSubcommand(cwd, step.args);
137
+ report.steps.push(buildStepReport(step.name, stepResult));
138
+
139
+ if (!stepResult.ok) {
140
+ report.ok = false;
141
+ break;
142
+ }
143
+ }
144
+
145
+ if (jsonFile) {
146
+ const outPath = path.resolve(cwd, jsonFile);
147
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
148
+ fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
149
+ }
150
+
151
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
152
+ if (!report.ok) {
153
+ process.exit(2);
154
+ }
155
+ }
@@ -1,9 +1,16 @@
1
1
  agent-toolkit commands:
2
+ enforce-node-baseline
2
3
  preflight
3
4
  run-capture
4
5
  summarize-log
6
+ sync-coverage-script
7
+ sync-husky-hooks
5
8
  validate-commit-msg
6
9
  sync-instructions
7
10
 
11
+ Default:
12
+ agent-toolkit
13
+ # equivalent to: agent-toolkit enforce-node-baseline
14
+
8
15
  Use:
9
16
  agent-toolkit <command> --help
@@ -1,3 +1,9 @@
1
1
  Usage:
2
2
  agent-toolkit preflight [--cwd <dir>] [--require <path>] ...
3
3
  [--ensure-dir <path>] ... [--json <file>]
4
+ [--check-workspace-package-json <path>]
5
+
6
+ Rules for --check-workspace-package-json:
7
+ - `private` must be true
8
+ - `workspaces` must be a non-empty array of explicit paths (no glob tokens)
9
+ - `scripts` must include deps:install, test, produck:coverage, lint
@@ -2,53 +2,142 @@ 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 } 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));
9
9
  const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
10
+ const GLOB_TOKEN_PATTERN = /[*?{}[\]]/;
11
+ const REQUIRED_WORKSPACE_FIELDS = ['private', 'workspaces', 'scripts'];
12
+ const REQUIRED_WORKSPACE_SCRIPTS = ['deps:install', 'test', 'produck:coverage', 'lint'];
10
13
 
11
14
  export function printPreflightHelp() {
12
15
  printTextResource(HELP_FILE);
13
16
  }
14
17
 
18
+ function validateWorkspacePackageJson(cwd, checkPath) {
19
+ const packagePath = path.resolve(cwd, checkPath);
20
+ const check = {
21
+ file: checkPath,
22
+ ok: true,
23
+ exists: true,
24
+ validJson: true,
25
+ missingFields: [],
26
+ missingScripts: [],
27
+ wildcardWorkspaces: [],
28
+ scriptTypeValid: true,
29
+ };
30
+
31
+ if (!fs.existsSync(packagePath)) {
32
+ check.ok = false;
33
+ check.exists = false;
34
+ return check;
35
+ }
36
+
37
+ let json;
38
+ try {
39
+ json = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
40
+ } catch {
41
+ check.ok = false;
42
+ check.validJson = false;
43
+ return check;
44
+ }
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
+ }
80
+
81
+ return check;
82
+ }
83
+
15
84
  export function runPreflight(options) {
16
85
  const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
17
- const required = getMulti(options, '--require');
18
- const ensureDir = getMulti(options, '--ensure-dir');
86
+ const requireTargets = options['--require'] || [];
87
+ const ensureDirs = options['--ensure-dir'] || [];
88
+ const checkWorkspacePackageJson = getSingle(options, '--check-workspace-package-json', '');
89
+ const dryRun = hasFlag(options, '--dry-run');
19
90
  const jsonFile = getSingle(options, '--json', '');
20
91
 
21
- if (!fs.existsSync(cwd)) {
22
- console.error(`CWD does not exist: ${cwd}`);
23
- process.exit(2);
24
- }
25
-
26
92
  const report = {
27
93
  cwd,
28
- required: [],
29
- ensuredDirectories: [],
94
+ dryRun,
30
95
  ok: true,
96
+ required: [],
97
+ ensuredDirs: [],
98
+ workspacePackageJson: null,
31
99
  };
32
100
 
33
- for (const rel of required) {
34
- const resolved = path.resolve(cwd, rel);
35
- const exists = fs.existsSync(resolved);
36
- report.required.push({ path: rel, resolved, exists });
101
+ if (!fs.existsSync(cwd)) {
102
+ console.error(`CWD does not exist: ${cwd}`);
103
+ process.exit(2);
104
+ }
105
+
106
+ for (const target of requireTargets) {
107
+ const absolute = path.resolve(cwd, String(target));
108
+ const exists = fs.existsSync(absolute);
109
+ report.required.push({ target: String(target), absolute, exists });
37
110
  if (!exists) {
38
111
  report.ok = false;
39
112
  }
40
113
  }
41
114
 
42
- for (const rel of ensureDir) {
43
- const resolved = path.resolve(cwd, rel);
44
- fs.mkdirSync(resolved, { recursive: true });
45
- report.ensuredDirectories.push({ path: rel, resolved, created: true });
115
+ for (const dir of ensureDirs) {
116
+ const absolute = path.resolve(cwd, String(dir));
117
+ const existedBefore = fs.existsSync(absolute);
118
+ if (!dryRun && !existedBefore) {
119
+ fs.mkdirSync(absolute, { recursive: true });
120
+ }
121
+ report.ensuredDirs.push({
122
+ target: String(dir),
123
+ absolute,
124
+ existedBefore,
125
+ existsAfter: fs.existsSync(absolute),
126
+ });
127
+ }
128
+
129
+ if (checkWorkspacePackageJson) {
130
+ const workspaceCheck = validateWorkspacePackageJson(cwd, checkWorkspacePackageJson);
131
+ report.workspacePackageJson = workspaceCheck;
132
+ if (!workspaceCheck.ok) {
133
+ report.ok = false;
134
+ }
46
135
  }
47
136
 
48
137
  if (jsonFile) {
49
- const out = path.resolve(cwd, jsonFile);
50
- fs.mkdirSync(path.dirname(out), { recursive: true });
51
- fs.writeFileSync(out, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
138
+ const outPath = path.resolve(cwd, jsonFile);
139
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
140
+ fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
52
141
  }
53
142
 
54
143
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
@@ -0,0 +1,22 @@
1
+ Usage:
2
+ agent-toolkit sync-coverage-script [--cwd <dir>]
3
+ [--workspace <path>] ... [--check] [--dry-run] [--json <file>]
4
+
5
+ Behavior:
6
+ - Applies organization-required coverage script to workspace package.json files
7
+ - Organization-reserved script key is scripts.produck:coverage
8
+ - Applies organization-required pinned local c8 devDependency to workspace
9
+ package.json files
10
+ - Target script is rendered from organization tooling baseline file
11
+ (lookup order):
12
+ 1) .github/distribution/produck/tooling-version-baseline.json
13
+ 2) publish-assets/instructions/produck/tooling-version-baseline.json
14
+ - Baseline template:
15
+ c8 --reporter=lcov --reporter=html --reporter=text-summary npm test
16
+
17
+ Rules:
18
+ - When --workspace is omitted, root package.json workspaces are used
19
+ - Root workspaces must be explicit paths (no glob tokens)
20
+ - Workspace package.json files must pin devDependencies.c8 to baseline version
21
+ - --check validates without writing and exits non-zero on mismatch
22
+ - --dry-run prints planned changes without writing