@webpieces/ai-hook-rules 0.3.207 → 0.3.209

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/ai-hook-rules",
3
- "version": "0.3.207",
3
+ "version": "0.3.209",
4
4
  "description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -35,7 +35,7 @@
35
35
  "directory": "packages/tooling/ai-hook-rules"
36
36
  },
37
37
  "dependencies": {
38
- "@webpieces/rules-config": "0.3.207"
38
+ "@webpieces/rules-config": "0.3.209"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
@@ -10,7 +10,7 @@ export declare class GuardDecision {
10
10
  constructor(rule: string, tool: string, target: string, branch: string, verdict: Verdict, reason: string, cache?: string);
11
11
  }
12
12
  /**
13
- * Append one tab-separated line per decision to `.webpieces/hooks/sync-decisions.log`. `root` is
13
+ * Append one tab-separated line per decision to `.webpieces/hooks/guard-sync-decisions.log`. `root` is
14
14
  * the repo/workspace root that holds `.webpieces` (callers pass workspaceRoot, or process.cwd() at
15
15
  * the pre-load config-bypass site). Swallows all errors — logging must never block or fail a hook.
16
16
  */
@@ -9,13 +9,13 @@ const fs = tslib_1.__importStar(require("fs"));
9
9
  const path = tslib_1.__importStar(require("path"));
10
10
  const to_error_1 = require("./to-error");
11
11
  // The SYNC decision log — what the synchronous hook DID on each invocation and WHY. Its companion is
12
- // the ASYNC log (async-refresh.log, written by the detached refresher in main-sync-log.ts). This one
13
- // records EVERY guard decision — allow, block, config-bypass, and the fail-open cases — and CITES the
14
- // async-written cache snapshot (`cache` field) that drove the decision, so a wrong allow/block is
15
- // traceable to a stale or missing async write. Writes to `.webpieces/hooks/sync-decisions.log`.
12
+ // the ASYNC log (guard-async-work.log, written by the detached refresher in main-sync-log.ts). This
13
+ // one records EVERY guard decision — allow, block, config-bypass, and the fail-open cases — and CITES
14
+ // the async-written cache snapshot (`cache` field) that drove the decision, so a wrong allow/block is
15
+ // traceable to a stale or missing async write. Writes to `.webpieces/hooks/guard-sync-decisions.log`.
16
16
  const HOOKS_DIR = '.webpieces/hooks';
17
- const LOG_FILE = 'sync-decisions.log';
18
- const LOG_FILE_PREV = 'sync-decisions.1.log';
17
+ const LOG_FILE = 'guard-sync-decisions.log';
18
+ const LOG_FILE_PREV = 'guard-sync-decisions.1.log';
19
19
  const MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors rejection-log)
20
20
  const MAX_TARGET_LEN = 160;
21
21
  // Data-only record of one guard decision (per CLAUDE.md: classes for data, not object literals).
@@ -42,7 +42,7 @@ class GuardDecision {
42
42
  }
43
43
  exports.GuardDecision = GuardDecision;
