@produck/agent-toolkit 0.9.2 → 0.10.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/bin/agent-toolkit.mjs +8 -0
- package/bin/build-publish-assets.mjs +28 -6
- package/bin/command/main/help.txt +1 -0
- package/bin/command/sync-coverage/index.mjs +30 -1
- package/bin/command/sync-coverage/required-c8-config.json +10 -5
- package/bin/command/sync-format/index.mjs +30 -3
- package/bin/command/sync-git/index.mjs +31 -2
- package/bin/command/sync-lint/index.mjs +29 -7
- package/bin/command/sync-typescript/help.txt +14 -0
- package/bin/command/sync-typescript/index.mjs +154 -0
- package/package.json +21 -21
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +3 -3
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +151 -118
- package/publish-assets/instructions/produck/12-produck-test.instructions.md +92 -0
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +33 -36
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +12 -0
- package/publish-assets/instructions/produck/tooling-version-baseline.json +53 -48
- package/publish-assets/lerna.json +3 -3
- package/publish-assets/prettierrc +9 -1
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -42,6 +42,10 @@ 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';
|
|
45
49
|
import { hasFlag, parseCommonArgs } from './command/shared/args.mjs';
|
|
46
50
|
import {
|
|
47
51
|
printValidateCommitMsgHelp,
|
|
@@ -101,6 +105,10 @@ const COMMANDS = {
|
|
|
101
105
|
printHelp: printSyncPublishHelp,
|
|
102
106
|
run: runSyncPublish,
|
|
103
107
|
},
|
|
108
|
+
'sync-typescript': {
|
|
109
|
+
printHelp: printSyncTypescriptHelp,
|
|
110
|
+
run: runSyncTypescript,
|
|
111
|
+
},
|
|
104
112
|
};
|
|
105
113
|
|
|
106
114
|
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
|
|
|
@@ -51,6 +51,35 @@ function parseJsonFile(filePath, label) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function resolveSemverExact(text) {
|
|
55
|
+
return text.replace(/^[\^~>=<]+\s*/, '').trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveToolVersionFromDevDeps(baseline, toolName) {
|
|
59
|
+
// If baseline has a concrete version (not "auto"), use it directly.
|
|
60
|
+
// This is the case when reading the published publish-assets baseline.
|
|
61
|
+
const baselineVersion = String(
|
|
62
|
+
baseline?.tools?.[toolName]?.version || '',
|
|
63
|
+
).trim();
|
|
64
|
+
if (baselineVersion && baselineVersion !== 'auto') {
|
|
65
|
+
return baselineVersion;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fall back to resolving from local root package.json devDependencies.
|
|
69
|
+
// This covers source baseline with version="auto" during local dev.
|
|
70
|
+
const repoRoot = path.resolve(PACKAGE_ROOT, '../..');
|
|
71
|
+
const pkgJsonPath = path.resolve(repoRoot, 'package.json');
|
|
72
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
73
|
+
const pkg = parseJsonFile(pkgJsonPath, 'root package.json');
|
|
74
|
+
const dep = pkg?.devDependencies?.[toolName];
|
|
75
|
+
if (typeof dep === 'string' && dep.trim()) {
|
|
76
|
+
return resolveSemverExact(dep);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
function loadToolingBaseline() {
|
|
55
84
|
const toolingBaselinePath = TOOLING_BASELINE_CANDIDATE_PATHS.find(
|
|
56
85
|
(candidatePath) => {
|
|
@@ -76,7 +105,7 @@ function loadToolingBaseline() {
|
|
|
76
105
|
process.exit(2);
|
|
77
106
|
}
|
|
78
107
|
|
|
79
|
-
const c8Version = baseline
|
|
108
|
+
const c8Version = resolveToolVersionFromDevDeps(baseline, 'c8');
|
|
80
109
|
if (typeof c8Version !== 'string' || c8Version.trim() === '') {
|
|
81
110
|
console.error(
|
|
82
111
|
`Tooling baseline tools.c8.version must be a non-empty string: ${toolingBaselinePath}`,
|
|
@@ -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
|
-
"
|
|
14
|
-
"
|
|
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 =
|
|
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 =
|
|
138
|
-
const lernaVersion =
|
|
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
|
|
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}`,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit sync-typescript --package-root <pkg> [--cwd <dir>]
|
|
3
|
+
[--check] [--dry-run] [--json <file>]
|
|
4
|
+
|
|
5
|
+
Behavior:
|
|
6
|
+
- Creates a tsconfig.json for a sub-package that extends the root
|
|
7
|
+
tsconfig.json with the correct relative path
|
|
8
|
+
- If tsconfig.json already exists, skips without modifying
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
- --package-root is required
|
|
12
|
+
- --check validates without writing and exits non-zero if file is missing
|
|
13
|
+
- --dry-run prints planned changes without writing
|
|
14
|
+
- --check takes precedence over --dry-run
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getSingle, hasFlag } from '../shared/args.mjs';
|
|
6
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
7
|
+
|
|
8
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
10
|
+
const TSCONFIG_FILE = 'tsconfig.json';
|
|
11
|
+
|
|
12
|
+
const PACKAGE_TSCONFIG_TEMPLATE = {
|
|
13
|
+
compilerOptions: {
|
|
14
|
+
lib: ['ESNext'],
|
|
15
|
+
types: ['node'],
|
|
16
|
+
strictNullChecks: true,
|
|
17
|
+
allowJs: true,
|
|
18
|
+
noEmit: true,
|
|
19
|
+
module: 'NodeNext',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function printSyncTypescriptHelp() {
|
|
24
|
+
printTextResource(HELP_FILE);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJsonIfExists(filePath) {
|
|
28
|
+
if (!fs.existsSync(filePath)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function computeExtendsPath(packageRoot, repoRoot) {
|
|
39
|
+
const relative = path.relative(packageRoot, repoRoot);
|
|
40
|
+
const normalized = relative.replace(/\\/g, '/');
|
|
41
|
+
if (!normalized || normalized === '.') {
|
|
42
|
+
return './tsconfig.json';
|
|
43
|
+
}
|
|
44
|
+
return normalized.startsWith('..')
|
|
45
|
+
? `${normalized}/tsconfig.json`
|
|
46
|
+
: `./${normalized}/tsconfig.json`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function runSyncTypescript(options) {
|
|
50
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
51
|
+
const check = hasFlag(options, '--check');
|
|
52
|
+
const dryRun = hasFlag(options, '--dry-run') && !check;
|
|
53
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
54
|
+
const packageRoot = getSingle(options, '--package-root', '');
|
|
55
|
+
const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
|
|
56
|
+
|
|
57
|
+
if (!packageRoot) {
|
|
58
|
+
console.error('--package-root is required');
|
|
59
|
+
process.exit(2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(cwd)) {
|
|
63
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
64
|
+
process.exit(2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pkgDir = path.resolve(cwd, packageRoot);
|
|
68
|
+
if (!fs.existsSync(pkgDir)) {
|
|
69
|
+
console.error(`Package root does not exist: ${pkgDir}`);
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tsconfigPath = path.resolve(pkgDir, TSCONFIG_FILE);
|
|
74
|
+
const current = readJsonIfExists(tsconfigPath);
|
|
75
|
+
const fileExists = current !== null;
|
|
76
|
+
|
|
77
|
+
// If tsconfig.json already exists, skip without checking content
|
|
78
|
+
if (fileExists) {
|
|
79
|
+
const report = {
|
|
80
|
+
cwd,
|
|
81
|
+
mode,
|
|
82
|
+
ok: true,
|
|
83
|
+
tsconfigPath,
|
|
84
|
+
required: {
|
|
85
|
+
file: path.join(packageRoot, TSCONFIG_FILE),
|
|
86
|
+
},
|
|
87
|
+
status: {
|
|
88
|
+
fileExistsBefore: true,
|
|
89
|
+
mismatchesBefore: [],
|
|
90
|
+
fileExistsAfter: true,
|
|
91
|
+
mismatchesAfter: [],
|
|
92
|
+
updated: false,
|
|
93
|
+
skipped: true,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (jsonFile) {
|
|
98
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
99
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
100
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const expectedExtends = computeExtendsPath(pkgDir, cwd);
|
|
108
|
+
const expectedPkgConfig = {
|
|
109
|
+
extends: expectedExtends,
|
|
110
|
+
...PACKAGE_TSCONFIG_TEMPLATE,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const plannedContent = `${JSON.stringify(expectedPkgConfig, null, 2)}\n`;
|
|
114
|
+
|
|
115
|
+
if (mode === 'sync') {
|
|
116
|
+
fs.writeFileSync(tsconfigPath, plannedContent, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const mismatches = [
|
|
120
|
+
{
|
|
121
|
+
file: path.join(packageRoot, TSCONFIG_FILE),
|
|
122
|
+
expected: JSON.stringify(expectedPkgConfig, null, 2),
|
|
123
|
+
actual: 'missing',
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const report = {
|
|
128
|
+
cwd,
|
|
129
|
+
mode,
|
|
130
|
+
ok: mode !== 'check',
|
|
131
|
+
tsconfigPath,
|
|
132
|
+
required: {
|
|
133
|
+
file: path.join(packageRoot, TSCONFIG_FILE),
|
|
134
|
+
},
|
|
135
|
+
status: {
|
|
136
|
+
fileExistsBefore: false,
|
|
137
|
+
mismatchesBefore: mismatches,
|
|
138
|
+
fileExistsAfter: mode === 'sync',
|
|
139
|
+
mismatchesAfter: mode === 'sync' ? [] : mismatches,
|
|
140
|
+
updated: mode === 'sync',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (jsonFile) {
|
|
145
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
146
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
147
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
151
|
+
if (!report.ok) {
|
|
152
|
+
process.exit(2);
|
|
153
|
+
}
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "@produck/agent-toolkit",
|
|
3
|
-
"version": "0.9.2",
|
|
4
|
-
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/produck/.github.git",
|
|
9
|
-
"directory": "packages/agent-toolkit"
|
|
10
|
-
},
|
|
11
2
|
"bin": {
|
|
12
3
|
"agent-toolkit": "bin/agent-toolkit.mjs"
|
|
13
4
|
},
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
5
|
+
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@produck/eslint-rules": "^0.4.1",
|
|
8
|
+
"c8": "11.0.0"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
19
12
|
},
|
|
20
13
|
"files": [
|
|
21
14
|
"bin",
|
|
22
15
|
"publish-assets"
|
|
23
16
|
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"name": "@produck/agent-toolkit",
|
|
24
19
|
"publishConfig": {
|
|
25
20
|
"access": "public"
|
|
26
21
|
},
|
|
27
|
-
"
|
|
28
|
-
"
|
|
22
|
+
"repository": {
|
|
23
|
+
"directory": "packages/agent-toolkit",
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/produck/.github.git"
|
|
29
26
|
},
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepack": "node ./bin/build-publish-assets.mjs",
|
|
29
|
+
"produck:coverage": "c8 --reporter=lcov --reporter=html --reporter=text-summary npm test",
|
|
30
|
+
"produck:lint": "eslint --fix . --max-warnings=0",
|
|
31
|
+
"test": "node test/index.mjs"
|
|
34
32
|
},
|
|
35
|
-
"
|
|
33
|
+
"type": "module",
|
|
34
|
+
"version": "0.10.0",
|
|
35
|
+
"gitHead": "d0d432838dddbbc90f340b525bdd41153f8e6502"
|
|
36
36
|
}
|
|
@@ -32,12 +32,12 @@ repositories:
|
|
|
32
32
|
- `.github/copilot-instructions.md` — repository-specific exceptions and
|
|
33
33
|
stricter local constraints.
|
|
34
34
|
|
|
35
|
-
Editing rule
|
|
35
|
+
Editing rule:
|
|
36
36
|
|
|
37
37
|
- Update downstream baseline rules directly in
|
|
38
38
|
`.github/distribution/produck/*.instructions.md`.
|
|
39
|
-
-
|
|
40
|
-
`.github/instructions/produck
|
|
39
|
+
- For internal-only governance (this repo only, not distributed), add files
|
|
40
|
+
under `.github/instructions/produck/`.
|
|
41
41
|
|
|
42
42
|
## Default expectations
|
|
43
43
|
|
|
@@ -53,16 +53,8 @@ Notes:
|
|
|
53
53
|
- Use central remediation command to deploy root lint script/config and
|
|
54
54
|
eslint integration baseline:
|
|
55
55
|
`npm exec -- agent-toolkit sync-lint --cwd .`.
|
|
56
|
-
- `c8` execution baseline for deployed coverage scripts is fixed to the
|
|
57
|
-
version specified in `tooling-version-baseline.json`.
|
|
58
56
|
- Downstream repositories must not use unversioned `npx c8` or `c8@latest`
|
|
59
57
|
in shared scripts/CI.
|
|
60
|
-
- Root local governance must pin `devDependencies.c8`,
|
|
61
|
-
`devDependencies.husky`, `devDependencies.lerna`, and
|
|
62
|
-
`devDependencies.@produck/agent-toolkit` via
|
|
63
|
-
`agent-toolkit sync-git`.
|
|
64
|
-
- Root local governance must pin `devDependencies.@produck/eslint-rules`
|
|
65
|
-
via `agent-toolkit sync-lint`.
|
|
66
58
|
|
|
67
59
|
- Testing strategy and framework are repository-defined.
|
|
68
60
|
- `verify` scripts are optional repository-local health checks and are not
|
|
@@ -78,43 +70,14 @@ Notes:
|
|
|
78
70
|
- Commit prechecks still require passing repository style gates (for example
|
|
79
71
|
`produck:format` and `produck:lint`).
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
organization baseline instructions.
|
|
85
|
-
- `sync-instructions` is not a hard gate; use it to reduce instruction drift,
|
|
86
|
-
but do not assume it can fully prevent AI hallucination or iterative drift.
|
|
87
|
-
- `agent-toolkit preflight` is the hard guard for organization engineering
|
|
88
|
-
baseline and is mandatory for required baseline checks.
|
|
89
|
-
- `agent-toolkit sync-install` is the hard guard for root install script
|
|
90
|
-
governance and is mandatory in monorepo mode.
|
|
91
|
-
- `agent-toolkit sync-coverage` is the hard guard for monorepo coverage
|
|
92
|
-
governance and is mandatory in monorepo mode.
|
|
93
|
-
- `agent-toolkit sync-git` is the hard guard for local anti-drift hook
|
|
94
|
-
governance and is mandatory in monorepo mode.
|
|
95
|
-
- `agent-toolkit sync-format` is the hard guard for root format
|
|
96
|
-
script/config governance and is mandatory in monorepo mode.
|
|
97
|
-
- `agent-toolkit sync-lint` is the hard guard for root lint
|
|
98
|
-
script/config and eslint integration governance and is mandatory in monorepo
|
|
99
|
-
mode.
|
|
100
|
-
- `agent-toolkit sync-publish` is the hard guard for root publish script
|
|
101
|
-
governance when `lerna.json` is present.
|
|
102
|
-
- For simplified downstream execution of mandatory flow (1 -> 2 -> ... -> 9),
|
|
103
|
-
use:
|
|
104
|
-
`npm exec -- agent-toolkit`.
|
|
105
|
-
- Equivalent explicit form:
|
|
73
|
+
Enforcement:
|
|
74
|
+
|
|
75
|
+
- For one-step execution of all mandatory checks, use:
|
|
106
76
|
`npm exec -- agent-toolkit enforce-node-baseline --cwd .`.
|
|
107
|
-
- `agent-toolkit validate-commit-msg` is
|
|
108
|
-
`git commit` and `git commit --amend
|
|
109
|
-
- For human engineers, commit-message validation is recommended
|
|
110
|
-
|
|
111
|
-
- Do not require retroactive rewrite/amend of historical commits solely to
|
|
112
|
-
satisfy commit-message validator rules.
|
|
113
|
-
- `agent-toolkit run-capture` and `agent-toolkit summarize-log` are AI-agent
|
|
114
|
-
execution guardrails.
|
|
115
|
-
- These guardrails pair with node-first execution policy: prefer Node.js
|
|
116
|
-
interpreter workflows for parsing/filtering over brittle OS-shell pipelines.
|
|
117
|
-
- For human engineers, `run-capture` and `summarize-log` are optional helpers.
|
|
77
|
+
- `agent-toolkit validate-commit-msg` is required for AI-agent-authored
|
|
78
|
+
`git commit` and `git commit --amend`.
|
|
79
|
+
- For human engineers, commit-message validation is recommended.
|
|
80
|
+
- Do not require retroactive rewrite/amend of historical commits.
|
|
118
81
|
|
|
119
82
|
Test authoring baseline:
|
|
120
83
|
|
|
@@ -143,67 +106,164 @@ Team conventions for `.gitignore`:
|
|
|
143
106
|
|
|
144
107
|
## Monorepo mode
|
|
145
108
|
|
|
146
|
-
Repository layout
|
|
109
|
+
### Repository layout
|
|
147
110
|
|
|
148
111
|
- Root-level `docs/` is required.
|
|
149
112
|
- Each package/app should contain its own `src/` and `test/`.
|
|
150
113
|
|
|
151
|
-
|
|
114
|
+
### Root `package.json` governance
|
|
115
|
+
|
|
116
|
+
The root `package.json` exists only for development orchestration. It is
|
|
117
|
+
never consumed as a downstream dependency, so runtime-oriented fields are
|
|
118
|
+
prohibited.
|
|
119
|
+
|
|
120
|
+
#### Required fields
|
|
121
|
+
|
|
122
|
+
- `name` — A fixed name stabilizes the `name` field in `package-lock.json`.
|
|
123
|
+
- `private`: `true` — Prevents accidental npm publish.
|
|
124
|
+
- `workspaces` — Explicit path list only; see constraints below.
|
|
125
|
+
- `scripts` — See [Required scripts](#required-scripts) section below.
|
|
126
|
+
- `devDependencies` — The development materials manifest. Root
|
|
127
|
+
`devDependencies` is the single source of truth for organization-level and
|
|
128
|
+
repository-level tooling.
|
|
129
|
+
|
|
130
|
+
#### Optional fields
|
|
131
|
+
|
|
132
|
+
- `version` — Root is not published; version is meaningless.
|
|
133
|
+
- `description` — Root is not published; description has no practical use.
|
|
134
|
+
- `engines` — Not required. Leave this to Node.js version managers if
|
|
135
|
+
needed.
|
|
136
|
+
|
|
137
|
+
#### Prohibited fields
|
|
138
|
+
|
|
139
|
+
The following fields must never appear in root `package.json`:
|
|
140
|
+
|
|
141
|
+
- `type`
|
|
142
|
+
- `main`
|
|
143
|
+
- `exports`
|
|
144
|
+
- `types`
|
|
145
|
+
- `files`
|
|
146
|
+
- `publishConfig`
|
|
147
|
+
- `dependencies`
|
|
148
|
+
|
|
149
|
+
#### `workspaces` field constraints
|
|
150
|
+
|
|
151
|
+
- Do not use wildcard/glob patterns (for example `packages/*`, `**`, `?`,
|
|
152
|
+
`{}` or `[]`).
|
|
153
|
+
- List each workspace package path explicitly.
|
|
154
|
+
|
|
155
|
+
### Required scripts
|
|
156
|
+
|
|
157
|
+
Root `package.json` must define:
|
|
158
|
+
|
|
159
|
+
| Script key | Required value |
|
|
160
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `produck:install` | `npm -v && npm install` |
|
|
162
|
+
| `prepare` | `husky` |
|
|
163
|
+
| `test` | Repository-defined (may use `--workspaces --if-present`) |
|
|
164
|
+
| `produck:coverage` | Organization-defined via `agent-toolkit sync-coverage` |
|
|
165
|
+
| `produck:lint` | Organization-defined via `agent-toolkit sync-lint` |
|
|
166
|
+
| `produck:format` | Organization-defined via `agent-toolkit sync-format` |
|
|
167
|
+
| `produck:commit:check` | `npm run produck:format && npm run produck:lint` |
|
|
168
|
+
| `produck:baseline` | `npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd .` |
|
|
169
|
+
| `produck:publish` | Required when `lerna.json` is present; governed by `agent-toolkit sync-publish` |
|
|
170
|
+
| `produck:publish:check` | Required when `lerna.json` is present; governed by `agent-toolkit sync-publish` |
|
|
171
|
+
|
|
172
|
+
Notes:
|
|
152
173
|
|
|
153
|
-
- Root `package.json` must provide `produck:install`, `test`, `produck:coverage`,
|
|
154
|
-
and `produck:lint` orchestration scripts.
|
|
155
|
-
- Root `package.json` must reserve `produck:commit:check` for organization
|
|
156
|
-
anti-drift gate with required value:
|
|
157
|
-
`npm run produck:format && npm run produck:lint`.
|
|
158
|
-
- Root `package.json` must reserve `prepare` for husky setup with required
|
|
159
|
-
value: `husky`.
|
|
160
|
-
- Root `package.json` must reserve `produck:format` and `produck:lint` for
|
|
161
|
-
organization-controlled format/lint gates.
|
|
162
|
-
- Root `package.json` must reserve `produck:publish` for organization-controlled
|
|
163
|
-
publish gate when `lerna.json` is present (governed by
|
|
164
|
-
`agent-toolkit sync-publish`).
|
|
165
174
|
- `publish` may be defined at root or package level based on release workflow.
|
|
166
|
-
-
|
|
167
|
-
`agent-toolkit sync-
|
|
168
|
-
-
|
|
169
|
-
`agent-toolkit sync-git
|
|
170
|
-
-
|
|
171
|
-
`
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
-
|
|
194
|
-
|
|
175
|
+
- Remediation commands (run from root):
|
|
176
|
+
- `npm exec -- agent-toolkit sync-install --cwd .` — deploy root install script
|
|
177
|
+
- `npm exec -- agent-toolkit sync-coverage --cwd .` — deploy coverage scripts
|
|
178
|
+
- `npm exec -- agent-toolkit sync-git --cwd .` — deploy git hooks and deps
|
|
179
|
+
- `npm exec -- agent-toolkit sync-format --cwd .` — deploy format config
|
|
180
|
+
- `npm exec -- agent-toolkit sync-lint --cwd .` — deploy lint config
|
|
181
|
+
|
|
182
|
+
### Workspace `package.json` governance
|
|
183
|
+
|
|
184
|
+
#### Property restrictions
|
|
185
|
+
|
|
186
|
+
Workspace-level `package.json` must NOT contain:
|
|
187
|
+
|
|
188
|
+
- `private` — Package publication state is managed by the root workspace;
|
|
189
|
+
individual workspace packages should not declare it.
|
|
190
|
+
- `workspaces` — Only the root `package.json` defines workspace paths.
|
|
191
|
+
Nested declarations violate the centralization principle.
|
|
192
|
+
- Root orchestration scripts — These are the root `package.json`'s
|
|
193
|
+
responsibility and must not be duplicated in workspace packages:
|
|
194
|
+
`produck:install`, `produck:baseline`, `produck:commit:check`, `prepare`,
|
|
195
|
+
`produck:format`, `produck:publish`, `produck:publish:check`
|
|
196
|
+
|
|
197
|
+
#### Recommended structure
|
|
198
|
+
|
|
199
|
+
Workspace packages should keep their `package.json` lean, containing only:
|
|
200
|
+
|
|
201
|
+
- Package metadata: `name`, `version`, `type`, `main`, `exports`, etc.
|
|
202
|
+
- Dependencies: `dependencies`, `devDependencies`
|
|
203
|
+
- Package-level scripts: `test`, `produck:coverage`
|
|
204
|
+
|
|
205
|
+
#### Publish metadata governance
|
|
206
|
+
|
|
207
|
+
Workspace packages that are published to npm must correctly declare the
|
|
208
|
+
following fields. These control the package's npm registry page appearance and
|
|
209
|
+
link back to the git hosting platform.
|
|
210
|
+
|
|
211
|
+
**Required fields:**
|
|
212
|
+
|
|
213
|
+
- `description` — Short summary shown on npm search results and package page.
|
|
214
|
+
Must be meaningful and accurate.
|
|
215
|
+
- `license` — SPDX license identifier (for example `"MIT"`). Displayed on npm
|
|
216
|
+
package page.
|
|
217
|
+
|
|
218
|
+
**Repository linkage fields** (affect npm "repository", "bugs", "homepage"
|
|
219
|
+
links):
|
|
220
|
+
|
|
221
|
+
- `repository` — Must use the expanded object form with `directory` to
|
|
222
|
+
identify the subpackage location within the monorepo:
|
|
195
223
|
```json
|
|
196
|
-
"
|
|
224
|
+
"repository": {
|
|
225
|
+
"type": "git",
|
|
226
|
+
"url": "git+https://github.com/produck/<repo>.git",
|
|
227
|
+
"directory": "packages/<name>"
|
|
228
|
+
}
|
|
197
229
|
```
|
|
230
|
+
The `url` value must match the repository's canonical git remote. The
|
|
231
|
+
`directory` value must be the package's workspace-relative path from the
|
|
232
|
+
monorepo root.
|
|
233
|
+
- `bugs` — Must link to the GitHub issues page:
|
|
234
|
+
```json
|
|
235
|
+
"bugs": {
|
|
236
|
+
"url": "https://github.com/produck/<repo>/issues"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
- `homepage` — Must point to the package's README on the default branch, using
|
|
240
|
+
the `directory` form to navigate to the subpackage:
|
|
241
|
+
```json
|
|
242
|
+
"homepage": "https://github.com/produck/<repo>/tree/main/packages/<name>#readme"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Recommended fields:**
|
|
246
|
+
|
|
247
|
+
- `keywords` — Array of strings that describe the package. Improves npm search
|
|
248
|
+
discoverability. Omit if the package has no meaningful keywords.
|
|
249
|
+
|
|
250
|
+
**Rationale:**
|
|
251
|
+
|
|
252
|
+
- Without `repository.directory`, npm links the repository field to the
|
|
253
|
+
monorepo root, which is not helpful for subpackage visitors.
|
|
254
|
+
- Without `bugs`, npm omits the "Report issues" link on the package page.
|
|
255
|
+
- Without `description`, npm shows "(not yet filled)" on the package page.
|
|
256
|
+
- Invalid or missing `license` causes npm warnings during publish.
|
|
198
257
|
|
|
199
|
-
Release tooling policy
|
|
258
|
+
### Release tooling policy
|
|
200
259
|
|
|
201
260
|
- Monorepo release workflow must use `lerna`.
|
|
202
261
|
- `lerna` execution version is governed at organization level, not per
|
|
203
262
|
repository.
|
|
204
263
|
- Source of truth for `lerna` version baseline:
|
|
205
264
|
`.github/distribution/produck/tooling-version-baseline.json`.
|
|
206
|
-
- Required execution baseline: version specified in
|
|
265
|
+
- Required execution baseline: version specified in the
|
|
266
|
+
`tooling-version-baseline.json`.
|
|
207
267
|
- Required invocation:
|
|
208
268
|
`npm exec -- lerna <subcommand>`.
|
|
209
269
|
- Downstream repositories must not use unversioned `npx lerna` or
|
|
@@ -212,34 +272,7 @@ Release tooling policy (required):
|
|
|
212
272
|
- Keep an emergency organization-level rollback path when baseline version is
|
|
213
273
|
updated.
|
|
214
274
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
- `private`: `true`
|
|
218
|
-
- `workspaces` (explicit package path list only)
|
|
219
|
-
- `scripts` with at least: `produck:install`, `test`, `produck:coverage`,
|
|
220
|
-
`produck:lint`
|
|
221
|
-
- `publish` script is optional at root when release is managed per package or
|
|
222
|
-
by external workflow.
|
|
223
|
-
|
|
224
|
-
`workspaces` field constraints (required):
|
|
225
|
-
|
|
226
|
-
- Do not use wildcard/glob patterns (for example `packages/*`, `**`, `?`,
|
|
227
|
-
`{}` or `[]`).
|
|
228
|
-
- List each workspace package path explicitly.
|
|
229
|
-
|
|
230
|
-
Avoid unused root runtime/publish fields by default:
|
|
231
|
-
|
|
232
|
-
- `type`
|
|
233
|
-
- `main`
|
|
234
|
-
- `exports`
|
|
235
|
-
- `types`
|
|
236
|
-
- `files`
|
|
237
|
-
- `publishConfig`
|
|
238
|
-
|
|
239
|
-
Add the fields above only when the monorepo root itself is an executable
|
|
240
|
-
runtime package or is intentionally published.
|
|
241
|
-
|
|
242
|
-
Ignore strategy:
|
|
275
|
+
### Ignore strategy
|
|
243
276
|
|
|
244
277
|
- Keep ignore rules centralized at repository root whenever possible.
|
|
245
278
|
- Add package-level `.gitignore` only when a package has unique generated
|
|
@@ -52,6 +52,35 @@ import './feature-b.test.mjs';
|
|
|
52
52
|
- Avoid global side effects that persist across test cases.
|
|
53
53
|
- Clean up resources (temp files, open handles) at the end of each test case.
|
|
54
54
|
|
|
55
|
+
## API usage conventions
|
|
56
|
+
|
|
57
|
+
- Callback functions for `describe` and `it` MUST use arrow functions. Do not
|
|
58
|
+
use `function` keyword or method shorthand syntax.
|
|
59
|
+
|
|
60
|
+
Correct:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
describe('my feature', () => {
|
|
64
|
+
it('should work', () => {
|
|
65
|
+
// assertions
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Wrong:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
describe('my feature', function () {
|
|
74
|
+
it('should work', function () {
|
|
75
|
+
// assertions
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Deep nesting of `describe` blocks can make test output harder to read. When
|
|
81
|
+
nesting becomes excessive, split the test file and use `await import()` to
|
|
82
|
+
reorganize — this keeps the actual test code at shallow nesting levels.
|
|
83
|
+
|
|
55
84
|
## Naming conventions
|
|
56
85
|
|
|
57
86
|
- Test files use the `.test.mjs` suffix.
|
|
@@ -59,6 +88,69 @@ import './feature-b.test.mjs';
|
|
|
59
88
|
the module name or command name).
|
|
60
89
|
- `it` labels should describe the expected behavior in plain language.
|
|
61
90
|
|
|
91
|
+
### Suite name conventions
|
|
92
|
+
|
|
93
|
+
The `describe` suite name encodes the member type under test using a prefix
|
|
94
|
+
convention:
|
|
95
|
+
|
|
96
|
+
| Prefix | Meaning | Example |
|
|
97
|
+
| ------ | ---------------------------------------------------- | ------------------------- |
|
|
98
|
+
| `::` | Class static public member or namespace static | `describe('::parse()')` |
|
|
99
|
+
| `.` | Class instance member | `describe('.validate()')` |
|
|
100
|
+
| `()` | Marks the target as a function (composable) | `describe('::create()')` |
|
|
101
|
+
| `>` | Return-value testing, nested inside a function suite | `describe('>result')` |
|
|
102
|
+
| `#` | Event emitter — test event dispatch behavior | `describe('#error')` |
|
|
103
|
+
|
|
104
|
+
Prefixes and `()` compose freely. For example:
|
|
105
|
+
|
|
106
|
+
| Suite name | Meaning |
|
|
107
|
+
| ------------- | ----------------------------- |
|
|
108
|
+
| `::parse()` | Static function `parse` |
|
|
109
|
+
| `::VERSION` | Static property `VERSION` |
|
|
110
|
+
| `.validate()` | Instance method `validate` |
|
|
111
|
+
| `.name` | Instance property `name` |
|
|
112
|
+
| `validate()` | Standalone function (unbound) |
|
|
113
|
+
|
|
114
|
+
Examples:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
describe('::parse()', () => {
|
|
118
|
+
it('should parse valid input', () => {
|
|
119
|
+
/* ... */
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('::VERSION', () => {
|
|
124
|
+
it('should be a semver string', () => {
|
|
125
|
+
/* ... */
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('.validate()', () => {
|
|
130
|
+
it('should reject invalid data', () => {
|
|
131
|
+
/* ... */
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('>result', () => {
|
|
135
|
+
it('should be a boolean', () => {
|
|
136
|
+
/* ... */
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('.name', () => {
|
|
142
|
+
it('should be non-empty', () => {
|
|
143
|
+
/* ... */
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('#error', () => {
|
|
148
|
+
it('should emit on failure', () => {
|
|
149
|
+
/* ... */
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
62
154
|
## Local debug workflow (recommended)
|
|
63
155
|
|
|
64
156
|
When debugging a failing test case:
|
|
@@ -34,17 +34,14 @@ The Produck monorepo provides unified configuration across all packages for cons
|
|
|
34
34
|
- If a repository overrides inherited rules, include only the deltas and
|
|
35
35
|
document the rationale.
|
|
36
36
|
|
|
37
|
-
**Usage
|
|
37
|
+
**Usage:**
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Package-specific overrides here
|
|
46
|
-
];
|
|
47
|
-
```
|
|
39
|
+
- Root `eslint.config.mjs` applies to all packages automatically via ESLint
|
|
40
|
+
flat config resolution — sub-packages are NOT required to create their own
|
|
41
|
+
`eslint.config.mjs`.
|
|
42
|
+
- Sub-packages may create a package-level `eslint.config.mjs` on demand for
|
|
43
|
+
workspace-specific overrides, but this is opt-in and not proactively
|
|
44
|
+
deployed.
|
|
48
45
|
|
|
49
46
|
### 2. TypeScript Configuration (`tsconfig.json`, conditional)
|
|
50
47
|
|
|
@@ -59,24 +56,34 @@ export default [
|
|
|
59
56
|
- If any package uses TypeScript source files or needs centralized strict/type
|
|
60
57
|
options, create root `tsconfig.json` and let TypeScript packages extend it.
|
|
61
58
|
|
|
62
|
-
**
|
|
59
|
+
**Governance (enforced by `agent-toolkit sync-typescript`):**
|
|
60
|
+
|
|
61
|
+
Run `agent-toolkit sync-typescript --package-root packages/<name> --cwd .`
|
|
62
|
+
to ensure a sub-package has a `tsconfig.json` that extends the root with the
|
|
63
|
+
correct relative path and uses standard compiler options. If the file already
|
|
64
|
+
exists, it is skipped without modification.
|
|
65
|
+
|
|
66
|
+
**Enforced settings:**
|
|
63
67
|
|
|
64
68
|
- Target: ES2022
|
|
65
69
|
- Strict mode: enabled
|
|
66
|
-
- Module resolution:
|
|
70
|
+
- Module resolution: bundler
|
|
67
71
|
- Source maps: enabled
|
|
68
72
|
- Declaration files: generated
|
|
69
73
|
|
|
70
|
-
**Usage in TypeScript packages:**
|
|
74
|
+
**Usage in TypeScript packages (auto-generated by `--package-root`):**
|
|
71
75
|
|
|
72
76
|
```json
|
|
73
77
|
{
|
|
74
78
|
"extends": "../../tsconfig.json",
|
|
75
79
|
"compilerOptions": {
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
"lib": ["ESNext"],
|
|
81
|
+
"types": ["node"],
|
|
82
|
+
"strictNullChecks": true,
|
|
83
|
+
"allowJs": true,
|
|
84
|
+
"noEmit": true,
|
|
85
|
+
"module": "NodeNext"
|
|
86
|
+
}
|
|
80
87
|
}
|
|
81
88
|
```
|
|
82
89
|
|
|
@@ -161,32 +168,22 @@ npm run produck:coverage
|
|
|
161
168
|
|
|
162
169
|
1. Create `packages/my-package/` directory
|
|
163
170
|
2. Create `packages/my-package/package.json` with workspace configuration
|
|
164
|
-
3.
|
|
165
|
-
- ESLint:
|
|
171
|
+
3. Config inheritance:
|
|
172
|
+
- ESLint: root `eslint.config.mjs` applies automatically — package-level
|
|
173
|
+
`eslint.config.mjs` is opt-in and not proactively deployed.
|
|
166
174
|
- TypeScript (when root `tsconfig.json` exists): extend
|
|
167
175
|
`../../tsconfig.json`
|
|
168
176
|
- Prettier: uses root `.prettierrc` automatically
|
|
169
177
|
|
|
170
178
|
### Package-Level Overrides
|
|
171
179
|
|
|
172
|
-
Each package
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
Each package's specific needs should be handled within the root shared config
|
|
181
|
+
whenever possible. Per-package ESLint config files are opt-in and not
|
|
182
|
+
proactively deployed; sub-packages may create one on demand.
|
|
175
183
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
export default [
|
|
181
|
-
...rootConfig,
|
|
182
|
-
{
|
|
183
|
-
plugins: { 'only-warn': onlyWarn },
|
|
184
|
-
rules: {
|
|
185
|
-
'custom-rule': 'warn', // package-specific override
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
];
|
|
189
|
-
```
|
|
184
|
+
For TypeScript configuration, packages may extend root `tsconfig.json` (see
|
|
185
|
+
[TypeScript Configuration](#2-typescript-configuration-tsconfigjson-conditional)
|
|
186
|
+
above).
|
|
190
187
|
|
|
191
188
|
## .editorconfig
|
|
192
189
|
|
|
@@ -191,6 +191,18 @@ Commit precheck gate (AI-agent required, human recommended):
|
|
|
191
191
|
|
|
192
192
|
If validation fails, fix the message and rerun until it passes.
|
|
193
193
|
|
|
194
|
+
## Workspace Draft Files
|
|
195
|
+
|
|
196
|
+
AI agents **must not** create or organize commit message draft files (such as
|
|
197
|
+
`.git/COMMIT_EDITMSG` snapshots, staged-message backups, or raw draft text
|
|
198
|
+
files) anywhere in the working tree.
|
|
199
|
+
|
|
200
|
+
**Exception**: draft files are allowed if the filename matches the
|
|
201
|
+
`*.ign*` glob pattern (for example `commit-msg.ign` or
|
|
202
|
+
`message.draft.ignore`). This ensures the file is covered by the
|
|
203
|
+
organization-level `.gitignore` rules and will never be accidentally
|
|
204
|
+
committed.
|
|
205
|
+
|
|
194
206
|
## Notes
|
|
195
207
|
|
|
196
208
|
- Keep the summary concise and specific.
|
|
@@ -1,79 +1,84 @@
|
|
|
1
1
|
{
|
|
2
|
+
"coverage": {
|
|
3
|
+
"scriptTemplate": "c8 --reporter=lcov --reporter=html --reporter=text-summary npm test"
|
|
4
|
+
},
|
|
5
|
+
"enforce": {
|
|
6
|
+
"sharedScriptsDisallow": [
|
|
7
|
+
"npx c8",
|
|
8
|
+
"c8@latest",
|
|
9
|
+
"npx lerna",
|
|
10
|
+
"lerna@latest",
|
|
11
|
+
"npx prettier",
|
|
12
|
+
"prettier@latest"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
2
15
|
"schemaVersion": 1,
|
|
3
|
-
"updatedAt": "2026-05-12",
|
|
4
16
|
"tools": {
|
|
5
|
-
"
|
|
6
|
-
"
|
|
17
|
+
"@eslint/config-helpers": {
|
|
18
|
+
"allowLatest": false,
|
|
7
19
|
"policy": "pinned",
|
|
8
|
-
"
|
|
20
|
+
"version": "0.6.0"
|
|
9
21
|
},
|
|
10
|
-
"
|
|
11
|
-
"
|
|
22
|
+
"@eslint/js": {
|
|
23
|
+
"allowLatest": false,
|
|
12
24
|
"policy": "pinned",
|
|
13
|
-
"
|
|
25
|
+
"version": "10.0.1"
|
|
14
26
|
},
|
|
15
|
-
"
|
|
16
|
-
"
|
|
27
|
+
"@eslint/json": {
|
|
28
|
+
"allowLatest": false,
|
|
17
29
|
"policy": "pinned",
|
|
18
|
-
"
|
|
30
|
+
"version": "1.2.0"
|
|
19
31
|
},
|
|
20
|
-
"
|
|
21
|
-
"
|
|
32
|
+
"@eslint/markdown": {
|
|
33
|
+
"allowLatest": false,
|
|
22
34
|
"policy": "pinned",
|
|
23
|
-
"
|
|
35
|
+
"version": "8.0.2"
|
|
24
36
|
},
|
|
25
|
-
"
|
|
26
|
-
"
|
|
37
|
+
"@types/node": {
|
|
38
|
+
"allowLatest": false,
|
|
27
39
|
"policy": "pinned",
|
|
28
|
-
"
|
|
40
|
+
"version": "22.19.19"
|
|
29
41
|
},
|
|
30
|
-
"
|
|
31
|
-
"
|
|
42
|
+
"c8": {
|
|
43
|
+
"allowLatest": false,
|
|
32
44
|
"policy": "pinned",
|
|
33
|
-
"
|
|
45
|
+
"version": "11.0.0"
|
|
34
46
|
},
|
|
35
|
-
"
|
|
36
|
-
"
|
|
47
|
+
"eslint": {
|
|
48
|
+
"allowLatest": false,
|
|
37
49
|
"policy": "pinned",
|
|
38
|
-
"
|
|
50
|
+
"version": "10.4.0"
|
|
39
51
|
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
52
|
+
"globals": {
|
|
53
|
+
"allowLatest": false,
|
|
42
54
|
"policy": "pinned",
|
|
43
|
-
"
|
|
55
|
+
"version": "17.6.0"
|
|
44
56
|
},
|
|
45
|
-
"
|
|
46
|
-
"
|
|
57
|
+
"husky": {
|
|
58
|
+
"allowLatest": false,
|
|
47
59
|
"policy": "pinned",
|
|
48
|
-
"
|
|
60
|
+
"version": "9.1.7"
|
|
49
61
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
62
|
+
"lerna": {
|
|
63
|
+
"allowLatest": false,
|
|
52
64
|
"policy": "pinned",
|
|
53
|
-
"
|
|
65
|
+
"version": "9.0.7"
|
|
54
66
|
},
|
|
55
|
-
"
|
|
56
|
-
"
|
|
67
|
+
"prettier": {
|
|
68
|
+
"allowLatest": false,
|
|
57
69
|
"policy": "pinned",
|
|
58
|
-
"
|
|
70
|
+
"version": "3.8.3"
|
|
71
|
+
},
|
|
72
|
+
"typescript-eslint": {
|
|
73
|
+
"allowLatest": false,
|
|
74
|
+
"policy": "pinned",
|
|
75
|
+
"version": "8.60.0"
|
|
59
76
|
},
|
|
60
77
|
"@produck/eslint-rules": {
|
|
61
|
-
"version": "0.4.
|
|
78
|
+
"version": "0.4.1",
|
|
62
79
|
"policy": "pinned",
|
|
63
80
|
"allowLatest": false
|
|
64
81
|
}
|
|
65
82
|
},
|
|
66
|
-
"
|
|
67
|
-
"scriptTemplate": "c8 --reporter=lcov --reporter=html --reporter=text-summary npm test"
|
|
68
|
-
},
|
|
69
|
-
"enforce": {
|
|
70
|
-
"sharedScriptsDisallow": [
|
|
71
|
-
"npx c8",
|
|
72
|
-
"c8@latest",
|
|
73
|
-
"npx lerna",
|
|
74
|
-
"lerna@latest",
|
|
75
|
-
"npx prettier",
|
|
76
|
-
"prettier@latest"
|
|
77
|
-
]
|
|
78
|
-
}
|
|
83
|
+
"updatedAt": "2026-05-12"
|
|
79
84
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./node_modules/lerna/schemas/lerna-schema.json",
|
|
3
3
|
"command": {
|
|
4
|
-
"version": {
|
|
5
|
-
"commitHooks": false
|
|
6
|
-
},
|
|
7
4
|
"publish": {
|
|
8
5
|
"ignoreScripts": false,
|
|
9
6
|
"message": "[PUBLISH]"
|
|
7
|
+
},
|
|
8
|
+
"version": {
|
|
9
|
+
"commitHooks": false
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"useNx": false,
|