@prism-d1/cli 1.0.99 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/assets/github-workflows/prism-agent-eval.yml +2 -1
  2. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +11 -1
  3. package/dist/assets/gitlab-workflows/prism-eval-gate.yml +8 -0
  4. package/dist/assets/infra/lib/constructs/otel-collector-construct.ts +312 -0
  5. package/dist/assets/infra/lib/dashboard-stack.ts +74 -45
  6. package/dist/assets/infra/lib/lambda/otel-receiver.ts +324 -12
  7. package/dist/assets/infra/lib/lambda/pre-token-generation.ts +79 -0
  8. package/dist/assets/infra/lib/lambda/pricing/litellm-snapshot.json +3769 -0
  9. package/dist/assets/infra/lib/lambda/pricing/price-source.ts +56 -0
  10. package/dist/assets/infra/lib/lambda/pricing/price-table.ts +240 -0
  11. package/dist/assets/infra/lib/lambda/pricing/prices-key.ts +6 -0
  12. package/dist/assets/infra/lib/lambda/pricing/reprice.ts +108 -0
  13. package/dist/assets/infra/lib/lambda/pricing-refresh.ts +352 -0
  14. package/dist/assets/infra/lib/lambda/velocity-widget.ts +397 -1
  15. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +13 -0
  16. package/dist/assets/infra/package.json +5 -2
  17. package/dist/assets/metric-hooks/prepare-commit-msg +3 -1
  18. package/dist/src/codewatch/config.d.ts +17 -0
  19. package/dist/src/codewatch/config.d.ts.map +1 -0
  20. package/dist/src/codewatch/config.js +40 -0
  21. package/dist/src/codewatch/config.js.map +1 -0
  22. package/dist/src/codewatch/gitleaks.d.ts +49 -0
  23. package/dist/src/codewatch/gitleaks.d.ts.map +1 -0
  24. package/dist/src/codewatch/gitleaks.js +160 -0
  25. package/dist/src/codewatch/gitleaks.js.map +1 -0
  26. package/dist/src/codewatch/globalHooks.d.ts +52 -0
  27. package/dist/src/codewatch/globalHooks.d.ts.map +1 -0
  28. package/dist/src/codewatch/globalHooks.js +235 -0
  29. package/dist/src/codewatch/globalHooks.js.map +1 -0
  30. package/dist/src/codewatch/hooks.d.ts +38 -0
  31. package/dist/src/codewatch/hooks.d.ts.map +1 -0
  32. package/dist/src/codewatch/hooks.js +176 -0
  33. package/dist/src/codewatch/hooks.js.map +1 -0
  34. package/dist/src/codewatch/prepush.d.ts +37 -0
  35. package/dist/src/codewatch/prepush.d.ts.map +1 -0
  36. package/dist/src/codewatch/prepush.js +231 -0
  37. package/dist/src/codewatch/prepush.js.map +1 -0
  38. package/dist/src/codewatch/results.d.ts +90 -0
  39. package/dist/src/codewatch/results.d.ts.map +1 -0
  40. package/dist/src/codewatch/results.js +121 -0
  41. package/dist/src/codewatch/results.js.map +1 -0
  42. package/dist/src/codewatch/reviewers/claude.d.ts +9 -0
  43. package/dist/src/codewatch/reviewers/claude.d.ts.map +1 -0
  44. package/dist/src/codewatch/reviewers/claude.js +48 -0
  45. package/dist/src/codewatch/reviewers/claude.js.map +1 -0
  46. package/dist/src/codewatch/reviewers/index.d.ts +39 -0
  47. package/dist/src/codewatch/reviewers/index.d.ts.map +1 -0
  48. package/dist/src/codewatch/reviewers/index.js +97 -0
  49. package/dist/src/codewatch/reviewers/index.js.map +1 -0
  50. package/dist/src/codewatch/reviewers/kiro.d.ts +14 -0
  51. package/dist/src/codewatch/reviewers/kiro.d.ts.map +1 -0
  52. package/dist/src/codewatch/reviewers/kiro.js +32 -0
  53. package/dist/src/codewatch/reviewers/kiro.js.map +1 -0
  54. package/dist/src/codewatch/reviewers/types.d.ts +56 -0
  55. package/dist/src/codewatch/reviewers/types.d.ts.map +1 -0
  56. package/dist/src/codewatch/reviewers/types.js +91 -0
  57. package/dist/src/codewatch/reviewers/types.js.map +1 -0
  58. package/dist/src/codewatch/rules.d.ts +12 -0
  59. package/dist/src/codewatch/rules.d.ts.map +1 -0
  60. package/dist/src/codewatch/rules.js +27 -0
  61. package/dist/src/codewatch/rules.js.map +1 -0
  62. package/dist/src/commands/_top/dashboard.d.ts +17 -0
  63. package/dist/src/commands/_top/dashboard.d.ts.map +1 -0
  64. package/dist/src/commands/_top/dashboard.js +76 -0
  65. package/dist/src/commands/_top/dashboard.js.map +1 -0
  66. package/dist/src/commands/bedrock-protection/scan-account.d.ts.map +1 -1
  67. package/dist/src/commands/bedrock-protection/scan-account.js +2 -1
  68. package/dist/src/commands/bedrock-protection/scan-account.js.map +1 -1
  69. package/dist/src/commands/bedrock-protection/scan-org.d.ts.map +1 -1
  70. package/dist/src/commands/bedrock-protection/scan-org.js +16 -4
  71. package/dist/src/commands/bedrock-protection/scan-org.js.map +1 -1
  72. package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
  73. package/dist/src/commands/bootstrapper/install-eval-harness.js +15 -48
  74. package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
  75. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts +13 -0
  76. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
  77. package/dist/src/commands/bootstrapper/install-git-hooks.js +28 -147
  78. package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
  79. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts.map +1 -1
  80. package/dist/src/commands/bootstrapper/setup-otel-sync.js +5 -39
  81. package/dist/src/commands/bootstrapper/setup-otel-sync.js.map +1 -1
  82. package/dist/src/commands/codewatch/pre-commit.d.ts +7 -0
  83. package/dist/src/commands/codewatch/pre-commit.d.ts.map +1 -0
  84. package/dist/src/commands/codewatch/pre-commit.js +30 -0
  85. package/dist/src/commands/codewatch/pre-commit.js.map +1 -0
  86. package/dist/src/commands/codewatch/pre-push.d.ts +16 -0
  87. package/dist/src/commands/codewatch/pre-push.d.ts.map +1 -0
  88. package/dist/src/commands/codewatch/pre-push.js +32 -0
  89. package/dist/src/commands/codewatch/pre-push.js.map +1 -0
  90. package/dist/src/commands/codewatch/report.d.ts +22 -0
  91. package/dist/src/commands/codewatch/report.d.ts.map +1 -0
  92. package/dist/src/commands/codewatch/report.js +64 -0
  93. package/dist/src/commands/codewatch/report.js.map +1 -0
  94. package/dist/src/commands/enable/codewatch.d.ts +15 -0
  95. package/dist/src/commands/enable/codewatch.d.ts.map +1 -0
  96. package/dist/src/commands/enable/codewatch.js +186 -0
  97. package/dist/src/commands/enable/codewatch.js.map +1 -0
  98. package/dist/src/commands/git/claude-session-context.d.ts.map +1 -1
  99. package/dist/src/commands/git/claude-session-context.js +2 -1
  100. package/dist/src/commands/git/claude-session-context.js.map +1 -1
  101. package/dist/src/commands/git/commit-trailers.js +1 -1
  102. package/dist/src/commands/git/commit-trailers.js.map +1 -1
  103. package/dist/src/commands/workshop/verify-setup.d.ts.map +1 -1
  104. package/dist/src/commands/workshop/verify-setup.js +5 -3
  105. package/dist/src/commands/workshop/verify-setup.js.map +1 -1
  106. package/dist/src/dashboard/codewatch.d.ts +106 -0
  107. package/dist/src/dashboard/codewatch.d.ts.map +1 -0
  108. package/dist/src/dashboard/codewatch.js +119 -0
  109. package/dist/src/dashboard/codewatch.js.map +1 -0
  110. package/dist/src/dashboard/commits.d.ts +96 -0
  111. package/dist/src/dashboard/commits.d.ts.map +1 -0
  112. package/dist/src/dashboard/commits.js +207 -0
  113. package/dist/src/dashboard/commits.js.map +1 -0
  114. package/dist/src/dashboard/page.d.ts +10 -0
  115. package/dist/src/dashboard/page.d.ts.map +1 -0
  116. package/dist/src/dashboard/page.js +315 -0
  117. package/dist/src/dashboard/page.js.map +1 -0
  118. package/dist/src/dashboard/payload.d.ts +111 -0
  119. package/dist/src/dashboard/payload.d.ts.map +1 -0
  120. package/dist/src/dashboard/payload.js +93 -0
  121. package/dist/src/dashboard/payload.js.map +1 -0
  122. package/dist/src/dashboard/report.d.ts +99 -0
  123. package/dist/src/dashboard/report.d.ts.map +1 -0
  124. package/dist/src/dashboard/report.js +300 -0
  125. package/dist/src/dashboard/report.js.map +1 -0
  126. package/dist/src/dashboard/repos.d.ts +58 -0
  127. package/dist/src/dashboard/repos.d.ts.map +1 -0
  128. package/dist/src/dashboard/repos.js +111 -0
  129. package/dist/src/dashboard/repos.js.map +1 -0
  130. package/dist/src/dashboard/server.d.ts +52 -0
  131. package/dist/src/dashboard/server.d.ts.map +1 -0
  132. package/dist/src/dashboard/server.js +163 -0
  133. package/dist/src/dashboard/server.js.map +1 -0
  134. package/dist/src/dashboard/setup.d.ts +52 -0
  135. package/dist/src/dashboard/setup.d.ts.map +1 -0
  136. package/dist/src/dashboard/setup.js +129 -0
  137. package/dist/src/dashboard/setup.js.map +1 -0
  138. package/dist/src/dashboard/usage.d.ts +43 -0
  139. package/dist/src/dashboard/usage.d.ts.map +1 -0
  140. package/dist/src/dashboard/usage.js +88 -0
  141. package/dist/src/dashboard/usage.js.map +1 -0
  142. package/dist/src/index.d.ts +1 -0
  143. package/dist/src/index.d.ts.map +1 -1
  144. package/dist/src/index.js +40 -1
  145. package/dist/src/index.js.map +1 -1
  146. package/dist/src/utils/audit-bedrock.d.ts +1 -0
  147. package/dist/src/utils/audit-bedrock.d.ts.map +1 -1
  148. package/dist/src/utils/audit-bedrock.js +133 -0
  149. package/dist/src/utils/audit-bedrock.js.map +1 -1
  150. package/dist/src/utils/audit-org.d.ts +1 -0
  151. package/dist/src/utils/audit-org.d.ts.map +1 -1
  152. package/dist/src/utils/audit-org.js +155 -0
  153. package/dist/src/utils/audit-org.js.map +1 -1
  154. package/dist/src/utils/otel-sync-schedule.d.ts +22 -0
  155. package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -0
  156. package/dist/src/utils/otel-sync-schedule.js +68 -0
  157. package/dist/src/utils/otel-sync-schedule.js.map +1 -0
  158. package/dist/src/utils/tool-check.d.ts +14 -0
  159. package/dist/src/utils/tool-check.d.ts.map +1 -0
  160. package/dist/src/utils/tool-check.js +42 -0
  161. package/dist/src/utils/tool-check.js.map +1 -0
  162. package/package.json +3 -2