44
44
  /**
45
- * Append one tab-separated line per decision to `.webpieces/hooks/sync-decisions.log`. `root` is
45
+ * Append one tab-separated line per decision to `.webpieces/hooks/guard-sync-decisions.log`. `root` is
46
46
  * the repo/workspace root that holds `.webpieces` (callers pass workspaceRoot, or process.cwd() at
47
47
  * the pre-load config-bypass site). Swallows all errors — logging must never block or fail a hook.
48
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"decision-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/decision-log.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n// The SYNC decision log — what the synchronous hook DID on each invocation and WHY. Its companion is\n// the ASYNC log (async-refresh.log, written by the detached refresher in main-sync-log.ts). This one\n// records EVERY guard decision — allow, block, config-bypass, and the fail-open cases — and CITES the\n// async-written cache snapshot (`cache` field) that drove the decision, so a wrong allow/block is\n// traceable to a stale or missing async write. Writes to `.webpieces/hooks/sync-decisions.log`.\nconst HOOKS_DIR = '.webpieces/hooks';\nconst LOG_FILE = 'sync-decisions.log';\nconst LOG_FILE_PREV = 'sync-decisions.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors rejection-log)\nconst MAX_TARGET_LEN = 160;\n\nexport type Verdict = 'ALLOW' | 'BLOCK';\n\n// Data-only record of one guard decision (per CLAUDE.md: classes for data, not object literals).\n// `cache` summarizes the async-written main-sync-status.json that drove a feature-branch-guard\n// decision (branch/merged/conflict/fork + the cache timestamp), or '-' when no cache was consulted\n// (bash guards, on-main, config-bypass).\nexport class GuardDecision {\n rule: string;\n tool: string;\n target: string; // file path (file guards) or the bash command (bash guards)\n branch: string;\n verdict: Verdict;\n reason: string;\n cache: string;\n\n constructor(rule: string, tool: string, target: string, branch: string, verdict: Verdict, reason: string, cache: string = '-') {\n this.rule = rule;\n this.tool = tool;\n this.target = target;\n this.branch = branch;\n this.verdict = verdict;\n this.reason = reason;\n this.cache = cache;\n }\n}\n\n/**\n * Append one tab-separated line per decision to `.webpieces/hooks/sync-decisions.log`. `root` is\n * the repo/workspace root that holds `.webpieces` (callers pass workspaceRoot, or process.cwd() at\n * the pre-load config-bypass site). Swallows all errors — logging must never block or fail a hook.\n */\nexport function logGuardDecision(root: string, decision: GuardDecision): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, 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 decision.verdict,\n decision.tool,\n oneLine(decision.target),\n decision.branch,\n decision.rule,\n oneLine(decision.reason),\n oneLine(decision.cache),\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// Best-effort current branch for the log line. Returns 'unknown' on any failure (e.g. not a git\n// repo) — this is for display only, never for a control decision.\nexport function branchForLog(root: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: root,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim() || 'unknown';\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return 'unknown';\n }\n}\n\n// Collapse newlines/tabs and cap length so one decision is always one log line.\nfunction oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_TARGET_LEN ? flat : flat.slice(0, MAX_TARGET_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":"decision-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/decision-log.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n// The SYNC decision log — what the synchronous hook DID on each invocation and WHY. Its companion is\n// the ASYNC log (guard-async-work.log, written by the detached refresher in main-sync-log.ts). This\n// one records EVERY guard decision — allow, block, config-bypass, and the fail-open cases — and CITES\n// the async-written cache snapshot (`cache` field) that drove the decision, so a wrong allow/block is\n// traceable to a stale or missing async write. Writes to `.webpieces/hooks/guard-sync-decisions.log`.\nconst HOOKS_DIR = '.webpieces/hooks';\nconst LOG_FILE = 'guard-sync-decisions.log';\nconst LOG_FILE_PREV = 'guard-sync-decisions.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors rejection-log)\nconst MAX_TARGET_LEN = 160;\n\nexport type Verdict = 'ALLOW' | 'BLOCK';\n\n// Data-only record of one guard decision (per CLAUDE.md: classes for data, not object literals).\n// `cache` summarizes the async-written main-sync-status.json that drove a feature-branch-guard\n// decision (branch/merged/conflict/fork + the cache timestamp), or '-' when no cache was consulted\n// (bash guards, on-main, config-bypass).\nexport class GuardDecision {\n rule: string;\n tool: string;\n target: string; // file path (file guards) or the bash command (bash guards)\n branch: string;\n verdict: Verdict;\n reason: string;\n cache: string;\n\n constructor(rule: string, tool: string, target: string, branch: string, verdict: Verdict, reason: string, cache: string = '-') {\n this.rule = rule;\n this.tool = tool;\n this.target = target;\n this.branch = branch;\n this.verdict = verdict;\n this.reason = reason;\n this.cache = cache;\n }\n}\n\n/**\n * Append one tab-separated line per decision to `.webpieces/hooks/guard-sync-decisions.log`. `root` is\n * the repo/workspace root that holds `.webpieces` (callers pass workspaceRoot, or process.cwd() at\n * the pre-load config-bypass site). Swallows all errors — logging must never block or fail a hook.\n */\nexport function logGuardDecision(root: string, decision: GuardDecision): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, 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 decision.verdict,\n decision.tool,\n oneLine(decision.target),\n decision.branch,\n decision.rule,\n oneLine(decision.reason),\n oneLine(decision.cache),\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// Best-effort current branch for the log line. Returns 'unknown' on any failure (e.g. not a git\n// repo) — this is for display only, never for a control decision.\nexport function branchForLog(root: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: root,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim() || 'unknown';\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return 'unknown';\n }\n}\n\n// Collapse newlines/tabs and cap length so one decision is always one log line.\nfunction oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_TARGET_LEN ? flat : flat.slice(0, MAX_TARGET_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"]}
@@ -7,7 +7,7 @@ export declare class SyncLogEvent {
7
7
  constructor(phase: SyncPhase, pid: number, branchArg: string, detail: string);
8
8
  }
