@reneturcios/greenlight 0.9.3 → 0.10.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/README.md +34 -0
- package/dist/automate.js +27 -17
- package/dist/automate.js.map +1 -1
- package/dist/babysit.d.ts +10 -1
- package/dist/babysit.js +37 -6
- package/dist/babysit.js.map +1 -1
- package/dist/capabilities.d.ts +52 -0
- package/dist/capabilities.js +135 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/claims-registry.d.ts +21 -0
- package/dist/claims-registry.js +6 -1
- package/dist/claims-registry.js.map +1 -1
- package/dist/cli.js +140 -4
- package/dist/cli.js.map +1 -1
- package/dist/digest.d.ts +0 -1
- package/dist/digest.js +2 -2
- package/dist/digest.js.map +1 -1
- package/dist/guard.d.ts +0 -1
- package/dist/guard.js +2 -2
- package/dist/guard.js.map +1 -1
- package/dist/help.js +61 -1
- package/dist/help.js.map +1 -1
- package/dist/index.js +36 -2
- package/dist/index.js.map +1 -1
- package/dist/portfolio.d.ts +77 -0
- package/dist/portfolio.js +108 -0
- package/dist/portfolio.js.map +1 -0
- package/dist/posting-ledger.d.ts +51 -1
- package/dist/posting-ledger.js +145 -8
- package/dist/posting-ledger.js.map +1 -1
- package/dist/preflight.d.ts +39 -0
- package/dist/preflight.js +169 -0
- package/dist/preflight.js.map +1 -0
- package/dist/scout.d.ts +21 -0
- package/dist/scout.js +27 -1
- package/dist/scout.js.map +1 -1
- package/dist/shipcheck.d.ts +37 -0
- package/dist/shipcheck.js +228 -0
- package/dist/shipcheck.js.map +1 -0
- package/dist/sync.d.ts +88 -0
- package/dist/sync.js +320 -0
- package/dist/sync.js.map +1 -0
- package/package.json +1 -1
- package/server.json +26 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `greenlight portfolio`: one-screen campaign summary (v0.10.0).
|
|
3
|
+
*
|
|
4
|
+
* Multi-agent field lesson 2026-10-18: with several agents opening PRs
|
|
5
|
+
* in parallel, nobody holds the whole board in their head. `portfolio`
|
|
6
|
+
* combines the API census (open/merged/closed), claims by state, the
|
|
7
|
+
* pending posting queue, and the sync drift count into one screen, as
|
|
8
|
+
* human text or --json. READS ONLY: it never posts or claims.
|
|
9
|
+
*/
|
|
10
|
+
import { type ClaimsRegistryFile } from "./claims-registry.js";
|
|
11
|
+
import { type PostingLedger } from "./posting-ledger.js";
|
|
12
|
+
import { type CensusPr, type SyncDrift } from "./sync.js";
|
|
13
|
+
export interface ClaimBuckets {
|
|
14
|
+
/** Claims with a linked PR URL. */
|
|
15
|
+
linked: number;
|
|
16
|
+
/** No PR, older than the stale window. */
|
|
17
|
+
unlinkedStale: number;
|
|
18
|
+
/** The rest: recent, unlinked, still in play. */
|
|
19
|
+
active: number;
|
|
20
|
+
}
|
|
21
|
+
export interface PendingQueue {
|
|
22
|
+
/** Ledger entries that never got a confirmed outcome. */
|
|
23
|
+
unconfirmedPosts: Array<{
|
|
24
|
+
ts: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
targetUrl: string;
|
|
27
|
+
agent?: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** Claims with no linked PR (built work that may not be posted). */
|
|
30
|
+
unlinkedClaims: Array<{
|
|
31
|
+
issueUrl: string;
|
|
32
|
+
postedAt: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface Portfolio {
|
|
36
|
+
author: string;
|
|
37
|
+
census: {
|
|
38
|
+
total: number;
|
|
39
|
+
open: number;
|
|
40
|
+
merged: number;
|
|
41
|
+
closedUnmerged: number;
|
|
42
|
+
};
|
|
43
|
+
claims: ClaimBuckets & {
|
|
44
|
+
total: number;
|
|
45
|
+
};
|
|
46
|
+
knownPrs: number;
|
|
47
|
+
pending: PendingQueue;
|
|
48
|
+
drift: {
|
|
49
|
+
total: number;
|
|
50
|
+
unknownPrs: number;
|
|
51
|
+
stateMismatches: number;
|
|
52
|
+
staleClaims: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export declare function bucketClaims(claims: ClaimsRegistryFile["claims"], staleDays: number, nowMs: number): ClaimBuckets;
|
|
56
|
+
export declare function pendingQueue(registry: ClaimsRegistryFile, ledger: PostingLedger): PendingQueue;
|
|
57
|
+
export declare function buildPortfolio(args: {
|
|
58
|
+
author: string;
|
|
59
|
+
census: CensusPr[];
|
|
60
|
+
registry: ClaimsRegistryFile;
|
|
61
|
+
ledger: PostingLedger;
|
|
62
|
+
drift: SyncDrift;
|
|
63
|
+
staleDays: number;
|
|
64
|
+
nowMs: number;
|
|
65
|
+
}): Portfolio;
|
|
66
|
+
/** Human one-screen summary: counts first, then the items that need action. */
|
|
67
|
+
export declare function formatPortfolioHuman(p: Portfolio): string;
|
|
68
|
+
export declare function portfolioJson(p: Portfolio): Record<string, unknown>;
|
|
69
|
+
export interface PortfolioRunOptions {
|
|
70
|
+
author: string;
|
|
71
|
+
staleDays?: number;
|
|
72
|
+
registryPath?: string;
|
|
73
|
+
ledgerPath?: string;
|
|
74
|
+
nowMs?: number;
|
|
75
|
+
}
|
|
76
|
+
/** Full portfolio run: census, registry, ledger, drift, one Portfolio. */
|
|
77
|
+
export declare function runPortfolio(opts: PortfolioRunOptions): Promise<Portfolio>;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `greenlight portfolio`: one-screen campaign summary (v0.10.0).
|
|
3
|
+
*
|
|
4
|
+
* Multi-agent field lesson 2026-10-18: with several agents opening PRs
|
|
5
|
+
* in parallel, nobody holds the whole board in their head. `portfolio`
|
|
6
|
+
* combines the API census (open/merged/closed), claims by state, the
|
|
7
|
+
* pending posting queue, and the sync drift count into one screen, as
|
|
8
|
+
* human text or --json. READS ONLY: it never posts or claims.
|
|
9
|
+
*/
|
|
10
|
+
import { defaultRegistryPath, loadRegistry, } from "./claims-registry.js";
|
|
11
|
+
import { defaultLedgerPath, readLedger, } from "./posting-ledger.js";
|
|
12
|
+
import { computeDrift, fetchAuthorCensus, } from "./sync.js";
|
|
13
|
+
export function bucketClaims(claims, staleDays, nowMs) {
|
|
14
|
+
const out = { linked: 0, unlinkedStale: 0, active: 0 };
|
|
15
|
+
const staleMs = staleDays * 24 * 60 * 60 * 1000;
|
|
16
|
+
for (const c of claims) {
|
|
17
|
+
if (c.prUrl) {
|
|
18
|
+
out.linked++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const age = nowMs - Date.parse(c.postedAt);
|
|
22
|
+
if (Number.isFinite(age) && age >= staleMs)
|
|
23
|
+
out.unlinkedStale++;
|
|
24
|
+
else
|
|
25
|
+
out.active++;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
export function pendingQueue(registry, ledger) {
|
|
30
|
+
return {
|
|
31
|
+
unconfirmedPosts: ledger.entries
|
|
32
|
+
.filter((e) => e.outcome === "unconfirmed")
|
|
33
|
+
.map((e) => ({ ts: e.ts, kind: e.kind, targetUrl: e.targetUrl, agent: e.agent })),
|
|
34
|
+
unlinkedClaims: registry.claims
|
|
35
|
+
.filter((c) => !c.prUrl)
|
|
36
|
+
.map((c) => ({ issueUrl: c.issueUrl, postedAt: c.postedAt })),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function buildPortfolio(args) {
|
|
40
|
+
const { census, registry, drift, staleDays, nowMs, author } = args;
|
|
41
|
+
const claims = bucketClaims(registry.claims, staleDays, nowMs);
|
|
42
|
+
return {
|
|
43
|
+
author,
|
|
44
|
+
census: {
|
|
45
|
+
total: census.length,
|
|
46
|
+
open: census.filter((c) => c.state === "open").length,
|
|
47
|
+
merged: census.filter((c) => c.merged).length,
|
|
48
|
+
closedUnmerged: census.filter((c) => c.state === "closed" && !c.merged).length,
|
|
49
|
+
},
|
|
50
|
+
claims: { ...claims, total: registry.claims.length },
|
|
51
|
+
knownPrs: registry.prs?.length ?? 0,
|
|
52
|
+
pending: pendingQueue(registry, args.ledger),
|
|
53
|
+
drift: {
|
|
54
|
+
total: drift.unknownPrs.length + drift.stateMismatches.length + drift.staleClaims.length,
|
|
55
|
+
unknownPrs: drift.unknownPrs.length,
|
|
56
|
+
stateMismatches: drift.stateMismatches.length,
|
|
57
|
+
staleClaims: drift.staleClaims.length,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Human one-screen summary: counts first, then the items that need action. */
|
|
62
|
+
export function formatPortfolioHuman(p) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
lines.push(`**portfolio: ${p.author}**`);
|
|
65
|
+
lines.push(`PRs: ${p.census.total} total (${p.census.open} open, ${p.census.merged} merged, ${p.census.closedUnmerged} closed unmerged)`);
|
|
66
|
+
lines.push(`Claims: ${p.claims.total} total (${p.claims.linked} linked, ${p.claims.active} active, ${p.claims.unlinkedStale} unlinked-stale)`);
|
|
67
|
+
lines.push(`Known PRs in registry: ${p.knownPrs}`);
|
|
68
|
+
lines.push(`Drift: ${p.drift.total} item${p.drift.total === 1 ? "" : "s"} (${p.drift.unknownPrs} unknown PRs, ${p.drift.stateMismatches} state mismatches, ${p.drift.staleClaims} stale claims)`);
|
|
69
|
+
lines.push("");
|
|
70
|
+
const unconfirmed = p.pending.unconfirmedPosts;
|
|
71
|
+
lines.push(`## Pending posts (${unconfirmed.length})`);
|
|
72
|
+
if (unconfirmed.length === 0)
|
|
73
|
+
lines.push("_None._");
|
|
74
|
+
else
|
|
75
|
+
for (const u of unconfirmed.slice(0, 10)) {
|
|
76
|
+
lines.push(`- ${u.kind} ${u.targetUrl} (${u.ts}${u.agent ? `, agent ${u.agent}` : ""})`);
|
|
77
|
+
}
|
|
78
|
+
if (unconfirmed.length > 10)
|
|
79
|
+
lines.push(`_... and ${unconfirmed.length - 10} more._`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
const unlinked = p.pending.unlinkedClaims;
|
|
82
|
+
lines.push(`## Unlinked claims (${unlinked.length})`);
|
|
83
|
+
if (unlinked.length === 0)
|
|
84
|
+
lines.push("_None._");
|
|
85
|
+
else
|
|
86
|
+
for (const u of unlinked.slice(0, 10)) {
|
|
87
|
+
lines.push(`- ${u.issueUrl} (claimed ${u.postedAt})`);
|
|
88
|
+
}
|
|
89
|
+
if (unlinked.length > 10)
|
|
90
|
+
lines.push(`_... and ${unlinked.length - 10} more._`);
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
export function portfolioJson(p) {
|
|
94
|
+
return { portfolio: p };
|
|
95
|
+
}
|
|
96
|
+
/** Full portfolio run: census, registry, ledger, drift, one Portfolio. */
|
|
97
|
+
export async function runPortfolio(opts) {
|
|
98
|
+
const registryPath = opts.registryPath ?? defaultRegistryPath();
|
|
99
|
+
const ledgerPath = opts.ledgerPath ?? defaultLedgerPath();
|
|
100
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
101
|
+
const staleDays = opts.staleDays ?? 7;
|
|
102
|
+
const census = await fetchAuthorCensus(opts.author);
|
|
103
|
+
const registry = loadRegistry(registryPath);
|
|
104
|
+
const ledger = readLedger(ledgerPath);
|
|
105
|
+
const drift = computeDrift(census, registry, null, staleDays, nowMs);
|
|
106
|
+
return buildPortfolio({ author: opts.author, census, registry, ledger, drift, staleDays, nowMs });
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=portfolio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portfolio.js","sourceRoot":"","sources":["../src/portfolio.ts"],"names":[],"mappings":"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"}
|
package/dist/posting-ledger.d.ts
CHANGED
|
@@ -4,13 +4,32 @@ export type PostingOutcome = "confirmed" | "blocked" | "unconfirmed" | "supersed
|
|
|
4
4
|
export interface PostingLedgerEntry {
|
|
5
5
|
/** ISO timestamp of the attempt. */
|
|
6
6
|
ts: string;
|
|
7
|
+
/**
|
|
8
|
+
* Unique entry id (v0.10.0). Two posts in the same millisecond share a
|
|
9
|
+
* ts, so finishPost matches on this, never on ts alone. Older entries
|
|
10
|
+
* may lack it; matching falls back to ts + targetUrl for those.
|
|
11
|
+
*/
|
|
12
|
+
id?: string;
|
|
7
13
|
kind: PostingKind;
|
|
8
14
|
/** Canonical target, e.g. https://github.com/owner/repo/issues/123. */
|
|
9
15
|
targetUrl: string;
|
|
10
16
|
outcome: PostingOutcome;
|
|
11
17
|
/** Short human note, e.g. "comment #6238115199" or "403 Blocked". */
|
|
12
18
|
note?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Which agent recorded the entry (v0.10.0). GREENLIGHT_AGENT_ID when set,
|
|
21
|
+
* otherwise "<hostname>:<pid>" captured at write time. Older entries may
|
|
22
|
+
* lack it; pacing counts every entry regardless.
|
|
23
|
+
*/
|
|
24
|
+
agent?: string;
|
|
13
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The identity stamped on each ledger entry. Operators running several
|
|
28
|
+
* agents (or machines) in parallel set GREENLIGHT_AGENT_ID per agent
|
|
29
|
+
* (e.g. "worker-a", "cron-scan") so the ledger shows who posted what.
|
|
30
|
+
* Unset, the tool falls back to "<hostname>:<pid>".
|
|
31
|
+
*/
|
|
32
|
+
export declare function agentId(env?: NodeJS.ProcessEnv): string;
|
|
14
33
|
export interface PostingLedger {
|
|
15
34
|
v: 1;
|
|
16
35
|
entries: PostingLedgerEntry[];
|
|
@@ -19,9 +38,40 @@ export declare function defaultLedgerPath(): string;
|
|
|
19
38
|
export declare function emptyLedger(): PostingLedger;
|
|
20
39
|
/** Read the ledger; a missing or corrupt file means an empty ledger. */
|
|
21
40
|
export declare function readLedger(path?: string): PostingLedger;
|
|
22
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Exclusive lock around read-modify-write, so concurrent agents cannot
|
|
43
|
+
* both read an empty-looking ledger and race past the daily cap or the
|
|
44
|
+
* claim cadence. Implemented with an exclusive-create lock file and a
|
|
45
|
+
* staleness rule (a lock older than 30s is a crashed holder and is
|
|
46
|
+
* stolen), so a dead process can never wedge the ledger forever.
|
|
47
|
+
*/
|
|
48
|
+
export declare function withLedgerLock<T>(path: string, fn: () => T): T;
|
|
49
|
+
export declare function appendLedgerEntry(entry: Omit<PostingLedgerEntry, "ts" | "agent"> & {
|
|
23
50
|
ts?: string;
|
|
51
|
+
agent?: string;
|
|
24
52
|
}, path?: string): PostingLedgerEntry;
|
|
53
|
+
/**
|
|
54
|
+
* Result of reserving a post under the lock: the pacing check and the
|
|
55
|
+
* ledger write happen in one critical section, so two agents cannot both
|
|
56
|
+
* observe an allowed window and both post. The reservation is recorded
|
|
57
|
+
* immediately with outcome "unconfirmed"; finishPost updates it once the
|
|
58
|
+
* post resolves.
|
|
59
|
+
*/
|
|
60
|
+
export interface ReserveResult {
|
|
61
|
+
allowed: boolean;
|
|
62
|
+
reason: string | null;
|
|
63
|
+
/** The reserved entry (matched by ts + targetUrl in finishPost), or null. */
|
|
64
|
+
entry: PostingLedgerEntry | null;
|
|
65
|
+
decision: PacingDecision;
|
|
66
|
+
}
|
|
67
|
+
export declare function reservePost(path: string, entry: Omit<PostingLedgerEntry, "ts" | "agent" | "outcome"> & {
|
|
68
|
+
outcome?: PostingOutcome;
|
|
69
|
+
agent?: string;
|
|
70
|
+
}, config: PacingConfig, nowMs: number, opts?: {
|
|
71
|
+
force?: boolean;
|
|
72
|
+
}): ReserveResult;
|
|
73
|
+
/** Update a reserved entry's outcome under the lock (matched by ts + targetUrl). */
|
|
74
|
+
export declare function finishPost(path: string, entry: PostingLedgerEntry, outcome: PostingOutcome, note?: string): void;
|
|
25
75
|
/** Comment-class writes (the surface GitHub's anti-spam flag watches). */
|
|
26
76
|
export declare const COMMENT_KINDS: PostingKind[];
|
|
27
77
|
export interface PacingConfig {
|
package/dist/posting-ledger.js
CHANGED
|
@@ -17,9 +17,22 @@
|
|
|
17
17
|
* All time-dependent decisions take an explicit `nowMs`, so the tests
|
|
18
18
|
* drive them with an injected clock.
|
|
19
19
|
*/
|
|
20
|
-
import { homedir } from "node:os";
|
|
20
|
+
import { homedir, hostname } from "node:os";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import {
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { mkdirSync, readFileSync, writeFileSync, openSync, closeSync, statSync, unlinkSync, } from "node:fs";
|
|
24
|
+
/**
|
|
25
|
+
* The identity stamped on each ledger entry. Operators running several
|
|
26
|
+
* agents (or machines) in parallel set GREENLIGHT_AGENT_ID per agent
|
|
27
|
+
* (e.g. "worker-a", "cron-scan") so the ledger shows who posted what.
|
|
28
|
+
* Unset, the tool falls back to "<hostname>:<pid>".
|
|
29
|
+
*/
|
|
30
|
+
export function agentId(env = process.env) {
|
|
31
|
+
const fromEnv = (env.GREENLIGHT_AGENT_ID ?? "").trim();
|
|
32
|
+
if (fromEnv.length > 0)
|
|
33
|
+
return fromEnv;
|
|
34
|
+
return `${hostname()}:${process.pid}`;
|
|
35
|
+
}
|
|
23
36
|
export function defaultLedgerPath() {
|
|
24
37
|
return join(homedir(), ".greenlight", "posting-ledger.json");
|
|
25
38
|
}
|
|
@@ -42,19 +55,143 @@ export function readLedger(path = defaultLedgerPath()) {
|
|
|
42
55
|
}
|
|
43
56
|
return emptyLedger();
|
|
44
57
|
}
|
|
58
|
+
/** Lock-file path for the atomic ledger operations below. */
|
|
59
|
+
function ledgerLockPath(path) {
|
|
60
|
+
return `${path}.lock`;
|
|
61
|
+
}
|
|
62
|
+
const LOCK_STALE_MS = 30_000;
|
|
63
|
+
const LOCK_POLL_MS = 25;
|
|
64
|
+
const LOCK_TIMEOUT_MS = 10_000;
|
|
65
|
+
function sleepMs(ms) {
|
|
66
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Exclusive lock around read-modify-write, so concurrent agents cannot
|
|
70
|
+
* both read an empty-looking ledger and race past the daily cap or the
|
|
71
|
+
* claim cadence. Implemented with an exclusive-create lock file and a
|
|
72
|
+
* staleness rule (a lock older than 30s is a crashed holder and is
|
|
73
|
+
* stolen), so a dead process can never wedge the ledger forever.
|
|
74
|
+
*/
|
|
75
|
+
export function withLedgerLock(path, fn) {
|
|
76
|
+
const lp = ledgerLockPath(path);
|
|
77
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
78
|
+
for (;;) {
|
|
79
|
+
try {
|
|
80
|
+
const fd = openSync(lp, "wx");
|
|
81
|
+
try {
|
|
82
|
+
writeFileSync(fd, `${process.pid}`);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Best effort: the lock's existence is the signal, not its content.
|
|
86
|
+
}
|
|
87
|
+
closeSync(fd);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
if (err?.code !== "EEXIST")
|
|
92
|
+
throw err;
|
|
93
|
+
try {
|
|
94
|
+
const st = statSync(lp);
|
|
95
|
+
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(lp);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Lost the race to steal it; loop back and re-check.
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Lock vanished between checks; loop back and re-create it.
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (Date.now() > deadline) {
|
|
110
|
+
throw new Error(`timed out acquiring posting-ledger lock for ${path}`);
|
|
111
|
+
}
|
|
112
|
+
sleepMs(LOCK_POLL_MS);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
return fn();
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
try {
|
|
120
|
+
unlinkSync(lp);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Another process already stole and released it; nothing to do.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function writeLedger(path, ledger) {
|
|
128
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
129
|
+
writeFileSync(path, JSON.stringify(ledger, null, 2));
|
|
130
|
+
}
|
|
45
131
|
export function appendLedgerEntry(entry, path = defaultLedgerPath()) {
|
|
46
|
-
const full = {
|
|
47
|
-
|
|
48
|
-
|
|
132
|
+
const full = {
|
|
133
|
+
ts: new Date().toISOString(),
|
|
134
|
+
agent: agentId(),
|
|
135
|
+
...entry,
|
|
136
|
+
};
|
|
49
137
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
138
|
+
withLedgerLock(path, () => {
|
|
139
|
+
const ledger = readLedger(path);
|
|
140
|
+
ledger.entries.push(full);
|
|
141
|
+
writeLedger(path, ledger);
|
|
142
|
+
});
|
|
52
143
|
}
|
|
53
144
|
catch {
|
|
54
|
-
// The ledger is advisory: a write failure must never break posting.
|
|
145
|
+
// The ledger is advisory: a lock or write failure must never break posting.
|
|
146
|
+
// Fall through and still return the entry we meant to record.
|
|
55
147
|
}
|
|
56
148
|
return full;
|
|
57
149
|
}
|
|
150
|
+
export function reservePost(path, entry, config, nowMs, opts = {}) {
|
|
151
|
+
return withLedgerLock(path, () => {
|
|
152
|
+
const ledger = readLedger(path);
|
|
153
|
+
const decision = checkPacing(ledger, nowMs, config);
|
|
154
|
+
if (!decision.allowed && !opts.force) {
|
|
155
|
+
return { allowed: false, reason: decision.reason, entry: null, decision };
|
|
156
|
+
}
|
|
157
|
+
const full = {
|
|
158
|
+
ts: new Date(nowMs).toISOString(),
|
|
159
|
+
agent: agentId(),
|
|
160
|
+
outcome: "unconfirmed",
|
|
161
|
+
...entry,
|
|
162
|
+
// Unique per reservation: two posts in the same millisecond must
|
|
163
|
+
// not share an identity (finishPost matches on this). The caller's
|
|
164
|
+
// partial never overrides it.
|
|
165
|
+
id: randomUUID(),
|
|
166
|
+
};
|
|
167
|
+
ledger.entries.push(full);
|
|
168
|
+
writeLedger(path, ledger);
|
|
169
|
+
return { allowed: true, reason: null, entry: full, decision };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/** Update a reserved entry's outcome under the lock (matched by ts + targetUrl). */
|
|
173
|
+
export function finishPost(path, entry, outcome, note) {
|
|
174
|
+
try {
|
|
175
|
+
withLedgerLock(path, () => {
|
|
176
|
+
const ledger = readLedger(path);
|
|
177
|
+
// Match on the reservation id: ts + targetUrl is not unique when
|
|
178
|
+
// two posts land in the same millisecond. Entries written before
|
|
179
|
+
// ids existed fall back to ts + targetUrl.
|
|
180
|
+
const stored = entry.id
|
|
181
|
+
? ledger.entries.find((e) => e.id === entry.id)
|
|
182
|
+
: ledger.entries.find((e) => e.ts === entry.ts && e.targetUrl === entry.targetUrl);
|
|
183
|
+
if (stored) {
|
|
184
|
+
stored.outcome = outcome;
|
|
185
|
+
if (note !== undefined)
|
|
186
|
+
stored.note = note;
|
|
187
|
+
writeLedger(path, ledger);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Advisory, like every ledger write.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
58
195
|
/** Comment-class writes (the surface GitHub's anti-spam flag watches). */
|
|
59
196
|
export const COMMENT_KINDS = ["claim", "release", "heartbeat", "comment"];
|
|
60
197
|
export function pacingConfigFromEnv(env = process.env) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posting-ledger.js","sourceRoot":"","sources":["../src/posting-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"posting-ledger.js","sourceRoot":"","sources":["../src/posting-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface PreflightOptions {
|
|
2
|
+
owner: string;
|
|
3
|
+
repo: string;
|
|
4
|
+
/** Base SHA the fix was built against; with --files, enables drift detection. */
|
|
5
|
+
base?: string;
|
|
6
|
+
/** Repo-relative file paths to check for upstream drift. */
|
|
7
|
+
files?: string[];
|
|
8
|
+
capabilitiesPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ForkCheck {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
detail: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CapabilityCheck {
|
|
15
|
+
verdict: "KNOWN-BLOCKED" | "CLEAR" | "NO-RECORD";
|
|
16
|
+
detail: string;
|
|
17
|
+
}
|
|
18
|
+
export interface GateCheck {
|
|
19
|
+
collaboratorsOnly: boolean;
|
|
20
|
+
detected: string[];
|
|
21
|
+
detail: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DriftFile {
|
|
24
|
+
path: string;
|
|
25
|
+
status: "CLEAN" | "DRIFTED" | "MISSING";
|
|
26
|
+
detail: string;
|
|
27
|
+
}
|
|
28
|
+
export interface PreflightResult {
|
|
29
|
+
owner: string;
|
|
30
|
+
repo: string;
|
|
31
|
+
fork: ForkCheck;
|
|
32
|
+
capability: CapabilityCheck;
|
|
33
|
+
gates: GateCheck;
|
|
34
|
+
drift?: DriftFile[];
|
|
35
|
+
verdict: "GO" | "NO-GO";
|
|
36
|
+
reasons: string[];
|
|
37
|
+
}
|
|
38
|
+
export declare function runPreflight(opts: PreflightOptions): Promise<PreflightResult>;
|
|
39
|
+
export declare function formatPreflightHuman(r: PreflightResult): string;
|