@webpieces/rules-config 0.3.357 → 0.3.359

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.3.357",
3
+ "version": "0.3.359",
4
4
  "description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -14,8 +14,8 @@
14
14
  "README.md"
15
15
  ],
16
16
  "dependencies": {
17
- "@webpieces/core-context": "0.3.357",
18
- "@webpieces/core-util": "0.3.357",
17
+ "@webpieces/core-context": "0.3.359",
18
+ "@webpieces/core-util": "0.3.359",
19
19
  "@inversifyjs/binding-decorators": "1.1.5",
20
20
  "inversify": "7.10.4",
21
21
  "reflect-metadata": "0.2.2",
@@ -13,10 +13,18 @@ export declare class BranchMutationEvent {
13
13
  artifacts: string[];
14
14
  constructor(verb: MutationVerb, phase: MutationPhase);
15
15
  }
16
+ /** Appends branch-mutation audit lines. `@provideSingleton` so it's injectable + drawn in the design. */
17
+ export declare class BranchMutationLog {
18
+ branchMutationLogPath(root: string): string;
19
+ /**
20
+ * Append one tab-separated line per branch-mutation event to
21
+ * `.webpieces/hooks/branch-mutations.log`. Swallows all errors — logging must NEVER block or fail
22
+ * the workflow it is observing.
23
+ */
24
+ logBranchMutation(root: string, event: BranchMutationEvent): void;
25
+ private formatDetail;
26
+ private oneLine;
27
+ private rotateLogFile;
28
+ }
16
29
  export declare function branchMutationLogPath(root: string): string;
17
- /**
18
- * Append one tab-separated line per branch-mutation event to `.webpieces/hooks/branch-mutations.log`.
19
- * `root` is the workspace root holding `.webpieces`. Swallows all errors — logging must NEVER block or
20
- * fail the workflow it is observing (mirrors logSyncEvent's contract).
21
- */
22
30
  export declare function logBranchMutation(root: string, event: BranchMutationEvent): void;
@@ -1,32 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BranchMutationEvent = void 0;
3
+ exports.BranchMutationLog = exports.BranchMutationEvent = void 0;
4
4
  exports.branchMutationLogPath = branchMutationLogPath;
5
5
  exports.logBranchMutation = logBranchMutation;
6
6
  const tslib_1 = require("tslib");
7
7
  const fs = tslib_1.__importStar(require("fs"));
8
8
  const path = tslib_1.__importStar(require("path"));
9
+ const core_context_1 = require("@webpieces/core-context");
10
+ const inversify_1 = require("inversify");
9
11
  const constants_1 = require("./constants");
10
12
  const to_error_1 = require("./to-error");
11
- // The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches
12
- // (wp-start-update / wp-finish-update / wp-start-upsert-pr / wp-finish-upsert-pr and the merge-start /
13
- // merge-end primitives they compose). Before this, a branch could silently rename wpN → wpN+1 under
14
- // the agent (backup, checkout main, pull, squash-merge, rename) with ONLY `git reflog` as evidence —
15
- // nothing in `.webpieces/`. This log records START / each phase boundary / END-with-outcome so the
16
- // next agent (or a human) can reconstruct what the tooling did to the branches and where the merge/PR
17
- // artifacts landed. Kept SEPARATE from the read-only refresher log (guard-async-work.log) so that log
18
- // stays purely about the async cache. Writes to `.webpieces/hooks/branch-mutations.log`.
19
- //
13
+ // The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches.
14
+ // Records START / each phase boundary / END-with-outcome so the next agent (or a human) can
15
+ // reconstruct what the tooling did to the branches. Writes to `.webpieces/hooks/branch-mutations.log`.
20
16
  // Lives in rules-config (the shared dep of pr-gate) so the pr-gate scripts can call it directly.
21
17
  const HOOKS_DIR = 'hooks';
22
18
  const LOG_FILE = 'branch-mutations.log';
23
19
  const LOG_FILE_PREV = 'branch-mutations.1.log';
24
20
  const MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors the other webpieces logs)
25
21
  const MAX_DETAIL_LEN = 400;
