@produck/agent-toolkit 0.5.0 → 0.6.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/command/enforce-node-baseline/index.mjs +8 -0
- package/bin/command/main/help.txt +1 -0
- package/bin/command/sync-editorconfig/editorconfig.template +15 -0
- package/bin/command/sync-editorconfig/help.txt +13 -0
- package/bin/command/sync-editorconfig/index.mjs +233 -0
- package/package.json +2 -2
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
printSyncPrettierConfigHelp,
|
|
21
21
|
runSyncPrettierConfig,
|
|
22
22
|
} from './command/sync-prettier-config/index.mjs';
|
|
23
|
+
import {
|
|
24
|
+
printSyncEditorconfigHelp,
|
|
25
|
+
runSyncEditorconfig,
|
|
26
|
+
} from './command/sync-editorconfig/index.mjs';
|
|
23
27
|
import {
|
|
24
28
|
printSyncEslintConfigHelp,
|
|
25
29
|
runSyncEslintConfig,
|
|
@@ -79,6 +83,10 @@ const COMMANDS = {
|
|
|
79
83
|
printHelp: printSyncInstructionsHelp,
|
|
80
84
|
run: runSyncInstructions,
|
|
81
85
|
},
|
|
86
|
+
'sync-editorconfig': {
|
|
87
|
+
printHelp: printSyncEditorconfigHelp,
|
|
88
|
+
run: runSyncEditorconfig,
|
|
89
|
+
},
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
const DEFAULT_COMMAND = 'enforce-node-baseline';
|
|
@@ -127,6 +127,13 @@ export function runEnforceNodeBaseline(options) {
|
|
|
127
127
|
syncWorkspaceConfigArgs.push('--dry-run');
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
const syncEditorconfigArgs = ['sync-editorconfig', '--cwd', cwd];
|
|
131
|
+
if (check) {
|
|
132
|
+
syncEditorconfigArgs.push('--check');
|
|
133
|
+
} else if (dryRun) {
|
|
134
|
+
syncEditorconfigArgs.push('--dry-run');
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
const syncPrettierConfigArgs = ['sync-prettier-config', '--cwd', cwd];
|
|
131
138
|
if (check) {
|
|
132
139
|
syncPrettierConfigArgs.push('--check');
|
|
@@ -144,6 +151,7 @@ export function runEnforceNodeBaseline(options) {
|
|
|
144
151
|
const plan = [
|
|
145
152
|
{ name: 'sync-instructions', args: syncInstructionsArgs },
|
|
146
153
|
{ name: 'preflight', args: preflightArgs },
|
|
154
|
+
{ name: 'sync-editorconfig', args: syncEditorconfigArgs },
|
|
147
155
|
{ name: 'sync-prettier-config', args: syncPrettierConfigArgs },
|
|
148
156
|
{ name: 'sync-eslint-config', args: syncEslintConfigArgs },
|
|
149
157
|
{ name: 'sync-workspace-config', args: syncWorkspaceConfigArgs },
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Usage:
|
|
2
|
+
agent-toolkit sync-editorconfig [--cwd <dir>] [--check] [--dry-run]
|
|
3
|
+
[--json <file>]
|
|
4
|
+
|
|
5
|
+
Behavior:
|
|
6
|
+
- Applies organization-required root .editorconfig file
|
|
7
|
+
- Creates .editorconfig if missing, or merges missing required entries
|
|
8
|
+
if file already exists
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
- --check validates without writing and exits non-zero on mismatch
|
|
12
|
+
- --dry-run prints planned changes without writing
|
|
13
|
+
- --check takes precedence over --dry-run
|
|
@@ -0,0 +1,233 @@
|
|
|
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 EDITORCONFIG_FILE = '.editorconfig';
|
|
11
|
+
const TEMPLATE_FILE = path.resolve(COMMAND_DIR, 'editorconfig.template');
|
|
12
|
+
|
|
13
|
+
const REQUIRED_EDITORCONFIG_CONTENT = fs.readFileSync(TEMPLATE_FILE, 'utf8');
|
|
14
|
+
|
|
15
|
+
// Required key-value pairs for validation
|
|
16
|
+
const REQUIRED_SECTIONS = {
|
|
17
|
+
root: {
|
|
18
|
+
line: 'root = true',
|
|
19
|
+
},
|
|
20
|
+
'*': {
|
|
21
|
+
keys: {
|
|
22
|
+
charset: 'utf-8',
|
|
23
|
+
indent_style: 'space',
|
|
24
|
+
indent_size: '2',
|
|
25
|
+
trim_trailing_whitespace: 'true',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
'*.{yml,yaml}': {
|
|
29
|
+
keys: {
|
|
30
|
+
indent_style: 'space',
|
|
31
|
+
indent_size: '2',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
'*.md': {
|
|
35
|
+
keys: {
|
|
36
|
+
trim_trailing_whitespace: 'false',
|
|
37
|
+
max_line_length: '80',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function printSyncEditorconfigHelp() {
|
|
43
|
+
printTextResource(HELP_FILE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseEditorconfig(content) {
|
|
47
|
+
const sections = {};
|
|
48
|
+
let currentSection = null;
|
|
49
|
+
|
|
50
|
+
for (const line of content.split('\n')) {
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
|
|
53
|
+
// Skip empty lines and comments
|
|
54
|
+
if (trimmed === '' || trimmed.startsWith('#') || trimmed.startsWith(';')) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for section header
|
|
59
|
+
const sectionMatch = trimmed.match(/^\[(.+)\]$/);
|
|
60
|
+
if (sectionMatch) {
|
|
61
|
+
currentSection = sectionMatch[1];
|
|
62
|
+
sections[currentSection] = {};
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check for root = true
|
|
67
|
+
const rootMatch = trimmed.match(/^root\s*=\s*(.+)$/i);
|
|
68
|
+
if (rootMatch && !currentSection) {
|
|
69
|
+
sections._root = rootMatch[1].trim().toLowerCase();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Parse key-value pair
|
|
74
|
+
if (currentSection) {
|
|
75
|
+
const kvMatch = trimmed.match(/^([^=]+)\s*=\s*(.+)$/);
|
|
76
|
+
if (kvMatch) {
|
|
77
|
+
sections[currentSection][kvMatch[1].trim().toLowerCase()] = kvMatch[2].trim().toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return sections;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateEditorconfig(sections) {
|
|
86
|
+
const mismatches = [];
|
|
87
|
+
|
|
88
|
+
// Check root
|
|
89
|
+
if (sections._root !== 'true') {
|
|
90
|
+
mismatches.push({ section: '_root', expected: 'true', actual: sections._root || 'missing' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check each required section
|
|
94
|
+
for (const [sectionName, config] of Object.entries(REQUIRED_SECTIONS)) {
|
|
95
|
+
if (sectionName === 'root') continue;
|
|
96
|
+
|
|
97
|
+
if (!sections[sectionName]) {
|
|
98
|
+
mismatches.push({ section: `[${sectionName}]`, expected: 'present', actual: 'missing' });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (config.keys) {
|
|
103
|
+
for (const [key, expectedValue] of Object.entries(config.keys)) {
|
|
104
|
+
const actualValue = sections[sectionName][key];
|
|
105
|
+
if (actualValue !== expectedValue) {
|
|
106
|
+
mismatches.push({
|
|
107
|
+
section: `[${sectionName}]`,
|
|
108
|
+
key,
|
|
109
|
+
expected: expectedValue,
|
|
110
|
+
actual: actualValue || 'missing',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return mismatches;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildUpdatedContent(existingContent) {
|
|
121
|
+
const existingSections = parseEditorconfig(existingContent);
|
|
122
|
+
const lines = [];
|
|
123
|
+
|
|
124
|
+
// Add root if missing
|
|
125
|
+
if (existingSections._root !== 'true') {
|
|
126
|
+
lines.push('root = true');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Process each required section
|
|
130
|
+
for (const [sectionName, config] of Object.entries(REQUIRED_SECTIONS)) {
|
|
131
|
+
if (sectionName === 'root') continue;
|
|
132
|
+
|
|
133
|
+
const existingSection = existingSections[sectionName] || {};
|
|
134
|
+
const missingKeys = [];
|
|
135
|
+
|
|
136
|
+
if (config.keys) {
|
|
137
|
+
for (const [key, expectedValue] of Object.entries(config.keys)) {
|
|
138
|
+
if (existingSection[key] !== expectedValue) {
|
|
139
|
+
missingKeys.push({ key, value: expectedValue });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (missingKeys.length > 0 || !existingSections[sectionName]) {
|
|
145
|
+
lines.push('');
|
|
146
|
+
lines.push(`[${sectionName}]`);
|
|
147
|
+
for (const { key, value } of missingKeys) {
|
|
148
|
+
lines.push(`${key} = ${value}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If no updates needed, return original
|
|
154
|
+
// c8 ignore next 3
|
|
155
|
+
if (lines.length === 0) {
|
|
156
|
+
return existingContent;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Append missing entries to existing content
|
|
160
|
+
return existingContent.trimEnd() + lines.join('\n') + '\n';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readFileIfExists(filePath) {
|
|
164
|
+
if (!fs.existsSync(filePath)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function runSyncEditorconfig(options) {
|
|
172
|
+
const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
|
|
173
|
+
const check = hasFlag(options, '--check');
|
|
174
|
+
const dryRun = hasFlag(options, '--dry-run') && !check;
|
|
175
|
+
const jsonFile = getSingle(options, '--json', '');
|
|
176
|
+
const mode = check ? 'check' : dryRun ? 'dry-run' : 'sync';
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(cwd)) {
|
|
179
|
+
console.error(`CWD does not exist: ${cwd}`);
|
|
180
|
+
process.exit(2);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const editorconfigPath = path.resolve(cwd, EDITORCONFIG_FILE);
|
|
184
|
+
const currentContent = readFileIfExists(editorconfigPath);
|
|
185
|
+
const fileExists = currentContent !== null;
|
|
186
|
+
|
|
187
|
+
const sections = currentContent ? parseEditorconfig(currentContent) : {};
|
|
188
|
+
const mismatches = validateEditorconfig(sections);
|
|
189
|
+
const requiresUpdate = mismatches.length > 0 || !fileExists;
|
|
190
|
+
|
|
191
|
+
let plannedContent = null;
|
|
192
|
+
if (requiresUpdate) {
|
|
193
|
+
plannedContent = fileExists
|
|
194
|
+
? buildUpdatedContent(currentContent)
|
|
195
|
+
: REQUIRED_EDITORCONFIG_CONTENT;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (mode === 'sync' && requiresUpdate && plannedContent) {
|
|
199
|
+
fs.writeFileSync(editorconfigPath, plannedContent, 'utf8');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const report = {
|
|
203
|
+
cwd,
|
|
204
|
+
mode,
|
|
205
|
+
ok: true,
|
|
206
|
+
editorconfigPath,
|
|
207
|
+
required: {
|
|
208
|
+
file: EDITORCONFIG_FILE,
|
|
209
|
+
},
|
|
210
|
+
status: {
|
|
211
|
+
fileExistsBefore: fileExists,
|
|
212
|
+
mismatchesBefore: mismatches,
|
|
213
|
+
fileExistsAfter: requiresUpdate && mode === 'sync' ? true : fileExists,
|
|
214
|
+
mismatchesAfter: requiresUpdate && mode === 'sync' ? [] : mismatches,
|
|
215
|
+
updated: requiresUpdate && mode === 'sync',
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (mode === 'check' && requiresUpdate) {
|
|
220
|
+
report.ok = false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (jsonFile) {
|
|
224
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
225
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
226
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
230
|
+
if (!report.ok) {
|
|
231
|
+
process.exit(2);
|
|
232
|
+
}
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produck/agent-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"c8": "11.0.0"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "8618167ffe15e39c5a7b350903f0fd8861902a62"
|
|
36
36
|
}
|