@produck/agent-toolkit 0.2.1 → 0.4.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 +117 -8
- package/bin/agent-toolkit.mjs +28 -3
- package/bin/build-publish-assets.mjs +49 -0
- package/bin/command/enforce-node-baseline/help.txt +19 -0
- package/bin/command/enforce-node-baseline/index.mjs +150 -0
- package/bin/command/main/help.txt +7 -0
- package/bin/command/preflight/help.txt +6 -0
- package/bin/command/preflight/index.mjs +110 -21
- package/bin/command/sync-coverage-script/help.txt +22 -0
- package/bin/command/sync-coverage-script/index.mjs +275 -0
- package/bin/command/sync-husky-hooks/help.txt +22 -0
- package/bin/command/sync-husky-hooks/index.mjs +267 -0
- package/bin/command/validate-commit-msg/help.txt +1 -0
- package/bin/command/validate-commit-msg/index.mjs +31 -0
- package/package.json +7 -3
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +25 -27
- package/publish-assets/instructions/produck/10-produck-node.instructions.md +130 -26
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +53 -27
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +24 -11
- package/publish-assets/instructions/produck/tooling-version-baseline.json +32 -0
package/README.md
CHANGED
|
@@ -2,44 +2,152 @@
|
|
|
2
2
|
|
|
3
3
|
Central CLI toolkit for organization-level AI execution workflows.
|
|
4
4
|
|
|
5
|
+
## First-time bootstrap (downstream repositories)
|
|
6
|
+
|
|
7
|
+
For a new or existing downstream repository that has not yet applied the
|
|
8
|
+
organization baseline, run:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
npm create @produck/agent-toolkit@latest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This command installs `@produck/create-agent-toolkit` and runs
|
|
15
|
+
`enforce-node-baseline` in the current directory. No prior installation is
|
|
16
|
+
required — npm handles the download automatically.
|
|
17
|
+
|
|
18
|
+
What it does (in order):
|
|
19
|
+
|
|
20
|
+
1. Syncs organization AI instruction files into `.github/instructions/produck/`
|
|
21
|
+
2. Runs preflight to verify required files and directories
|
|
22
|
+
3. Deploys the pinned `produck:coverage` script and `c8` devDependency
|
|
23
|
+
4. Deploys `.husky/pre-commit` and `.husky/commit-msg`, and pins `c8`, `husky`,
|
|
24
|
+
`lerna`, `@produck/agent-toolkit` in root `devDependencies`
|
|
25
|
+
|
|
26
|
+
After running, add the persistent enforcement entry to the repository
|
|
27
|
+
`package.json`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
"produck:baseline": "npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd ."
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then future enforcement runs via:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
npm run produck:baseline
|
|
37
|
+
```
|
|
38
|
+
|
|
5
39
|
## Commands
|
|
6
40
|
|
|
41
|
+
- agent-toolkit enforce-node-baseline
|
|
7
42
|
- agent-toolkit preflight
|
|
8
43
|
- agent-toolkit run-capture
|
|
9
44
|
- agent-toolkit summarize-log
|
|
45
|
+
- agent-toolkit sync-coverage-script
|
|
46
|
+
- agent-toolkit sync-husky-hooks
|
|
10
47
|
- agent-toolkit validate-commit-msg
|
|
11
48
|
- agent-toolkit sync-instructions
|
|
12
49
|
|
|
13
50
|
## Examples
|
|
14
51
|
|
|
15
|
-
Run
|
|
52
|
+
Run default mandatory baseline flow in downstream repository root:
|
|
16
53
|
|
|
17
54
|
```
|
|
18
|
-
npm exec --
|
|
55
|
+
npm exec -- agent-toolkit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Equivalent explicit form:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
npm exec -- agent-toolkit enforce-node-baseline --cwd .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`enforce-node-baseline` runs four steps in fixed order and stops at the first
|
|
65
|
+
failure:
|
|
66
|
+
|
|
67
|
+
1. `sync-instructions` — distribute organization AI instruction files into
|
|
68
|
+
`.github/instructions/produck/`
|
|
69
|
+
2. `preflight` — verify required files and directories exist
|
|
70
|
+
3. `sync-coverage-script` — deploy pinned `produck:coverage` script and `c8`
|
|
71
|
+
devDependency into each workspace package
|
|
72
|
+
4. `sync-husky-hooks` — deploy `.husky/pre-commit` and `.husky/commit-msg`,
|
|
73
|
+
and pin `c8`, `husky`, `lerna`, `@produck/agent-toolkit` in root
|
|
74
|
+
`devDependencies`
|
|
75
|
+
|
|
76
|
+
Add to downstream repository root `package.json` for one-command enforcement:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
"produck:baseline": "npm exec --package=@produck/agent-toolkit@latest -- agent-toolkit enforce-node-baseline --cwd ."
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then run:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
npm run produck:baseline
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Dry-run to preview changes without writing files:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
npm exec -- agent-toolkit enforce-node-baseline --cwd . --dry-run
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Check-only mode to validate without writing:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
npm exec -- agent-toolkit enforce-node-baseline --cwd . --check
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Validate monorepo root `package.json` scripts and workspace structure:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
npm exec -- agent-toolkit preflight --cwd . --check-workspace-package-json package.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Run preflight with required-file and directory guards:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
npm exec -- agent-toolkit preflight --cwd . --require package.json --ensure-dir logs
|
|
19
110
|
```
|
|
20
111
|
|
|
21
112
|
Capture long output safely:
|
|
22
113
|
|
|
23
114
|
```
|
|
24
|
-
npm exec --
|
|
115
|
+
npm exec -- agent-toolkit run-capture --cwd . --cmd "npm run test" --out logs/test.log
|
|
25
116
|
```
|
|
26
117
|
|
|
27
118
|
Summarize captured output:
|
|
28
119
|
|
|
29
120
|
```
|
|
30
|
-
npm exec --
|
|
121
|
+
npm exec -- agent-toolkit summarize-log --file logs/test.log --match "FAIL|ERROR"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Deploy organization coverage script and pinned local c8 devDependency to
|
|
125
|
+
workspace packages:
|
|
126
|
+
|
|
31
127
|
```
|
|
128
|
+
npm exec -- agent-toolkit sync-coverage-script --cwd .
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Deploy organization local anti-drift husky hooks to repository root:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
npm exec -- agent-toolkit sync-husky-hooks --cwd .
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
This command pins root local hook dependencies (`c8`, `husky`, `lerna`, and
|
|
138
|
+
`@produck/agent-toolkit`) to organization baseline fixed versions and syncs
|
|
139
|
+
`.husky/pre-commit` and `.husky/commit-msg`.
|
|
32
140
|
|
|
33
141
|
Validate commit message format:
|
|
34
142
|
|
|
35
143
|
```
|
|
36
|
-
npm exec --
|
|
144
|
+
npm exec -- agent-toolkit validate-commit-msg --file .git/COMMIT_EDITMSG
|
|
37
145
|
```
|
|
38
146
|
|
|
39
147
|
Manual per-repository instruction distribution (write .github/instructions/produck/\*.instructions.md):
|
|
40
148
|
|
|
41
149
|
```
|
|
42
|
-
npm exec --
|
|
150
|
+
npm exec -- agent-toolkit sync-instructions --cwd .
|
|
43
151
|
```
|
|
44
152
|
|
|
45
153
|
Legacy repository bootstrap behavior:
|
|
@@ -52,7 +160,7 @@ Legacy repository bootstrap behavior:
|
|
|
52
160
|
Use organization source directory instead of built-in assets:
|
|
53
161
|
|
|
54
162
|
```
|
|
55
|
-
npm exec --
|
|
163
|
+
npm exec -- agent-toolkit sync-instructions --cwd . --source path/to/org/.github/distribution/produck --force --prune
|
|
56
164
|
```
|
|
57
165
|
|
|
58
166
|
Built-in command-local resource locations (for review and updates):
|
|
@@ -118,7 +226,8 @@ Workflow behavior:
|
|
|
118
226
|
|
|
119
227
|
Release policy:
|
|
120
228
|
|
|
121
|
-
-
|
|
229
|
+
- Central package is installed locally in downstream repositories at a fixed
|
|
230
|
+
version managed by `agent-toolkit sync-husky-hooks`.
|
|
122
231
|
- Run format:check and test first, then workspace `publish:dry-run` before
|
|
123
232
|
`publish`.
|
|
124
233
|
- Keep rollback option by republishing previous stable version if needed.
|
package/bin/agent-toolkit.mjs
CHANGED
|
@@ -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 (
|
|
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(
|
|
84
|
+
printCommandHelp(effectiveCommand);
|
|
60
85
|
process.exit(0);
|
|
61
86
|
}
|
|
62
87
|
|
|
63
|
-
const entry = COMMANDS[
|
|
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,150 @@
|
|
|
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 = ['preflight', '--cwd', cwd, '--require', 'package.json'];
|
|
102
|
+
if (mode !== 'sync') {
|
|
103
|
+
preflightArgs.push('--check-workspace-package-json', 'package.json');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const syncCoverageArgs = ['sync-coverage-script', '--cwd', cwd];
|
|
107
|
+
for (const workspacePath of workspaces) {
|
|
108
|
+
syncCoverageArgs.push('--workspace', workspacePath);
|
|
109
|
+
}
|
|
110
|
+
if (check) {
|
|
111
|
+
syncCoverageArgs.push('--check');
|
|
112
|
+
} else if (dryRun) {
|
|
113
|
+
syncCoverageArgs.push('--dry-run');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const syncHuskyArgs = ['sync-husky-hooks', '--cwd', cwd];
|
|
117
|
+
if (check) {
|
|
118
|
+
syncHuskyArgs.push('--check');
|
|
119
|
+
} else if (dryRun) {
|
|
120
|
+
syncHuskyArgs.push('--dry-run');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const plan = [
|
|
124
|
+
{ name: 'sync-instructions', args: syncInstructionsArgs },
|
|
125
|
+
{ name: 'preflight', args: preflightArgs },
|
|
126
|
+
{ name: 'sync-coverage-script', args: syncCoverageArgs },
|
|
127
|
+
{ name: 'sync-husky-hooks', args: syncHuskyArgs },
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
for (const step of plan) {
|
|
131
|
+
const stepResult = runToolkitSubcommand(cwd, step.args);
|
|
132
|
+
report.steps.push(buildStepReport(step.name, stepResult));
|
|
133
|
+
|
|
134
|
+
if (!stepResult.ok) {
|
|
135
|
+
report.ok = false;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (jsonFile) {
|
|
141
|
+
const outPath = path.resolve(cwd, jsonFile);
|
|
142
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
143
|
+
fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
147
|
+
if (!report.ok) {
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -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 {
|
|
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
|
|
18
|
-
const
|
|
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
|
-
|
|
29
|
-
ensuredDirectories: [],
|
|
94
|
+
dryRun,
|
|
30
95
|
ok: true,
|
|
96
|
+
required: [],
|
|
97
|
+
ensuredDirs: [],
|
|
98
|
+
workspacePackageJson: null,
|
|
31
99
|
};
|
|
32
100
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
43
|
-
const
|
|
44
|
-
fs.
|
|
45
|
-
|
|
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
|
|
50
|
-
fs.mkdirSync(path.dirname(
|
|
51
|
-
fs.writeFileSync(
|
|
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`);
|