26
- // Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit
27
- // construction). `verb` + `phase` are always set; the rest describe the transition and default to
28
- // empty so a call site fills only what that phase knows (e.g. RENAME sets from/to, PULL sets
29
- // oldMain/newMain, CONFLICT sets conflictFiles + artifacts, END sets outcome).
22
+ // Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit construction).
30
23
  class BranchMutationEvent {
31
24
  verb;
32
25
  phase;
@@ -44,71 +37,88 @@ class BranchMutationEvent {
44
37
  }
45
38
  }
46
39
  exports.BranchMutationEvent = BranchMutationEvent;
47
- function branchMutationLogPath(root) {
48
- return path.join(root, constants_1.WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);
49
- }
50
- /**
51
- * Append one tab-separated line per branch-mutation event to `.webpieces/hooks/branch-mutations.log`.
52
- * `root` is the workspace root holding `.webpieces`. Swallows all errors — logging must NEVER block or
53
- * fail the workflow it is observing (mirrors logSyncEvent's contract).
54
- */
55
- function logBranchMutation(root, event) {
56
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
57
- try {
58
- const timestamp = new Date().toISOString();
59
- const hooksDir = path.join(root, constants_1.WEBPIECES_TMP_DIR, HOOKS_DIR);
60
- fs.mkdirSync(hooksDir, { recursive: true });
61
- const logPath = path.join(hooksDir, LOG_FILE);
62
- rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));
63
- const line = [
64
- `[${timestamp}]`,
65
- event.verb,
66
- event.phase,
67
- oneLine(formatDetail(event)),
68
- ].join('\t') + '\n';
69
- fs.appendFileSync(logPath, line);
70
- }
71
- catch (err) {
72
- const error = (0, to_error_1.toError)(err);
73
- void error;
40
+ /** Appends branch-mutation audit lines. `@provideSingleton` so it's injectable + drawn in the design. */
41
+ let BranchMutationLog = class BranchMutationLog {
42
+ branchMutationLogPath(root) {
43
+ return path.join(root, constants_1.WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);
74
44
  }
75
- }
76
- // Render only the fields this event actually set, as `key=value` tokens — so a RENAME line reads
77
- // `from=… to=…` and a PULL line reads `oldMain=… newMain=…`, all greppable on one line.
78
- function formatDetail(event) {
79
- const parts = [];
80
- if (event.fromBranch !== '' || event.toBranch !== '')
81
- parts.push(`from=${event.fromBranch || '?'} to=${event.toBranch || '?'}`);
82
- if (event.oldMain !== '' || event.newMain !== '')
83
- parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);
84
- if (event.conflict)
85
- parts.push('conflict=true');
86
- if (event.conflictFiles.length > 0)
87
- parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);
88
- if (event.outcome !== '')
89
- parts.push(`outcome=${event.outcome}`);
90
- for (const artifact of event.artifacts)
91
- parts.push(`artifact=${artifact}`);
92
- return parts.join(' ');
93
- }
94
- // Collapse newlines/tabs and cap length so one event is always exactly one log line.
95
- function oneLine(value) {
96
- const flat = value.replace(/[\t\r\n]+/g, ' ').trim();
97
- return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';
98
- }
99
- function rotateLogFile(logPath, prevPath) {
100
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
101
- try {
102
- const stat = fs.statSync(logPath);
103
- if (stat.size > MAX_LOG_BYTES) {
104
- if (fs.existsSync(prevPath))
105
- fs.unlinkSync(prevPath);
106
- fs.renameSync(logPath, prevPath);
45
+ /**
46
+ * Append one tab-separated line per branch-mutation event to
47
+ * `.webpieces/hooks/branch-mutations.log`. Swallows all errors logging must NEVER block or fail
48
+ * the workflow it is observing.
49
+ */
50
+ logBranchMutation(root, event) {
51
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
52
+ try {
53
+ const timestamp = new Date().toISOString();
54
+ const hooksDir = path.join(root, constants_1.WEBPIECES_TMP_DIR, HOOKS_DIR);
55
+ fs.mkdirSync(hooksDir, { recursive: true });
56
+ const logPath = path.join(hooksDir, LOG_FILE);
57
+ this.rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));
58
+ const line = [
59
+ `[${timestamp}]`,
60
+ event.verb,
61
+ event.phase,
62
+ this.oneLine(this.formatDetail(event)),
63
+ ].join('\t') + '\n';
64
+ fs.appendFileSync(logPath, line);
65
+ }
66
+ catch (err) {
67
+ const error = (0, to_error_1.toError)(err);
68
+ void error;
107
69
  }
108
70
  }
109
- catch (err) {
110
- const error = (0, to_error_1.toError)(err);
111
- void error;
71
+ // Render only the fields this event actually set, as `key=value` tokens — greppable on one line.
72
+ formatDetail(event) {
73
+ const parts = [];
74
+ if (event.fromBranch !== '' || event.toBranch !== '')
75
+ parts.push(`from=${event.fromBranch || '?'} to=${event.toBranch || '?'}`);
76
+ if (event.oldMain !== '' || event.newMain !== '')
77
+ parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);
78
+ if (event.conflict)
79
+ parts.push('conflict=true');
80
+ if (event.conflictFiles.length > 0)
81
+ parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);
82
+ if (event.outcome !== '')
83
+ parts.push(`outcome=${event.outcome}`);
84
+ for (const artifact of event.artifacts)
85
+ parts.push(`artifact=${artifact}`);
86
+ return parts.join(' ');
112
87
  }
