thumbgate 1.35.0 → 1.37.1
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/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +336 -3
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +581 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +9 -3
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +6 -0
- package/config/model-candidates.json +312 -29
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +390 -148
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-schema.js +163 -1
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard.js +251 -32
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gates-engine.js +810 -63
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +724 -153
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- package/src/security-questionnaire.js +195 -0
|
@@ -18,6 +18,16 @@ const path = require('node:path');
|
|
|
18
18
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
19
19
|
const DEFAULT_OUT_DIR = path.join(PROJECT_ROOT, 'docs', 'proof', 'outreach');
|
|
20
20
|
|
|
21
|
+
function proofLinks(anchor) {
|
|
22
|
+
return {
|
|
23
|
+
caseStudyPath: `/case-studies#${anchor}`,
|
|
24
|
+
scorecardPath: '/eval-scorecard',
|
|
25
|
+
whitepaperPath: '/whitepaper',
|
|
26
|
+
diagnosticPath: '/diagnostic',
|
|
27
|
+
proPath: '/checkout/pro',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
const CASES = Object.freeze({
|
|
22
32
|
'sudo-evasion': {
|
|
23
33
|
id: 'sudo-evasion',
|
|
@@ -28,13 +38,7 @@ const CASES = Object.freeze({
|
|
|
28
38
|
result: 'Canonicalization + an adversarial grid (14 commands × 9 transforms) closed the class; CI + 6-hourly published-artifact jobs keep it closed.',
|
|
29
39
|
buyerPain: 'Your coding agent can re-spell a blocked command and walk past a regex denylist.',
|
|
30
40
|
ctaPrimary: 'diagnostic',
|
|
31
|
-
proofLinks:
|
|
32
|
-
caseStudyPath: '/case-studies#sudo-evasion',
|
|
33
|
-
scorecardPath: '/eval-scorecard',
|
|
34
|
-
whitepaperPath: '/whitepaper',
|
|
35
|
-
diagnosticPath: '/diagnostic',
|
|
36
|
-
proPath: '/checkout/pro',
|
|
37
|
-
},
|
|
41
|
+
proofLinks: proofLinks('sudo-evasion'),
|
|
38
42
|
},
|
|
39
43
|
'fail-open': {
|
|
40
44
|
id: 'fail-open',
|
|
@@ -45,13 +49,18 @@ const CASES = Object.freeze({
|
|
|
45
49
|
result: 'Enforcement restored and verified with known-dangerous commands; silence is now treated as a P0 class.',
|
|
46
50
|
buyerPain: 'Green uptime does not mean your agent firewall is still firing.',
|
|
47
51
|
ctaPrimary: 'diagnostic',
|
|
48
|
-
proofLinks:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
proofLinks: proofLinks('fail-open'),
|
|
53
|
+
},
|
|
54
|
+
'memory-compaction': {
|
|
55
|
+
id: 'memory-compaction',
|
|
56
|
+
title: 'Agent memory crowded by near-duplicate lessons',
|
|
57
|
+
anchor: 'memory-compaction',
|
|
58
|
+
problem: 'Promoted lessons accumulated paraphrases of the same failure, so one real lesson could occupy every retrieval slot and starve distinct ones.',
|
|
59
|
+
metric: '3 dupe records → 1 survivor per cluster; slot budget back to full',
|
|
60
|
+
result: 'Retrieval dedupes the candidate pool before ranking, and a compaction CLI merges near-duplicates with summed occurrence counts (opposite-signal lessons never merge); a July 2026 audit found ~86% of promoted lessons were near-duplicates.',
|
|
61
|
+
buyerPain: 'Your agent memory fills with the same lesson five times, so the distinct lesson never surfaces.',
|
|
62
|
+
ctaPrimary: 'diagnostic',
|
|
63
|
+
proofLinks: proofLinks('memory-compaction'),
|
|
55
64
|
},
|
|
56
65
|
});
|
|
57
66
|
|
|
@@ -117,7 +126,7 @@ function buildPack(caseDef, options = {}) {
|
|
|
117
126
|
].join('\n');
|
|
118
127
|
|
|
119
128
|
const email = [
|
|
120
|
-
`Subject:
|
|
129
|
+
`Subject: ${caseDef.title}`,
|
|
121
130
|
'',
|
|
122
131
|
`Hi —`,
|
|
123
132
|
'',
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Client-side Git scale hygiene (Cursor "Git at any scale" / Continuity).
|
|
6
|
+
*
|
|
7
|
+
* Steal the mechanic, not Origin hosting:
|
|
8
|
+
* 1. Packfile sprawl → commit-graph + multi-pack-index (optional geometric repack)
|
|
9
|
+
* 2. Agent worktrees as cattle under .claude/worktrees (clean prune only)
|
|
10
|
+
* 3. Tip consistency vs origin (Git hates eventual views)
|
|
11
|
+
*
|
|
12
|
+
* Not a Git host. Not ThumbGate Continuity (VPS). Not net-new agent-governance IP.
|
|
13
|
+
* Mutating commands honor scripts/session-lease.js.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { spawnSync } = require('node:child_process');
|
|
19
|
+
|
|
20
|
+
const sessionLease = require('./session-lease');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_AGENT_WORKTREE_BASE = '.claude/worktrees';
|
|
23
|
+
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'production', 'gh-pages']);
|
|
24
|
+
const DEFAULT_MAX_AGE_DAYS = 5;
|
|
25
|
+
const PACK_UNHEALTHY = 40;
|
|
26
|
+
const LOOSE_UNHEALTHY = 500;
|
|
27
|
+
const WORKTREE_UNHEALTHY = 25;
|
|
28
|
+
const SCALE_TUNE = Object.freeze([
|
|
29
|
+
['fetch.writeCommitGraph', 'true'],
|
|
30
|
+
['gc.writeCommitGraph', 'true'],
|
|
31
|
+
['pack.writeReverseIndex', 'true'],
|
|
32
|
+
]);
|
|
33
|
+
const SAFE_GIT_PATH = '/usr/bin:/bin';
|
|
34
|
+
const GIT_BIN = ['/usr/bin/git', '/usr/local/bin/git', '/opt/homebrew/bin/git'].find((candidate) => {
|
|
35
|
+
try { return fs.existsSync(candidate); } catch { return false; }
|
|
36
|
+
}) || 'git';
|
|
37
|
+
|
|
38
|
+
function runGit(args, cwd = process.cwd(), opts = {}) {
|
|
39
|
+
const env = { ...process.env, PATH: SAFE_GIT_PATH };
|
|
40
|
+
delete env.GIT_DIR;
|
|
41
|
+
delete env.GIT_WORK_TREE;
|
|
42
|
+
delete env.GIT_COMMON_DIR;
|
|
43
|
+
const res = spawnSync(GIT_BIN, args, {
|
|
44
|
+
cwd,
|
|
45
|
+
encoding: 'utf8',
|
|
46
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
47
|
+
timeout: opts.timeoutMs || 0,
|
|
48
|
+
env,
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
status: res.status === null ? 1 : res.status,
|
|
52
|
+
stdout: (res.stdout || '').trim(),
|
|
53
|
+
stderr: (res.stderr || '').trim(),
|
|
54
|
+
signal: res.signal || null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getRepoRoot(cwd = process.cwd()) {
|
|
59
|
+
const res = runGit(['rev-parse', '--show-toplevel'], cwd);
|
|
60
|
+
if (res.status !== 0 || !res.stdout) {
|
|
61
|
+
throw new Error(`Not a git repository: ${cwd}`);
|
|
62
|
+
}
|
|
63
|
+
return res.stdout;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function gitCommonDirAbs(repoRoot) {
|
|
67
|
+
const res = runGit(['rev-parse', '--git-common-dir'], repoRoot);
|
|
68
|
+
if (res.status !== 0 || !res.stdout) return path.join(repoRoot, '.git');
|
|
69
|
+
return path.isAbsolute(res.stdout) ? res.stdout : path.join(repoRoot, res.stdout);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseCountObjects(repoRoot) {
|
|
73
|
+
const common = gitCommonDirAbs(repoRoot);
|
|
74
|
+
const res = runGit(['--git-dir', common, 'count-objects', '-v'], repoRoot);
|
|
75
|
+
const out = {};
|
|
76
|
+
if (res.status !== 0) return out;
|
|
77
|
+
for (const line of res.stdout.split('\n')) {
|
|
78
|
+
const idx = line.indexOf(':');
|
|
79
|
+
if (idx < 0) continue;
|
|
80
|
+
const key = line.slice(0, idx).trim();
|
|
81
|
+
const raw = line.slice(idx + 1).trim();
|
|
82
|
+
const num = Number(raw);
|
|
83
|
+
out[key] = Number.isFinite(num) && String(num) === raw ? num : raw;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasCommitGraph(repoRoot) {
|
|
89
|
+
const abs = gitCommonDirAbs(repoRoot);
|
|
90
|
+
return (
|
|
91
|
+
fs.existsSync(path.join(abs, 'objects', 'info', 'commit-graph'))
|
|
92
|
+
|| fs.existsSync(path.join(abs, 'objects', 'info', 'commit-graphs'))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasMultiPackIndex(repoRoot) {
|
|
97
|
+
const abs = gitCommonDirAbs(repoRoot);
|
|
98
|
+
return (
|
|
99
|
+
fs.existsSync(path.join(abs, 'objects', 'pack', 'multi-pack-index'))
|
|
100
|
+
|| fs.existsSync(path.join(abs, 'objects', 'info', 'multi-pack-index'))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hasPackBitmap(repoRoot) {
|
|
105
|
+
const packDir = path.join(gitCommonDirAbs(repoRoot), 'objects', 'pack');
|
|
106
|
+
try {
|
|
107
|
+
if (fs.existsSync(path.join(packDir, 'multi-pack-index.bitmap'))) return true;
|
|
108
|
+
return fs.readdirSync(packDir).some((name) => name.endsWith('.bitmap'));
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readLocalConfig(repoRoot, key) {
|
|
115
|
+
const res = runGit(['config', '--local', '--get', key], repoRoot);
|
|
116
|
+
return res.status === 0 ? res.stdout : '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function hasScaleTune(repoRoot) {
|
|
120
|
+
return SCALE_TUNE.every(([key, value]) => readLocalConfig(repoRoot, key) === value);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function applyScaleTune(repoRoot = getRepoRoot()) {
|
|
124
|
+
const applied = [];
|
|
125
|
+
const errors = [];
|
|
126
|
+
for (const [key, value] of SCALE_TUNE) {
|
|
127
|
+
const res = runGit(['config', '--local', key, value], repoRoot);
|
|
128
|
+
if (res.status === 0) applied.push(key);
|
|
129
|
+
else errors.push(`${key}: ${res.stderr || 'failed'}`);
|
|
130
|
+
}
|
|
131
|
+
return { ok: errors.length === 0, applied, errors };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeMultiPackIndex(repoRoot, results) {
|
|
135
|
+
let midx = runGit(['multi-pack-index', 'write', '--bitmap'], repoRoot);
|
|
136
|
+
if (midx.status !== 0) {
|
|
137
|
+
midx = runGit(['multi-pack-index', 'write'], repoRoot);
|
|
138
|
+
}
|
|
139
|
+
results.multiPackIndexUpdated = midx.status === 0;
|
|
140
|
+
results.packBitmapUpdated = hasPackBitmap(repoRoot);
|
|
141
|
+
recordGitError(results, 'multi-pack-index', midx);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function primaryCheckout(repoRoot) {
|
|
145
|
+
const common = gitCommonDirAbs(repoRoot);
|
|
146
|
+
const candidate = path.basename(common) === '.git' ? path.dirname(common) : repoRoot;
|
|
147
|
+
try {
|
|
148
|
+
return fs.realpathSync(candidate);
|
|
149
|
+
} catch {
|
|
150
|
+
return path.resolve(candidate);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resolveAgentBase(repoRoot, override) {
|
|
155
|
+
const raw = override || DEFAULT_AGENT_WORKTREE_BASE;
|
|
156
|
+
return path.isAbsolute(raw) ? raw : path.join(primaryCheckout(repoRoot), raw);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function requireLiveLease(repoRoot) {
|
|
160
|
+
const result = sessionLease.check(repoRoot);
|
|
161
|
+
if (!result.ok) {
|
|
162
|
+
return { ok: false, code: result.code || 'LEASED', message: result.message };
|
|
163
|
+
}
|
|
164
|
+
if (!result.held) {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
code: 'UNCLAIMED',
|
|
168
|
+
message: 'No session lease held. Claim the checkout before mutating git internals or pruning worktrees.',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return { ok: true, lease: result.lease };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function recordGitError(results, label, res) {
|
|
175
|
+
if (res.status === 0) return;
|
|
176
|
+
const detail = res.stderr || res.signal || 'failed';
|
|
177
|
+
if (detail) results.errors.push(`${label}: ${detail}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function runMaintenance(repoRoot = getRepoRoot(), options = {}) {
|
|
181
|
+
// Cheap indexes do not mutate the working tree. Geometric repack is CPU-heavy
|
|
182
|
+
// and must not race a foreign writer — require the checkout lease.
|
|
183
|
+
if (options.geometric && !options.skipLease) {
|
|
184
|
+
const lease = requireLiveLease(repoRoot);
|
|
185
|
+
if (!lease.ok) {
|
|
186
|
+
return { blocked: true, ...lease };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const results = {
|
|
191
|
+
commitGraphUpdated: false,
|
|
192
|
+
multiPackIndexUpdated: false,
|
|
193
|
+
packBitmapUpdated: false,
|
|
194
|
+
geometricRepackDone: false,
|
|
195
|
+
maintenanceAuto: false,
|
|
196
|
+
errors: [],
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const auto = runGit(['maintenance', 'run', '--auto'], repoRoot);
|
|
200
|
+
results.maintenanceAuto = auto.status === 0;
|
|
201
|
+
recordGitError(results, 'maintenance', auto);
|
|
202
|
+
|
|
203
|
+
const graph = runGit(
|
|
204
|
+
['commit-graph', 'write', '--reachable', '--changed-paths'],
|
|
205
|
+
repoRoot,
|
|
206
|
+
);
|
|
207
|
+
results.commitGraphUpdated = graph.status === 0;
|
|
208
|
+
recordGitError(results, 'commit-graph', graph);
|
|
209
|
+
|
|
210
|
+
writeMultiPackIndex(repoRoot, results);
|
|
211
|
+
|
|
212
|
+
if (options.geometric) {
|
|
213
|
+
const repack = runGit(['repack', '-d', '-l', '--geometric=2'], repoRoot, {
|
|
214
|
+
timeoutMs: options.timeoutMs || 600000,
|
|
215
|
+
});
|
|
216
|
+
results.geometricRepackDone = repack.status === 0;
|
|
217
|
+
recordGitError(results, 'geometric repack', repack);
|
|
218
|
+
if (repack.status === 0) writeMultiPackIndex(repoRoot, results);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return results;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function listWorktrees(repoRoot = getRepoRoot()) {
|
|
225
|
+
const res = runGit(['worktree', 'list', '--porcelain'], repoRoot);
|
|
226
|
+
if (res.status !== 0) return [];
|
|
227
|
+
const worktrees = [];
|
|
228
|
+
let current = {};
|
|
229
|
+
for (const line of res.stdout.split('\n')) {
|
|
230
|
+
if (!line.trim()) {
|
|
231
|
+
if (current.worktree) worktrees.push(current);
|
|
232
|
+
current = {};
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (line.startsWith('worktree ')) current.worktree = line.slice(9);
|
|
236
|
+
else if (line.startsWith('HEAD ')) current.head = line.slice(5);
|
|
237
|
+
else if (line.startsWith('branch ')) {
|
|
238
|
+
current.branch = line.slice(7).replace(/^refs\/heads\//, '');
|
|
239
|
+
} else if (line === 'bare') current.bare = true;
|
|
240
|
+
else if (line === 'detached') current.detached = true;
|
|
241
|
+
else if (line === 'locked' || line.startsWith('locked ')) current.locked = true;
|
|
242
|
+
}
|
|
243
|
+
if (current.worktree) worktrees.push(current);
|
|
244
|
+
return worktrees;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isUnderBase(wtPath, basePath) {
|
|
248
|
+
let realWt;
|
|
249
|
+
let realBase;
|
|
250
|
+
try {
|
|
251
|
+
realWt = fs.existsSync(wtPath) ? fs.realpathSync(wtPath) : path.resolve(wtPath);
|
|
252
|
+
realBase = fs.existsSync(basePath) ? fs.realpathSync(basePath) : path.resolve(basePath);
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return realWt === realBase || realWt.startsWith(realBase + path.sep);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function isPorcelainClean(wtPath) {
|
|
260
|
+
const st = runGit(['status', '--porcelain'], wtPath);
|
|
261
|
+
return st.status === 0 && st.stdout === '';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isLiveLeaseOnPath(wtPath) {
|
|
265
|
+
try {
|
|
266
|
+
const result = sessionLease.check(wtPath);
|
|
267
|
+
if (!result) return false;
|
|
268
|
+
if (result.ok === false && result.code === 'LEASED') return true;
|
|
269
|
+
if (result.held && result.ok && !result.stale) return true;
|
|
270
|
+
return false;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveWorktreePath(wtPath) {
|
|
277
|
+
try {
|
|
278
|
+
return fs.existsSync(wtPath) ? fs.realpathSync(wtPath) : path.resolve(wtPath);
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function classifyPruneSkip(wt, ctx) {
|
|
285
|
+
const realWt = resolveWorktreePath(wt.worktree);
|
|
286
|
+
if (!realWt) return 'unreadable';
|
|
287
|
+
if (realWt === ctx.primary || realWt === ctx.current) return 'primary';
|
|
288
|
+
if (wt.locked) return 'locked';
|
|
289
|
+
if (!isUnderBase(wt.worktree, ctx.worktreeBase)) return 'outside-agent-base';
|
|
290
|
+
if (wt.branch && PROTECTED_BRANCHES.has(wt.branch)) return 'protected-branch';
|
|
291
|
+
if (isLiveLeaseOnPath(wt.worktree)) return 'live-lease';
|
|
292
|
+
if (!isPorcelainClean(wt.worktree)) return 'dirty';
|
|
293
|
+
if (ctx.maxAgeMs == null) return null;
|
|
294
|
+
try {
|
|
295
|
+
const mtime = fs.statSync(wt.worktree).mtimeMs;
|
|
296
|
+
if ((ctx.now - mtime) < ctx.maxAgeMs) return `younger-than-${ctx.maxAgeDays}d`;
|
|
297
|
+
} catch {
|
|
298
|
+
return 'mtime-unavailable';
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function pruneWorktrees(options = {}) {
|
|
304
|
+
const repoRoot = options.repoRoot || getRepoRoot();
|
|
305
|
+
const worktreeBase = resolveAgentBase(repoRoot, options.worktreeBase);
|
|
306
|
+
const maxAgeDays = options.maxAgeDays != null
|
|
307
|
+
? Number(options.maxAgeDays)
|
|
308
|
+
: DEFAULT_MAX_AGE_DAYS;
|
|
309
|
+
const dryRun = options.dryRun !== false;
|
|
310
|
+
const pruned = [];
|
|
311
|
+
const skipped = [];
|
|
312
|
+
|
|
313
|
+
if (!dryRun) {
|
|
314
|
+
const lease = requireLiveLease(repoRoot);
|
|
315
|
+
if (!lease.ok) {
|
|
316
|
+
return { blocked: true, ...lease, prunedCount: 0, prunedPaths: [], skipped, dryRun, worktreeBase };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (!fs.existsSync(worktreeBase)) {
|
|
321
|
+
return { prunedCount: 0, prunedPaths: [], skipped, dryRun, worktreeBase };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const primary = fs.realpathSync(primaryCheckout(repoRoot));
|
|
325
|
+
let current = repoRoot;
|
|
326
|
+
try { current = fs.realpathSync(repoRoot); } catch { /* keep repoRoot */ }
|
|
327
|
+
const now = Date.now();
|
|
328
|
+
const maxAgeMs = Number.isFinite(maxAgeDays) ? maxAgeDays * 24 * 3600000 : null;
|
|
329
|
+
const ctx = { primary, current, worktreeBase, now, maxAgeMs, maxAgeDays };
|
|
330
|
+
|
|
331
|
+
for (const wt of listWorktrees(repoRoot)) {
|
|
332
|
+
if (!wt.worktree) continue;
|
|
333
|
+
const reason = classifyPruneSkip(wt, ctx);
|
|
334
|
+
if (reason) {
|
|
335
|
+
skipped.push({ path: wt.worktree, reason });
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (dryRun) {
|
|
339
|
+
pruned.push(wt.worktree);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const rm = runGit(['worktree', 'remove', '--force', wt.worktree], repoRoot);
|
|
343
|
+
if (rm.status === 0) pruned.push(wt.worktree);
|
|
344
|
+
else skipped.push({ path: wt.worktree, reason: rm.stderr || 'remove-failed' });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!dryRun) runGit(['worktree', 'prune'], repoRoot);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
prunedCount: pruned.length,
|
|
351
|
+
prunedPaths: pruned,
|
|
352
|
+
skipped,
|
|
353
|
+
dryRun,
|
|
354
|
+
worktreeBase,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function countRevs(fromSha, toSha, repoRoot) {
|
|
359
|
+
const res = runGit(['rev-list', '--count', `${fromSha}..${toSha}`], repoRoot);
|
|
360
|
+
if (res.status !== 0) return null;
|
|
361
|
+
const n = Number(res.stdout);
|
|
362
|
+
return Number.isFinite(n) ? n : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function indeterminateTip(partial) {
|
|
366
|
+
return {
|
|
367
|
+
consistent: null,
|
|
368
|
+
behind: null,
|
|
369
|
+
ahead: null,
|
|
370
|
+
behindCount: null,
|
|
371
|
+
aheadCount: null,
|
|
372
|
+
indeterminate: true,
|
|
373
|
+
...partial,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function checkTipConsistency(options = {}) {
|
|
378
|
+
const repoRoot = options.repoRoot || getRepoRoot();
|
|
379
|
+
const branch =
|
|
380
|
+
options.branch
|
|
381
|
+
|| runGit(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot).stdout
|
|
382
|
+
|| 'main';
|
|
383
|
+
const remote = options.remote || 'origin';
|
|
384
|
+
const doFetch = options.fetch === true;
|
|
385
|
+
|
|
386
|
+
const before = runGit(['rev-parse', 'HEAD'], repoRoot).stdout;
|
|
387
|
+
let fetch = { status: 0, stderr: '' };
|
|
388
|
+
if (doFetch) {
|
|
389
|
+
fetch = runGit(['fetch', remote, branch, '--quiet'], repoRoot, {
|
|
390
|
+
timeoutMs: options.timeoutMs || 120000,
|
|
391
|
+
});
|
|
392
|
+
// Opted-in fetch failed: never grade a cached origin/<branch> tip.
|
|
393
|
+
if (fetch.status !== 0) {
|
|
394
|
+
return indeterminateTip({
|
|
395
|
+
branch,
|
|
396
|
+
remote,
|
|
397
|
+
localTip: before || null,
|
|
398
|
+
remoteTip: null,
|
|
399
|
+
fetched: false,
|
|
400
|
+
fetchError: fetch.stderr || 'fetch failed',
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const after = runGit(['rev-parse', 'HEAD'], repoRoot).stdout;
|
|
406
|
+
const localTip = after || before;
|
|
407
|
+
const remoteTip = runGit(['rev-parse', `${remote}/${branch}`], repoRoot);
|
|
408
|
+
if (remoteTip.status !== 0 || !remoteTip.stdout || !localTip) {
|
|
409
|
+
return indeterminateTip({
|
|
410
|
+
branch,
|
|
411
|
+
remote,
|
|
412
|
+
localTip: localTip || null,
|
|
413
|
+
remoteTip: null,
|
|
414
|
+
fetched: doFetch && fetch.status === 0,
|
|
415
|
+
fetchError: remoteTip.stderr || 'missing remote tip',
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Ancestry, not SHA inequality: ahead-only must not report behind.
|
|
420
|
+
const behindCount = countRevs(localTip, remoteTip.stdout, repoRoot);
|
|
421
|
+
const aheadCount = countRevs(remoteTip.stdout, localTip, repoRoot);
|
|
422
|
+
if (behindCount == null || aheadCount == null) {
|
|
423
|
+
return indeterminateTip({
|
|
424
|
+
branch,
|
|
425
|
+
remote,
|
|
426
|
+
localTip,
|
|
427
|
+
remoteTip: remoteTip.stdout,
|
|
428
|
+
fetched: doFetch && fetch.status === 0,
|
|
429
|
+
fetchError: 'rev-list ancestry unavailable',
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const behind = behindCount > 0;
|
|
434
|
+
const ahead = aheadCount > 0;
|
|
435
|
+
return {
|
|
436
|
+
branch,
|
|
437
|
+
remote,
|
|
438
|
+
localTip,
|
|
439
|
+
remoteTip: remoteTip.stdout,
|
|
440
|
+
fetched: doFetch && fetch.status === 0,
|
|
441
|
+
fetchError: null,
|
|
442
|
+
consistent: !behind && !ahead,
|
|
443
|
+
behind,
|
|
444
|
+
ahead,
|
|
445
|
+
behindCount,
|
|
446
|
+
aheadCount,
|
|
447
|
+
indeterminate: false,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function getScaleScorecard(repoRoot = getRepoRoot()) {
|
|
452
|
+
const objects = parseCountObjects(repoRoot);
|
|
453
|
+
const worktrees = listWorktrees(repoRoot);
|
|
454
|
+
const packs = Number(objects.packs) || 0;
|
|
455
|
+
const loose = Number(objects.count) || 0;
|
|
456
|
+
const commitGraph = hasCommitGraph(repoRoot);
|
|
457
|
+
const midx = hasMultiPackIndex(repoRoot);
|
|
458
|
+
const packBitmap = hasPackBitmap(repoRoot);
|
|
459
|
+
const scaleTune = hasScaleTune(repoRoot);
|
|
460
|
+
|
|
461
|
+
const unhealthyReasons = [];
|
|
462
|
+
if (loose >= LOOSE_UNHEALTHY) unhealthyReasons.push('loose-objects>=500');
|
|
463
|
+
if (packs >= PACK_UNHEALTHY) unhealthyReasons.push('packs>=40');
|
|
464
|
+
if (!commitGraph) unhealthyReasons.push('missing-commit-graph');
|
|
465
|
+
if (packs >= 2 && !midx) unhealthyReasons.push('missing-multi-pack-index');
|
|
466
|
+
if (packs >= 2 && !packBitmap) unhealthyReasons.push('missing-pack-bitmap');
|
|
467
|
+
if (!scaleTune) unhealthyReasons.push('missing-scale-tune');
|
|
468
|
+
if (worktrees.length >= WORKTREE_UNHEALTHY) unhealthyReasons.push('worktrees>=25');
|
|
469
|
+
const blockingReasons = unhealthyReasons.filter((reason) => (
|
|
470
|
+
reason.startsWith('loose-objects') || reason.startsWith('packs>=')
|
|
471
|
+
));
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
timestamp: new Date().toISOString(),
|
|
475
|
+
repoRoot,
|
|
476
|
+
looseObjects: loose,
|
|
477
|
+
packfiles: packs,
|
|
478
|
+
packedObjects: Number(objects['in-pack']) || 0,
|
|
479
|
+
packedSizeKb: Number(objects['size-pack']) || 0,
|
|
480
|
+
commitGraph,
|
|
481
|
+
multiPackIndex: midx,
|
|
482
|
+
packBitmap,
|
|
483
|
+
scaleTune,
|
|
484
|
+
activeWorktrees: worktrees.length,
|
|
485
|
+
healthy: unhealthyReasons.length === 0,
|
|
486
|
+
unhealthyReasons,
|
|
487
|
+
blockingReasons,
|
|
488
|
+
sourceOfTruth: 'origin',
|
|
489
|
+
localRole: 'warm-cache',
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function evaluateGitScaleHealth(repoRoot = getRepoRoot()) {
|
|
494
|
+
const card = getScaleScorecard(repoRoot);
|
|
495
|
+
return {
|
|
496
|
+
...card,
|
|
497
|
+
blocking: Array.isArray(card.blockingReasons) && card.blockingReasons.length > 0,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function applyScaleHeal(repoRoot = getRepoRoot()) {
|
|
502
|
+
const before = getScaleScorecard(repoRoot);
|
|
503
|
+
const tune = applyScaleTune(repoRoot);
|
|
504
|
+
const maintenance = runMaintenance(repoRoot, { geometric: false });
|
|
505
|
+
return {
|
|
506
|
+
applied: true,
|
|
507
|
+
tune,
|
|
508
|
+
maintenance,
|
|
509
|
+
before,
|
|
510
|
+
scorecard: getScaleScorecard(repoRoot),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function flagValue(args, name) {
|
|
515
|
+
const idx = args.indexOf(name);
|
|
516
|
+
if (idx < 0) return undefined;
|
|
517
|
+
return args[idx + 1];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function main(argv = process.argv.slice(2)) {
|
|
521
|
+
const command = argv[0] || 'scorecard';
|
|
522
|
+
const repoRoot = flagValue(argv, '--cwd')
|
|
523
|
+
? getRepoRoot(flagValue(argv, '--cwd'))
|
|
524
|
+
: getRepoRoot();
|
|
525
|
+
|
|
526
|
+
const emit = (obj, code = 0) => {
|
|
527
|
+
process.stdout.write(JSON.stringify(obj, null, 2) + '\n');
|
|
528
|
+
return code;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
if (command === 'scorecard' || command === '--scorecard' || command === '-s') {
|
|
532
|
+
return emit(getScaleScorecard(repoRoot));
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (command === 'maintenance' || command === '--maintenance' || command === '-m') {
|
|
536
|
+
const maintenance = runMaintenance(repoRoot, { geometric: argv.includes('--geometric') });
|
|
537
|
+
const code = maintenance.blocked ? 1 : 0;
|
|
538
|
+
return emit({
|
|
539
|
+
status: maintenance.blocked ? 'blocked' : 'ok',
|
|
540
|
+
maintenance,
|
|
541
|
+
scorecard: maintenance.blocked ? null : getScaleScorecard(repoRoot),
|
|
542
|
+
}, code);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (command === 'prune-worktrees' || command === '--prune-worktrees') {
|
|
546
|
+
const age = flagValue(argv, '--max-age-days');
|
|
547
|
+
const result = pruneWorktrees({
|
|
548
|
+
repoRoot,
|
|
549
|
+
worktreeBase: flagValue(argv, '--base'),
|
|
550
|
+
maxAgeDays: age != null ? Number(age) : DEFAULT_MAX_AGE_DAYS,
|
|
551
|
+
dryRun: !argv.includes('--apply'),
|
|
552
|
+
});
|
|
553
|
+
return emit(result, result.blocked ? 1 : 0);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (command === 'tip' || command === '--tip-consistency') {
|
|
557
|
+
const wantFetch = argv.includes('--fetch');
|
|
558
|
+
const tip = checkTipConsistency({
|
|
559
|
+
repoRoot,
|
|
560
|
+
branch: flagValue(argv, '--branch'),
|
|
561
|
+
fetch: wantFetch,
|
|
562
|
+
});
|
|
563
|
+
// --fetch asked for a current remote view; indeterminate is not success.
|
|
564
|
+
const code = tip.indeterminate && wantFetch ? 1 : 0;
|
|
565
|
+
return emit(tip, code);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (command === 'tune' || command === '--tune') {
|
|
569
|
+
return emit({
|
|
570
|
+
status: 'ok',
|
|
571
|
+
tune: applyScaleTune(repoRoot),
|
|
572
|
+
scorecard: getScaleScorecard(repoRoot),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (command === 'check' || command === '--check') {
|
|
577
|
+
const health = evaluateGitScaleHealth(repoRoot);
|
|
578
|
+
return emit(health, health.blocking ? 1 : 0);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (command === 'heal' || command === '--heal') {
|
|
582
|
+
return emit({ status: 'ok', ...applyScaleHeal(repoRoot) });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
process.stderr.write(
|
|
586
|
+
'usage: node scripts/git-at-scale.js <scorecard|maintenance|prune-worktrees|tip|tune|check|heal> [flags]\n'
|
|
587
|
+
+ ' maintenance [--geometric]\n'
|
|
588
|
+
+ ' prune-worktrees [--base DIR] [--max-age-days N] [--apply]\n'
|
|
589
|
+
+ ' tip [--branch main] [--fetch]\n'
|
|
590
|
+
+ ' tune\n'
|
|
591
|
+
+ ' check\n'
|
|
592
|
+
+ ' heal\n'
|
|
593
|
+
);
|
|
594
|
+
return 2;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
module.exports = {
|
|
598
|
+
DEFAULT_AGENT_WORKTREE_BASE,
|
|
599
|
+
DEFAULT_MAX_AGE_DAYS,
|
|
600
|
+
GIT_BIN,
|
|
601
|
+
PACK_UNHEALTHY,
|
|
602
|
+
SAFE_GIT_PATH,
|
|
603
|
+
SCALE_TUNE,
|
|
604
|
+
applyScaleHeal,
|
|
605
|
+
applyScaleTune,
|
|
606
|
+
checkTipConsistency,
|
|
607
|
+
evaluateGitScaleHealth,
|
|
608
|
+
getRepoRoot,
|
|
609
|
+
getScaleScorecard,
|
|
610
|
+
gitCommonDirAbs,
|
|
611
|
+
hasCommitGraph,
|
|
612
|
+
hasPackBitmap,
|
|
613
|
+
hasScaleTune,
|
|
614
|
+
primaryCheckout,
|
|
615
|
+
hasMultiPackIndex,
|
|
616
|
+
listWorktrees,
|
|
617
|
+
main,
|
|
618
|
+
parseCountObjects,
|
|
619
|
+
pruneWorktrees,
|
|
620
|
+
requireLiveLease,
|
|
621
|
+
resolveAgentBase,
|
|
622
|
+
runGit,
|
|
623
|
+
runMaintenance,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
|
|
627
|
+
process.exit(main());
|
|
628
|
+
}
|