@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.
@@ -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.0",
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": "8fbe9d26ada86a17e4f0444c2342e3904ba0f7f2"
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 allowed, but should layer on top of
122
- `@produck/eslint-rules` instead of bypassing it.
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
- - Use uppercase tags from this whitelist: `[INIT]`, `[ADD]`, `[REMOVE]`,
148
- `[FIX]`, `[REFACTOR]`, `[UPGRADE]`.
149
- - Legacy tag mapping for migration is `[ADDED]` -> `[ADD]`, `[REMOVED]` ->
150
- `[REMOVE]`, and `[FIXED]` -> `[FIX]`.
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
 
@@ -1,7 +0,0 @@
1
- Usage:
2
- agent-toolkit validate-commit-msg --file <message-file>
3
-
4
- Rules:
5
- - Every line must start with [TAG]
6
- - No empty lines are allowed
7
- - Optional target form: [TAG] <target>: <summary>