88
+ // Collapse newlines/tabs and cap length so one event is always exactly one log line.
89
+ oneLine(value) {
90
+ const flat = value.replace(/[\t\r\n]+/g, ' ').trim();
91
+ return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';
92
+ }
93
+ rotateLogFile(logPath, prevPath) {
94
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
95
+ try {
96
+ const stat = fs.statSync(logPath);
97
+ if (stat.size > MAX_LOG_BYTES) {
98
+ if (fs.existsSync(prevPath))
99
+ fs.unlinkSync(prevPath);
100
+ fs.renameSync(logPath, prevPath);
101
+ }
102
+ }
103
+ catch (err) {
104
+ const error = (0, to_error_1.toError)(err);
105
+ void error;
106
+ }
107
+ }
108
+ };
109
+ exports.BranchMutationLog = BranchMutationLog;
110
+ exports.BranchMutationLog = BranchMutationLog = tslib_1.__decorate([
111
+ (0, core_context_1.provideSingleton)(),
112
+ (0, inversify_1.injectable)()
113
+ ], BranchMutationLog);
114
+ // Temporary migration delegators to BranchMutationLog — removed once consumers inject it.
115
+ const branchMutationLogSvc = new BranchMutationLog();
116
+ // webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it
117
+ function branchMutationLogPath(root) {
118
+ return branchMutationLogSvc.branchMutationLogPath(root);
119
+ }
120
+ // webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it
121
+ function logBranchMutation(root, event) {
122
+ branchMutationLogSvc.logBranchMutation(root, event);
113
123
  }
