@webpieces/rules-config 0.3.199 → 0.3.200
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.
|
|
3
|
+
"version": "0.3.200",
|
|
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",
|
package/src/main-sync-status.js
CHANGED
|
@@ -19,7 +19,6 @@ const child_process_1 = require("child_process");
|
|
|
19
19
|
const fs = tslib_1.__importStar(require("fs"));
|
|
20
20
|
const path = tslib_1.__importStar(require("path"));
|
|
21
21
|
const constants_1 = require("./constants");
|
|
22
|
-
const skip_rule_1 = require("./skip-rule");
|
|
23
22
|
const to_error_1 = require("./to-error");
|
|
24
23
|
// Shared "is my feature branch healthy relative to origin/main?" state. The SLOW signals (git fetch
|
|
25
24
|
// + merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher
|
|
@@ -151,6 +150,12 @@ function capture(repoRoot, cmd, args) {
|
|
|
151
150
|
return { ok: false, out: '' };
|
|
152
151
|
return { ok: true, out: result.stdout.trim() };
|
|
153
152
|
}
|
|
153
|
+
// The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label always
|
|
154
|
+
// matches what the feature-branch-guard compares against (its own `git rev-parse` in the workspace).
|
|
155
|
+
function gitBranch(repoRoot) {
|
|
156
|
+
const result = capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
157
|
+
return result.ok ? result.out : '';
|
|
158
|
+
}
|
|
154
159
|
function changedFiles(repoRoot, base, head) {
|
|
155
160
|
const result = capture(repoRoot, 'git', ['diff', '--name-only', base, head]);
|
|
156
161
|
if (!result.ok || result.out === '')
|
|
@@ -178,7 +183,7 @@ function benignStatus(branch, featureHead) {
|
|
|
178
183
|
* conflict). Never run on the hook's blocking path.
|
|
179
184
|
*/
|
|
180
185
|
function computeMainSyncStatus(repoRoot) {
|
|
181
|
-
const branch = (
|
|
186
|
+
const branch = gitBranch(repoRoot);
|
|
182
187
|
const mergedPr = detectMergedPr(repoRoot, branch);
|
|
183
188
|
// Best-effort network refresh; offline just means we evaluate against the last-fetched ref.
|
|
184
189
|
(0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });
|
|
@@ -221,7 +226,7 @@ function squashRecoverySteps(currentBranch) {
|
|
|
221
226
|
function stampCleanMainSyncStatus(repoRoot) {
|
|
222
227
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
223
228
|
try {
|
|
224
|
-
const branch = (
|
|
229
|
+
const branch = gitBranch(repoRoot);
|
|
225
230
|
const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);
|
|
226
231
|
const featureHead = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);
|
|
227
232
|
if (!originMain.ok || !featureHead.ok)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { getCurrentBranch } from './skip-rule';\nimport { toError } from './to-error';\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch\n// + merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher\n// in a DETACHED background process so the PreToolUse hook never blocks on the network. The\n// feature-branch-guard then only READS this cached file (instant). pr-gate's merge flow also writes\n// it synchronously after a merge so the next edit is unblocked immediately. Lives here (the shared\n// dep of both packages) so neither depends on the other.\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung\n// and proceeds anyway. Overridable per-rule via FeatureBranchGuardConfig.hangTimeoutMinutes.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Data-only (per CLAUDE.md, classes for data). `forkPoint` is null exactly when `hasForkPoint` is\n// false (no merge-base with origin/main). `branchAlreadyMerged` flags that this feature branch was\n// already merged into main (a merged PR exists) — the \"you're working on a finished branch\" case.\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch milliseconds.\nexport class MainSyncLock {\n state: string;\n started: number;\n\n constructor(state: string, started: number) {\n this.state = state;\n this.started = started;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary (mirrors global SkipHooksFile-style casts) —\n// keeps `any`/`unknown` out of the cast so no-any-unknown stays clean.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n}\n\ninterface RawLock {\n state?: string;\n started?: number;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\nexport function mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n}\n\nexport function mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n}\n\nfunction ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n}\n\n// Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN\n// (never block an edit because the cache is unreadable).\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n return new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = mainSyncStatusPath(repoRoot);\n ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n}\n\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = mainSyncLockPath(repoRoot);\n ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n}\n\n// A lock is stale (the prior refresher is assumed hung) once it has been `inprocess` longer than\n// hangTimeoutMinutes. `now` is injectable for tests; defaults to Date.now().\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n}\n\n// True when another refresher is actively running and we should NOT start a second one. A finished\n// lock, a missing lock, or a stale (hung) inprocess lock all return false.\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n return !isLockStale(lock, hangTimeoutMinutes, now);\n}\n\nexport function inProcessLock(now: number = Date.now()): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now);\n}\n\nexport function finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started);\n}\n\n// Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\nfunction capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n}\n\nfunction changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n}\n\n// Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists for the\n// branch. Best-effort — if gh is missing/unauthenticated we just report not-merged (false).\nfunction detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n}\n\n// A benign status that never blocks — used when origin/main can't be resolved (no remote yet,\n// offline before first fetch). hasForkPoint=true + conflict=false so the guard allows the edit.\nfunction benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n}\n\n/**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs: whether the branch is already merged (merged PR), whether a fork point\n * with origin/main still exists, and whether origin/main and this branch touched the SAME file since\n * the fork point (the deliberately-simple conflict heuristic — it over-blocks rather than miss a real\n * conflict). Never run on the hook's blocking path.\n */\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = getCurrentBranch();\n const mergedPr = detectMergedPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n return status;\n }\n\n const forkPoint = capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n // No common ancestor — main was merged into the branch. Force the human to squash.\n return new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n }\n\n const featureFiles = new Set(changedFiles(repoRoot, forkPoint.out, 'HEAD'));\n const mainFiles = changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n return new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n}\n\n// The recovery steps when there is no fork point with origin/main (someone merged main into the\n// branch, so a clean squash-merge is impossible). Shared so the feature-branch-guard and pr-gate's\n// findForkPoint check present the SAME instructions. The human must redo the work on a fresh branch —\n// deliberately painful so the bad merge gets noticed and reported.\nexport function squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Switch to main: git checkout main',\n '2. Pull latest: git pull',\n `3. New branch (new name): git checkout -b ${currentBranch}-v2`,\n `4. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `5. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '6. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n}\n\n// Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge\n// (the branch now contains origin/main). Unblocks the next edit immediately without waiting for the\n// async refresher. Best-effort: a git failure is swallowed (the refresher will recompute later).\nexport function stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = getCurrentBranch();\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch\n// + merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher\n// in a DETACHED background process so the PreToolUse hook never blocks on the network. The\n// feature-branch-guard then only READS this cached file (instant). pr-gate's merge flow also writes\n// it synchronously after a merge so the next edit is unblocked immediately. Lives here (the shared\n// dep of both packages) so neither depends on the other.\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung\n// and proceeds anyway. Overridable per-rule via FeatureBranchGuardConfig.hangTimeoutMinutes.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Data-only (per CLAUDE.md, classes for data). `forkPoint` is null exactly when `hasForkPoint` is\n// false (no merge-base with origin/main). `branchAlreadyMerged` flags that this feature branch was\n// already merged into main (a merged PR exists) — the \"you're working on a finished branch\" case.\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch milliseconds.\nexport class MainSyncLock {\n state: string;\n started: number;\n\n constructor(state: string, started: number) {\n this.state = state;\n this.started = started;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary (mirrors global SkipHooksFile-style casts) —\n// keeps `any`/`unknown` out of the cast so no-any-unknown stays clean.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n}\n\ninterface RawLock {\n state?: string;\n started?: number;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\nexport function mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n}\n\nexport function mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n}\n\nfunction ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n}\n\n// Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN\n// (never block an edit because the cache is unreadable).\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n return new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = mainSyncStatusPath(repoRoot);\n ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n}\n\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = mainSyncLockPath(repoRoot);\n ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n}\n\n// A lock is stale (the prior refresher is assumed hung) once it has been `inprocess` longer than\n// hangTimeoutMinutes. `now` is injectable for tests; defaults to Date.now().\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n}\n\n// True when another refresher is actively running and we should NOT start a second one. A finished\n// lock, a missing lock, or a stale (hung) inprocess lock all return false.\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n return !isLockStale(lock, hangTimeoutMinutes, now);\n}\n\nexport function inProcessLock(now: number = Date.now()): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now);\n}\n\nexport function finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started);\n}\n\n// Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\nfunction capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n}\n\n// The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label always\n// matches what the feature-branch-guard compares against (its own `git rev-parse` in the workspace).\nfunction gitBranch(repoRoot: string): string {\n const result = capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n}\n\nfunction changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n}\n\n// Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists for the\n// branch. Best-effort — if gh is missing/unauthenticated we just report not-merged (false).\nfunction detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n}\n\n// A benign status that never blocks — used when origin/main can't be resolved (no remote yet,\n// offline before first fetch). hasForkPoint=true + conflict=false so the guard allows the edit.\nfunction benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n}\n\n/**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs: whether the branch is already merged (merged PR), whether a fork point\n * with origin/main still exists, and whether origin/main and this branch touched the SAME file since\n * the fork point (the deliberately-simple conflict heuristic — it over-blocks rather than miss a real\n * conflict). Never run on the hook's blocking path.\n */\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = gitBranch(repoRoot);\n const mergedPr = detectMergedPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n return status;\n }\n\n const forkPoint = capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n // No common ancestor — main was merged into the branch. Force the human to squash.\n return new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n }\n\n const featureFiles = new Set(changedFiles(repoRoot, forkPoint.out, 'HEAD'));\n const mainFiles = changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n return new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n}\n\n// The recovery steps when there is no fork point with origin/main (someone merged main into the\n// branch, so a clean squash-merge is impossible). Shared so the feature-branch-guard and pr-gate's\n// findForkPoint check present the SAME instructions. The human must redo the work on a fresh branch —\n// deliberately painful so the bad merge gets noticed and reported.\nexport function squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Switch to main: git checkout main',\n '2. Pull latest: git pull',\n `3. New branch (new name): git checkout -b ${currentBranch}-v2`,\n `4. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `5. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '6. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n}\n\n// Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge\n// (the branch now contains origin/main). Unblocks the next edit immediately without waiting for the\n// async refresher. Best-effort: a git failure is swallowed (the refresher will recompute later).\nexport function stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = gitBranch(repoRoot);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n"]}
|
package/src/skip-rule.js
CHANGED
|
@@ -5,15 +5,11 @@ exports.shouldSkipRule = shouldSkipRule;
|
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const inform_ai_error_1 = require("./inform-ai-error");
|
|
7
7
|
const to_error_1 = require("./to-error");
|
|
8
|
+
// The actual checked-out branch. Always uses git directly — env vars (BRANCH_NAME, GIT_BRANCH,
|
|
9
|
+
// GITHUB_*, CI_COMMIT_BRANCH, …) were intentionally REMOVED: a stray GIT_BRANCH=main locally made
|
|
10
|
+
// this return "main" on a feature branch, which (a) mislabeled the main-sync cache and (b) silently
|
|
11
|
+
// disabled merged-PR detection (detectMergedPr skips "main"). git rev-parse is the source of truth.
|
|
8
12
|
function getCurrentBranch() {
|
|
9
|
-
const envBranch = process.env['BRANCH_NAME'] ||
|
|
10
|
-
process.env['GIT_BRANCH'] ||
|
|
11
|
-
process.env['GITHUB_HEAD_REF'] ||
|
|
12
|
-
process.env['GITHUB_REF_NAME'] ||
|
|
13
|
-
process.env['CI_COMMIT_BRANCH'] ||
|
|
14
|
-
process.env['CIRCLE_BRANCH'];
|
|
15
|
-
if (envBranch)
|
|
16
|
-
return envBranch;
|
|
17
13
|
// webpieces-disable no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI
|
|
18
14
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI
|
|
19
15
|
try {
|
package/src/skip-rule.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skip-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/skip-rule.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"skip-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/skip-rule.ts"],"names":[],"mappings":";;AAmBA,4CASC;AAED,wCAkBC;AAhDD,iDAAyC;AAEzC,uDAAkD;AAClD,yCAAqC;AAYrC,+FAA+F;AAC/F,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,SAAgB,gBAAgB;IAC5B,wHAAwH;IACxH,0IAA0I;IAC1I,IAAI,CAAC;QACD,OAAO,IAAA,wBAAQ,EAAC,iCAAiC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpF,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,IAAI,+BAAa,CAAC,2CAA2C,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1G,CAAC;AACL,CAAC;AAED,SAAgB,cAAc,CAC1B,KAAyB,EACzB,aAAiC;IAEjC,IAAI,aAAa,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;QACnC,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC5B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,aAAa,GAAG,EAAE,CAAC;QAClE,CAAC;IACL,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACrC,IAAI,UAAU,GAAG,KAAK,EAAE,CAAC;YACrB,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,6CAA6C,WAAW,EAAE,EAAE,CAAC;QAC9F,CAAC;IACL,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import { execSync } from 'child_process';\n\nimport { InformAiError } from './inform-ai-error';\nimport { toError } from './to-error';\n\n// Universal \"should this rule be skipped right now?\" logic, shared by code-rules,\n// ai-hook-rules and the Nx executors so every rule honors the same two escape\n// hatches: ignoreRuleWhileOnBranch (skip while on a named branch) and\n// ignoreModifiedUntilEpoch (skip until an epoch passes).\n\nexport interface SkipRuleResult {\n skip: boolean;\n reason?: string;\n}\n\n// The actual checked-out branch. Always uses git directly — env vars (BRANCH_NAME, GIT_BRANCH,\n// GITHUB_*, CI_COMMIT_BRANCH, …) were intentionally REMOVED: a stray GIT_BRANCH=main locally made\n// this return \"main\" on a feature branch, which (a) mislabeled the main-sync cache and (b) silently\n// disabled merged-PR detection (detectMergedPr skips \"main\"). git rev-parse is the source of truth.\nexport function getCurrentBranch(): string {\n // webpieces-disable no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- rethrow as InformAiError so global catch surfaces readable message to AI\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Failed to determine current git branch: ${error.message}`, { cause: error });\n }\n}\n\nexport function shouldSkipRule(\n epoch: number | undefined,\n branchPattern: string | undefined\n): SkipRuleResult {\n if (branchPattern) {\n const current = getCurrentBranch();\n if (current === branchPattern) {\n return { skip: true, reason: `on branch \"${branchPattern}\"` };\n }\n }\n if (epoch !== undefined) {\n const nowSeconds = Date.now() / 1000;\n if (nowSeconds < epoch) {\n const expiresDate = new Date(epoch * 1000).toISOString().split('T')[0];\n return { skip: true, reason: `ignoreModifiedUntilEpoch active, expires: ${expiresDate}` };\n }\n }\n return { skip: false };\n}\n"]}
|