@produck/agent-toolkit 0.2.0 → 0.2.1
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 +3 -3
- package/bin/agent-toolkit.mjs +50 -653
- package/bin/command/main/index.mjs +11 -0
- package/bin/command/preflight/index.mjs +58 -0
- package/bin/command/run-capture/index.mjs +100 -0
- package/bin/command/shared/args.mjs +45 -0
- package/bin/command/shared/text-resource.mjs +19 -0
- package/bin/command/summarize-log/index.mjs +64 -0
- package/bin/command/sync-instructions/index.mjs +272 -0
- package/bin/command/validate-commit-msg/help.txt +8 -0
- package/bin/command/validate-commit-msg/index.mjs +152 -0
- package/package.json +5 -3
- package/publish-assets/instructions/produck/00-produck-base.instructions.md +12 -12
- package/publish-assets/instructions/produck/15-produck-workspace.instructions.md +6 -0
- package/publish-assets/instructions/produck/20-produck-commit.instructions.md +3 -0
- package/templates/help/validate-commit-msg.txt +0 -7
- /package/{templates/help/main.txt → bin/command/main/help.txt} +0 -0
- /package/{templates/help/preflight.txt → bin/command/preflight/help.txt} +0 -0
- /package/{templates/help/run-capture.txt → bin/command/run-capture/help.txt} +0 -0
- /package/{templates/help/summarize-log.txt → bin/command/summarize-log/help.txt} +0 -0
- /package/{templates/help/sync-instructions.txt → bin/command/sync-instructions/help.txt} +0 -0
- /package/{templates → bin/command/sync-instructions}/user-space-bootstrap.md +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getSingle } from '../shared/args.mjs';
|
|
6
|
+
import { printTextResource } from '../shared/text-resource.mjs';
|
|
7
|
+
|
|
8
|
+
const ALLOWED_TAGS = ['INIT', 'ADD', 'REMOVE', 'FIX', 'REFACTOR', 'UPGRADE', 'PUBLISH'];
|
|
9
|
+
const ALLOWED_TARGETS = ['docs', 'test', 'ci', 'deps', 'api', 'schema', 'infra', 'fmt'];
|
|
10
|
+
const SECTION_HEADER_RE = /^(?:@[\w.-]+\/)?[\w.-]+:$/;
|
|
11
|
+
const COMMAND_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const HELP_FILE = path.resolve(COMMAND_DIR, 'help.txt');
|
|
13
|
+
|
|
14
|
+
export function printValidateCommitMsgHelp() {
|
|
15
|
+
printTextResource(HELP_FILE);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validateCommitLine(line, lineNo) {
|
|
19
|
+
if (line.trim() === '') {
|
|
20
|
+
return `Line ${lineNo}: empty line is not allowed`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const head = line.match(/^\[([A-Z]+)\]\s+/);
|
|
24
|
+
if (!head) {
|
|
25
|
+
return `Line ${lineNo}: must start with [TAG] followed by a space`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tag = head[1];
|
|
29
|
+
if (!ALLOWED_TAGS.includes(tag)) {
|
|
30
|
+
return `Line ${lineNo}: tag [${tag}] is not allowed`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rest = line.slice(head[0].length);
|
|
34
|
+
if (rest.trim() === '') {
|
|
35
|
+
return `Line ${lineNo}: summary is required after tag`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targetMatch = rest.match(/^<([^>]+)>:\s+(.+)$/);
|
|
39
|
+
if (targetMatch) {
|
|
40
|
+
const target = targetMatch[1];
|
|
41
|
+
const summary = targetMatch[2];
|
|
42
|
+
if (!ALLOWED_TARGETS.includes(target)) {
|
|
43
|
+
return `Line ${lineNo}: target <${target}> is not allowed`;
|
|
44
|
+
}
|
|
45
|
+
if (summary.trim() === '') {
|
|
46
|
+
return `Line ${lineNo}: summary is required after target`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isSectionHeaderLine(line) {
|
|
54
|
+
return SECTION_HEADER_RE.test(line.trim());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validateSectionFormat(lines) {
|
|
58
|
+
const errors = [];
|
|
59
|
+
let currentSection = '';
|
|
60
|
+
let currentSectionLineNo = 0;
|
|
61
|
+
let currentSectionHasTaggedLine = false;
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
64
|
+
const lineNo = i + 1;
|
|
65
|
+
const line = lines[i];
|
|
66
|
+
|
|
67
|
+
if (line.trim() === '') {
|
|
68
|
+
errors.push(`Line ${lineNo}: empty line is not allowed`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (isSectionHeaderLine(line)) {
|
|
73
|
+
if (currentSection && !currentSectionHasTaggedLine) {
|
|
74
|
+
errors.push(
|
|
75
|
+
`Line ${currentSectionLineNo}: section header "${currentSection}" must be followed by at least one tagged line`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
currentSection = line.trim();
|
|
80
|
+
currentSectionLineNo = lineNo;
|
|
81
|
+
currentSectionHasTaggedLine = false;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!currentSection) {
|
|
86
|
+
errors.push(
|
|
87
|
+
`Line ${lineNo}: section header is required before tagged lines when package/workspace sections are used`,
|
|
88
|
+
);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const err = validateCommitLine(line, lineNo);
|
|
93
|
+
if (err) {
|
|
94
|
+
errors.push(err);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
currentSectionHasTaggedLine = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (currentSection && !currentSectionHasTaggedLine) {
|
|
102
|
+
errors.push(
|
|
103
|
+
`Line ${currentSectionLineNo}: section header "${currentSection}" must be followed by at least one tagged line`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return errors;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function runValidateCommitMsg(options) {
|
|
111
|
+
const file = getSingle(options, '--file', '');
|
|
112
|
+
if (!file) {
|
|
113
|
+
printValidateCommitMsgHelp();
|
|
114
|
+
process.exit(2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const filePath = path.resolve(file);
|
|
118
|
+
if (!fs.existsSync(filePath)) {
|
|
119
|
+
console.error(`Message file not found: ${filePath}`);
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const raw = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
124
|
+
const lines = raw.endsWith('\n') ? raw.slice(0, -1).split('\n') : raw.split('\n');
|
|
125
|
+
|
|
126
|
+
if (lines.length === 0 || (lines.length === 1 && lines[0].trim() === '')) {
|
|
127
|
+
console.error('Commit message is empty');
|
|
128
|
+
process.exit(2);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const hasSectionHeaders = lines.some((line) => isSectionHeaderLine(line));
|
|
132
|
+
|
|
133
|
+
const errors = hasSectionHeaders ? validateSectionFormat(lines) : [];
|
|
134
|
+
if (!hasSectionHeaders) {
|
|
135
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
136
|
+
const err = validateCommitLine(lines[i], i + 1);
|
|
137
|
+
if (err) {
|
|
138
|
+
errors.push(err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (errors.length > 0) {
|
|
144
|
+
console.error('Commit message validation failed:');
|
|
145
|
+
for (const err of errors) {
|
|
146
|
+
console.error(`- ${err}`);
|
|
147
|
+
}
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('Commit message validation passed');
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produck/agent-toolkit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Central CLI toolkit for organization AI execution workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,12 +13,14 @@
|
|
|
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
|
+
"test": "node --test test/index.mjs",
|
|
16
19
|
"verify": "node ./bin/agent-toolkit.mjs --help && node ./bin/agent-toolkit.mjs preflight --cwd . --require package.json",
|
|
17
20
|
"pack:check": "npm pack --dry-run"
|
|
18
21
|
},
|
|
19
22
|
"files": [
|
|
20
23
|
"bin",
|
|
21
|
-
"templates",
|
|
22
24
|
"publish-assets"
|
|
23
25
|
],
|
|
24
26
|
"publishConfig": {
|
|
@@ -28,5 +30,5 @@
|
|
|
28
30
|
"node": ">=18.0.0"
|
|
29
31
|
},
|
|
30
32
|
"license": "MIT",
|
|
31
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "555d39b0f0ed95f59d457230f784c9dafe7b84d2"
|
|
32
34
|
}
|
|
@@ -118,8 +118,11 @@ max_line_length = 80
|
|
|
118
118
|
be present in repository lint configuration.
|
|
119
119
|
- Apply minimal patching only: keep existing repository/framework structure and
|
|
120
120
|
add the smallest necessary changes.
|
|
121
|
-
- Repository-specific overrides are
|
|
122
|
-
|
|
121
|
+
- Repository-specific overrides are optional and should be added only when
|
|
122
|
+
behavior intentionally differs from shared presets.
|
|
123
|
+
- In ESLint flat config, "layer on top" means local override items must appear
|
|
124
|
+
after shared preset items in exported order.
|
|
125
|
+
- No-op overrides that repeat inherited values should be avoided.
|
|
123
126
|
|
|
124
127
|
## Language conventions
|
|
125
128
|
|
|
@@ -144,16 +147,10 @@ max_line_length = 80
|
|
|
144
147
|
- Recommended local validation:
|
|
145
148
|
`npm exec --package=@produck/agent-toolkit@latest agent-toolkit
|
|
146
149
|
validate-commit-msg --file <message-file>`.
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
- To express content domain, summary may use target syntax: `[TAG] <target>:
|
|
152
|
-
<summary>`.
|
|
153
|
-
- Allowed targets are `docs`, `test`, `ci`, `deps`, `api`, `schema`, and
|
|
154
|
-
`infra`.
|
|
155
|
-
- If target syntax is used, target must be wrapped in angle brackets and must be
|
|
156
|
-
from the allowed target list.
|
|
150
|
+
- Canonical source for commit tag/target whitelists, legacy mapping, and
|
|
151
|
+
target syntax is
|
|
152
|
+
`.github/distribution/produck/20-produck-commit.instructions.md`.
|
|
153
|
+
- Do not redefine commit tag or target whitelists in other instruction files.
|
|
157
154
|
- For non-monorepo repositories, use `[TAG] summary` directly (no
|
|
158
155
|
package/workspace section headers).
|
|
159
156
|
- Bracketed commit summaries should be in English
|
|
@@ -247,6 +244,9 @@ When CI enforcement is deferred, use manual sync per repository:
|
|
|
247
244
|
`.github/instructions/produck/`.
|
|
248
245
|
2. Keep repository-specific exceptions in `.github/copilot-instructions.md`.
|
|
249
246
|
3. Validate critical policies manually in each update cycle.
|
|
247
|
+
4. After instruction sync, validate duplicated policy sections remain
|
|
248
|
+
consistent across instruction files, especially commit tag and target
|
|
249
|
+
whitelists.
|
|
250
250
|
|
|
251
251
|
Recommended command:
|
|
252
252
|
|
|
@@ -34,6 +34,12 @@ This document is maintained directly as a downstream-distributable source.
|
|
|
34
34
|
- Semicolons: Always required (error)
|
|
35
35
|
- Trailing commas: Always in multiline (error)
|
|
36
36
|
- No inline config allowed: `noInlineConfig: true`
|
|
37
|
+
- The listed ESLint rules can be satisfied either by explicit local
|
|
38
|
+
declarations or by inherited shared presets.
|
|
39
|
+
- Repositories are not required to redeclare rules locally when those rules are
|
|
40
|
+
already provided by inherited presets.
|
|
41
|
+
- If a repository overrides inherited rules, include only the deltas and
|
|
42
|
+
document the rationale.
|
|
37
43
|
|
|
38
44
|
**Usage in packages:**
|
|
39
45
|
|
|
@@ -163,6 +163,9 @@ Avoid vague or low-signal messages such as:
|
|
|
163
163
|
|
|
164
164
|
Use the local validator before commit:
|
|
165
165
|
|
|
166
|
+
- Validation is required before both `git commit` and `git commit --amend`.
|
|
167
|
+
- Do not create or amend a commit when validation fails.
|
|
168
|
+
|
|
166
169
|
- `npm exec --package=@produck/agent-toolkit@latest agent-toolkit
|
|
167
170
|
validate-commit-msg --file <message-file>`
|
|
168
171
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|