114
124
  //# sourceMappingURL=branch-mutation-log.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-mutation-log.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/branch-mutation-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches\n// (wp-start-update / wp-finish-update / wp-start-upsert-pr / wp-finish-upsert-pr and the merge-start /\n// merge-end primitives they compose). Before this, a branch could silently rename wpN → wpN+1 under\n// the agent (backup, checkout main, pull, squash-merge, rename) with ONLY `git reflog` as evidence —\n// nothing in `.webpieces/`. This log records START / each phase boundary / END-with-outcome so the\n// next agent (or a human) can reconstruct what the tooling did to the branches and where the merge/PR\n// artifacts landed. Kept SEPARATE from the read-only refresher log (guard-async-work.log) so that log\n// stays purely about the async cache. Writes to `.webpieces/hooks/branch-mutations.log`.\n//\n// Lives in rules-config (the shared dep of pr-gate) so the pr-gate scripts can call it directly.\n\nconst HOOKS_DIR = 'hooks';\nconst LOG_FILE = 'branch-mutations.log';\nconst LOG_FILE_PREV = 'branch-mutations.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors the other webpieces logs)\nconst MAX_DETAIL_LEN = 400;\n\n// The workflow verb whose branch mutation is being logged (the bin the AI/human invoked).\nexport type MutationVerb = 'wp-start-update' | 'wp-finish-update' | 'wp-start-upsert-pr' | 'wp-finish-upsert-pr';\n\n// A boundary within a verb's execution. START/END bracket the whole run; the middle phases mark each\n// irreversible git step so an interrupt leaves a breadcrumb at the last phase reached.\nexport type MutationPhase =\n | 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME'\n | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END';\n\n// Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit\n// construction). `verb` + `phase` are always set; the rest describe the transition and default to\n// empty so a call site fills only what that phase knows (e.g. RENAME sets from/to, PULL sets\n// oldMain/newMain, CONFLICT sets conflictFiles + artifacts, END sets outcome).\nexport class BranchMutationEvent {\n verb: MutationVerb;\n phase: MutationPhase;\n fromBranch: string = '';\n toBranch: string = '';\n oldMain: string = '';\n newMain: string = '';\n conflict: boolean = false;\n conflictFiles: string[] = [];\n outcome: string = '';\n artifacts: string[] = [];\n\n constructor(verb: MutationVerb, phase: MutationPhase) {\n this.verb = verb;\n this.phase = phase;\n }\n}\n\nexport function branchMutationLogPath(root: string): string {\n return path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);\n}\n\n/**\n * Append one tab-separated line per branch-mutation event to `.webpieces/hooks/branch-mutations.log`.\n * `root` is the workspace root holding `.webpieces`. Swallows all errors — logging must NEVER block or\n * fail the workflow it is observing (mirrors logSyncEvent's contract).\n */\nexport function logBranchMutation(root: string, event: BranchMutationEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR);\n fs.mkdirSync(hooksDir, { recursive: true });\n\n const logPath = path.join(hooksDir, LOG_FILE);\n rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));\n\n const line = [\n `[${timestamp}]`,\n event.verb,\n event.phase,\n oneLine(formatDetail(event)),\n ].join('\\t') + '\\n';\n fs.appendFileSync(logPath, line);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n\n// Render only the fields this event actually set, as `key=value` tokens — so a RENAME line reads\n// `from=… to=…` and a PULL line reads `oldMain=… newMain=…`, all greppable on one line.\nfunction formatDetail(event: BranchMutationEvent): string {\n const parts: string[] = [];\n if (event.fromBranch !== '' || event.toBranch !== '') parts.push(`from=${event.fromBranch || '?'} to=${event.toBranch || '?'}`);\n if (event.oldMain !== '' || event.newMain !== '') parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);\n if (event.conflict) parts.push('conflict=true');\n if (event.conflictFiles.length > 0) parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);\n if (event.outcome !== '') parts.push(`outcome=${event.outcome}`);\n for (const artifact of event.artifacts) parts.push(`artifact=${artifact}`);\n return parts.join(' ');\n}\n\n// Collapse newlines/tabs and cap length so one event is always exactly one log line.\nfunction oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';\n}\n\nfunction rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n"]}