9
9
  /**
10
- * Append one tab-separated line per refresher event to `.webpieces/hooks/async-refresh.log`. `root` is
10
+ * Append one tab-separated line per refresher event to `.webpieces/hooks/guard-async-work.log`. `root` is
11
11
  * the workspace root holding `.webpieces`. Swallows all errors — logging must never block or fail
12
12
  * the refresher (or the hook that spawns it).
13
13
  */
@@ -13,11 +13,11 @@ const to_error_1 = require("./to-error");
13
13
  // exited, with stdio discarded, so when it fails to update the cache there is normally no trace.
14
14
  // This log captures its lifecycle — SPAWN_ATTEMPT (parent side), then START / SKIP_INPROGRESS /
15
15
  // FINISH / ERROR (child side) — so we can tell whether the detached child never launched, was killed
16
- // mid-run (START with no FINISH), or threw. Writes to `.webpieces/hooks/async-refresh.log`.
16
+ // mid-run (START with no FINISH), or threw. Writes to `.webpieces/hooks/guard-async-work.log`.
17
17
  const HOOKS_DIR = '.webpieces/hooks';
18
- const LOG_FILE = 'async-refresh.log';
19
- const LOG_FILE_PREV = 'async-refresh.1.log';
20
- const STDERR_FILE = 'async-refresh.stderr.log';
18
+ const LOG_FILE = 'guard-async-work.log';
19
+ const LOG_FILE_PREV = 'guard-async-work.1.log';
20
+ const STDERR_FILE = 'guard-async-work.stderr.log';
21
21
  const MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors decision-log)
22
22
  const MAX_DETAIL_LEN = 300;
23
23
  // Data-only record of one refresher lifecycle event (per CLAUDE.md: classes for data).
@@ -35,7 +35,7 @@ class SyncLogEvent {
35
35
  }
36
36
  exports.SyncLogEvent = SyncLogEvent;
37
37
  /**
38
- * Append one tab-separated line per refresher event to `.webpieces/hooks/async-refresh.log`. `root` is
38
+ * Append one tab-separated line per refresher event to `.webpieces/hooks/guard-async-work.log`. `root` is
39
39
  * the workspace root holding `.webpieces`. Swallows all errors — logging must never block or fail
40
40
  * the refresher (or the hook that spawns it).
41
41
  */
