@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 +140 -13
- package/bin/agent-toolkit.mjs +24 -0
- package/bin/command/enforce-node-baseline/help.txt +8 -5
- package/bin/command/enforce-node-baseline/index.mjs +28 -9
- package/bin/command/main/help.txt +3 -0
- package/bin/command/preflight/index.mjs +7 -35
- package/bin/command/shared/workspace-validation.mjs +63 -0
- package/bin/command/sync-coverage-script/help.txt +10 -3
- package/bin/command/sync-coverage-script/index.mjs +29 -2
- package/bin/command/sync-eslint-config/help.txt +18 -0
- package/bin/command/sync-eslint-config/index.mjs +248 -0
- package/bin/command/sync-husky-hooks/help.txt +1 -9
- package/bin/command/sync-husky-hooks/index.mjs +1 -179
- package/bin/command/sync-prettier-config/help.txt +14 -0
- package/bin/command/sync-prettier-config/index.mjs +130 -0
- package/bin/command/sync-workspace-config/help.txt +21 -0
- package/bin/command/sync-workspace-config/index.mjs +290 -0
- package/bin/command/validate-commit-msg/help.txt +2 -1
- package/bin/command/validate-commit-msg/index.mjs +53 -3
- package/package.json +7 -5
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +52 -7
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +7 -0
|
@@ -0,0 +1,290 @@
|
|
|
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 { getSingle, hasFlag } from '../shared/args.mjs';
|
|
7
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
8
|
+
import { validateRequiredExactEntries } from '../shared/workspace-validation.mjs';
|
|
9
|
+
|
|
10
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
12
|
+
const PACKAGE_ROOT = path.resolve(COMMAND_DIR, '../../..');
|
|
13
|
+
const REPO_ROOT = path.resolve(PACKAGE_ROOT, '../..');
|
|
14
|
+
const TOOLKIT_PACKAGE_JSON = path.resolve(PACKAGE_ROOT, 'package.json');
|
|
15
|
+
const TOOLING_BASELINE_CANDIDATE_PATHS = [
|
|
16
|
+
path.resolve(REPO_ROOT, '.github/distribution/produck/tooling-version-baseline.json'),
|
|
17
|
+
path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck/tooling-version-baseline.json'),
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const REQUIRED_BASELINE_SCRIPT_KEY = 'produck:baseline';
|
|
21
|
+
const REQUIRED_BASELINE_SCRIPT_VALUE =
|
|
22
|
+
'npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd .';
|
|
23
|
+
const REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY = 'produck:coverage';
|
|
24
|
+
const REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE =
|
|
25
|
+
'c8 --config .c8rc.json npm run test --workspaces --if-present';
|
|
26
|
+
const REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY = 'produck:precommit-check';
|
|
27
|
+
const REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE = 'npm run produck:format && npm run produck:lint';
|
|
28
|
+
const REQUIRED_C8_CONFIG_FILE = '.c8rc.json';
|
|
29
|
+
const REQUIRED_C8_CONFIG_CONTENT = `${JSON.stringify(
|
|
30
|
+
{
|
|
31
|
+
reporter: ['lcov', 'html', 'text-summary'],
|
|
32
|
+
},
|
|
33
|
+
null,
|
|
34
|
+
2,
|
|
35
|
+
)}\n`;
|
|
36
|
+
|
|
37
|
+
export function printSyncWorkspaceConfigHelp() {
|
|
38
|
+
printTextResource(HELP_FILE);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseJsonFile(filePath, label) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
44
|
+
} catch {
|
|
45
|
+
console.error(`${label} is not valid JSON: ${filePath}`);
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getRequiredToolkitDevDependency() {
|
|
51
|
+
const overrideVersion = String(process.env.PRODUCK_TOOLKIT_VERSION_OVERRIDE || '').trim();
|
|
52
|
+
if (overrideVersion) {
|
|
53
|
+
return overrideVersion;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
57
|
+
const latestResult = spawnSync(npmCommand, ['view', '@produck/agent-toolkit', 'version'], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const latestVersion = String(latestResult.stdout || '').trim();
|
|
62
|
+
if (latestResult.status === 0 && latestVersion) {
|
|
63
|
+
return latestVersion;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const pkg = parseJsonFile(TOOLKIT_PACKAGE_JSON, 'Toolkit package.json');
|
|
67
|
+
const version = typeof pkg.version === 'string' ? pkg.version.trim() : '';
|
|
68
|
+
|
|
69
|
+
if (!version) {
|
|
70
|
+
console.error(`Toolkit package version is missing: ${TOOLKIT_PACKAGE_JSON}`);
|
|
71
|
+
process.exit(2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return version;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function loadToolingBaseline() {
|
|
78
|
+
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find((candidatePath) => {
|
|
79
|
+
return fs.existsSync(candidatePath);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!toolingBaselinePath) {
|
|
83
|
+
console.error('Tooling baseline file does not exist in expected locations:');
|
|
84
|
+
for (const candidatePath of TOOLING_BASELINE_CANDIDATE_PATHS) {
|
|
85
|
+
console.error(`- ${candidatePath}`);
|
|
86
|
+
}
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const baseline = parseJsonFile(toolingBaselinePath, 'Tooling baseline file');
|
|
91
|
+
const c8Version = String(baseline?.tools?.c8?.version || '').trim();
|
|
92
|
+
const huskyVersion = String(baseline?.tools?.husky?.version || '').trim();
|
|
93
|
+
const lernaVersion = String(baseline?.tools?.lerna?.version || '').trim();
|
|
94
|
+
|
|
95
|
+
if (!c8Version || !huskyVersion || !lernaVersion) {
|
|
96
|
+
console.error(
|
|
97
|
+
`Tooling baseline must define fixed tools.c8/husky/lerna.version: ${toolingBaselinePath}`,
|
|
98
|
+
);
|
|
99
|
+
process.exit(2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
toolingBaselinePath,
|
|
104
|
+
c8Version,
|
|
105
|
+
huskyVersion,
|
|
106
|
+
lernaVersion,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function buildScriptState(pkg) {
|
|
111
|
+
const scripts =
|
|
112
|
+
pkg.scripts && typeof pkg.scripts === 'object' && !Array.isArray(pkg.scripts)
|
|
113
|
+
? { ...pkg.scripts }
|
|
114
|
+
: {};
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
scripts,
|
|
118
|
+
previousBaseline:
|
|
119
|
+
typeof scripts[REQUIRED_BASELINE_SCRIPT_KEY] === 'string'
|
|
120
|
+
? scripts[REQUIRED_BASELINE_SCRIPT_KEY]
|
|
121
|
+
: null,
|
|
122
|
+
previousCoverage:
|
|
123
|
+
typeof scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY] === 'string'
|
|
124
|
+
? scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY]
|
|
125
|
+
: null,
|
|
126
|
+
previousPrecommitCheck:
|
|
127
|
+
typeof scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY] === 'string'
|
|
128
|
+
? scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY]
|
|
129
|
+
: null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function readFileIfExists(filePath) {
|
|
134
|
+
if (!fs.existsSync(filePath)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildDevDependencyState(pkg) {
|
|
142
|
+
const devDependencies =
|
|
143
|
+
pkg.devDependencies &&
|
|
144
|
+
typeof pkg.devDependencies === 'object' &&
|
|
145
|
+
!Array.isArray(pkg.devDependencies)
|
|
146
|
+
? { ...pkg.devDependencies }
|
|
147
|
+
: {};
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
devDependencies,
|
|
151
|
+
previousManaged: {
|
|
152
|
+
c8: typeof devDependencies.c8 === 'string' ? devDependencies.c8 : null,
|
|
153
|
+
husky: typeof devDependencies.husky === 'string' ? devDependencies.husky : null,
|
|
154
|
+
lerna: typeof devDependencies.lerna === 'string' ? devDependencies.lerna : null,
|
|
155
|
+
'@produck/agent-toolkit':
|
|
156
|
+
typeof devDependencies['@produck/agent-toolkit'] === 'string'
|
|
157
|
+
? devDependencies['@produck/agent-toolkit']
|
|
158
|
+
: null,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function runSyncWorkspaceConfig(options) {
|
|
164
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
165
|
+
const check = hasFlag(options, '--check');
|
|
166
|
+
const dryRun = hasFlag(options, '--dry-run') && !check;
|
|
167
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
168
|
+
const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
|
|
169
|
+
|
|
170
|
+
if (!fs.existsSync(cwd)) {
|
|
171
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
172
|
+
process.exit(2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const rootPackageJsonPath = path.resolve(cwd, 'package.json');
|
|
176
|
+
if (!fs.existsSync(rootPackageJsonPath)) {
|
|
177
|
+
console.error(`Root package.json does not exist: ${rootPackageJsonPath}`);
|
|
178
|
+
process.exit(2);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const pkg = parseJsonFile(rootPackageJsonPath, 'Root package.json');
|
|
182
|
+
const toolingBaseline = loadToolingBaseline();
|
|
183
|
+
const requiredToolkitDependency = getRequiredToolkitDevDependency();
|
|
184
|
+
const requiredDevDependencies = {
|
|
185
|
+
c8: toolingBaseline.c8Version,
|
|
186
|
+
husky: toolingBaseline.huskyVersion,
|
|
187
|
+
lerna: toolingBaseline.lernaVersion,
|
|
188
|
+
'@produck/agent-toolkit': requiredToolkitDependency,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const scriptState = buildScriptState(pkg);
|
|
192
|
+
const dependencyState = buildDevDependencyState(pkg);
|
|
193
|
+
const c8ConfigPath = path.resolve(cwd, REQUIRED_C8_CONFIG_FILE);
|
|
194
|
+
const currentC8ConfigContent = readFileIfExists(c8ConfigPath);
|
|
195
|
+
|
|
196
|
+
const scriptValidation = validateRequiredExactEntries(scriptState.scripts, {
|
|
197
|
+
[REQUIRED_BASELINE_SCRIPT_KEY]: REQUIRED_BASELINE_SCRIPT_VALUE,
|
|
198
|
+
[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY]: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE,
|
|
199
|
+
[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY]: REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE,
|
|
200
|
+
});
|
|
201
|
+
const dependencyValidation = validateRequiredExactEntries(
|
|
202
|
+
dependencyState.devDependencies,
|
|
203
|
+
requiredDevDependencies,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const matchesRequiredBaseline = !(REQUIRED_BASELINE_SCRIPT_KEY in scriptValidation.mismatches);
|
|
207
|
+
const matchesRequiredWorkspaceCoverage = !(
|
|
208
|
+
REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY in scriptValidation.mismatches
|
|
209
|
+
);
|
|
210
|
+
const matchesRequiredPrecommitCheck = !(
|
|
211
|
+
REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY in scriptValidation.mismatches
|
|
212
|
+
);
|
|
213
|
+
const matchesRequiredManagedDevDependencies = dependencyValidation.ok;
|
|
214
|
+
const matchesRequiredC8Config = currentC8ConfigContent === REQUIRED_C8_CONFIG_CONTENT;
|
|
215
|
+
|
|
216
|
+
const requiresUpdate =
|
|
217
|
+
!matchesRequiredBaseline ||
|
|
218
|
+
!matchesRequiredWorkspaceCoverage ||
|
|
219
|
+
!matchesRequiredPrecommitCheck ||
|
|
220
|
+
!matchesRequiredManagedDevDependencies ||
|
|
221
|
+
!matchesRequiredC8Config;
|
|
222
|
+
|
|
223
|
+
if (mode === 'sync' && requiresUpdate) {
|
|
224
|
+
scriptState.scripts[REQUIRED_BASELINE_SCRIPT_KEY] = REQUIRED_BASELINE_SCRIPT_VALUE;
|
|
225
|
+
scriptState.scripts[REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY] =
|
|
226
|
+
REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE;
|
|
227
|
+
scriptState.scripts[REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY] =
|
|
228
|
+
REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE;
|
|
229
|
+
pkg.scripts = scriptState.scripts;
|
|
230
|
+
|
|
231
|
+
for (const [name, version] of Object.entries(requiredDevDependencies)) {
|
|
232
|
+
dependencyState.devDependencies[name] = version;
|
|
233
|
+
}
|
|
234
|
+
pkg.devDependencies = dependencyState.devDependencies;
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(rootPackageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
|
|
237
|
+
fs.writeFileSync(c8ConfigPath, REQUIRED_C8_CONFIG_CONTENT, 'utf8');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const report = {
|
|
241
|
+
cwd,
|
|
242
|
+
mode,
|
|
243
|
+
ok: true,
|
|
244
|
+
rootPackageJsonPath,
|
|
245
|
+
toolingBaselinePath: toolingBaseline.toolingBaselinePath,
|
|
246
|
+
required: {
|
|
247
|
+
baselineScriptKey: REQUIRED_BASELINE_SCRIPT_KEY,
|
|
248
|
+
baselineScriptValue: REQUIRED_BASELINE_SCRIPT_VALUE,
|
|
249
|
+
workspaceCoverageScriptKey: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_KEY,
|
|
250
|
+
workspaceCoverageScriptValue: REQUIRED_WORKSPACE_COVERAGE_SCRIPT_VALUE,
|
|
251
|
+
precommitCheckScriptKey: REQUIRED_PRECOMMIT_CHECK_SCRIPT_KEY,
|
|
252
|
+
precommitCheckScriptValue: REQUIRED_PRECOMMIT_CHECK_SCRIPT_VALUE,
|
|
253
|
+
c8ConfigFile: REQUIRED_C8_CONFIG_FILE,
|
|
254
|
+
managedDevDependencies: requiredDevDependencies,
|
|
255
|
+
},
|
|
256
|
+
status: {
|
|
257
|
+
matchesRequiredBaselineBefore: matchesRequiredBaseline,
|
|
258
|
+
matchesRequiredWorkspaceCoverageBefore: matchesRequiredWorkspaceCoverage,
|
|
259
|
+
matchesRequiredPrecommitCheckBefore: matchesRequiredPrecommitCheck,
|
|
260
|
+
matchesRequiredManagedDevDependenciesBefore: matchesRequiredManagedDevDependencies,
|
|
261
|
+
matchesRequiredC8ConfigBefore: matchesRequiredC8Config,
|
|
262
|
+
matchesRequiredBaselineAfter:
|
|
263
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredBaseline,
|
|
264
|
+
matchesRequiredWorkspaceCoverageAfter:
|
|
265
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredWorkspaceCoverage,
|
|
266
|
+
matchesRequiredPrecommitCheckAfter:
|
|
267
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredPrecommitCheck,
|
|
268
|
+
matchesRequiredManagedDevDependenciesAfter:
|
|
269
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredManagedDevDependencies,
|
|
270
|
+
matchesRequiredC8ConfigAfter:
|
|
271
|
+
requiresUpdate && mode === 'sync' ? true : matchesRequiredC8Config,
|
|
272
|
+
updated: requiresUpdate && mode === 'sync',
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (mode === 'check' && requiresUpdate) {
|
|
277
|
+
report.ok = false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (jsonFile) {
|
|
281
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
282
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
283
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
287
|
+
if (!report.ok) {
|
|
288
|
+
process.exit(2);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -3,7 +3,8 @@ Usage:
|
|
|
3
3
|
|
|
4
4
|
Rules:
|
|
5
5
|
- In monorepo mode, a section header is required before tagged lines
|
|
6
|
-
-
|
|
6
|
+
- In monorepo mode, section headers must use one of: workspace:, *:, or a workspace package name section (for example @produck/agent-toolkit:)
|
|
7
|
+
- Non-empty lines must be either a section header or start with [TAG]
|
|
7
8
|
- If section headers are used, each section header must be followed by at least one tagged line
|
|
8
9
|
- No empty lines are allowed
|
|
9
10
|
- Optional target form: [TAG] <target>: <summary>
|
|
@@ -7,10 +7,12 @@ import { printTextResource } from '../shared/text-resource.mjs';
|
|
|
7
7
|
|
|
8
8
|
const ALLOWED_TAGS = ['INIT', 'ADD', 'REMOVE', 'FIX', 'REFACTOR', 'UPGRADE', 'PUBLISH'];
|
|
9
9
|
const ALLOWED_TARGETS = ['docs', 'test', 'ci', 'deps', 'api', 'schema', 'infra', 'fmt'];
|
|
10
|
-
const SECTION_HEADER_RE = /^(?:@[\w.-]+\/)?[\w.-]
|
|
10
|
+
const SECTION_HEADER_RE = /^(?:\*|(?:@[\w.-]+\/)?[\w.-]+):$/;
|
|
11
11
|
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
13
13
|
const ROOT_PACKAGE_FILE = path.resolve(COMMAND_DIR, '../../../../../package.json');
|
|
14
|
+
const WORKSPACE_SCOPE = 'workspace';
|
|
15
|
+
const WILDCARD_SCOPE = '*';
|
|
14
16
|
|
|
15
17
|
export function printValidateCommitMsgHelp() {
|
|
16
18
|
printTextResource(HELP_FILE);
|
|
@@ -68,7 +70,47 @@ function isMonorepoRoot() {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
function
|
|
73
|
+
function getMonorepoAllowedSectionScopes() {
|
|
74
|
+
if (!fs.existsSync(ROOT_PACKAGE_FILE)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let rootPackage;
|
|
79
|
+
try {
|
|
80
|
+
rootPackage = JSON.parse(fs.readFileSync(ROOT_PACKAGE_FILE, 'utf8'));
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!Array.isArray(rootPackage.workspaces) || rootPackage.workspaces.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const rootDir = path.dirname(ROOT_PACKAGE_FILE);
|
|
90
|
+
const allowedScopes = new Set([WORKSPACE_SCOPE, WILDCARD_SCOPE]);
|
|
91
|
+
|
|
92
|
+
for (const workspaceEntry of rootPackage.workspaces) {
|
|
93
|
+
const workspacePath = path.resolve(rootDir, String(workspaceEntry));
|
|
94
|
+
const workspacePackageJsonPath = path.resolve(workspacePath, 'package.json');
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(workspacePackageJsonPath)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const workspacePackage = JSON.parse(fs.readFileSync(workspacePackageJsonPath, 'utf8'));
|
|
102
|
+
if (typeof workspacePackage.name === 'string' && workspacePackage.name.trim() !== '') {
|
|
103
|
+
allowedScopes.add(workspacePackage.name.trim());
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return allowedScopes;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function validateSectionFormat(lines, allowedSectionScopes = null) {
|
|
72
114
|
const errors = [];
|
|
73
115
|
let currentSection = '';
|
|
74
116
|
let currentSectionLineNo = 0;
|
|
@@ -90,6 +132,13 @@ function validateSectionFormat(lines) {
|
|
|
90
132
|
);
|
|
91
133
|
}
|
|
92
134
|
|
|
135
|
+
const sectionName = line.trim().slice(0, -1);
|
|
136
|
+
if (allowedSectionScopes && !allowedSectionScopes.has(sectionName)) {
|
|
137
|
+
errors.push(
|
|
138
|
+
`Line ${lineNo}: section header "${line.trim()}" is not allowed in monorepo mode`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
93
142
|
currentSection = line.trim();
|
|
94
143
|
currentSectionLineNo = lineNo;
|
|
95
144
|
currentSectionHasTaggedLine = false;
|
|
@@ -143,6 +192,7 @@ export function runValidateCommitMsg(options) {
|
|
|
143
192
|
}
|
|
144
193
|
|
|
145
194
|
const mustUseSectionHeaders = isMonorepoRoot();
|
|
195
|
+
const allowedSectionScopes = mustUseSectionHeaders ? getMonorepoAllowedSectionScopes() : null;
|
|
146
196
|
const hasSectionHeaders = lines.some((line) => isSectionHeaderLine(line));
|
|
147
197
|
|
|
148
198
|
// [PUBLISH] is generated by lerna and is always a repo-wide tag.
|
|
@@ -161,7 +211,7 @@ export function runValidateCommitMsg(options) {
|
|
|
161
211
|
process.exit(1);
|
|
162
212
|
}
|
|
163
213
|
|
|
164
|
-
const errors = hasSectionHeaders ? validateSectionFormat(lines) : [];
|
|
214
|
+
const errors = hasSectionHeaders ? validateSectionFormat(lines, allowedSectionScopes) : [];
|
|
165
215
|
if (!hasSectionHeaders) {
|
|
166
216
|
for (let i = 0; i < lines.length; i += 1) {
|
|
167
217
|
const err = validateCommitLine(lines[i], i + 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produck/agent-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"prepack": "node ./bin/build-publish-assets.mjs",
|
|
16
|
-
"coverage": "npm exec --yes -- c8 --reporter=lcov --reporter=html --reporter=text-summary node --test test/index.mjs",
|
|
17
|
-
"coverage:check": "npm exec --yes -- c8 --check-coverage --lines 100 --functions 100 --branches 100 --statements 100 node --test test/index.mjs",
|
|
18
16
|
"test": "node --test test/index.mjs",
|
|
19
17
|
"verify": "node ./bin/agent-toolkit.mjs --help && node ./bin/agent-toolkit.mjs preflight --cwd . --require package.json",
|
|
20
|
-
"pack:check": "npm pack --dry-run"
|
|
18
|
+
"pack:check": "npm pack --dry-run",
|
|
19
|
+
"produck:coverage": "c8 --reporter=lcov --reporter=html --reporter=text-summary npm test"
|
|
21
20
|
},
|
|
22
21
|
"files": [
|
|
23
22
|
"bin",
|
|
@@ -30,5 +29,8 @@
|
|
|
30
29
|
"node": ">=18.0.0"
|
|
31
30
|
},
|
|
32
31
|
"license": "MIT",
|
|
33
|
-
"
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"c8": "11.0.0"
|
|
34
|
+
},
|
|
35
|
+
"gitHead": "fb8c64919617ff5a76b1c736d1fe3903206ed131"
|
|
34
36
|
}
|
|
@@ -41,14 +41,31 @@ Notes:
|
|
|
41
41
|
`npm exec -- agent-toolkit sync-coverage-script --cwd .`.
|
|
42
42
|
- Use central remediation command to deploy local anti-drift hook baseline:
|
|
43
43
|
`npm exec -- agent-toolkit sync-husky-hooks --cwd .`.
|
|
44
|
+
- Use central remediation command to deploy root format script/config
|
|
45
|
+
baseline:
|
|
46
|
+
`npm exec -- agent-toolkit sync-prettier-config --cwd .`.
|
|
47
|
+
- Use central remediation command to deploy root lint script/config and
|
|
48
|
+
eslint integration baseline:
|
|
49
|
+
`npm exec -- agent-toolkit sync-eslint-config --cwd .`.
|
|
50
|
+
- Use central remediation command to deploy root shared scripts/dependencies
|
|
51
|
+
baseline:
|
|
52
|
+
`npm exec -- agent-toolkit sync-workspace-config --cwd .`.
|
|
44
53
|
- `c8` execution baseline for deployed coverage scripts is fixed to the
|
|
45
54
|
version specified in `tooling-version-baseline.json`.
|
|
46
55
|
- Downstream repositories must not use unversioned `npx c8` or `c8@latest`
|
|
47
56
|
in shared scripts/CI.
|
|
48
|
-
- Root
|
|
49
|
-
|
|
57
|
+
- Root local governance must pin `devDependencies.c8`,
|
|
58
|
+
`devDependencies.husky`, `devDependencies.lerna`, and
|
|
59
|
+
`devDependencies.@produck/agent-toolkit` via
|
|
60
|
+
`agent-toolkit sync-workspace-config`.
|
|
61
|
+
- Root local governance must pin `devDependencies.@produck/eslint-rules`
|
|
62
|
+
via `agent-toolkit sync-eslint-config`.
|
|
50
63
|
|
|
51
64
|
- Testing strategy and framework are repository-defined.
|
|
65
|
+
- `verify` scripts are optional repository-local health checks and are not
|
|
66
|
+
organization-required script keys.
|
|
67
|
+
- `verify` is not part of organization commit gates; style gates remain
|
|
68
|
+
repository `format:check` and `lint` policy.
|
|
52
69
|
- `test` script implementation is repository-defined and is not overwritten by
|
|
53
70
|
organization coverage remediation.
|
|
54
71
|
- Repositories should keep `npm run test` and `npm run produck:coverage`
|
|
@@ -70,7 +87,15 @@ Central toolkit command role model:
|
|
|
70
87
|
governance and is mandatory in monorepo mode.
|
|
71
88
|
- `agent-toolkit sync-husky-hooks` is the hard guard for local anti-drift hook
|
|
72
89
|
governance and is mandatory in monorepo mode.
|
|
73
|
-
-
|
|
90
|
+
- `agent-toolkit sync-prettier-config` is the hard guard for root format
|
|
91
|
+
script/config governance and is mandatory in monorepo mode.
|
|
92
|
+
- `agent-toolkit sync-eslint-config` is the hard guard for root lint
|
|
93
|
+
script/config and eslint integration governance and is mandatory in monorepo
|
|
94
|
+
mode.
|
|
95
|
+
- `agent-toolkit sync-workspace-config` is the hard guard for root shared
|
|
96
|
+
scripts/dependencies governance and is mandatory in monorepo mode.
|
|
97
|
+
- For simplified downstream execution of mandatory flow (1 -> 2 -> 3 -> 4 ->
|
|
98
|
+
5 -> 6 -> 7),
|
|
74
99
|
use:
|
|
75
100
|
`npm exec -- agent-toolkit`.
|
|
76
101
|
- Equivalent explicit form:
|
|
@@ -157,18 +182,38 @@ Script placement:
|
|
|
157
182
|
and `lint` orchestration scripts.
|
|
158
183
|
- Root `package.json` must reserve `produck:precommit-check` for organization
|
|
159
184
|
anti-drift gate with required value:
|
|
160
|
-
`npm run format
|
|
185
|
+
`npm run produck:format && npm run produck:lint`.
|
|
161
186
|
- Root `package.json` must reserve `prepare` for husky setup with required
|
|
162
187
|
value: `husky`.
|
|
188
|
+
- Root `package.json` must reserve `produck:format` and `produck:lint` for
|
|
189
|
+
organization-controlled format/lint gates.
|
|
163
190
|
- `publish` may be defined at root or package level based on release workflow.
|
|
164
191
|
- Workspace subpackage `produck:coverage` scripts must be synchronized by
|
|
165
192
|
`agent-toolkit sync-coverage-script`.
|
|
166
193
|
- Root local hook governance must be synchronized by
|
|
167
194
|
`agent-toolkit sync-husky-hooks`.
|
|
168
|
-
- Root local
|
|
169
|
-
`
|
|
195
|
+
- Root local format governance must be synchronized by
|
|
196
|
+
`agent-toolkit sync-prettier-config`.
|
|
197
|
+
- Root local lint governance must be synchronized by
|
|
198
|
+
`agent-toolkit sync-eslint-config`.
|
|
199
|
+
- Root local shared script/dependency governance must be synchronized by
|
|
200
|
+
`agent-toolkit sync-workspace-config`.
|
|
201
|
+
- Root local shared script/dependency governance must pin root
|
|
202
|
+
`devDependencies.c8`,
|
|
203
|
+
`devDependencies.husky`, `devDependencies.lerna`,
|
|
170
204
|
`devDependencies.@produck/agent-toolkit` via
|
|
171
|
-
`agent-toolkit sync-
|
|
205
|
+
`agent-toolkit sync-workspace-config`.
|
|
206
|
+
- Root local shared script/dependency governance must initialize
|
|
207
|
+
`scripts.produck:coverage` with workspace-level execution behavior:
|
|
208
|
+
attempt `test` on all workspace packages using `--workspaces --if-present`.
|
|
209
|
+
- Root local shared script/dependency governance must initialize `.c8rc.json`
|
|
210
|
+
via `agent-toolkit sync-workspace-config`.
|
|
211
|
+
- Root local format governance must initialize `.prettierrc` and
|
|
212
|
+
`scripts.produck:format` via `agent-toolkit sync-prettier-config`.
|
|
213
|
+
- Root local lint governance must initialize `eslint.config.mjs`,
|
|
214
|
+
`scripts.produck:lint`, and `devDependencies.@produck/eslint-rules`
|
|
215
|
+
(including append-mode integration for existing eslint config) via
|
|
216
|
+
`agent-toolkit sync-eslint-config`.
|
|
172
217
|
- Root `package.json` must define a `produck:baseline` script for organization
|
|
173
218
|
baseline enforcement:
|
|
174
219
|
```json
|
|
@@ -28,6 +28,11 @@ Monorepo format (required for multi-package repositories):
|
|
|
28
28
|
|
|
29
29
|
- Package/workspace labels appear as **section headers** followed by a colon
|
|
30
30
|
(format: `package-name:` or `@scope/package:`).
|
|
31
|
+
- Section scope naming convention:
|
|
32
|
+
- For subpackage changes, use the package `name` as section header
|
|
33
|
+
(for example `@produck/agent-toolkit:`).
|
|
34
|
+
- For non-subpackage or root-level changes, use `workspace:`.
|
|
35
|
+
- For complex mixed commits across multiple scopes, `*:` is allowed.
|
|
31
36
|
- All lines under a package header belong to that package.
|
|
32
37
|
- Every line under a package header must start with `[TAG]`.
|
|
33
38
|
- No empty lines between tagged lines within a package section.
|
|
@@ -172,6 +177,8 @@ Commit precheck gate (AI-agent required, human recommended):
|
|
|
172
177
|
- For AI-agent-authored operations, complete repository style gates before both
|
|
173
178
|
`git commit` and `git commit --amend` (for example `format:check` and
|
|
174
179
|
`lint`).
|
|
180
|
+
- For AI-agent-authored operations, `git commit --no-verify` and
|
|
181
|
+
`git commit --amend --no-verify` are forbidden.
|
|
175
182
|
- For human engineer-authored operations, style gates are recommended baseline
|
|
176
183
|
practice unless repository-specific hooks/CI enforce them.
|
|
177
184
|
- Temporary non-executable state or failing tests are allowed for
|