1
+ {"version":3,"file":"branch-mutation-log.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/branch-mutation-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from '@webpieces/core-context';\nimport { injectable } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches.\n// Records START / each phase boundary / END-with-outcome so the next agent (or a human) can\n// reconstruct what the tooling did to the branches. Writes to `.webpieces/hooks/branch-mutations.log`.\n// Lives in rules-config (the shared dep of pr-gate) so the pr-gate scripts can call it directly.\n\nconst HOOKS_DIR = 'hooks';\nconst LOG_FILE = 'branch-mutations.log';\nconst LOG_FILE_PREV = 'branch-mutations.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors the other webpieces logs)\nconst MAX_DETAIL_LEN = 400;\n\n// The workflow verb whose branch mutation is being logged (the bin the AI/human invoked).\nexport type MutationVerb = 'wp-start-update' | 'wp-finish-update' | 'wp-start-upsert-pr' | 'wp-finish-upsert-pr';\n\n// A boundary within a verb's execution. START/END bracket the whole run; the middle phases mark each\n// irreversible git step so an interrupt leaves a breadcrumb at the last phase reached.\nexport type MutationPhase =\n | 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME'\n | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END';\n\n// Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit construction).\nexport class BranchMutationEvent {\n verb: MutationVerb;\n phase: MutationPhase;\n fromBranch: string = '';\n toBranch: string = '';\n oldMain: string = '';\n newMain: string = '';\n conflict: boolean = false;\n conflictFiles: string[] = [];\n outcome: string = '';\n artifacts: string[] = [];\n\n constructor(verb: MutationVerb, phase: MutationPhase) {\n this.verb = verb;\n this.phase = phase;\n }\n}\n\n/** Appends branch-mutation audit lines. `@provideSingleton` so it's injectable + drawn in the design. */\n@provideSingleton()\n@injectable()\nexport class BranchMutationLog {\n branchMutationLogPath(root: string): string {\n return path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);\n }\n\n /**\n * Append one tab-separated line per branch-mutation event to\n * `.webpieces/hooks/branch-mutations.log`. Swallows all errors — logging must NEVER block or fail\n * the workflow it is observing.\n */\n logBranchMutation(root: string, event: BranchMutationEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR);\n fs.mkdirSync(hooksDir, { recursive: true });\n\n const logPath = path.join(hooksDir, LOG_FILE);\n this.rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));\n\n const line = [\n `[${timestamp}]`,\n event.verb,\n event.phase,\n this.oneLine(this.formatDetail(event)),\n ].join('\\t') + '\\n';\n fs.appendFileSync(logPath, line);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // Render only the fields this event actually set, as `key=value` tokens — greppable on one line.\n private formatDetail(event: BranchMutationEvent): string {\n const parts: string[] = [];\n if (event.fromBranch !== '' || event.toBranch !== '') parts.push(`from=${event.fromBranch || '?'} to=${event.toBranch || '?'}`);\n if (event.oldMain !== '' || event.newMain !== '') parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);\n if (event.conflict) parts.push('conflict=true');\n if (event.conflictFiles.length > 0) parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);\n if (event.outcome !== '') parts.push(`outcome=${event.outcome}`);\n for (const artifact of event.artifacts) parts.push(`artifact=${artifact}`);\n return parts.join(' ');\n }\n\n // Collapse newlines/tabs and cap length so one event is always exactly one log line.\n private oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';\n }\n\n private rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\n// Temporary migration delegators to BranchMutationLog — removed once consumers inject it.\nconst branchMutationLogSvc = new BranchMutationLog();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function branchMutationLogPath(root: string): string {\n return branchMutationLogSvc.branchMutationLogPath(root);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function logBranchMutation(root: string, event: BranchMutationEvent): void {\n branchMutationLogSvc.logBranchMutation(root, event);\n}\n"]}
@@ -1,13 +1,10 @@
1
1
  /**
2
- * Shared git-diff + diff-scoping helpers for ALL rule validators (code-rules) and nx executors
3
- * (nx-webpieces-rules). Centralized here in rules-config because it is the one package both of
4
- * those depend on, and it already shells out to git (see skip-rule.ts).
2
+ * Shared git-diff + diff-scoping service for ALL rule validators (code-rules) and nx executors
3
+ * (nx-webpieces-rules). Centralized here in rules-config because it is the one package both depend on.
5
4
  *
6
- * Before this module these functions were copy-pasted ~15× (one private copy per validator + the
7
- * validate-ts-in-src executor). They are now defined once; every consumer imports from
8
- * `@webpieces/rules-config`. The two historical variants are preserved as parameters:
9
- * - `getChangedFiles(..., { tsOnly: false })` reproduces validate-dtos' all-files diff.
10
- * - `detectBase` replaces both the nested-try and the ref-loop spellings (identical behavior).
5
+ * `@provideSingleton` so it can be injected and appear in the rules-config DI design. Free-function
6
+ * delegators are kept temporarily so the many existing consumers stay green; they migrate to injecting
7
+ * {@link DiffScope} over follow-up PRs, then the delegators are removed.
11
8
  */
12
9
  /** A git diff range: the base ref to compare against and an optional head (else the working tree). */