@@ -1 +1 @@
1
- {"version":3,"file":"main-sync-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/main-sync-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n// The ASYNC log — observability for the detached background refresher (sync-main.ts) that writes\n// main-sync-status.json. Its companion is the SYNC log (sync-decisions.log, decision-log.ts) which\n// records what the hook DECIDED using that cache. The refresher runs AFTER the spawning hook has\n// exited, with stdio discarded, so when it fails to update the cache there is normally no trace.\n// This log captures its lifecycle — SPAWN_ATTEMPT (parent side), then START / SKIP_INPROGRESS /\n// FINISH / ERROR (child side) — so we can tell whether the detached child never launched, was killed\n// mid-run (START with no FINISH), or threw. Writes to `.webpieces/hooks/async-refresh.log`.\nconst HOOKS_DIR = '.webpieces/hooks';\nconst LOG_FILE = 'async-refresh.log';\nconst LOG_FILE_PREV = 'async-refresh.1.log';\nconst STDERR_FILE = 'async-refresh.stderr.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors decision-log)\nconst MAX_DETAIL_LEN = 300;\n\nexport type SyncPhase = 'SPAWN_ATTEMPT' | 'START' | 'SKIP_INPROGRESS' | 'FINISH' | 'ERROR';\n\n// Data-only record of one refresher lifecycle event (per CLAUDE.md: classes for data).\nexport class SyncLogEvent {\n phase: SyncPhase;\n pid: number;\n branchArg: string;\n detail: string;\n\n constructor(phase: SyncPhase, pid: number, branchArg: string, detail: string) {\n this.phase = phase;\n this.pid = pid;\n this.branchArg = branchArg;\n this.detail = detail;\n }\n}\n\n/**\n * Append one tab-separated line per refresher event to `.webpieces/hooks/async-refresh.log`. `root` is\n * the workspace root holding `.webpieces`. Swallows all errors — logging must never block or fail\n * the refresher (or the hook that spawns it).\n */\nexport function logSyncEvent(root: string, event: SyncLogEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, 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.phase,\n `pid=${String(event.pid)}`,\n event.branchArg,\n oneLine(event.detail),\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// Absolute path the detached child's stdout/stderr are redirected to (opened with fs.openSync(p,'a')\n// by the spawner), so even a crash BEFORE our own logging runs — e.g. a module-load failure — is\n// captured instead of vanishing into /dev/null. Callers must ensure the hooks dir exists first\n// (logSyncEvent's mkdir, called for SPAWN_ATTEMPT, does that).\nexport function syncStderrLogPath(root: string): string {\n return path.join(root, HOOKS_DIR, STDERR_FILE);\n}\n\n// Collapse newlines/tabs and cap length so one event is always 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":"main-sync-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/main-sync-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { toError } from './to-error';\n\n// The ASYNC log — observability for the detached background refresher (sync-main.ts) that writes\n// main-sync-status.json. Its companion is the SYNC log (sync-decisions.log, decision-log.ts) which\n// records what the hook DECIDED using that cache. The refresher runs AFTER the spawning hook has\n// exited, with stdio discarded, so when it fails to update the cache there is normally no trace.\n// This log captures its lifecycle — SPAWN_ATTEMPT (parent side), then START / SKIP_INPROGRESS /\n// FINISH / ERROR (child side) — so we can tell whether the detached child never launched, was killed\n// mid-run (START with no FINISH), or threw. Writes to `.webpieces/hooks/guard-async-work.log`.\nconst HOOKS_DIR = '.webpieces/hooks';\nconst LOG_FILE = 'guard-async-work.log';\nconst LOG_FILE_PREV = 'guard-async-work.1.log';\nconst STDERR_FILE = 'guard-async-work.stderr.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors decision-log)\nconst MAX_DETAIL_LEN = 300;\n\nexport type SyncPhase = 'SPAWN_ATTEMPT' | 'START' | 'SKIP_INPROGRESS' | 'FINISH' | 'ERROR';\n\n// Data-only record of one refresher lifecycle event (per CLAUDE.md: classes for data).\nexport class SyncLogEvent {\n phase: SyncPhase;\n pid: number;\n branchArg: string;\n detail: string;\n\n constructor(phase: SyncPhase, pid: number, branchArg: string, detail: string) {\n this.phase = phase;\n this.pid = pid;\n this.branchArg = branchArg;\n this.detail = detail;\n }\n}\n\n/**\n * Append one tab-separated line per refresher event to `.webpieces/hooks/guard-async-work.log`. `root` is\n * the workspace root holding `.webpieces`. Swallows all errors — logging must never block or fail\n * the refresher (or the hook that spawns it).\n */\nexport function logSyncEvent(root: string, event: SyncLogEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, 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.phase,\n `pid=${String(event.pid)}`,\n event.branchArg,\n oneLine(event.detail),\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// Absolute path the detached child's stdout/stderr are redirected to (opened with fs.openSync(p,'a')\n// by the spawner), so even a crash BEFORE our own logging runs — e.g. a module-load failure — is\n// captured instead of vanishing into /dev/null. Callers must ensure the hooks dir exists first\n// (logSyncEvent's mkdir, called for SPAWN_ATTEMPT, does that).\nexport function syncStderrLogPath(root: string): string {\n return path.join(root, HOOKS_DIR, STDERR_FILE);\n}\n\n// Collapse newlines/tabs and cap length so one event is always 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"]}
@@ -5,8 +5,8 @@
5
5
  * the NEXT call. This is the first detached spawn in the codebase — every existing hook is synchronous.
6
6
  *
7
7
  * Observability: we log SPAWN_ATTEMPT here and the child logs START/FINISH/ERROR, all to
8
- * `.webpieces/hooks/async-refresh.log`. The child's stdout/stderr are redirected to a sibling file (not
9
- * /dev/null) so a crash before the child's own logging is still captured. If async-refresh.log shows
8
+ * `.webpieces/hooks/guard-async-work.log`. The child's stdout/stderr are redirected to a sibling file (not
9
+ * /dev/null) so a crash before the child's own logging is still captured. If guard-async-work.log shows
10
10
  * SPAWN_ATTEMPT but never START, the detached child was killed before it ran.
11
11
  */
12
12
  export declare function triggerMainSyncRefresh(workspaceRoot: string, hangTimeoutMinutes?: number): void;
@@ -15,8 +15,8 @@ const main_sync_log_1 = require("./main-sync-log");
15
15
  * the NEXT call. This is the first detached spawn in the codebase — every existing hook is synchronous.
16
16
  *
17
17
  * Observability: we log SPAWN_ATTEMPT here and the child logs START/FINISH/ERROR, all to
18
- * `.webpieces/hooks/async-refresh.log`. The child's stdout/stderr are redirected to a sibling file (not
19
- * /dev/null) so a crash before the child's own logging is still captured. If async-refresh.log shows
18
+ * `.webpieces/hooks/guard-async-work.log`. The child's stdout/stderr are redirected to a sibling file (not
19
+ * /dev/null) so a crash before the child's own logging is still captured. If guard-async-work.log shows
20
20
  * SPAWN_ATTEMPT but never START, the detached child was killed before it ran.