@@ -0,0 +1,160 @@
1
+ /**
2
+ * gitleaks: shared starter config, report parsing, and the pre-commit scan.
3
+ */
4
+ import { spawnSync } from 'node:child_process';
5
+ import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { tmpdir } from 'node:os';
7
+ import { join, resolve } from 'node:path';
8
+ import { GITLEAKS_BASELINE_PATH, GITLEAKS_CONFIG_PATH } from './config.js';
9
+ import { makeResult, REASON_GITLEAKS_MISSING, writeResult } from './results.js';
10
+ import { run } from '../utils/exec.js';
11
+ import { toolVersion } from './reviewers/index.js';
12
+ /**
13
+ * Starter gitleaks config, written only when the repo has none.
14
+ *
15
+ * The secret-scan step in the workflow runs with or without this file --
16
+ * gitleaks ships usable defaults. What the defaults do not know is which
17
+ * credential-shaped strings in THIS repo are deliberate, so the first PR that
18
+ * touches a test fixture fails the gate on a fake key. A team that hits that
19
+ * with no obvious place to record the exception tends to delete the gate rather
20
+ * than tune it, so the tuning surface ships up front and is documented inline.
21
+ *
22
+ * `useDefault = true` is what keeps this additive: without it, declaring a
23
+ * config REPLACES the entire default ruleset and the gate silently stops
24
+ * detecting anything it does not name explicitly.
25
+ */
26
+ export const GITLEAKS_STARTER_CONFIG = `# gitleaks configuration for the PRISM eval gate.
27
+ #
28
+ # The gate scans only the commits a PR introduces, so anything flagged here is
29
+ # something this PR added. Two ways to handle a false positive:
30
+ #
31
+ # 1. Allowlist the path or pattern below (preferred -- reviewable, and it
32
+ # keeps working as the file changes).
33
+ # 2. Add an inline "gitleaks:allow" comment on the offending line.
34
+ #
35
+ # A real exposure is NOT a false positive: rotate the credential first, because
36
+ # deleting it from the branch does not un-expose what was already pushed.
37
+
38
+ [extend]
39
+ # Keep gitleaks' built-in ruleset. Removing this line replaces the defaults
40
+ # entirely rather than adding to them, which turns the gate into a no-op for
41
+ # every credential type not restated in this file.
42
+ useDefault = true
43
+
44
+ [[allowlists]]
45
+ description = "Test fixtures and documented examples"
46
+ paths = [
47
+ '''(^|/)tests?/''',
48
+ '''(^|/)__fixtures__/''',
49
+ '''(^|/)__tests__/''',
50
+ '''\\.example($|\\.)''',
51
+ ]
52
+ `;
53
+ /**
54
+ * One-line, platform-specific gitleaks install hint. Never a curl-piped-to-shell.
55
+ * Single source for the installer, the pre-commit warning and the dashboard.
56
+ */
57
+ export function gitleaksInstallHint(platform = process.platform) {
58
+ const release = 'a release binary from https://github.com/gitleaks/gitleaks/releases';
59
+ if (platform === 'darwin')
60
+ return `brew install gitleaks (or ${release})`;
61
+ if (platform === 'win32')
62
+ return `scoop install gitleaks (or ${release})`;
63
+ return `${release} on your PATH, or: go install github.com/zricethezav/gitleaks/v8@latest`;
64
+ }
65
+ /** Writes the starter config unless the repo already has one. Returns true when written. */
66
+ export function installGitleaksConfig(gitRoot) {
67
+ const dest = resolve(gitRoot, GITLEAKS_CONFIG_PATH);
68
+ if (existsSync(dest))
69
+ return false;
70
+ writeFileSync(dest, GITLEAKS_STARTER_CONFIG);
71
+ return true;
72
+ }
73
+ /**
74
+ * Converts a gitleaks JSON report to schema findings.
75
+ *
76
+ * Only location and rule fields are projected. `Secret` and `Match` are never
77
+ * read, so a result file cannot carry the credential even if --redact was lost.
78
+ */
79
+ export function parseGitleaksReport(json) {
80
+ const arr = JSON.parse(json);
81
+ if (!Array.isArray(arr))
82
+ throw new Error('gitleaks report is not a JSON array');
83
+ return arr.map((l) => ({
84
+ severity: 'high',
85
+ category: 'security',
86
+ rule: String(l?.RuleID || 'gitleaks'),
87
+ file: String(l?.File || ''),
88
+ line: Number.isFinite(l?.StartLine) && l.StartLine > 0 ? l.StartLine : null,
89
+ message: `${String(l?.Description || 'Secret detected').slice(0, 300)} (value redacted)`,
90
+ }));
91
+ }
92
+ function currentBranch() {
93
+ const r = run('git', ['symbolic-ref', '--quiet', '--short', 'HEAD']);
94
+ return r.ok && r.stdout ? r.stdout : null;
95
+ }
96
+ /**
97
+ * `gitleaks protect --staged` against the repo config and baseline.
98
+ *
99
+ * Blocks (exit 1) on a finding and when gitleaks runs but cannot produce a
100
+ * verdict (bad config, unreadable report): a broken secret scan should be
101
+ * fixed, not silently passed. A MISSING gitleaks does not block -- it warns and
102
+ * records a `skipped` result with reason `gitleaks-missing`; CI's gitleaks
103
+ * step remains the backstop.
104
+ */
105
+ export function runPreCommit(repoRoot, gitleaksBin = 'gitleaks') {
106
+ const started = new Date().toISOString();
107
+ const treeR = run('git', ['write-tree']);
108
+ const tree = treeR.ok ? treeR.stdout : null;
109
+ const branch = currentBranch();
110
+ const base = { kind: 'secrets', hook: 'pre-commit', tool: 'gitleaks', tree, branch, started_at: started };
111
+ const version = toolVersion(gitleaksBin);
112
+ if (!version) {
113
+ // Allowed, not blocked (decided): recorded as skipped so the dashboard can
114
+ // count commits that were never secret-scanned.
115
+ const result = makeResult({ ...base, status: 'skipped', finished_at: new Date().toISOString(), reason: REASON_GITLEAKS_MISSING });
116
+ return { exitCode: 0, result, resultPath: safeWrite(repoRoot, result) };
117
+ }
118
+ const dir = mkdtempSync(join(tmpdir(), 'prism-gitleaks-'));
119
+ const report = join(dir, 'report.json');
120
+ try {
121
+ const args = ['protect', '--staged', '--redact', '--no-banner', '--exit-code=1',
122
+ '--report-format=json', `--report-path=${report}`];
123
+ if (existsSync(resolve(repoRoot, GITLEAKS_CONFIG_PATH)))
124
+ args.push(`--config=${GITLEAKS_CONFIG_PATH}`);
125
+ if (existsSync(resolve(repoRoot, GITLEAKS_BASELINE_PATH)))
126
+ args.push(`--baseline-path=${GITLEAKS_BASELINE_PATH}`);
127
+ const r = spawnSync(gitleaksBin, args, { cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 120_000 });
128
+ let findings = null;
129
+ try {
130
+ findings = parseGitleaksReport(readFileSync(report, 'utf8'));
131
+ }
132
+ catch {
133
+ findings = null;
134
+ }
135
+ if (r.status === 0 && (findings === null || findings.length === 0)) {
136
+ const result = makeResult({ ...base, tool_version: version, status: 'ok', finished_at: new Date().toISOString() });
137
+ return { exitCode: 0, result, resultPath: safeWrite(repoRoot, result) };
138
+ }
139
+ if (findings && findings.length > 0) {
140
+ const result = makeResult({ ...base, tool_version: version, status: 'findings', findings, finished_at: new Date().toISOString(), summary: `${findings.length} secret(s) in staged changes` });
141
+ return { exitCode: 1, result, resultPath: safeWrite(repoRoot, result) };
142
+ }
143
+ const why = (r.error?.message || r.stderr || '').trim().split('\n').slice(-3).join(' ').slice(0, 400);
144
+ const result = makeResult({ ...base, tool_version: version, status: 'error', finished_at: new Date().toISOString(), reason: `gitleaks exited ${r.status} without a readable report: ${why}` });
145
+ return { exitCode: 1, result, resultPath: safeWrite(repoRoot, result) };
146
+ }
147
+ finally {
148
+ rmSync(dir, { recursive: true, force: true });
149
+ }
150
+ }
151
+ /** A result that cannot be written must never change the hook's verdict. */
152
+ export function safeWrite(repoRoot, r) {
153
+ try {
154
+ return writeResult(repoRoot, r);
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ //# sourceMappingURL=gitleaks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitleaks.js","sourceRoot":"","sources":["../../../src/codewatch/gitleaks.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,WAAW,EAAkC,MAAM,cAAc,CAAC;AAChH,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BtC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAA4B,OAAO,CAAC,QAAQ;IAC9E,MAAM,OAAO,GAAG,qEAAqE,CAAC;IACtF,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,6BAA6B,OAAO,GAAG,CAAC;IAC1E,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,8BAA8B,OAAO,GAAG,CAAC;IAC1E,OAAO,GAAG,OAAO,yEAAyE,CAAC;AAC7F,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,qBAAqB,CAAC,OAAe;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;IACpD,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAChF,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;QAC1B,QAAQ,EAAE,MAAe;QACzB,QAAQ,EAAE,UAAmB;QAC7B,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,IAAI,UAAU,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;QAC3E,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,WAAW,IAAI,iBAAiB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,mBAAmB;KACzF,CAAC,CAAC,CAAC;AACN,CAAC;AASD,SAAS,aAAa;IACpB,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,WAAW,GAAG,UAAU;IACrE,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,SAAkB,EAAE,IAAI,EAAE,YAAqB,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IAE5H,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,2EAA2E;QAC3E,gDAAgD;QAChD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC,CAAC;QAClI,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;IAC1E,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe;YAC7E,sBAAsB,EAAE,iBAAiB,MAAM,EAAE,CAAC,CAAC;QACrD,IAAI,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,YAAY,oBAAoB,EAAE,CAAC,CAAC;QACvG,IAAI,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,sBAAsB,CAAC,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,sBAAsB,EAAE,CAAC,CAAC;QAClH,MAAM,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAEjI,IAAI,QAAQ,GAAqB,IAAI,CAAC;QACtC,IAAI,CAAC;YAAC,QAAQ,GAAG,mBAAmB,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,QAAQ,GAAG,IAAI,CAAC;QAAC,CAAC;QAEhG,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YACnE,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACnH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QAC1E,CAAC;QACD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,MAAM,8BAA8B,EAAE,CAAC,CAAC;YAC9L,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QAC1E,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtG,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC,MAAM,+BAA+B,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/L,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;IAC1E,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,CAAc;IACxD,IAAI,CAAC;QAAC,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { type ManagedHook } from './hooks.js';
2
+ export declare const GLOBAL_MARKER = "# prism-codewatch-global";
3
+ /** Saved value of the global core.hooksPath that --global replaced. Absent when there was none. */
4
+ export declare const PREVIOUS_FILE = ".previous-hookspath";
5
+ /** With --force, the replaced hooks directory, whose hooks are chained after ours. */
6
+ export declare const CHAIN_FILE = ".chain-hookspath";
7
+ /** `git config prism.codewatch false` in a repo skips codewatch there (repo-local hooks still run). */
8
+ export declare const OPT_OUT_KEY = "prism.codewatch";
9
+ export declare function globalHooksDir(home?: string): string;
10
+ /** git expands a leading ~ in core.hooksPath; do the same so values compare. */
11
+ export declare function expandHome(p: string, home?: string): string;
12
+ export declare function readGlobalHooksPath(): string | null;
13
+ /** True when a core.hooksPath value points at our global hooks directory. */
14
+ export declare function isOurHooksPath(value: string | null, home?: string): boolean;
15
+ export declare const GLOBAL_PRE_COMMIT_SCRIPT: string;
16
+ export declare const GLOBAL_PRE_PUSH_SCRIPT: string;
17
+ export declare const GLOBAL_HOOK_SCRIPTS: Record<ManagedHook, string>;
18
+ export type GlobalInstallResult = {
19
+ ok: true;
20
+ dir: string;
21
+ previous: string | null;
22
+ chained: string | null;
23
+ reinstalled: boolean;
24
+ } | {
25
+ ok: false;
26
+ refused: string;
27
+ current: string;
28
+ };
29
+ export declare function installGlobal(opts?: {
30
+ force?: boolean;
31
+ home?: string;
32
+ }): GlobalInstallResult;
33
+ export interface GlobalUninstallResult {
34
+ /** What happened to core.hooksPath. */
35
+ config: 'restored' | 'unset' | 'left-alone' | 'not-set';
36
+ restoredTo: string | null;
37
+ removed: string[];
38
+ }
39
+ /** Restores the previous core.hooksPath (or unsets it) and removes our files. Never touches repo-local hooks. */
40
+ export declare function uninstallGlobal(opts?: {
41
+ home?: string;
42
+ }): GlobalUninstallResult;
43
+ export interface GlobalStatus {
44
+ state: 'installed' | 'not-installed' | 'elsewhere' | 'incomplete';
45
+ hooksPath: string | null;
46
+ dir: string;
47
+ }
48
+ export declare function globalStatus(home?: string): GlobalStatus;
49
+ /** A repo's own core.hooksPath (husky and similar), set in its local config. */
50
+ export declare function repoLocalHooksPath(repo: string): string | null;
51
+ export declare function isOptedOut(repo: string): boolean;
52
+ //# sourceMappingURL=globalHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalHooks.d.ts","sourceRoot":"","sources":["../../../src/codewatch/globalHooks.ts"],"names":[],"mappings":"AAgBA,OAAO,EAA6C,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzF,eAAO,MAAM,aAAa,6BAA6B,CAAC;AACxD,mGAAmG;AACnG,eAAO,MAAM,aAAa,wBAAwB,CAAC;AACnD,sFAAsF;AACtF,eAAO,MAAM,UAAU,qBAAqB,CAAC;AAC7C,uGAAuG;AACvG,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAE7C,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAE/D;AAED,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAItE;AAOD,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAGnD;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,GAAE,MAAkB,GAAG,OAAO,CAItF;AAiDD,eAAO,MAAM,wBAAwB,QAqBf,CAAC;AAEvB,eAAO,MAAM,sBAAsB,QAcd,CAAC;AAEtB,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAG3D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GAChG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,wBAAgB,aAAa,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,mBAAmB,CA2BhG;AAMD,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;IACxD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,iHAAiH;AACjH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,qBAAqB,CA8BnF;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,WAAW,GAAG,YAAY,CAAC;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,YAAY,CAAC,IAAI,GAAE,MAAkB,GAAG,YAAY,CAOnE;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGhD"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Global codewatch hooks: `prism-cli enable codewatch --global`.
3
+ *
4
+ * The hooks live in ~/.prism/hooks/ and `git config --global core.hooksPath`
5
+ * points there, so every existing and future repo is covered with no per-repo
6
+ * step. A global core.hooksPath makes git ignore each repo's own .git/hooks,
7
+ * so these hooks run that repo-local hook themselves, after codewatch, and pass
8
+ * its exit code through.
9
+ *
10
+ * Every git call here goes through `git config --global`, which honours HOME
11
+ * and GIT_CONFIG_GLOBAL: tests point both at a scratch directory.
12
+ */
13
+ import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmdirSync, rmSync, writeFileSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { isAbsolute, join, resolve } from 'node:path';
16
+ import { run } from '../utils/exec.js';
17
+ import { BACKUP_SUFFIX, HOOK_MARKER, MANAGED_HOOKS } from './hooks.js';
18
+ export const GLOBAL_MARKER = '# prism-codewatch-global';
19
+ /** Saved value of the global core.hooksPath that --global replaced. Absent when there was none. */
20
+ export const PREVIOUS_FILE = '.previous-hookspath';
21
+ /** With --force, the replaced hooks directory, whose hooks are chained after ours. */
22
+ export const CHAIN_FILE = '.chain-hookspath';
23
+ /** `git config prism.codewatch false` in a repo skips codewatch there (repo-local hooks still run). */
24
+ export const OPT_OUT_KEY = 'prism.codewatch';
25
+ export function globalHooksDir(home = homedir()) {
26
+ return join(home, '.prism', 'hooks');
27
+ }
28
+ /** git expands a leading ~ in core.hooksPath; do the same so values compare. */
29
+ export function expandHome(p, home = homedir()) {
30
+ if (p === '~')
31
+ return home;
32
+ if (p.startsWith('~/'))
33
+ return join(home, p.slice(2));
34
+ return p;
35
+ }
36
+ function samePath(a, b) {
37
+ const norm = (p) => { try {
38
+ return realpathSync(p);
39
+ }
40
+ catch {
41
+ return resolve(p);
42
+ } };
43
+ return norm(a) === norm(b);
44
+ }
45
+ export function readGlobalHooksPath() {
46
+ const r = run('git', ['config', '--global', '--get', 'core.hooksPath']);
47
+ return r.ok && r.stdout ? r.stdout : null;
48
+ }
49
+ /** True when a core.hooksPath value points at our global hooks directory. */
50
+ export function isOurHooksPath(value, home = homedir()) {
51
+ if (!value)
52
+ return false;
53
+ const v = expandHome(value, home);
54
+ return isAbsolute(v) && samePath(v, globalHooksDir(home));
55
+ }
56
+ /**
57
+ * Shared shell for both global hooks. Prints, one per line, the hooks to run
58
+ * after ours: the repo-local hook (unless it is a per-repo codewatch hook, in
59
+ * which case the team hook it had backed up), then the hook in the directory
60
+ * --force replaced. Never a file carrying our marker, so nothing runs twice.
61
+ */
62
+ function chainPrelude(hook) {
63
+ return `HOOK_NAME=${hook}
64
+ HOOK_DIR=$(cd "$(dirname "$0")" && pwd)
65
+ PRISM_MARKER='${HOOK_MARKER}'
66
+ prism_enabled() { [ "$(git config --bool --get ${OPT_OUT_KEY} 2>/dev/null)" != "false" ]; }
67
+ prism_chain_targets() {
68
+ GD=$(git rev-parse --git-dir 2>/dev/null) || return 0
69
+ L="$GD/hooks/$HOOK_NAME"
70
+ if [ -f "$L" ] && [ -x "$L" ]; then
71
+ if grep -qF "$PRISM_MARKER" "$L" 2>/dev/null; then
72
+ if [ -x "$L${BACKUP_SUFFIX}" ]; then printf '%s\\n' "$L${BACKUP_SUFFIX}"; fi
73
+ else
74
+ printf '%s\\n' "$L"
75
+ fi
76
+ fi
77
+ if [ -f "$HOOK_DIR/${CHAIN_FILE}" ]; then
78
+ F="$(cat "$HOOK_DIR/${CHAIN_FILE}")/$HOOK_NAME"
79
+ if [ -f "$F" ] && [ -x "$F" ] && ! grep -qF "$PRISM_MARKER" "$F" 2>/dev/null; then printf '%s\\n' "$F"; fi
80
+ fi
81
+ }
82
+ `;
83
+ }
84
+ /** Runs each chained hook in turn; the first non-zero exit code is the hook's result. */
85
+ function chainRunner(feedStdin) {
86
+ const call = feedStdin ? 'prism_feed | "$t" "$@"' : '"$t" "$@"';
87
+ return `TARGETS=$(prism_chain_targets)
88
+ set -f
89
+ OLDIFS=$IFS
90
+ IFS='
91
+ '
92
+ for t in $TARGETS; do
93
+ IFS=$OLDIFS
94
+ ${call}
95
+ rc=$?
96
+ if [ "$rc" -ne 0 ]; then exit "$rc"; fi
97
+ done
98
+ exit 0
99
+ `;
100
+ }
101
+ export const GLOBAL_PRE_COMMIT_SCRIPT = `#!/bin/sh
102
+ ${HOOK_MARKER} -- global pre-commit: secret scan (blocks on a finding), then this repo's own pre-commit.
103
+ ${GLOBAL_MARKER}
104
+ # Managed file. Remove with: prism-cli enable codewatch --global --uninstall
105
+ ${chainPrelude('pre-commit')}
106
+ if prism_enabled; then
107
+ if command -v prism-cli >/dev/null 2>&1 && prism-cli codewatch pre-commit --help 2>/dev/null | grep -q 'codewatch pre-commit'; then
108
+ prism-cli codewatch pre-commit || exit $?
109
+ elif command -v gitleaks >/dev/null 2>&1; then
110
+ set -- protect --staged --redact --no-banner --exit-code=1
111
+ [ -f .gitleaks.toml ] && set -- "$@" --config=.gitleaks.toml
112
+ [ -f .prism/gitleaks-baseline.json ] && set -- "$@" --baseline-path=.prism/gitleaks-baseline.json
113
+ if ! gitleaks "$@"; then
114
+ echo "prism: gitleaks flagged the staged changes; commit blocked." >&2
115
+ exit 1
116
+ fi
117
+ set --
118
+ else
119
+ echo "prism: neither prism-cli nor gitleaks is on PATH; secret scan skipped." >&2
120
+ fi
121
+ fi
122
+ ${chainRunner(false)}`;
123
+ export const GLOBAL_PRE_PUSH_SCRIPT = `#!/bin/sh
124
+ ${HOOK_MARKER} -- global pre-push: report-only review, then this repo's own pre-push (its exit code counts).
125
+ ${GLOBAL_MARKER}
126
+ # Managed file. Remove with: prism-cli enable codewatch --global --uninstall
127
+ ${chainPrelude('pre-push')}
128
+ PRISM_PUSH_INPUT=$(cat)
129
+ prism_feed() { if [ -n "$PRISM_PUSH_INPUT" ]; then printf '%s\\n' "$PRISM_PUSH_INPUT"; fi; }
130
+ if prism_enabled; then
131
+ if command -v prism-cli >/dev/null 2>&1 && prism-cli codewatch pre-push --help 2>/dev/null | grep -q 'codewatch pre-push'; then
132
+ prism_feed | prism-cli codewatch pre-push --remote "\${1:-}" || true
133
+ else
134
+ echo "prism: prism-cli (with 'codewatch pre-push') is not on PATH; local review skipped (report only)." >&2
135
+ fi
136
+ fi
137
+ ${chainRunner(true)}`;
138
+ export const GLOBAL_HOOK_SCRIPTS = {
139
+ 'pre-commit': GLOBAL_PRE_COMMIT_SCRIPT,
140
+ 'pre-push': GLOBAL_PRE_PUSH_SCRIPT,
141
+ };
142
+ export function installGlobal(opts = {}) {
143
+ const home = opts.home || homedir();
144
+ const dir = globalHooksDir(home);
145
+ const current = readGlobalHooksPath();
146
+ const ours = isOurHooksPath(current, home);
147
+ if (current && !ours && !opts.force) {
148
+ return { ok: false, current, refused: `global core.hooksPath is already set to ${current}` };
149
+ }
150
+ mkdirSync(dir, { recursive: true });
151
+ let chained = null;
152
+ if (current && !ours) {
153
+ // --force: remember the old value for uninstall, and chain its hooks.
154
+ writeFileSync(join(dir, PREVIOUS_FILE), current + '\n');
155
+ chained = expandHome(current, home);
156
+ writeFileSync(join(dir, CHAIN_FILE), chained + '\n');
157
+ }
158
+ for (const hook of MANAGED_HOOKS) {
159
+ const p = join(dir, hook);
160
+ writeFileSync(p, GLOBAL_HOOK_SCRIPTS[hook]);
161
+ chmodSync(p, 0o755);
162
+ }
163
+ if (!ours) {
164
+ const set = run('git', ['config', '--global', 'core.hooksPath', dir]);
165
+ if (!set.ok)
166
+ throw new Error(`git config --global core.hooksPath failed: ${set.stderr}`);
167
+ }
168
+ const prev = readText(join(dir, PREVIOUS_FILE));
169
+ return { ok: true, dir, previous: prev, chained: chained ?? readText(join(dir, CHAIN_FILE)), reinstalled: ours };
170
+ }
171
+ function readText(p) {
172
+ try {
173
+ return readFileSync(p, 'utf8').trim() || null;
174
+ }
175
+ catch {
176
+ return null;
177
+ }
178
+ }
179
+ /** Restores the previous core.hooksPath (or unsets it) and removes our files. Never touches repo-local hooks. */
180
+ export function uninstallGlobal(opts = {}) {
181
+ const home = opts.home || homedir();
182
+ const dir = globalHooksDir(home);
183
+ const current = readGlobalHooksPath();
184
+ const previous = readText(join(dir, PREVIOUS_FILE));
185
+ let config = 'not-set';
186
+ if (isOurHooksPath(current, home)) {
187
+ if (previous) {
188
+ run('git', ['config', '--global', 'core.hooksPath', previous]);
189
+ config = 'restored';
190
+ }
191
+ else {
192
+ run('git', ['config', '--global', '--unset', 'core.hooksPath']);
193
+ config = 'unset';
194
+ }
195
+ }
196
+ else if (current) {
197
+ config = 'left-alone'; // someone pointed it elsewhere since; not ours to change
198
+ }
199
+ const removed = [];
200
+ if (existsSync(dir)) {
201
+ for (const f of readdirSync(dir)) {
202
+ const p = join(dir, f);
203
+ const text = readText(p) || '';
204
+ if (f === PREVIOUS_FILE || f === CHAIN_FILE || text.includes(GLOBAL_MARKER)) {
205
+ rmSync(p, { force: true });
206
+ removed.push(f);
207
+ }
208
+ }
209
+ try {
210
+ rmdirSync(dir);
211
+ }
212
+ catch { /* not empty: leave other files */ }
213
+ }
214
+ return { config, restoredTo: config === 'restored' ? previous : null, removed };
215
+ }
216
+ export function globalStatus(home = homedir()) {
217
+ const dir = globalHooksDir(home);
218
+ const hooksPath = readGlobalHooksPath();
219
+ if (!hooksPath)
220
+ return { state: 'not-installed', hooksPath, dir };
221
+ if (!isOurHooksPath(hooksPath, home))
222
+ return { state: 'elsewhere', hooksPath, dir };
223
+ const complete = MANAGED_HOOKS.every((h) => (readText(join(dir, h)) || '').includes(GLOBAL_MARKER));
224
+ return { state: complete ? 'installed' : 'incomplete', hooksPath, dir };
225
+ }
226
+ /** A repo's own core.hooksPath (husky and similar), set in its local config. */
227
+ export function repoLocalHooksPath(repo) {
228
+ const r = run('git', ['-C', repo, 'config', '--local', '--get', 'core.hooksPath']);
229
+ return r.ok && r.stdout ? r.stdout : null;
230
+ }
231
+ export function isOptedOut(repo) {
232
+ const r = run('git', ['-C', repo, 'config', '--bool', '--get', OPT_OUT_KEY]);
233
+ return r.ok && r.stdout === 'false';
234
+ }
235
+ //# sourceMappingURL=globalHooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"globalHooks.js","sourceRoot":"","sources":["../../../src/codewatch/globalHooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export declare const HOOK_MARKER = "# prism-cli enable codewatch";
2
+ export declare const BACKUP_SUFFIX = ".pre-prism";
3
+ export declare const MANAGED_HOOKS: readonly ["pre-commit", "pre-push"];
4
+ export type ManagedHook = typeof MANAGED_HOOKS[number];
5
+ /**
6
+ * pre-commit: blocks on a gitleaks finding.
7
+ *
8
+ * Prefers `prism-cli codewatch pre-commit` (writes a result file). Without prism-cli
9
+ * on PATH it still runs gitleaks directly, so the block does not depend on the
10
+ * CLI being installed globally.
11
+ */
12
+ export declare const PRE_COMMIT_SCRIPT = "#!/bin/sh\n# prism-cli enable codewatch -- pre-commit secret scan (blocks on a finding).\n# Managed file. Remove with: prism-cli enable codewatch --uninstall\nHOOK_DIR=$(dirname \"$0\")\nif [ -x \"$HOOK_DIR/pre-commit.pre-prism\" ]; then\n \"$HOOK_DIR/pre-commit.pre-prism\" \"$@\" || exit $?\nfi\n# Probe first: an older prism-cli on PATH has no \"codewatch pre-commit\" and would\n# fail every commit with \"unknown command\". Its --help exits 0 with the root help,\n# so the probe checks that the subcommand's own usage line is printed.\nif command -v prism-cli >/dev/null 2>&1 && prism-cli codewatch pre-commit --help 2>/dev/null | grep -q 'codewatch pre-commit'; then\n exec prism-cli codewatch pre-commit\nfi\nif command -v gitleaks >/dev/null 2>&1; then\n set -- protect --staged --redact --no-banner --exit-code=1\n [ -f .gitleaks.toml ] && set -- \"$@\" --config=.gitleaks.toml\n [ -f .prism/gitleaks-baseline.json ] && set -- \"$@\" --baseline-path=.prism/gitleaks-baseline.json\n if ! gitleaks \"$@\"; then\n echo \"prism: gitleaks flagged the staged changes; commit blocked.\" >&2\n echo \"prism: fix it, allowlist a false positive in .gitleaks.toml, or bypass once with --no-verify.\" >&2\n exit 1\n fi\n exit 0\nfi\necho \"prism: neither prism-cli nor gitleaks is on PATH; secret scan skipped.\" >&2\nexit 0\n";
13
+ /**
14
+ * pre-push: report-only review. Exits 0 no matter what the review does.
15
+ * stdin (the pushed refs) is buffered so both a chained hook and prism-cli see it.
16
+ */
17
+ export declare const PRE_PUSH_SCRIPT = "#!/bin/sh\n# prism-cli enable codewatch -- pre-push local eval review (report only, never blocks).\n# Managed file. Remove with: prism-cli enable codewatch --uninstall\nHOOK_DIR=$(dirname \"$0\")\nPRISM_PUSH_INPUT=$(cat)\nif [ -x \"$HOOK_DIR/pre-push.pre-prism\" ]; then\n printf '%s\\n' \"$PRISM_PUSH_INPUT\" | \"$HOOK_DIR/pre-push.pre-prism\" \"$@\" || exit $?\nfi\nif command -v prism-cli >/dev/null 2>&1 && prism-cli codewatch pre-push --help 2>/dev/null | grep -q 'codewatch pre-push'; then\n printf '%s\\n' \"$PRISM_PUSH_INPUT\" | prism-cli codewatch pre-push --remote \"${1:-}\" || true\nelse\n echo \"prism: prism-cli (with 'codewatch pre-push') is not on PATH; local review skipped (report only).\" >&2\nfi\nexit 0\n";
18
+ export declare const HOOK_SCRIPTS: Record<ManagedHook, string>;
19
+ /**
20
+ * Hooks directory as git resolves it: honours core.hooksPath and works in a
21
+ * linked worktree, where `.git` is a file and `.git/hooks` does not exist.
22
+ */
23
+ export declare function resolveHooksDir(gitRoot: string): string;
24
+ export interface HookChange {
25
+ hook: string;
26
+ action: string;
27
+ }
28
+ export declare function installHooks(hooksDir: string): HookChange[];
29
+ export declare function uninstallHooks(hooksDir: string): HookChange[];
30
+ /**
31
+ * Removes the retired prepare-commit-msg trailer hook (commit trailers and the
32
+ * token tracker are no longer used) and restores whatever it had backed up.
33
+ * Returns a description, or null when there was nothing to retire.
34
+ */
35
+ export declare function retireLegacyCommitMsgHook(hooksDir: string): string | null;
36
+ /** Adds `.prism/results/` to .gitignore unless an entry already covers it. */
37
+ export declare function ensureResultsIgnored(gitRoot: string): boolean;
38
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/codewatch/hooks.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,WAAW,iCAAiC,CAAC;AAW1D,eAAO,MAAM,aAAa,eAAe,CAAC;AAC1C,eAAO,MAAM,aAAa,qCAAsC,CAAC;AACjE,MAAM,MAAM,WAAW,GAAG,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,i0CA0B7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,6tBAc3B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAGpD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKvD;AAWD,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAE5D,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAsB3D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE,CAkB7D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzE;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ7D"}