13
10
  export declare class DiffRange {
@@ -18,48 +15,34 @@ export declare class DiffRange {
18
15
  export declare class ChangedFilesOptions {
19
16
  tsOnly?: boolean;
20
17
  }
21
- /**
22
- * Auto-detect the diff base: the merge-base of HEAD with origin/main, falling back to local main.
23
- * Returns null when neither ref resolves (e.g. shallow clone with no main) callers treat null as
24
- * "no base detected" and typically skip.
25
- */
18
+ export declare class DiffScope {
19
+ /** Auto-detect the diff base: merge-base of HEAD with origin/main, falling back to local main. */
20
+ detectBase(workspaceRoot: string): string | null;
21
+ /** Resolve the diff range a rule should compare against (honors nx's NX_BASE / NX_HEAD). */
22
+ resolveBase(workspaceRoot: string): DiffRange;
23
+ /**
24
+ * Changed files between base and head (or base→working-tree when head is omitted). Untracked files
25
+ * are unioned in for the working-tree case. `tsOnly` (default true) restricts to *.ts/*.tsx and
26
+ * drops test files. Deletions are excluded (`--diff-filter=d`).
27
+ */
28
+ getChangedFiles(workspaceRoot: string, base: string, head?: string, opts?: ChangedFilesOptions): string[];
29
+ /** Diff content for a single file (synthetic all-added diff for an untracked file with no head). */
30
+ getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string;
31
+ /** Added/changed line numbers (the `+` lines per hunk) — basis of NEW_AND_MODIFIED_CODE scoping. */
32
+ getChangedLineNumbers(diffContent: string): Set<number>;
33
+ /** Method names whose signature line is a `+` addition in the diff — the basis of "NEW" methods. */
34
+ findNewMethodSignaturesInDiff(diffContent: string): Set<string>;
35
+ /** True if any line in [startLine, endLine] is in the changedLines set. */
36
+ hasChangesInRange(startLine: number, endLine: number, changedLines: Set<number>): boolean;
37
+ /** True if a node (method/function) is newly added or has any changed line in its range. */
38
+ isNewOrModified(name: string, startLine: number, endLine: number, changedLines: Set<number>, newMethodNames: Set<string>): boolean;
39
+ private isTestFile;
40
+ }
26
41
  export declare function detectBase(workspaceRoot: string): string | null;
27
- /**
28
- * Resolve the diff range a rule should compare against. Honors nx's NX_BASE / NX_HEAD (set by
29
- * `nx affected --base=.. --head=..`); when NX_BASE is unset, auto-detects via detectBase. A returned
30
- * `base` of undefined means "could not determine a base" (caller should skip).
31
- */
32
42
  export declare function resolveBase(workspaceRoot: string): DiffRange;
33
- /**
34
- * Changed files between base and head (or base→working-tree when head is omitted). When head is
35
- * omitted, untracked files are unioned in too (matching `nx affected`). `tsOnly` (default true)
36
- * restricts to *.ts/*.tsx and drops test files; pass false for an all-files diff.
37
- *
38
- * Deletions are excluded (`--diff-filter=d`): every consumer reasons about a file's current
39
- * content or location, and a path that no longer exists can't violate anything. This also fixes
40
- * renames — without rename detection git reports a rename as delete+add, so filtering the delete
41
- * side leaves only the file's NEW path in the list.
42
- */
43
43
  export declare function getChangedFiles(workspaceRoot: string, base: string, head?: string, opts?: ChangedFilesOptions): string[];
44
- /**
45
- * Diff content for a single file. When the file is untracked (and no head is given) a synthetic
46
- * all-added diff is produced so new files count as fully-changed.
47
- */
48
44
  export declare function getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string;
49
- /**
50
- * Parse a unified diff and return the set of added/changed line numbers (the `+` lines per hunk).
51
- * This is the basis of NEW_AND_MODIFIED_CODE (line-level) scoping.
52
- */
53
45
  export declare function getChangedLineNumbers(diffContent: string): Set<number>;
54
- /**
55
- * Method names whose signature line is a `+` addition in the diff — the basis of "NEW" methods.
56
- */
57
46
  export declare function findNewMethodSignaturesInDiff(diffContent: string): Set<string>;
58
- /**
59
- * True if any line in [startLine, endLine] is in the changedLines set.
60
- */
61
47
  export declare function hasChangesInRange(startLine: number, endLine: number, changedLines: Set<number>): boolean;
62
- /**
63
- * True if a node (method/function) is newly added or has any changed line in its range.
64
- */
65
48
  export declare function isNewOrModified(name: string, startLine: number, endLine: number, changedLines: Set<number>, newMethodNames: Set<string>): boolean;