21
21
  */
22
22
  function triggerMainSyncRefresh(workspaceRoot, hangTimeoutMinutes = rules_config_1.DEFAULT_HANG_TIMEOUT_MINUTES) {
@@ -1 +1 @@
1
- {"version":3,"file":"main-sync-refresh.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/main-sync-refresh.ts"],"names":[],"mappings":";;AAoBA,wDA2BC;;AA/CD,iDAAsC;AACtC,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAuE;AAEvE,yCAAqC;AACrC,mDAAgF;AAEhF;;;;;;;;;;GAUG;AACH,SAAgB,sBAAsB,CAAC,aAAqB,EAAE,qBAA6B,2CAA4B;IACnH,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACvD,4FAA4F;QAC5F,IAAA,4BAAY,EAAC,aAAa,EAAE,IAAI,4BAAY,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,aAAa,SAAS,EAAE,CAAC,CAAC,CAAC;QAE3G,6FAA6F;QAC7F,mFAAmF;QACnF,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAA,iCAAiB,EAAC,aAAa,CAAC,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE;YAC1F,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;SAClC,CAAC,CAAC;QACH,8FAA8F;QAC9F,wFAAwF;QACxF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAU,EAAQ,EAAE;YACrC,IAAA,4BAAY,EAAC,aAAa,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACjH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,mFAAmF;QACnF,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,0EAA0E;IAC9E,CAAC;AACL,CAAC","sourcesContent":["import { spawn } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { DEFAULT_HANG_TIMEOUT_MINUTES } from '@webpieces/rules-config';\n\nimport { toError } from './to-error';\nimport { logSyncEvent, SyncLogEvent, syncStderrLogPath } from './main-sync-log';\n\n/**\n * Fire-and-forget spawn of the detached refresher (sync-main.js in this same dir — spawned by path,\n * not a bin). The child outlives this hook process (`detached` + `unref`), does the slow\n * merged-PR/fetch/merge-base/overlap work, and writes the cache the feature-branch-guard reads on\n * the NEXT call. This is the first detached spawn in the codebase — every existing hook is synchronous.\n *\n * Observability: we log SPAWN_ATTEMPT here and the child logs START/FINISH/ERROR, all to\n * `.webpieces/hooks/async-refresh.log`. The child's stdout/stderr are redirected to a sibling file (not\n * /dev/null) so a crash before the child's own logging is still captured. If async-refresh.log shows\n * SPAWN_ATTEMPT but never START, the detached child was killed before it ran.\n */\nexport function triggerMainSyncRefresh(workspaceRoot: string, hangTimeoutMinutes: number = DEFAULT_HANG_TIMEOUT_MINUTES): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const refresher = path.join(__dirname, 'sync-main.js');\n // SPAWN_ATTEMPT first — this also creates .webpieces/hooks so the stderr fd below can open.\n logSyncEvent(workspaceRoot, new SyncLogEvent('SPAWN_ATTEMPT', process.pid, '-', `refresher=${refresher}`));\n\n // Redirect the detached child's stdout+stderr to a file (not /dev/null) so an uncaught crash\n // before the child's own logging — e.g. a module-load failure — is still captured.\n const errFd = fs.openSync(syncStderrLogPath(workspaceRoot), 'a');\n const child = spawn(process.execPath, [refresher, workspaceRoot, String(hangTimeoutMinutes)], {\n detached: true,\n stdio: ['ignore', errFd, errFd],\n });\n // spawn errors (e.g. ENOENT) arrive asynchronously; record one if it fires. The hook may exit\n // before this handler runs, but on POSIX a successful exec has already happened by now.\n child.once('error', (err: Error): void => {\n logSyncEvent(workspaceRoot, new SyncLogEvent('ERROR', child.pid ?? -1, '-', `spawn failed: ${err.message}`));\n });\n child.unref();\n // The child has its own dup'd copy of the fd after spawn; close the parent's copy.\n fs.closeSync(errFd);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n // Spawning the background refresh must never block or fail the tool call.\n }\n}\n"]}
1
+ {"version":3,"file":"main-sync-refresh.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/main-sync-refresh.ts"],"names":[],"mappings":";;AAoBA,wDA2BC;;AA/CD,iDAAsC;AACtC,+CAAyB;AACzB,mDAA6B;AAE7B,0DAAuE;AAEvE,yCAAqC;AACrC,mDAAgF;AAEhF;;;;;;;;;;GAUG;AACH,SAAgB,sBAAsB,CAAC,aAAqB,EAAE,qBAA6B,2CAA4B;IACnH,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;QACvD,4FAA4F;QAC5F,IAAA,4BAAY,EAAC,aAAa,EAAE,IAAI,4BAAY,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,aAAa,SAAS,EAAE,CAAC,CAAC,CAAC;QAE3G,6FAA6F;QAC7F,mFAAmF;QACnF,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAA,iCAAiB,EAAC,aAAa,CAAC,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE;YAC1F,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC;SAClC,CAAC,CAAC;QACH,8FAA8F;QAC9F,wFAAwF;QACxF,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAU,EAAQ,EAAE;YACrC,IAAA,4BAAY,EAAC,aAAa,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,iBAAiB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACjH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,mFAAmF;QACnF,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,0EAA0E;IAC9E,CAAC;AACL,CAAC","sourcesContent":["import { spawn } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { DEFAULT_HANG_TIMEOUT_MINUTES } from '@webpieces/rules-config';\n\nimport { toError } from './to-error';\nimport { logSyncEvent, SyncLogEvent, syncStderrLogPath } from './main-sync-log';\n\n/**\n * Fire-and-forget spawn of the detached refresher (sync-main.js in this same dir — spawned by path,\n * not a bin). The child outlives this hook process (`detached` + `unref`), does the slow\n * merged-PR/fetch/merge-base/overlap work, and writes the cache the feature-branch-guard reads on\n * the NEXT call. This is the first detached spawn in the codebase — every existing hook is synchronous.\n *\n * Observability: we log SPAWN_ATTEMPT here and the child logs START/FINISH/ERROR, all to\n * `.webpieces/hooks/guard-async-work.log`. The child's stdout/stderr are redirected to a sibling file (not\n * /dev/null) so a crash before the child's own logging is still captured. If guard-async-work.log shows\n * SPAWN_ATTEMPT but never START, the detached child was killed before it ran.\n */\nexport function triggerMainSyncRefresh(workspaceRoot: string, hangTimeoutMinutes: number = DEFAULT_HANG_TIMEOUT_MINUTES): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const refresher = path.join(__dirname, 'sync-main.js');\n // SPAWN_ATTEMPT first — this also creates .webpieces/hooks so the stderr fd below can open.\n logSyncEvent(workspaceRoot, new SyncLogEvent('SPAWN_ATTEMPT', process.pid, '-', `refresher=${refresher}`));\n\n // Redirect the detached child's stdout+stderr to a file (not /dev/null) so an uncaught crash\n // before the child's own logging — e.g. a module-load failure — is still captured.\n const errFd = fs.openSync(syncStderrLogPath(workspaceRoot), 'a');\n const child = spawn(process.execPath, [refresher, workspaceRoot, String(hangTimeoutMinutes)], {\n detached: true,\n stdio: ['ignore', errFd, errFd],\n });\n // spawn errors (e.g. ENOENT) arrive asynchronously; record one if it fires. The hook may exit\n // before this handler runs, but on POSIX a successful exec has already happened by now.\n child.once('error', (err: Error): void => {\n logSyncEvent(workspaceRoot, new SyncLogEvent('ERROR', child.pid ?? -1, '-', `spawn failed: ${err.message}`));\n });\n child.unref();\n // The child has its own dup'd copy of the fd after spawn; close the parent's copy.\n fs.closeSync(errFd);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n // Spawning the background refresh must never block or fail the tool call.\n }\n}\n"]}
@@ -21,7 +21,7 @@ function main() {
21
21
  const repoRoot = process.argv[2] ?? process.cwd();
22
22
  const hangTimeoutMinutes = Number(process.argv[3]) || rules_config_1.DEFAULT_HANG_TIMEOUT_MINUTES;
23
23
  const startedMs = Date.now();
24
- // First action: prove the detached child actually started. If async-refresh.log has no START line
24
+ // First action: prove the detached child actually started. If guard-async-work.log has no START line
25
25
  // for a spawn, the child never launched (or died before this point).
26
26
  (0, main_sync_log_1.logSyncEvent)(repoRoot, new main_sync_log_1.SyncLogEvent('START', process.pid, '-', `argv=${process.argv.slice(2).join(' ')}`));
27
27
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
@@ -1 +1 @@
1
- {"version":3,"file":"sync-main.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/sync-main.ts"],"names":[],"mappings":";;AA0BA,oBAsCC;AAhED,0DAQiC;AAEjC,yCAAqC;AACrC,mDAA6D;AAE7D;;;;;;;;;;;;GAYG;AACH,SAAgB,IAAI;IAChB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAClD,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,2CAA4B,CAAC;IACnF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,kGAAkG;IAClG,qEAAqE;IACrE,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/G,8DAA8D;IAC9D,IAAI,CAAC;QACD,IAAI,IAAA,kCAAmB,EAAC,QAAQ,EAAE,kBAAkB,CAAC,EAAE,CAAC;YACpD,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,gCAAgC,CAAC,CAAC,CAAC;YAChH,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAA,4BAAa,GAAE,CAAC;QAC7B,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClC,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAA,oCAAqB,EAAC,QAAQ,CAAC,CAAC;YAC/C,IAAA,kCAAmB,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACtC,uFAAuF;YACvF,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CACnC,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EACpC,UAAU,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,aAAa,MAAM,CAAC,QAAQ,cAAc,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,EAAE,CAC/L,CAAC,CAAC;QACP,CAAC;gBAAS,CAAC;YACP,gFAAgF;YAChF,8BAA8B;YAC9B,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAA,2BAAY,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,8FAA8F;QAC9F,qFAAqF;QACrF,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACnH,CAAC;AACL,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,CAAC;AACX,CAAC","sourcesContent":["import {\n DEFAULT_HANG_TIMEOUT_MINUTES,\n computeMainSyncStatus,\n writeMainSyncStatus,\n writeMainSyncLock,\n isRefreshInProgress,\n inProcessLock,\n finishedLock,\n} from '@webpieces/rules-config';\n\nimport { toError } from './to-error';\nimport { logSyncEvent, SyncLogEvent } from './main-sync-log';\n\n/**\n * The detached, fire-and-forget refresher spawned (by file path, not a bin) from\n * main-sync-refresh.ts. It does the SLOW work (merged-PR lookup + git fetch + merge-base +\n * same-file-overlap) and writes `.webpieces/main-sync-status.json` so the next hook call reads it\n * instantly. Nobody reads our exit code or output — we run after the spawning hook has returned.\n *\n * Concurrency: a lock file (`.webpieces/main-sync.lock.json`) holds `inprocess`/`finished` + a start\n * epoch. If another refresher is already `inprocess` and younger than hangTimeoutMinutes, we exit\n * immediately (don't pile up `git fetch`es). If it's `inprocess` but older than hangTimeoutMinutes,\n * we assume it hung and proceed anyway.\n *\n * argv: [, , repoRoot, hangTimeoutMinutes]\n */\nexport function main(): void {\n const repoRoot = process.argv[2] ?? process.cwd();\n const hangTimeoutMinutes = Number(process.argv[3]) || DEFAULT_HANG_TIMEOUT_MINUTES;\n const startedMs = Date.now();\n\n // First action: prove the detached child actually started. If async-refresh.log has no START line\n // for a spawn, the child never launched (or died before this point).\n logSyncEvent(repoRoot, new SyncLogEvent('START', process.pid, '-', `argv=${process.argv.slice(2).join(' ')}`));\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (isRefreshInProgress(repoRoot, hangTimeoutMinutes)) {\n logSyncEvent(repoRoot, new SyncLogEvent('SKIP_INPROGRESS', process.pid, '-', 'another refresh is in progress'));\n return;\n }\n\n const lock = inProcessLock();\n writeMainSyncLock(repoRoot, lock);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const status = computeMainSyncStatus(repoRoot);\n writeMainSyncStatus(repoRoot, status);\n // FINISH after a successful write — START-without-FINISH means we were killed mid-run.\n logSyncEvent(repoRoot, new SyncLogEvent(\n 'FINISH', process.pid, status.branch,\n `merged=${String(status.branchAlreadyMerged)} mergedPr=${status.mergedPr} forkPoint=${String(status.hasForkPoint)} conflict=${String(status.conflict)} ms=${String(Date.now() - startedMs)}`,\n ));\n } finally {\n // Always flip the lock off so a compute failure can't wedge the guard until the\n // staleness reclaim kicks in.\n writeMainSyncLock(repoRoot, finishedLock(lock.started));\n }\n } catch (err: unknown) {\n const error = toError(err);\n // Detached: swallow so a transient git/fs error never leaves poison state (the next hook call\n // spawns a fresh refresher) — but record WHY it died so the failure isn't invisible.\n logSyncEvent(repoRoot, new SyncLogEvent('ERROR', process.pid, '-', `${error.message} | ${error.stack ?? ''}`));\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}
1
+ {"version":3,"file":"sync-main.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/sync-main.ts"],"names":[],"mappings":";;AA0BA,oBAsCC;AAhED,0DAQiC;AAEjC,yCAAqC;AACrC,mDAA6D;AAE7D;;;;;;;;;;;;GAYG;AACH,SAAgB,IAAI;IAChB,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAClD,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,2CAA4B,CAAC;IACnF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,qGAAqG;IACrG,qEAAqE;IACrE,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/G,8DAA8D;IAC9D,IAAI,CAAC;QACD,IAAI,IAAA,kCAAmB,EAAC,QAAQ,EAAE,kBAAkB,CAAC,EAAE,CAAC;YACpD,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,gCAAgC,CAAC,CAAC,CAAC;YAChH,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAG,IAAA,4BAAa,GAAE,CAAC;QAC7B,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClC,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAA,oCAAqB,EAAC,QAAQ,CAAC,CAAC;YAC/C,IAAA,kCAAmB,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACtC,uFAAuF;YACvF,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CACnC,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EACpC,UAAU,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,aAAa,MAAM,CAAC,QAAQ,cAAc,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,EAAE,CAC/L,CAAC,CAAC;QACP,CAAC;gBAAS,CAAC;YACP,gFAAgF;YAChF,8BAA8B;YAC9B,IAAA,gCAAiB,EAAC,QAAQ,EAAE,IAAA,2BAAY,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5D,CAAC;IACL,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,8FAA8F;QAC9F,qFAAqF;QACrF,IAAA,4BAAY,EAAC,QAAQ,EAAE,IAAI,4BAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACnH,CAAC;AACL,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,CAAC;AACX,CAAC","sourcesContent":["import {\n DEFAULT_HANG_TIMEOUT_MINUTES,\n computeMainSyncStatus,\n writeMainSyncStatus,\n writeMainSyncLock,\n isRefreshInProgress,\n inProcessLock,\n finishedLock,\n} from '@webpieces/rules-config';\n\nimport { toError } from './to-error';\nimport { logSyncEvent, SyncLogEvent } from './main-sync-log';\n\n/**\n * The detached, fire-and-forget refresher spawned (by file path, not a bin) from\n * main-sync-refresh.ts. It does the SLOW work (merged-PR lookup + git fetch + merge-base +\n * same-file-overlap) and writes `.webpieces/main-sync-status.json` so the next hook call reads it\n * instantly. Nobody reads our exit code or output — we run after the spawning hook has returned.\n *\n * Concurrency: a lock file (`.webpieces/main-sync.lock.json`) holds `inprocess`/`finished` + a start\n * epoch. If another refresher is already `inprocess` and younger than hangTimeoutMinutes, we exit\n * immediately (don't pile up `git fetch`es). If it's `inprocess` but older than hangTimeoutMinutes,\n * we assume it hung and proceed anyway.\n *\n * argv: [, , repoRoot, hangTimeoutMinutes]\n */\nexport function main(): void {\n const repoRoot = process.argv[2] ?? process.cwd();\n const hangTimeoutMinutes = Number(process.argv[3]) || DEFAULT_HANG_TIMEOUT_MINUTES;\n const startedMs = Date.now();\n\n // First action: prove the detached child actually started. If guard-async-work.log has no START line\n // for a spawn, the child never launched (or died before this point).\n logSyncEvent(repoRoot, new SyncLogEvent('START', process.pid, '-', `argv=${process.argv.slice(2).join(' ')}`));\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n if (isRefreshInProgress(repoRoot, hangTimeoutMinutes)) {\n logSyncEvent(repoRoot, new SyncLogEvent('SKIP_INPROGRESS', process.pid, '-', 'another refresh is in progress'));\n return;\n }\n\n const lock = inProcessLock();\n writeMainSyncLock(repoRoot, lock);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const status = computeMainSyncStatus(repoRoot);\n writeMainSyncStatus(repoRoot, status);\n // FINISH after a successful write — START-without-FINISH means we were killed mid-run.\n logSyncEvent(repoRoot, new SyncLogEvent(\n 'FINISH', process.pid, status.branch,\n `merged=${String(status.branchAlreadyMerged)} mergedPr=${status.mergedPr} forkPoint=${String(status.hasForkPoint)} conflict=${String(status.conflict)} ms=${String(Date.now() - startedMs)}`,\n ));\n } finally {\n // Always flip the lock off so a compute failure can't wedge the guard until the\n // staleness reclaim kicks in.\n writeMainSyncLock(repoRoot, finishedLock(lock.started));\n }\n } catch (err: unknown) {\n const error = toError(err);\n // Detached: swallow so a transient git/fs error never leaves poison state (the next hook call\n // spawns a fresh refresher) — but record WHY it died so the failure isn't invisible.\n logSyncEvent(repoRoot, new SyncLogEvent('ERROR', process.pid, '-', `${error.message} | ${error.stack ?? ''}`));\n }\n}\n\nif (require.main === module) {\n main();\n}\n"]}