@produck/agent-toolkit 0.7.0 → 0.8.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.
@@ -12,8 +12,10 @@ const SOURCE_TOOLING_BASELINE_PATH = path.resolve(SOURCE_DIR, 'tooling-version-b
12
12
  const OUTPUT_TOOLING_BASELINE_PATH = path.resolve(OUTPUT_DIR, 'tooling-version-baseline.json');
13
13
  const SOURCE_GITATTRIBUTES_PATH = path.resolve(REPO_ROOT, '.gitattributes');
14
14
  const SOURCE_GITIGNORE_PATH = path.resolve(REPO_ROOT, '.gitignore');
15
+ const SOURCE_PRETTIERIGNORE_PATH = path.resolve(REPO_ROOT, '.prettierignore');
15
16
  const OUTPUT_GITATTRIBUTES_PATH = path.resolve(PACKAGE_ROOT, 'publish-assets/gitattributes');
16
17
  const OUTPUT_GITIGNORE_PATH = path.resolve(PACKAGE_ROOT, 'publish-assets/gitignore');
18
+ const OUTPUT_PRETTIERIGNORE_PATH = path.resolve(PACKAGE_ROOT, 'publish-assets/prettierignore');
17
19
  const LEGACY_OUTPUT_PATH = path.resolve(
18
20
  PACKAGE_ROOT,
19
21
  'publish-assets/instructions/org.instructions.md',
@@ -77,12 +79,19 @@ function readAndValidateToolingBaseline() {
77
79
  throw new Error(`Invalid coverage.scriptTemplate in: ${SOURCE_TOOLING_BASELINE_PATH}`);
78
80
  }
79
81
 
80
- if (!coverageScriptTemplate.includes('{c8.version}')) {
81
- throw new Error(
82
- `coverage.scriptTemplate must include {c8.version} in: ${SOURCE_TOOLING_BASELINE_PATH}`,
83
- );
82
+ const eslintRulesPkgPath = path.resolve(PACKAGE_ROOT, '../eslint-rules/package.json');
83
+ if (fs.existsSync(eslintRulesPkgPath)) {
84
+ const eslintRulesPkg = JSON.parse(fs.readFileSync(eslintRulesPkgPath, 'utf8'));
85
+ const version = eslintRulesPkg.version;
86
+ if (typeof version === 'string' && version.trim()) {
87
+ if (!baseline.tools) baseline.tools = {};
88
+ baseline.tools['@produck/eslint-rules'] = {
89
+ version,
90
+ policy: 'pinned',
91
+ allowLatest: false,
92
+ };
93
+ }
84
94
  }
85
-
86
95
  return `${JSON.stringify(baseline, null, 2)}\n`;
87
96
  }
88
97
 
@@ -154,6 +163,9 @@ function run() {
154
163
  if (!fs.existsSync(SOURCE_GITIGNORE_PATH)) {
155
164
  throw new Error(`Missing source .gitignore: ${SOURCE_GITIGNORE_PATH}`);
156
165
  }
166
+ if (!fs.existsSync(SOURCE_PRETTIERIGNORE_PATH)) {
167
+ throw new Error(`Missing source .prettierignore: ${SOURCE_PRETTIERIGNORE_PATH}`);
168
+ }
157
169
 
158
170
  fs.writeFileSync(
159
171
  OUTPUT_GITATTRIBUTES_PATH,
@@ -171,6 +183,15 @@ function run() {
171
183
  );
172
184
  process.stdout.write(`Generated ${OUTPUT_GITIGNORE_PATH} from ${SOURCE_GITIGNORE_PATH}\n`);
173
185
 
186
+ fs.writeFileSync(
187
+ OUTPUT_PRETTIERIGNORE_PATH,
188
+ normalize(fs.readFileSync(SOURCE_PRETTIERIGNORE_PATH, 'utf8')),
189
+ 'utf8',
190
+ );
191
+ process.stdout.write(
192
+ `Generated ${OUTPUT_PRETTIERIGNORE_PATH} from ${SOURCE_PRETTIERIGNORE_PATH}\n`,
193
+ );
194
+
174
195
  cleanStaleManagedFiles(expectedNames);
175
196
 
176
197
  if (fs.existsSync(LEGACY_OUTPUT_PATH)) {
@@ -4,9 +4,11 @@ Usage:
4
4
 
5
5
  Behavior:
6
6
  - Applies organization-required root format script:
7
- - scripts.produck:format = prettier --write .
7
+ - scripts.produck:format = prettier --write . --ignore-path .prettierignore --ignore-path .gitignore
8
8
  - Applies organization-required root Prettier config file:
9
9
  - .prettierrc
10
+ - Applies organization-required root Prettier ignore file:
11
+ - .prettierignore (prettier-specific patterns; .gitignore is also used via --ignore-path)
10
12
 
11
13
  Rules:
12
14
  - --check validates without writing and exits non-zero on mismatch
@@ -14,10 +14,17 @@ const TOOLING_BASELINE_CANDIDATE_PATHS = [
14
14
  path.resolve(PACKAGE_ROOT, 'publish-assets/instructions/produck/tooling-version-baseline.json'),
15
15
  ];
16
16
  const PRETTIER_CONFIG_FILE = '.prettierrc';
17
+ const PRETTIER_IGNORE_FILE = '.prettierignore';
17
18
  const REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY = 'prettier';
18
19
 
20
+ const PRETTIER_IGNORE_SOURCE_CANDIDATE_PATHS = [
21
+ path.resolve(REPO_ROOT, '.prettierignore'),
22
+ path.resolve(PACKAGE_ROOT, 'publish-assets/prettierignore'),
23
+ ];
24
+
19
25
  const REQUIRED_FORMAT_SCRIPT_KEY = 'produck:format';
20
- const REQUIRED_FORMAT_SCRIPT_VALUE = 'prettier --write .';
26
+ const REQUIRED_FORMAT_SCRIPT_VALUE =
27
+ 'prettier --write . --ignore-path .prettierignore --ignore-path .gitignore';
21
28
  const REQUIRED_PRETTIER_CONFIG = `${JSON.stringify(
22
29
  {
23
30
  semi: true,
@@ -33,6 +40,23 @@ const REQUIRED_PRETTIER_CONFIG = `${JSON.stringify(
33
40
  2,
34
41
  )}\n`;
35
42
 
43
+ function loadPrettierIgnoreContent() {
44
+ const sourcePath = PRETTIER_IGNORE_SOURCE_CANDIDATE_PATHS.find((p) => fs.existsSync(p));
45
+
46
+ if (!sourcePath) {
47
+ console.error('Org .prettierignore source not found in expected locations:');
48
+ for (const p of PRETTIER_IGNORE_SOURCE_CANDIDATE_PATHS) {
49
+ console.error(`- ${p}`);
50
+ }
51
+ process.exit(2);
52
+ }
53
+
54
+ return {
55
+ sourcePath,
56
+ content: fs.readFileSync(sourcePath, 'utf8'),
57
+ };
58
+ }
59
+
36
60
  export function printSyncFormatHelp() {
37
61
  printTextResource(HELP_FILE);
38
62
  }
@@ -101,6 +125,8 @@ export function runSyncFormat(options) {
101
125
  const pkg = parseJsonFile(rootPackageJsonPath, 'Root package.json');
102
126
  const toolingBaseline = loadToolingBaseline();
103
127
  const requiredPrettierVersion = toolingBaseline.prettierVersion;
128
+ const { sourcePath: prettierIgnoreSourcePath, content: REQUIRED_PRETTIER_IGNORE_CONTENT } =
129
+ loadPrettierIgnoreContent();
104
130
  const scripts =
105
131
  pkg.scripts && typeof pkg.scripts === 'object' && !Array.isArray(pkg.scripts)
106
132
  ? { ...pkg.scripts }
@@ -122,14 +148,20 @@ export function runSyncFormat(options) {
122
148
  : null;
123
149
 
124
150
  const prettierConfigPath = path.resolve(cwd, PRETTIER_CONFIG_FILE);
151
+ const prettierIgnorePath = path.resolve(cwd, PRETTIER_IGNORE_FILE);
125
152
  const previousPrettierConfig = readFileIfExists(prettierConfigPath);
153
+ const previousPrettierIgnore = readFileIfExists(prettierIgnorePath);
126
154
 
127
155
  const matchesRequiredFormat = previousFormat === REQUIRED_FORMAT_SCRIPT_VALUE;
128
156
  const matchesRequiredPrettierConfig = previousPrettierConfig === REQUIRED_PRETTIER_CONFIG;
129
157
  const matchesRequiredPrettierDep = previousPrettierDep === requiredPrettierVersion;
158
+ const matchesRequiredPrettierIgnore = previousPrettierIgnore === REQUIRED_PRETTIER_IGNORE_CONTENT;
130
159
 
131
160
  const requiresUpdate =
132
- !matchesRequiredFormat || !matchesRequiredPrettierConfig || !matchesRequiredPrettierDep;
161
+ !matchesRequiredFormat ||
162
+ !matchesRequiredPrettierConfig ||
163
+ !matchesRequiredPrettierDep ||
164
+ !matchesRequiredPrettierIgnore;
133
165
 
134
166
  if (mode === 'sync' && requiresUpdate) {
135
167
  scripts[REQUIRED_FORMAT_SCRIPT_KEY] = REQUIRED_FORMAT_SCRIPT_VALUE;
@@ -140,6 +172,7 @@ export function runSyncFormat(options) {
140
172
 
141
173
  fs.writeFileSync(rootPackageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8');
142
174
  fs.writeFileSync(prettierConfigPath, REQUIRED_PRETTIER_CONFIG, 'utf8');
175
+ fs.writeFileSync(prettierIgnorePath, REQUIRED_PRETTIER_IGNORE_CONTENT, 'utf8');
143
176
  }
144
177
 
145
178
  const report = {
@@ -152,17 +185,22 @@ export function runSyncFormat(options) {
152
185
  formatScriptKey: REQUIRED_FORMAT_SCRIPT_KEY,
153
186
  formatScriptValue: REQUIRED_FORMAT_SCRIPT_VALUE,
154
187
  prettierConfigPath: path.relative(cwd, prettierConfigPath),
188
+ prettierIgnorePath: path.relative(cwd, prettierIgnorePath),
189
+ prettierIgnoreSourcePath,
155
190
  managedDevDependencies: { [REQUIRED_PRETTIER_DEV_DEPENDENCY_KEY]: requiredPrettierVersion },
156
191
  },
157
192
  status: {
158
193
  matchesRequiredFormatBefore: matchesRequiredFormat,
159
194
  matchesRequiredPrettierConfigBefore: matchesRequiredPrettierConfig,
160
195
  matchesRequiredPrettierDepBefore: matchesRequiredPrettierDep,
196
+ matchesRequiredPrettierIgnoreBefore: matchesRequiredPrettierIgnore,
161
197
  matchesRequiredFormatAfter: requiresUpdate && mode === 'sync' ? true : matchesRequiredFormat,
162
198
  matchesRequiredPrettierConfigAfter:
163
199
  requiresUpdate && mode === 'sync' ? true : matchesRequiredPrettierConfig,
164
200
  matchesRequiredPrettierDepAfter:
165
201
  requiresUpdate && mode === 'sync' ? true : matchesRequiredPrettierDep,
202
+ matchesRequiredPrettierIgnoreAfter:
203
+ requiresUpdate && mode === 'sync' ? true : matchesRequiredPrettierIgnore,
166
204
  updated: requiresUpdate && mode === 'sync',
167
205
  },
168
206
  };
@@ -88,7 +88,7 @@ export function runSyncInstructions(options) {
88
88
  const cwd = path.resolve(getSingle(options, '--cwd', process.cwd()));
89
89
  const outArg = getSingle(options, '--out', DEFAULT_NAMESPACE_OUT_DIR);
90
90
  const sourceArg = getSingle(options, '--source', '');
91
- const force = hasFlag(options, '--force');
91
+ const force = true;
92
92
  const dryRun = hasFlag(options, '--dry-run');
93
93
  const prune = hasFlag(options, '--prune');
94
94
 
@@ -146,15 +146,6 @@ export function runSyncInstructions(options) {
146
146
  if (outLooksLikeFile) {
147
147
  const entry = entries[0];
148
148
  const exists = fs.existsSync(outPath);
149
- if (exists && !force) {
150
- const current = fs.readFileSync(outPath, 'utf8');
151
- if (current !== entry.content) {
152
- console.error(`Target already exists: ${outPath}`);
153
- console.error('Use --force to overwrite.');
154
- process.exit(2);
155
- }
156
- }
157
-
158
149
  const report = {
159
150
  mode: 'single-file',
160
151
  cwd,
@@ -162,18 +153,16 @@ export function runSyncInstructions(options) {
162
153
  source: sourceResolved,
163
154
  outPath,
164
155
  exists,
165
- overwritten: exists && force,
156
+ overwritten: exists,
166
157
  dryRun,
167
158
  prune: false,
168
159
  initializedUserSpaceEntry: false,
169
160
  userSpaceEntryPath: null,
170
161
  };
171
-
172
162
  if (dryRun) {
173
163
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
174
164
  process.exit(0);
175
165
  }
176
-
177
166
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
178
167
  fs.writeFileSync(outPath, entry.content, 'utf8');
179
168
  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
@@ -202,15 +191,6 @@ export function runSyncInstructions(options) {
202
191
  }
203
192
 
204
193
  const toWrite = planned.filter((item) => !unchanged.includes(item.targetPath));
205
- const conflicts = toWrite.filter((item) => fs.existsSync(item.targetPath));
206
- if (conflicts.length > 0 && !force) {
207
- console.error('Some target files already exist and would change:');
208
- for (const item of conflicts) {
209
- console.error(`- ${item.targetPath}`);
210
- }
211
- console.error('Use --force to overwrite.');
212
- process.exit(2);
213
- }
214
194
 
215
195
  const pruneDeletes = [];
216
196
  if (prune && fs.existsSync(outDir)) {
@@ -11,7 +11,7 @@ const LERNA_CONFIG_FILE = 'lerna.json';
11
11
 
12
12
  const REQUIRED_PUBLISH_CHECK_SCRIPT_KEY = 'produck:publish:check';
13
13
  const REQUIRED_PUBLISH_CHECK_SCRIPT_VALUE =
14
- 'npm run produck:format && npm run produck:lint && npm run produck:coverage';
14
+ 'npm run produck:install && npm run produck:format && npm run produck:lint && npm run produck:coverage';
15
15
  const USER_PUBLISH_SCRIPT_KEY = 'publish';
16
16
  const REQUIRED_PUBLISH_SCRIPT_KEY = 'produck:publish';
17
17
  const REQUIRED_PUBLISH_SCRIPT_FALLBACK_VALUE = 'npm run produck:publish:check && lerna publish';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produck/agent-toolkit",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Central CLI toolkit for organization AI execution workflows",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,5 +30,5 @@
30
30
  "devDependencies": {
31
31
  "c8": "11.0.0"
32
32
  },
33
- "gitHead": "61467ec369911fde65adc21bcf0c507c24735d90"
33
+ "gitHead": "a9e160249f664b28fa5971246e61d9e641c16586"
34
34
  }
@@ -18,20 +18,26 @@ in the `produck` organization.
18
18
 
19
19
  ## Instruction source split
20
20
 
21
- To separate organization-only governance from downstream-distributable
22
- baseline, policy sources are split as follows:
23
-
24
- - Downstream-distributable source:
25
- `.github/distribution/produck/*.instructions.md`
26
- - Organization-only source (not distributed):
27
- `.github/instructions/produck/*.instructions.md`
28
-
29
- Editing rule:
21
+ Path semantics differ between the upstream policy repository and downstream
22
+ repositories:
23
+
24
+ - Upstream policy repository (this `produck/.github` repo):
25
+ - `.github/distribution/produck/*.instructions.md` — canonical baseline,
26
+ distributed to downstream repositories.
27
+ - `.github/instructions/produck/*.instructions.md` — reserved for
28
+ organization-only governance that should NOT be distributed.
29
+ - Downstream repositories (consumers of the baseline):
30
+ - `.github/instructions/produck/*.instructions.md` — synced copy of the
31
+ upstream canonical baseline (managed by `agent-toolkit sync-instructions`).
32
+ - `.github/copilot-instructions.md` — repository-specific exceptions and
33
+ stricter local constraints.
34
+
35
+ Editing rule (upstream only):
30
36
 
31
37
  - Update downstream baseline rules directly in
32
38
  `.github/distribution/produck/*.instructions.md`.
33
- - Update organization-only workflow/governance in
34
- `.github/instructions/produck/`.
39
+ - Add organization-only governance under
40
+ `.github/instructions/produck/` only when it must not be distributed.
35
41
 
36
42
  ## Default expectations
37
43
 
@@ -134,27 +140,15 @@ max_line_length = 80
134
140
 
135
141
  ## Commit and PR conventions
136
142
 
137
- - Commit messages use bracketed tags: `[TAG] summary`.
138
- - Every non-empty commit message line must start with `[TAG]`.
139
- - Empty lines are not allowed between commit message lines.
140
- - Do not use untagged bullet lines in commit message body.
141
- - If details are needed, use additional tagged lines.
142
- - Do not keep summary as an untagged standalone line.
143
- - Recommended local validation:
144
- `npm exec -- agent-toolkit validate-commit-msg --file <message-file>`.
145
- - Commit precheck policy follows
143
+ - Commit and commit-precheck rules are defined in
146
144
  `.github/distribution/produck/20-produck-commit.instructions.md`.
147
- - Canonical source for commit tag/target whitelists, legacy mapping, and
148
- target syntax is
149
- `.github/distribution/produck/20-produck-commit.instructions.md`.
150
- - Do not redefine commit tag or target whitelists in other instruction files.
151
- - For non-monorepo repositories, use `[TAG] summary` directly (no
152
- package/workspace section headers).
153
- - Bracketed commit summaries should be in English
154
- - PR title format is repository-defined; no organization-level title format
155
- restriction
156
- - In PR descriptions, summarize what changed, why it changed, how it was
157
- validated, and any known risks or follow-up work
145
+ - Use commit message validator before commit and amend:
146
+ `npm exec -- agent-toolkit validate-commit-msg --file <message-file>`.
147
+ - Do not redefine commit tag, target, or monorepo section rules outside
148
+ `20-produck-commit.instructions.md`.
149
+ - PR title format is repository-defined (no organization-level restriction).
150
+ - PR descriptions should summarize what changed, why, validation, and known
151
+ risks or follow-up work.
158
152
 
159
153
  ## Terminal long-output protocol
160
154
 
@@ -255,7 +249,8 @@ locally in downstream repositories at a fixed version managed by the
255
249
  organization baseline.
256
250
 
257
251
  - Local install and pinned version are deployed by
258
- `agent-toolkit sync-husky-hooks`.
252
+ `agent-toolkit sync-git` (root devDependency pinning) and
253
+ `agent-toolkit sync-install` (root install script baseline).
259
254
  - Invocation uses the locally installed copy:
260
255
  `npm exec -- <bin> ...`.
261
256
  - Do not use `npm exec --package=<pkg>@latest` for routine invocations.
@@ -294,31 +289,21 @@ Rollback runbook (minimum):
294
289
 
295
290
  ### Recommended organization AI instruction template
296
291
 
297
- Use the following template text in organization AI instructions:
298
-
299
- - Use Chinese for discussion unless repository rules require another language.
300
- - Follow existing repository patterns; do not invent APIs, files, commands, or
301
- config keys.
302
- - Node-first execution policy:
303
- - Use Node scripts first for file/path/output processing.
304
- - For large output tasks, use two phases: capture full output, then analyze.
305
- - Avoid fragile shell pipeline post-processing for long-output commands.
306
- - Central package policy:
307
- - Central package is installed locally at a fixed organization-baseline version.
308
- - Invoke via `npm exec -- <bin> ...` to use the locally installed copy.
309
- - Print resolved package version before high-impact execution.
310
- - Use dry-run first for risky operations; keep rollback path to pinned version.
311
- - Commit message policy:
312
- - Every non-empty commit message line must start with `[TAG]`.
313
- - Empty lines are not allowed between commit message lines.
314
- - Use only allowed tags: `[INIT]`, `[ADD]`, `[REMOVE]`, `[FIX]`,
315
- `[REFACTOR]`, `[UPGRADE]`.
316
- - Optional target syntax is `[TAG] <target>: <summary>` with target in:
317
- `docs`, `test`, `ci`, `deps`, `api`, `schema`, `infra`, `fmt`.
318
- - Do not assume scripts from organization `.github` repository exist in target
319
- repositories.
320
- - If a repository provides stricter rules, repository rules override
321
- organization defaults.
292
+ When authoring organization-level AI instruction text (for example in
293
+ organization settings), reference the canonical sources in this baseline
294
+ instead of duplicating their content:
295
+
296
+ - Language and default expectations: see `Default expectations` and
297
+ `Language conventions` sections above.
298
+ - Node-first execution policy: see `Terminal long-output protocol` above.
299
+ - Central package policy: see `Central package execution policy` above.
300
+ - Commit message policy: see
301
+ [Commit Convention](20-produck-commit.instructions.md) (canonical source
302
+ for tag whitelist, target whitelist, and validator usage).
303
+ - Cross-repository assumptions: do not assume scripts from the organization
304
+ `.github` repository exist in target repositories.
305
+ - Precedence: repository-specific stricter rules override organization
306
+ defaults.
322
307
 
323
308
  ## Precedence
324
309
 
@@ -326,4 +311,5 @@ If a repository provides more specific instructions, follow the repository
326
311
  instructions over this organization baseline.
327
312
 
328
313
  For Node.js repositories, also follow [Node.js Initialization
329
- Baseline](10-produck-node.instructions.md).
314
+ Baseline](10-produck-node.instructions.md) and [Test Authoring
315
+ Baseline](12-produck-test.instructions.md).
@@ -29,8 +29,9 @@ Notes:
29
29
 
30
30
  - Script key names are fixed and must match exactly.
31
31
  - Keep the script key name `produck:install` (organization-reserved key).
32
- - Required root script value for `produck:install` is:
33
- `npm -v && npm install`
32
+ - The required value for `produck:install` is governed by
33
+ `agent-toolkit sync-install`; see the Monorepo mode section below for the
34
+ canonical value.
34
35
  - `publish` may be a no-op when repository-specific release workflow does not
35
36
  use npm publishing.
36
37
  - Coverage governance policy:
@@ -115,67 +116,30 @@ Central toolkit command role model:
115
116
  interpreter workflows for parsing/filtering over brittle OS-shell pipelines.
116
117
  - For human engineers, `run-capture` and `summarize-log` are optional helpers.
117
118
 
118
- Test authoring baseline (required):
119
+ Test authoring baseline:
119
120
 
120
- - MUST use Node.js standard library test runner (`node:test`) with `describe`
121
- and `it`.
122
- - Execution MUST follow the **Single Entrypoint Rule**: always run tests via a
123
- dedicated entrypoint (for example `test/index.mjs`) instead of targeting
124
- individual files.
125
- - Command-line execution MUST NOT use the `--test` flag. Use
126
- `node <entrypoint>` directly.
127
- - Each test case must be independently executable.
128
- - Test cases must not depend on execution order or state from other cases.
129
- - New test debugging should use local `only` mode for scoped regression.
130
- - After debugging, remove all `only` markers before final validation.
131
-
132
- Recommended local debug flow:
133
-
134
- 1. Add `{ only: true }` to the target `describe/it` and all ancestor
135
- `describe` blocks.
136
- 2. Run `node --test-only test/index.mjs`.
137
- 3. Remove all `only` markers.
138
- 4. Run full regression via repository standard test command.
121
+ - See [Test Authoring Baseline](12-produck-test.instructions.md) for the full
122
+ test writing, structure, debug, and coverage workflow rules.
139
123
 
140
124
  Script and output directory policy:
141
125
 
142
- - Reusable project scripts should be committed under root `scripts/`.
143
- - Organization-level shared tooling may use a central npm package bridge instead
144
- of repository-local `scripts/` duplication.
145
- - Runtime command outputs should be written under root `logs/` (or a documented
146
- equivalent) and ignored by git.
147
- - Temporary debug scripts should not be committed.
148
- - `.github/` should not be used as a temporary script workspace.
126
+ - Follow script/output placement and lifecycle policy from
127
+ `00-produck-base.instructions.md`.
128
+ - For monorepo shared configuration and root workspace practices, follow
129
+ `15-produck-workspace.instructions.md`.
149
130
 
150
131
  Required ignore baseline:
151
132
 
152
133
  - Each Node.js repository must include a root `.gitignore`.
153
- - The root `.gitignore` must start from the GitHub default template for Node.js
154
- projects (`Node.gitignore` from github/gitignore).
155
- - Team-specific ignore conventions should be appended on top of that baseline
156
- template, not used as a replacement.
157
- - The root `.gitignore` should at minimum ignore:
158
- - `node_modules/`
159
- - `coverage/`
160
- - `.env`
161
- - `.env.*`
162
- - npm logs (for example `npm-debug.log*`)
163
- - OS/editor noise (for example `.DS_Store`, `Thumbs.db`, `.vscode/` when
164
- workspace settings are not intended to be shared)
134
+ - Baseline template and required minimum entries follow
135
+ `00-produck-base.instructions.md`.
136
+ - Monorepo centralization policy and workspace-specific ignore guidance follow
137
+ `15-produck-workspace.instructions.md`.
165
138
 
166
139
  Team conventions for `.gitignore`:
167
140
 
168
- - Keep organization-wide additions grouped under a dedicated comment block for
169
- easy updates.
170
- - Do not remove baseline entries from the GitHub template unless repository
171
- owners document a justified exception.
172
- - Organization-approved team extension entries are:
173
- - `*.ign*` (manually created local directories/files that should not be
174
- committed)
175
- - `*.gen*` (generated artifacts created by program execution, for example
176
- during tests)
177
- - Append these team entries under a dedicated team block at the end of the root
178
- `.gitignore`.
141
+ - Team extension entries and placement rules follow
142
+ `00-produck-base.instructions.md`.
179
143
 
180
144
  ## Monorepo mode
181
145
 
@@ -0,0 +1,94 @@
1
+ ---
2
+ applyTo: '**'
3
+ ---
4
+
5
+ <!-- managed-by: @produck/agent-toolkit -->
6
+ <!-- source: .github/distribution/produck/12-produck-test.instructions.md -->
7
+
8
+ # Test Authoring Baseline
9
+
10
+ ## Scope
11
+
12
+ - Applies to Node.js repositories in the organization.
13
+ - This document governs how tests are authored, structured, and executed.
14
+ - Repository-specific rules may add stricter requirements; they override this
15
+ baseline when they conflict.
16
+
17
+ ## Framework
18
+
19
+ - MUST use Node.js standard library test runner (`node:test`) with `describe`
20
+ and `it`.
21
+ - Do not introduce a third-party test framework unless repository owners
22
+ explicitly document the justification.
23
+
24
+ ## File structure
25
+
26
+ - Each package must have a dedicated test directory: `test/`.
27
+ - Tests are organized in individual files per subject area under `test/`.
28
+ - Each package MUST provide a single entrypoint file: `test/index.mjs`.
29
+ - The entrypoint imports all test files to be executed.
30
+
31
+ Example entrypoint:
32
+
33
+ ```js
34
+ import './feature-a.test.mjs';
35
+ import './feature-b.test.mjs';
36
+ ```
37
+
38
+ ## Single Entrypoint Rule (required)
39
+
40
+ - Execution MUST always go through the entrypoint file.
41
+ - Command-line execution MUST NOT use the `--test` flag. Use
42
+ `node <entrypoint>` directly.
43
+ - Correct: `node test/index.mjs`
44
+ - Wrong: `node --test` or `node --test test/feature-a.test.mjs`
45
+ - All tooling (scripts, CI, coverage) must target the entrypoint only.
46
+
47
+ ## Test case rules
48
+
49
+ - Each test case must be independently executable.
50
+ - Test cases must not depend on execution order or shared mutable state from
51
+ other cases.
52
+ - Avoid global side effects that persist across test cases.
53
+ - Clean up resources (temp files, open handles) at the end of each test case.
54
+
55
+ ## Naming conventions
56
+
57
+ - Test files use the `.test.mjs` suffix.
58
+ - Top-level `describe` label should match the subject being tested (for example
59
+ the module name or command name).
60
+ - `it` labels should describe the expected behavior in plain language.
61
+
62
+ ## Local debug workflow (recommended)
63
+
64
+ When debugging a failing test case:
65
+
66
+ 1. Add `{ only: true }` to the target `it` and all ancestor `describe` blocks.
67
+ 2. Run `node --test-only test/index.mjs`.
68
+ 3. Verify the issue is isolated.
69
+ 4. Remove all `only` markers.
70
+ 5. Run full regression: `node test/index.mjs`.
71
+
72
+ Rules for `only` mode:
73
+
74
+ - `--test-name-pattern` does NOT match tests nested inside `describe()` blocks
75
+ — it may silently run zero tests. Do not use it for focused runs.
76
+ - Always remove `only` markers before committing.
77
+ - Never commit a test file with `{ only: true }` present.
78
+
79
+ ## Coverage
80
+
81
+ - Coverage is governed by `produck:coverage` script and `c8`.
82
+ - Scoped coverage (for partial branch verification during development):
83
+ `npm exec -- c8 --reporter=lcov --reporter=html --reporter=text-summary node --test-only test/index.mjs`
84
+ - Before merge or release, run full coverage via `npm run produck:coverage`.
85
+ - See [Node.js Baseline](10-produck-node.instructions.md) for coverage script
86
+ governance details.
87
+
88
+ ## Regression discipline
89
+
90
+ - Before merge or release: restore executable test commands and fix all failing
91
+ tests.
92
+ - Temporary non-executable state or failing tests are allowed for intermediate
93
+ commits.
94
+ - Full regression MUST pass before a release commit is made.
@@ -11,13 +11,6 @@ applyTo: '**'
11
11
 
12
12
  The Produck monorepo provides unified configuration across all packages for consistency and ease of maintenance.
13
13
 
14
- ## Distribution classification
15
-
16
- This document is maintained directly as a downstream-distributable source.
17
-
18
- - Authoritative path:
19
- `.github/distribution/produck/15-produck-workspace.instructions.md`
20
-
21
14
  ## Shared Configurations
22
15
 
23
16
  ### 1. ESLint Configuration (`eslint.config.mjs`)
@@ -152,38 +145,15 @@ npm run produck:coverage
152
145
 
153
146
  ### Release & Coverage Tooling
154
147
 
155
- - Monorepo release workflow is `lerna`-based and required.
156
- - Source of truth for `lerna`/`c8` versions and coverage script template:
148
+ - Release and coverage governance follows
149
+ `10-produck-node.instructions.md`.
150
+ - Source of truth for version pinning and script templates remains
157
151
  `.github/distribution/produck/tooling-version-baseline.json`.
158
- - `lerna` execution version is governed at organization level, not per
159
- repository.
160
- - Required execution baseline: version specified in `tooling-version-baseline.json`.
161
- - Required `lerna` invocation:
162
- `npm exec -- lerna <subcommand>`.
163
- - Shared scripts/CI must not use unversioned `npx lerna` or `lerna@latest`.
164
- - Wrapper scripts are allowed, but should keep parity with organization version
165
- policy.
166
- - Workspace subpackage coverage scripts are fully organization-governed.
167
- - Deploy/repair coverage scripts via central remediation command:
168
- `npm exec -- agent-toolkit sync-coverage --cwd .`.
169
- - Root anti-drift local hook baseline is organization-governed.
170
- - Deploy/repair root local hooks via central remediation command:
171
- `npm exec -- agent-toolkit sync-git --cwd .`.
172
- - Deployed coverage scripts use the `c8` version specified in
173
- `tooling-version-baseline.json` for each governed workspace package.
174
- - Deployed local hook baseline uses the `husky` version specified in
175
- `tooling-version-baseline.json` for root `devDependencies.husky`.
176
- - Deployed local hook baseline also pins root
177
- `devDependencies.@produck/agent-toolkit` to the fixed version managed by the organization baseline.
178
- - Shared scripts/CI must not use unversioned `npx c8` or `c8@latest`.
179
- - `test` script implementation remains repository-defined and is not overwritten
180
- by coverage remediation.
181
- - Root `devDependencies.c8` is pinned at root by `agent-toolkit sync-git`;
182
- workspace package `devDependencies.c8` is pinned per package by
183
- `agent-toolkit sync-coverage`.
184
- - Root local hooks may be bypassed intentionally by developers (for example via
185
- `--no-verify`) and are treated as local strong guardrails rather than
186
- immutable release gates.
152
+ - For monorepo remediation, use:
153
+ - `npm exec -- agent-toolkit sync-coverage --cwd .`
154
+ - `npm exec -- agent-toolkit sync-git --cwd .`
155
+ - Keep workspace wrappers and local scripts consistent with Node baseline
156
+ execution policy.
187
157
 
188
158
  ## Package Integration
189
159
 
@@ -21,10 +21,15 @@
21
21
  "version": "3.8.3",
22
22
  "policy": "pinned",
23
23
  "allowLatest": false
24
+ },
25
+ "@produck/eslint-rules": {
26
+ "version": "0.3.5",
27
+ "policy": "pinned",
28
+ "allowLatest": false
24
29
  }
25
30
  },
26
31
  "coverage": {
27
- "scriptTemplate": "c8@{c8.version} --reporter=lcov --reporter=html --reporter=text-summary npm test"
32
+ "scriptTemplate": "c8 --reporter=lcov --reporter=html --reporter=text-summary npm test"
28
33
  },
29
34
  "enforce": {
30
35
  "sharedScriptsDisallow": [
@@ -0,0 +1,2 @@
1
+ CHANGELOG.md
2
+ package-lock.json