@schoolai/shipyard 3.7.0 → 3.8.0-rc.20260529.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.
- package/dist/{auth-SS7LV5XK.js → auth-EXHO3AG5.js} +4 -4
- package/dist/capability-detector-worker.js +142 -0
- package/dist/capability-detector-worker.js.map +1 -0
- package/dist/{chunk-DKMDBOFU.js → chunk-2CNIEBKO.js} +21 -11
- package/dist/chunk-2CNIEBKO.js.map +1 -0
- package/dist/chunk-4T2OQAVL.js +51 -0
- package/dist/chunk-4T2OQAVL.js.map +1 -0
- package/dist/chunk-5ER6ZHA2.js +46 -0
- package/dist/chunk-5ER6ZHA2.js.map +1 -0
- package/dist/chunk-7LSEE26O.js +24227 -0
- package/dist/chunk-7LSEE26O.js.map +1 -0
- package/dist/{chunk-7AHRFPAL.js → chunk-7YOU7MBN.js} +183 -17
- package/dist/chunk-7YOU7MBN.js.map +1 -0
- package/dist/chunk-CMGJGK6R.js +382 -0
- package/dist/chunk-CMGJGK6R.js.map +1 -0
- package/dist/{chunk-VPMN47TL.js → chunk-CNR7O5YH.js} +1 -2
- package/dist/{chunk-2J3WSIAF.js → chunk-EF2DAODF.js} +18 -3
- package/dist/chunk-EF2DAODF.js.map +1 -0
- package/dist/chunk-HQ43PHOH.js +1203 -0
- package/dist/chunk-HQ43PHOH.js.map +1 -0
- package/dist/chunk-KITSAHTX.js +134 -0
- package/dist/chunk-KITSAHTX.js.map +1 -0
- package/dist/chunk-LESHN5J5.js +6898 -0
- package/dist/chunk-LESHN5J5.js.map +1 -0
- package/dist/{chunk-LW2MS4T5.js → chunk-LMJFHKRD.js} +15 -12
- package/dist/chunk-LMJFHKRD.js.map +1 -0
- package/dist/{chunk-SNYEQHUK.js → chunk-NACJENDW.js} +14 -21
- package/dist/chunk-NACJENDW.js.map +1 -0
- package/dist/{chunk-IISLTKYY.js → chunk-TU63KZFW.js} +2 -2
- package/dist/chunk-TX6DK4PK.js +186 -0
- package/dist/chunk-TX6DK4PK.js.map +1 -0
- package/dist/chunk-UQVXWOPT.js +48 -0
- package/dist/chunk-UQVXWOPT.js.map +1 -0
- package/dist/{chunk-3MNPDCO5.js → chunk-WBB4XHLH.js} +139 -140
- package/dist/chunk-WBB4XHLH.js.map +1 -0
- package/dist/chunk-X3MULCV5.js +11 -0
- package/dist/chunk-X3MULCV5.js.map +1 -0
- package/dist/chunk-YZ3Z3ZYI.js +787 -0
- package/dist/chunk-YZ3Z3ZYI.js.map +1 -0
- package/dist/{chunk-2UN5AR7V.js → chunk-ZAOPND5G.js} +2 -2
- package/dist/chunk-ZFKJAYAN.js +542 -0
- package/dist/chunk-ZFKJAYAN.js.map +1 -0
- package/dist/cursor-hook-shim.js +316 -0
- package/dist/cursor-hook-shim.js.map +1 -0
- package/dist/cursor-runner.js +358 -0
- package/dist/cursor-runner.js.map +1 -0
- package/dist/electron-utility.js +111 -0
- package/dist/electron-utility.js.map +1 -0
- package/dist/git-pool-V73Q53NX.js +18 -0
- package/dist/{git-repo-VRT57DGC.js → git-repo-TN3VZXQV.js} +9 -6
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/{logger-GQCSLSZH.js → logger-QHPTO22N.js} +4 -4
- package/dist/login-Q7SZI7JJ.js +20 -0
- package/dist/{logout-VUNCW5B2.js → logout-O4AVMO5S.js} +6 -6
- package/dist/mcp-servers-F64M5T4I.js +24 -0
- package/dist/{roi-Y3MX5UW4.js → roi-EYDLPOCS.js} +5 -5
- package/dist/rss-worker.js +159 -0
- package/dist/rss-worker.js.map +1 -0
- package/dist/{serve-O53FNK64.js → serve-6A7RJWEF.js} +89862 -102999
- package/dist/{serve-O53FNK64.js.map → serve-6A7RJWEF.js.map} +1 -1
- package/dist/skills-ZHEPSBHW.js +11 -0
- package/dist/{start-IDFDHRD6.js → start-YGYYIK53.js} +229 -27
- package/dist/start-YGYYIK53.js.map +1 -0
- package/dist/vault-crypto-BKDOA65F.js +13 -0
- package/dist/vault-crypto-BKDOA65F.js.map +1 -0
- package/dist/worker.js +6 -3
- package/dist/worker.js.map +1 -1
- package/package.json +17 -10
- package/dist/chunk-2J3WSIAF.js.map +0 -1
- package/dist/chunk-3MNPDCO5.js.map +0 -1
- package/dist/chunk-66OBOZ3X.js +0 -79
- package/dist/chunk-66OBOZ3X.js.map +0 -1
- package/dist/chunk-7AHRFPAL.js.map +0 -1
- package/dist/chunk-DKMDBOFU.js.map +0 -1
- package/dist/chunk-L2WQMPWS.js +0 -666
- package/dist/chunk-L2WQMPWS.js.map +0 -1
- package/dist/chunk-LW2MS4T5.js.map +0 -1
- package/dist/chunk-PI77CUEP.js +0 -49
- package/dist/chunk-PI77CUEP.js.map +0 -1
- package/dist/chunk-RXI4637N.js +0 -395
- package/dist/chunk-RXI4637N.js.map +0 -1
- package/dist/chunk-SNYEQHUK.js.map +0 -1
- package/dist/chunk-VBPHGPBR.js +0 -126
- package/dist/chunk-VBPHGPBR.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/login-L4BBPUYO.js +0 -20
- package/dist/mcp-servers-MXS5VAWI.js +0 -18
- package/dist/shell-V36EX2IJ.js +0 -27
- package/dist/skills-GPGRNV4R.js +0 -9
- package/dist/start-IDFDHRD6.js.map +0 -1
- package/dist/worker.d.ts +0 -49
- /package/dist/{auth-SS7LV5XK.js.map → auth-EXHO3AG5.js.map} +0 -0
- /package/dist/{chunk-VPMN47TL.js.map → chunk-CNR7O5YH.js.map} +0 -0
- /package/dist/{chunk-IISLTKYY.js.map → chunk-TU63KZFW.js.map} +0 -0
- /package/dist/{chunk-2UN5AR7V.js.map → chunk-ZAOPND5G.js.map} +0 -0
- /package/dist/{git-repo-VRT57DGC.js.map → git-pool-V73Q53NX.js.map} +0 -0
- /package/dist/{logger-GQCSLSZH.js.map → git-repo-TN3VZXQV.js.map} +0 -0
- /package/dist/{login-L4BBPUYO.js.map → logger-QHPTO22N.js.map} +0 -0
- /package/dist/{mcp-servers-MXS5VAWI.js.map → login-Q7SZI7JJ.js.map} +0 -0
- /package/dist/{logout-VUNCW5B2.js.map → logout-O4AVMO5S.js.map} +0 -0
- /package/dist/{shell-V36EX2IJ.js.map → mcp-servers-F64M5T4I.js.map} +0 -0
- /package/dist/{roi-Y3MX5UW4.js.map → roi-EYDLPOCS.js.map} +0 -0
- /package/dist/{skills-GPGRNV4R.js.map → skills-ZHEPSBHW.js.map} +0 -0
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
loadAuthToken,
|
|
6
6
|
readConfig,
|
|
7
7
|
writeConfig
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-EF2DAODF.js";
|
|
9
|
+
import "./chunk-KITSAHTX.js";
|
|
10
|
+
import "./chunk-CNR7O5YH.js";
|
|
11
11
|
import "./chunk-2H7UOFLK.js";
|
|
12
12
|
export {
|
|
13
13
|
deleteConfig,
|
|
@@ -16,4 +16,4 @@ export {
|
|
|
16
16
|
readConfig,
|
|
17
17
|
writeConfig
|
|
18
18
|
};
|
|
19
|
-
//# sourceMappingURL=auth-
|
|
19
|
+
//# sourceMappingURL=auth-EXHO3AG5.js.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCPTokenStore,
|
|
4
|
+
detectCapabilities,
|
|
5
|
+
refreshMcpServers,
|
|
6
|
+
registerBuiltInProfiles
|
|
7
|
+
} from "./chunk-LESHN5J5.js";
|
|
8
|
+
import {
|
|
9
|
+
getRepoMetadata
|
|
10
|
+
} from "./chunk-YZ3Z3ZYI.js";
|
|
11
|
+
import "./chunk-4T2OQAVL.js";
|
|
12
|
+
import "./chunk-HQ43PHOH.js";
|
|
13
|
+
import "./chunk-CMGJGK6R.js";
|
|
14
|
+
import "./chunk-ZFKJAYAN.js";
|
|
15
|
+
import "./chunk-7LSEE26O.js";
|
|
16
|
+
import "./chunk-7YOU7MBN.js";
|
|
17
|
+
import "./chunk-EHQITHQX.js";
|
|
18
|
+
import "./chunk-X3MULCV5.js";
|
|
19
|
+
import {
|
|
20
|
+
flushLogger,
|
|
21
|
+
logger
|
|
22
|
+
} from "./chunk-ZAOPND5G.js";
|
|
23
|
+
import "./chunk-KITSAHTX.js";
|
|
24
|
+
import "./chunk-CNR7O5YH.js";
|
|
25
|
+
import "./chunk-2H7UOFLK.js";
|
|
26
|
+
|
|
27
|
+
// src/shared/capabilities/detector-runner/worker.ts
|
|
28
|
+
import { parentPort } from "worker_threads";
|
|
29
|
+
|
|
30
|
+
// src/services/cwd-capability-refresh.ts
|
|
31
|
+
async function buildCwdScopedPatch(deps) {
|
|
32
|
+
const { newCwd, environments, preferredAuth, tokenStore, lastKnownMcpServers } = deps;
|
|
33
|
+
const alreadyKnown = environments.some((e) => e.path === newCwd);
|
|
34
|
+
const meta = alreadyKnown ? null : await getRepoMetadata(newCwd);
|
|
35
|
+
const nextEnvironments = meta ? [...environments, meta] : environments;
|
|
36
|
+
const mcpPatch = await refreshMcpServers({
|
|
37
|
+
environments: nextEnvironments,
|
|
38
|
+
tokenStore,
|
|
39
|
+
preferredAuth,
|
|
40
|
+
lastKnown: lastKnownMcpServers,
|
|
41
|
+
/**
|
|
42
|
+
* Incremental scope: only the new cwd's `.claude/settings.json` (and
|
|
43
|
+
* sibling project-tier configs) need a fresh read. The other 180+ env
|
|
44
|
+
* scans were the dominant cost (4-10s combined across the worktree-
|
|
45
|
+
* creation flow's 4 back-to-back scoped refreshes); now they reuse
|
|
46
|
+
* `lastKnownMcpServers` for project entries from unchanged envs.
|
|
47
|
+
*/
|
|
48
|
+
changedCwd: newCwd
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
...meta ? { environments: nextEnvironments } : {},
|
|
52
|
+
...mcpPatch
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/shared/capabilities/detector-runner/worker.ts
|
|
57
|
+
function tokenStoreFromHome(home) {
|
|
58
|
+
return home ? new MCPTokenStore(home) : void 0;
|
|
59
|
+
}
|
|
60
|
+
function ensureCapabilityDetectorWorkerProfilesRegistered() {
|
|
61
|
+
registerBuiltInProfiles();
|
|
62
|
+
}
|
|
63
|
+
async function runRequest(request, abortSignal) {
|
|
64
|
+
ensureCapabilityDetectorWorkerProfilesRegistered();
|
|
65
|
+
switch (request.kind) {
|
|
66
|
+
case "detectFull":
|
|
67
|
+
return detectCapabilities(
|
|
68
|
+
tokenStoreFromHome(request.tokenStoreHome),
|
|
69
|
+
request.methodHint,
|
|
70
|
+
request.lastKnownAuth,
|
|
71
|
+
request.lastKnownAgents,
|
|
72
|
+
request.lastKnownCapabilities,
|
|
73
|
+
request.codexMethodHint,
|
|
74
|
+
request.lastKnownCodexAuth,
|
|
75
|
+
abortSignal
|
|
76
|
+
);
|
|
77
|
+
case "refreshMcpServers":
|
|
78
|
+
return refreshMcpServers({
|
|
79
|
+
environments: request.environments,
|
|
80
|
+
tokenStore: tokenStoreFromHome(request.tokenStoreHome),
|
|
81
|
+
preferredAuth: request.preferredAuth,
|
|
82
|
+
lastKnown: request.lastKnown
|
|
83
|
+
});
|
|
84
|
+
case "buildCwdScopedPatch":
|
|
85
|
+
return buildCwdScopedPatch({
|
|
86
|
+
newCwd: request.newCwd,
|
|
87
|
+
environments: request.environments,
|
|
88
|
+
preferredAuth: request.preferredAuth,
|
|
89
|
+
tokenStore: tokenStoreFromHome(request.tokenStoreHome),
|
|
90
|
+
lastKnownMcpServers: request.lastKnownMcpServers
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function handleCapabilityDetectorMessage(request, port, flush = flushLogger, abortSignal = new AbortController().signal) {
|
|
95
|
+
const requestId = request.requestId ?? "";
|
|
96
|
+
try {
|
|
97
|
+
const data = await runRequest(request, abortSignal);
|
|
98
|
+
await flush();
|
|
99
|
+
port.postMessage({ requestId, ok: true, data });
|
|
100
|
+
} catch (err) {
|
|
101
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
102
|
+
logger.warn({
|
|
103
|
+
event: "capability_detector_worker_request_failed",
|
|
104
|
+
kind: request.kind,
|
|
105
|
+
error
|
|
106
|
+
});
|
|
107
|
+
await flush();
|
|
108
|
+
port.postMessage({ requestId, ok: false, error });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function handleShutdownSignal(requestAbortControllers, exit = process.exit) {
|
|
112
|
+
for (const ctrl of requestAbortControllers) ctrl.abort();
|
|
113
|
+
requestAbortControllers.clear();
|
|
114
|
+
exit(0);
|
|
115
|
+
}
|
|
116
|
+
if (parentPort !== null) {
|
|
117
|
+
const port = parentPort;
|
|
118
|
+
const requestAbortControllers = /* @__PURE__ */ new Set();
|
|
119
|
+
port.on("close", () => {
|
|
120
|
+
for (const ctrl of requestAbortControllers) ctrl.abort();
|
|
121
|
+
requestAbortControllers.clear();
|
|
122
|
+
});
|
|
123
|
+
port.on("message", (request) => {
|
|
124
|
+
if (request.kind === "shutdown") {
|
|
125
|
+
handleShutdownSignal(requestAbortControllers);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
requestAbortControllers.add(controller);
|
|
130
|
+
void handleCapabilityDetectorMessage(request, port, flushLogger, controller.signal).finally(
|
|
131
|
+
() => {
|
|
132
|
+
requestAbortControllers.delete(controller);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export {
|
|
138
|
+
ensureCapabilityDetectorWorkerProfilesRegistered,
|
|
139
|
+
handleCapabilityDetectorMessage,
|
|
140
|
+
handleShutdownSignal
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=capability-detector-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/detector-runner/worker.ts","../src/services/cwd-capability-refresh.ts"],"sourcesContent":["import { parentPort } from 'node:worker_threads';\nimport type { MachineCapabilities } from '@shipyard/session';\nimport { buildCwdScopedPatch } from '../../../services/cwd-capability-refresh.js';\nimport { registerBuiltInProfiles } from '../../../services/session/profiles/profile-registry.js';\nimport { flushLogger, logger } from '../../logger.js';\nimport { MCPTokenStore } from '../../mcp/token-store.js';\nimport { detectCapabilities } from '../index.js';\nimport { refreshMcpServers, type ScopedCapabilitiesPatch } from '../refresh-helpers.js';\nimport type {\n CapabilityDetectorReply,\n CapabilityDetectorRequest,\n CapabilityDetectorWorkRequest,\n} from './runner.js';\n\nfunction tokenStoreFromHome(home: string | undefined): MCPTokenStore | undefined {\n return home ? new MCPTokenStore(home) : undefined;\n}\n\nexport function ensureCapabilityDetectorWorkerProfilesRegistered(): void {\n /**\n * Worker threads get a fresh module graph, so the main daemon's\n * registerBuiltInProfiles() call does not populate this process singleton.\n */\n registerBuiltInProfiles();\n}\n\nasync function runRequest(\n request: CapabilityDetectorWorkRequest,\n abortSignal: AbortSignal\n): Promise<MachineCapabilities | ScopedCapabilitiesPatch> {\n ensureCapabilityDetectorWorkerProfilesRegistered();\n switch (request.kind) {\n case 'detectFull':\n return detectCapabilities(\n tokenStoreFromHome(request.tokenStoreHome),\n request.methodHint,\n request.lastKnownAuth,\n request.lastKnownAgents,\n request.lastKnownCapabilities,\n request.codexMethodHint,\n request.lastKnownCodexAuth,\n abortSignal\n );\n case 'refreshMcpServers':\n return refreshMcpServers({\n environments: request.environments,\n tokenStore: tokenStoreFromHome(request.tokenStoreHome),\n preferredAuth: request.preferredAuth,\n lastKnown: request.lastKnown,\n });\n case 'buildCwdScopedPatch':\n return buildCwdScopedPatch({\n newCwd: request.newCwd,\n environments: request.environments,\n preferredAuth: request.preferredAuth,\n tokenStore: tokenStoreFromHome(request.tokenStoreHome),\n lastKnownMcpServers: request.lastKnownMcpServers,\n });\n }\n}\n\ninterface WorkerPort {\n postMessage: (reply: CapabilityDetectorReply) => void;\n}\n\n/**\n * Handle one incoming request message. Exported for testing — the flush\n * ordering contract (flushLogger before postMessage) is verified there.\n *\n * Root cause context: runner.ts calls worker.terminate() shortly after\n * receiving the reply, which can kill pino's transport worker before it\n * drains — dropping the bootstrap_phase_end log. Flushing before postMessage\n * ensures buffered log entries reach disk before the runner tears the thread.\n *\n * Accepts an `abortSignal` so the runtime caller can wire it to the parent\n * port's `'close'` event; tests pass an unused fresh AbortController.\n */\nexport async function handleCapabilityDetectorMessage(\n request: CapabilityDetectorWorkRequest,\n port: WorkerPort,\n flush: () => Promise<void> = flushLogger,\n abortSignal: AbortSignal = new AbortController().signal\n): Promise<void> {\n const requestId = request.requestId ?? '';\n try {\n const data = await runRequest(request, abortSignal);\n await flush();\n port.postMessage({ requestId, ok: true, data } satisfies CapabilityDetectorReply);\n } catch (err: unknown) {\n const error = err instanceof Error ? err.message : String(err);\n logger.warn({\n event: 'capability_detector_worker_request_failed',\n kind: request.kind,\n error,\n });\n await flush();\n port.postMessage({ requestId, ok: false, error } satisfies CapabilityDetectorReply);\n }\n}\n\n/**\n * Handle a 'shutdown' signal from the runner. Aborts all in-flight requests\n * and exits the worker process cleanly, avoiding the c-ares destructor SIGTRAP\n * that fires when worker.terminate() abruptly tears down the V8 isolate while\n * DNS queries are still pending.\n *\n * Exported for testing — callers inject the abort controller set and the exit\n * function so neither I/O side-effect is required in unit tests.\n */\nexport function handleShutdownSignal(\n requestAbortControllers: Set<AbortController>,\n exit: (code: number) => void = process.exit\n): void {\n for (const ctrl of requestAbortControllers) ctrl.abort();\n requestAbortControllers.clear();\n exit(0);\n}\n\nif (parentPort !== null) {\n const port = parentPort;\n /**\n * Outer worker timeout terminates this thread without warning. The\n * port 'close' event is our only signal; abort all in-flight phases so\n * each stuck `bootstrapPhase` emits `outcome:'worker_killed'` before the\n * thread dies. Without it, the log shows `_start` but never `_end` for\n * the stuck phase.\n */\n const requestAbortControllers = new Set<AbortController>();\n port.on('close', () => {\n for (const ctrl of requestAbortControllers) ctrl.abort();\n requestAbortControllers.clear();\n });\n port.on('message', (request: CapabilityDetectorRequest) => {\n if (request.kind === 'shutdown') {\n handleShutdownSignal(requestAbortControllers);\n return;\n }\n const controller = new AbortController();\n requestAbortControllers.add(controller);\n void handleCapabilityDetectorMessage(request, port, flushLogger, controller.signal).finally(\n () => {\n requestAbortControllers.delete(controller);\n }\n );\n });\n}\n","import type { AnthropicAuthMethod } from '@shipyard/loro-schema';\nimport type { GitRepoInfo, MCPServerInfo } from '@shipyard/session';\nimport { getRepoMetadata } from '../shared/capabilities/git-repo.js';\nimport {\n refreshMcpServers,\n type ScopedCapabilitiesPatch,\n} from '../shared/capabilities/refresh-helpers.js';\nimport type { MCPTokenStore } from '../shared/mcp/token-store.js';\n\n/**\n * Pure helper: build the scoped capability patch for a cwd change.\n *\n * Only the cwd-dependent slices are populated — `environments` (so the new\n * cwd appears in the broadcast snapshot if it isn't already there) and\n * `mcpServers` (which scan project-level `.claude/settings.json` in each\n * environment). All other capability slices refresh via the FSEvents-driven\n * capability-watcher and the safety-net backstop tick — re-running them on\n * every task switch wasted ~25min CPU per 10.8h on the 24h log window and\n * accounted for 70% of event-loop stalls (P50 696ms, P99 15.6s).\n */\nexport async function buildCwdScopedPatch(deps: {\n newCwd: string;\n environments: GitRepoInfo[];\n preferredAuth: AnthropicAuthMethod | null | undefined;\n tokenStore: MCPTokenStore | undefined;\n lastKnownMcpServers: MCPServerInfo[] | undefined;\n}): Promise<ScopedCapabilitiesPatch> {\n const { newCwd, environments, preferredAuth, tokenStore, lastKnownMcpServers } = deps;\n const alreadyKnown = environments.some((e) => e.path === newCwd);\n const meta = alreadyKnown ? null : await getRepoMetadata(newCwd);\n const nextEnvironments = meta ? [...environments, meta] : environments;\n const mcpPatch = await refreshMcpServers({\n environments: nextEnvironments,\n tokenStore,\n preferredAuth,\n lastKnown: lastKnownMcpServers,\n /**\n * Incremental scope: only the new cwd's `.claude/settings.json` (and\n * sibling project-tier configs) need a fresh read. The other 180+ env\n * scans were the dominant cost (4-10s combined across the worktree-\n * creation flow's 4 back-to-back scoped refreshes); now they reuse\n * `lastKnownMcpServers` for project entries from unchanged envs.\n */\n changedCwd: newCwd,\n });\n return {\n ...(meta ? { environments: nextEnvironments } : {}),\n ...mcpPatch,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -16,7 +16,8 @@ var TaskStartedEventSchema = EventBase.extend({
|
|
|
16
16
|
kind: external_exports.literal("task_started"),
|
|
17
17
|
activeTaskCount: external_exports.number().int().nonnegative(),
|
|
18
18
|
taskType: external_exports.string().max(50).optional(),
|
|
19
|
-
mode: external_exports.string().max(50)
|
|
19
|
+
mode: external_exports.string().max(50),
|
|
20
|
+
runtimeId: external_exports.string().min(1).default("claude-code")
|
|
20
21
|
});
|
|
21
22
|
var TaskEndedEventSchema = EventBase.extend({
|
|
22
23
|
kind: external_exports.literal("task_ended"),
|
|
@@ -24,7 +25,8 @@ var TaskEndedEventSchema = EventBase.extend({
|
|
|
24
25
|
totalCostUsd: external_exports.number().nonnegative(),
|
|
25
26
|
totalOutputTokens: external_exports.number().int().nonnegative(),
|
|
26
27
|
turnCount: external_exports.number().int().nonnegative(),
|
|
27
|
-
durationMs: external_exports.number().int().nonnegative()
|
|
28
|
+
durationMs: external_exports.number().int().nonnegative(),
|
|
29
|
+
runtimeId: external_exports.string().min(1).default("claude-code")
|
|
28
30
|
});
|
|
29
31
|
var AttributionTypeSchema = external_exports.enum(["originated", "amended", "extended"]);
|
|
30
32
|
var CommitAttributedEventSchema = EventBase.extend({
|
|
@@ -36,13 +38,15 @@ var CommitAttributedEventSchema = EventBase.extend({
|
|
|
36
38
|
tokens: external_exports.number().int().nonnegative(),
|
|
37
39
|
costUsd: external_exports.number().nonnegative(),
|
|
38
40
|
turnCount: external_exports.number().int().nonnegative(),
|
|
39
|
-
attributionType: AttributionTypeSchema
|
|
41
|
+
attributionType: AttributionTypeSchema,
|
|
42
|
+
runtimeId: external_exports.string().min(1).default("claude-code")
|
|
40
43
|
});
|
|
41
44
|
var PrAttributedEventSchema = EventBase.extend({
|
|
42
45
|
kind: external_exports.literal("pr_attributed"),
|
|
43
46
|
prUrl: external_exports.string().url(),
|
|
44
47
|
prNumber: external_exports.number().int().positive(),
|
|
45
|
-
repo: external_exports.string().min(1)
|
|
48
|
+
repo: external_exports.string().min(1),
|
|
49
|
+
runtimeId: external_exports.string().min(1).default("claude-code")
|
|
46
50
|
});
|
|
47
51
|
var MergeMethodSchema = external_exports.enum(["github_native", "graphite_queue"]);
|
|
48
52
|
var PrMergedEventSchema = EventBase.extend({
|
|
@@ -53,7 +57,8 @@ var PrMergedEventSchema = EventBase.extend({
|
|
|
53
57
|
/** Null for Graphite merge queue — Graphite closes PRs as 'closed' rather than 'merged' and the mergeCommitSha is not exposed by the GitHub API for those. Null still means "this task's work landed in trunk", discoverable via the Shipyard-Task-Id trailer grep. */
|
|
54
58
|
mergeCommitSha: external_exports.string().nullable(),
|
|
55
59
|
mergeMethod: MergeMethodSchema,
|
|
56
|
-
mergedAt: external_exports.number().int().positive()
|
|
60
|
+
mergedAt: external_exports.number().int().positive(),
|
|
61
|
+
runtimeId: external_exports.string().min(1).default("claude-code")
|
|
57
62
|
});
|
|
58
63
|
var RoiEventSchema = external_exports.discriminatedUnion("kind", [
|
|
59
64
|
TaskStartedEventSchema,
|
|
@@ -81,7 +86,8 @@ function emitCommitAttributed(collector, input) {
|
|
|
81
86
|
tokens: input.tokens,
|
|
82
87
|
costUsd: input.costUsd,
|
|
83
88
|
turnCount: input.turnCount,
|
|
84
|
-
attributionType: input.attributionType
|
|
89
|
+
attributionType: input.attributionType,
|
|
90
|
+
runtimeId: input.runtimeId
|
|
85
91
|
};
|
|
86
92
|
const parsed = CommitAttributedEventSchema.safeParse(candidate);
|
|
87
93
|
if (!parsed.success) return;
|
|
@@ -98,7 +104,8 @@ function emitPrAttributed(collector, input) {
|
|
|
98
104
|
timestamp: input.timestamp ?? Date.now(),
|
|
99
105
|
prUrl: input.prUrl,
|
|
100
106
|
prNumber: input.prNumber,
|
|
101
|
-
repo: input.repo
|
|
107
|
+
repo: input.repo,
|
|
108
|
+
...input.runtimeId !== void 0 ? { runtimeId: input.runtimeId } : {}
|
|
102
109
|
};
|
|
103
110
|
const parsed = PrAttributedEventSchema.safeParse(candidate);
|
|
104
111
|
if (!parsed.success) return;
|
|
@@ -118,7 +125,8 @@ function emitPrMerged(collector, input) {
|
|
|
118
125
|
repo: input.repo,
|
|
119
126
|
mergeCommitSha: input.mergeCommitSha,
|
|
120
127
|
mergeMethod: input.mergeMethod,
|
|
121
|
-
mergedAt: input.mergedAt
|
|
128
|
+
mergedAt: input.mergedAt,
|
|
129
|
+
...input.runtimeId !== void 0 ? { runtimeId: input.runtimeId } : {}
|
|
122
130
|
};
|
|
123
131
|
const parsed = PrMergedEventSchema.safeParse(candidate);
|
|
124
132
|
if (!parsed.success) return;
|
|
@@ -137,7 +145,8 @@ function emitTaskEnded(collector, input) {
|
|
|
137
145
|
totalCostUsd: input.totalCostUsd,
|
|
138
146
|
totalOutputTokens: input.totalOutputTokens,
|
|
139
147
|
turnCount: input.turnCount,
|
|
140
|
-
durationMs: input.durationMs
|
|
148
|
+
durationMs: input.durationMs,
|
|
149
|
+
runtimeId: input.runtimeId
|
|
141
150
|
};
|
|
142
151
|
const parsed = TaskEndedEventSchema.safeParse(candidate);
|
|
143
152
|
if (!parsed.success) return;
|
|
@@ -154,7 +163,8 @@ function emitTaskStarted(collector, input) {
|
|
|
154
163
|
timestamp: input.timestamp ?? Date.now(),
|
|
155
164
|
activeTaskCount: input.activeTaskCount,
|
|
156
165
|
mode: input.mode,
|
|
157
|
-
taskType: input.taskType
|
|
166
|
+
taskType: input.taskType,
|
|
167
|
+
runtimeId: input.runtimeId
|
|
158
168
|
};
|
|
159
169
|
const parsed = TaskStartedEventSchema.safeParse(candidate);
|
|
160
170
|
if (!parsed.success) return;
|
|
@@ -317,4 +327,4 @@ export {
|
|
|
317
327
|
hasShipyardTrailer,
|
|
318
328
|
appendTrailerToMessage
|
|
319
329
|
};
|
|
320
|
-
//# sourceMappingURL=chunk-
|
|
330
|
+
//# sourceMappingURL=chunk-2CNIEBKO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/roi-aggregator/src/schemas/events.ts","../../../packages/roi-aggregator/src/emit/commit-attributed.ts","../../../packages/roi-aggregator/src/emit/pr-attributed.ts","../../../packages/roi-aggregator/src/emit/pr-merged.ts","../../../packages/roi-aggregator/src/emit/task-ended.ts","../../../packages/roi-aggregator/src/emit/task-started.ts","../../../packages/roi-aggregator/src/schemas/report.ts","../../../packages/roi-aggregator/src/schemas/trailers.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ROI_EVENT_TYPE = 'shipyard.roi' as const;\n\nexport const ROI_EVENT_SCHEMA_VERSION = 2 as const;\n\nconst EventBase = z.object({\n version: z.literal(ROI_EVENT_SCHEMA_VERSION),\n taskId: z.string().min(1),\n userId: z.string().min(1),\n timestamp: z.number().int().positive(),\n});\n\nexport const TaskStartedEventSchema = EventBase.extend({\n kind: z.literal('task_started'),\n activeTaskCount: z.number().int().nonnegative(),\n taskType: z.string().max(50).optional(),\n mode: z.string().max(50),\n runtimeId: z.string().min(1).default('claude-code'),\n});\nexport type TaskStartedEvent = z.infer<typeof TaskStartedEventSchema>;\n\nexport const TaskEndedEventSchema = EventBase.extend({\n kind: z.literal('task_ended'),\n finalStatus: z.enum(['completed', 'canceled', 'input_required']),\n totalCostUsd: z.number().nonnegative(),\n totalOutputTokens: z.number().int().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n durationMs: z.number().int().nonnegative(),\n runtimeId: z.string().min(1).default('claude-code'),\n});\nexport type TaskEndedEvent = z.infer<typeof TaskEndedEventSchema>;\n\nexport const AttributionTypeSchema = z.enum(['originated', 'amended', 'extended']);\nexport type AttributionType = z.infer<typeof AttributionTypeSchema>;\n\nexport const CommitAttributedEventSchema = EventBase.extend({\n kind: z.literal('commit_attributed'),\n commitSha: z.string().regex(/^[0-9a-f]{7,40}$/, 'expected hex SHA'),\n repo: z.string().min(1),\n branch: z.string().min(1),\n model: z.string().min(1),\n tokens: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n attributionType: AttributionTypeSchema,\n runtimeId: z.string().min(1).default('claude-code'),\n});\nexport type CommitAttributedEvent = z.infer<typeof CommitAttributedEventSchema>;\n\nexport const PrAttributedEventSchema = EventBase.extend({\n kind: z.literal('pr_attributed'),\n prUrl: z.string().url(),\n prNumber: z.number().int().positive(),\n repo: z.string().min(1),\n runtimeId: z.string().min(1).default('claude-code'),\n});\nexport type PrAttributedEvent = z.infer<typeof PrAttributedEventSchema>;\n\nexport const MergeMethodSchema = z.enum(['github_native', 'graphite_queue']);\nexport type MergeMethod = z.infer<typeof MergeMethodSchema>;\n\nexport const PrMergedEventSchema = EventBase.extend({\n kind: z.literal('pr_merged'),\n prUrl: z.string().url(),\n prNumber: z.number().int().positive(),\n repo: z.string().min(1),\n /** Null for Graphite merge queue — Graphite closes PRs as 'closed' rather than 'merged' and the mergeCommitSha is not exposed by the GitHub API for those. Null still means \"this task's work landed in trunk\", discoverable via the Shipyard-Task-Id trailer grep. */\n mergeCommitSha: z.string().nullable(),\n mergeMethod: MergeMethodSchema,\n mergedAt: z.number().int().positive(),\n runtimeId: z.string().min(1).default('claude-code'),\n});\nexport type PrMergedEvent = z.infer<typeof PrMergedEventSchema>;\n\nexport const RoiEventSchema = z.discriminatedUnion('kind', [\n TaskStartedEventSchema,\n TaskEndedEventSchema,\n CommitAttributedEventSchema,\n PrAttributedEventSchema,\n PrMergedEventSchema,\n]);\nexport type RoiEvent = z.infer<typeof RoiEventSchema>;\n\nexport type RoiEventKind = RoiEvent['kind'];\n\nexport function isRoiEvent(value: unknown): value is RoiEvent {\n return RoiEventSchema.safeParse(value).success;\n}\n\nexport function assertNeverEvent(event: never): never {\n throw new Error(`unhandled ROI event kind: ${JSON.stringify(event)}`);\n}\n","import type { AttributionType } from '../schemas/events';\nimport {\n CommitAttributedEventSchema,\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface CommitAttributedInput {\n taskId: string;\n userId: string;\n commitSha: string;\n repo: string;\n branch: string;\n model: string;\n tokens: number;\n costUsd: number;\n turnCount: number;\n attributionType: AttributionType;\n runtimeId: string;\n timestamp?: number;\n}\n\nexport function emitCommitAttributed(\n collector: MetricsCapture,\n input: CommitAttributedInput\n): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'commit_attributed' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n commitSha: input.commitSha,\n repo: input.repo,\n branch: input.branch,\n model: input.model,\n tokens: input.tokens,\n costUsd: input.costUsd,\n turnCount: input.turnCount,\n attributionType: input.attributionType,\n runtimeId: input.runtimeId,\n };\n const parsed = CommitAttributedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import {\n PrAttributedEventSchema,\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface PrAttributedInput {\n taskId: string;\n userId: string;\n prUrl: string;\n prNumber: number;\n repo: string;\n runtimeId?: string;\n timestamp?: number;\n}\n\nexport function emitPrAttributed(collector: MetricsCapture, input: PrAttributedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'pr_attributed' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n prUrl: input.prUrl,\n prNumber: input.prNumber,\n repo: input.repo,\n ...(input.runtimeId !== undefined ? { runtimeId: input.runtimeId } : {}),\n };\n const parsed = PrAttributedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import {\n type MergeMethod,\n PrMergedEventSchema,\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface PrMergedInput {\n taskId: string;\n userId: string;\n prUrl: string;\n prNumber: number;\n repo: string;\n mergeCommitSha: string | null;\n mergeMethod: MergeMethod;\n mergedAt: number;\n runtimeId?: string;\n timestamp?: number;\n}\n\nexport function emitPrMerged(collector: MetricsCapture, input: PrMergedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'pr_merged' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n prUrl: input.prUrl,\n prNumber: input.prNumber,\n repo: input.repo,\n mergeCommitSha: input.mergeCommitSha,\n mergeMethod: input.mergeMethod,\n mergedAt: input.mergedAt,\n ...(input.runtimeId !== undefined ? { runtimeId: input.runtimeId } : {}),\n };\n const parsed = PrMergedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import { ROI_EVENT_SCHEMA_VERSION, ROI_EVENT_TYPE, TaskEndedEventSchema } from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface TaskEndedInput {\n taskId: string;\n userId: string;\n finalStatus: 'completed' | 'canceled' | 'input_required';\n totalCostUsd: number;\n totalOutputTokens: number;\n turnCount: number;\n durationMs: number;\n runtimeId: string;\n timestamp?: number;\n}\n\nexport function emitTaskEnded(collector: MetricsCapture, input: TaskEndedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'task_ended' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n finalStatus: input.finalStatus,\n totalCostUsd: input.totalCostUsd,\n totalOutputTokens: input.totalOutputTokens,\n turnCount: input.turnCount,\n durationMs: input.durationMs,\n runtimeId: input.runtimeId,\n };\n const parsed = TaskEndedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import {\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n TaskStartedEventSchema,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface TaskStartedInput {\n taskId: string;\n userId: string;\n activeTaskCount: number;\n mode: string;\n taskType?: string;\n runtimeId: string;\n timestamp?: number;\n}\n\nexport function emitTaskStarted(collector: MetricsCapture, input: TaskStartedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'task_started' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n activeTaskCount: input.activeTaskCount,\n mode: input.mode,\n taskType: input.taskType,\n runtimeId: input.runtimeId,\n };\n const parsed = TaskStartedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import { z } from 'zod';\n\nexport const REPORT_SCHEMA_VERSION = 1 as const;\n\nexport const TimeWindowSchema = z.object({\n since: z.string(),\n until: z.string(),\n});\nexport type TimeWindow = z.infer<typeof TimeWindowSchema>;\n\nexport const TimeSeriesPointSchema = z.object({\n bucket: z.string(),\n value: z.number(),\n denominator: z.number().int().nonnegative().optional(),\n});\nexport type TimeSeriesPoint = z.infer<typeof TimeSeriesPointSchema>;\n\nexport const TimeSeriesMetricSchema = z.object({\n metric: z.string(),\n unit: z.string(),\n bucketSize: z.enum(['day', 'week', 'month']),\n series: z.array(TimeSeriesPointSchema),\n});\nexport type TimeSeriesMetric = z.infer<typeof TimeSeriesMetricSchema>;\n\nexport const TaskOutcomeFunnelSchema = z.object({\n tasksStarted: z.number().int().nonnegative(),\n producedCommits: z.number().int().nonnegative(),\n openedPr: z.number().int().nonnegative(),\n merged: z.number().int().nonnegative(),\n abandoned: z.number().int().nonnegative(),\n revertedWithin30d: z.number().int().nonnegative(),\n});\nexport type TaskOutcomeFunnel = z.infer<typeof TaskOutcomeFunnelSchema>;\n\nexport const EngineerScorecardRowSchema = z.object({\n userId: z.string(),\n prsPerWeekDelta: z.number().optional(),\n cycleTimeDelta: z.number().optional(),\n peakParallel: z.number().nonnegative(),\n costPerPrUsd: z.number().nonnegative(),\n revertRate: z.number().min(0).max(1),\n sampleSize: z.number().int().nonnegative(),\n signal: z.enum(['strong', 'modest', 'low-adoption', 'outlier-high', 'insufficient-data']),\n});\nexport type EngineerScorecardRow = z.infer<typeof EngineerScorecardRowSchema>;\n\nexport const FacetBreakdownSchema = z.object({\n facet: z.string(),\n rows: z.array(\n z.object({\n key: z.string(),\n count: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n mergedPct: z.number().min(0).max(1).optional(),\n abandonedPct: z.number().min(0).max(1).optional(),\n revertedPct: z.number().min(0).max(1).optional(),\n })\n ),\n});\nexport type FacetBreakdown = z.infer<typeof FacetBreakdownSchema>;\n\nexport const RoiTotalsSchema = z.object({\n tasksStarted: z.number().int().nonnegative(),\n tasksAbandoned: z.number().int().nonnegative(),\n tasksShipped: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n tokens: z.number().int().nonnegative(),\n engineersActive: z.number().int().nonnegative(),\n});\nexport type RoiTotals = z.infer<typeof RoiTotalsSchema>;\n\nexport const RoiKpisSchema = z.object({\n costPerMergedPr: z.number().nonnegative(),\n peakLeverageAvg: z.number().nonnegative(),\n peakLeverageHumanBaseline: z.number().nonnegative().default(1.0),\n revertRateShipyard: z.number().min(0).max(1),\n revertRateManualBaseline: z.number().min(0).max(1).optional(),\n});\nexport type RoiKpis = z.infer<typeof RoiKpisSchema>;\n\nexport const RoiWarningSchema = z.object({\n code: z.string(),\n message: z.string(),\n context: z.record(z.unknown()).default({}),\n});\nexport type RoiWarning = z.infer<typeof RoiWarningSchema>;\n\nexport const RoiReportSchema = z.object({\n schemaVersion: z.literal(REPORT_SCHEMA_VERSION),\n generatedAt: z.string(),\n window: TimeWindowSchema,\n totals: RoiTotalsSchema,\n kpis: RoiKpisSchema,\n series: z.object({\n tokensPerPrWeekly: TimeSeriesMetricSchema,\n concurrentTasksWeekly: TimeSeriesMetricSchema,\n }),\n funnel: TaskOutcomeFunnelSchema,\n byEngineer: z.array(EngineerScorecardRowSchema),\n facets: z.array(FacetBreakdownSchema),\n warnings: z.array(RoiWarningSchema),\n});\nexport type RoiReport = z.infer<typeof RoiReportSchema>;\n","import { z } from 'zod';\nimport { type AttributionType, AttributionTypeSchema } from './events';\n\nexport const TRAILER_SCHEMA_VERSION = 2 as const;\n\nexport const SHIPYARD_COAUTHOR = 'Shipyard <bot@shipyard.dev>' as const;\n\nexport const GitTrailerV2Schema = z.object({\n version: z.literal(TRAILER_SCHEMA_VERSION),\n taskId: z.string().min(1),\n sessionId: z.string().min(1),\n model: z.string().min(1),\n tokens: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n attributionType: AttributionTypeSchema,\n clientVersion: z.string().min(1),\n});\nexport type GitTrailerV2 = z.infer<typeof GitTrailerV2Schema>;\n\nconst HEADERS: Record<keyof GitTrailerV2, string> = {\n version: 'Shipyard-Version',\n taskId: 'Shipyard-Task-Id',\n sessionId: 'Shipyard-Session-Id',\n model: 'Shipyard-Model',\n tokens: 'Shipyard-Tokens',\n costUsd: 'Shipyard-Cost-Usd',\n turnCount: 'Shipyard-Turn-Count',\n attributionType: 'Shipyard-Attribution-Type',\n clientVersion: 'Shipyard-Client-Version',\n};\n\nexport function formatTrailer(trailer: GitTrailerV2): string {\n const lines = [\n `${HEADERS.version}: ${trailer.version}`,\n `${HEADERS.taskId}: ${trailer.taskId}`,\n `${HEADERS.sessionId}: ${trailer.sessionId}`,\n `${HEADERS.model}: ${trailer.model}`,\n `${HEADERS.tokens}: ${trailer.tokens}`,\n `${HEADERS.costUsd}: ${trailer.costUsd.toFixed(4)}`,\n `${HEADERS.turnCount}: ${trailer.turnCount}`,\n `${HEADERS.attributionType}: ${trailer.attributionType}`,\n `${HEADERS.clientVersion}: ${trailer.clientVersion}`,\n '',\n `Co-Authored-By: ${SHIPYARD_COAUTHOR}`,\n ];\n return lines.join('\\n');\n}\n\nexport type ParseTrailerResult =\n | { success: true; trailer: GitTrailerV2 }\n | { success: false; error: string };\n\nconst NUMERIC_FIELDS: Record<string, 'int' | 'float'> = {\n [HEADERS.version]: 'int',\n [HEADERS.tokens]: 'int',\n [HEADERS.costUsd]: 'float',\n [HEADERS.turnCount]: 'int',\n};\n\ntype ExtractFieldResult =\n | { ok: true; key: string; value: string | number }\n | { ok: false; error: string }\n | { ok: 'skip' };\n\nfunction extractField(rawLine: string): ExtractFieldResult {\n const line = rawLine.trimEnd();\n if (!line.startsWith('Shipyard-')) return { ok: 'skip' };\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) return { ok: 'skip' };\n\n const header = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n const numericKind = NUMERIC_FIELDS[header];\n\n if (numericKind === undefined) {\n return { ok: true, key: header, value };\n }\n const n = numericKind === 'int' ? Number.parseInt(value, 10) : Number.parseFloat(value);\n if (Number.isNaN(n)) {\n return { ok: false, error: `non-numeric ${header}: ${value}` };\n }\n return { ok: true, key: header, value: n };\n}\n\nexport function parseTrailer(commitMessage: string): ParseTrailerResult {\n const fields: Record<string, string | number> = {};\n for (const rawLine of commitMessage.split(/\\r?\\n/)) {\n const result = extractField(rawLine);\n if (result.ok === false) return { success: false, error: result.error };\n if (result.ok === 'skip') continue;\n fields[result.key] = result.value;\n }\n\n const candidate = {\n version: fields[HEADERS.version],\n taskId: fields[HEADERS.taskId],\n sessionId: fields[HEADERS.sessionId],\n model: fields[HEADERS.model],\n tokens: fields[HEADERS.tokens],\n costUsd: fields[HEADERS.costUsd],\n turnCount: fields[HEADERS.turnCount],\n attributionType: fields[HEADERS.attributionType],\n clientVersion: fields[HEADERS.clientVersion],\n };\n\n const parsed = GitTrailerV2Schema.safeParse(candidate);\n if (!parsed.success) {\n return { success: false, error: parsed.error.issues.map((i) => i.message).join('; ') };\n }\n return { success: true, trailer: parsed.data };\n}\n\nexport function hasShipyardTrailer(commitMessage: string): boolean {\n return commitMessage.includes(`${HEADERS.taskId}:`);\n}\n\nexport function appendTrailerToMessage(originalMessage: string, trailer: GitTrailerV2): string {\n const body = originalMessage.trimEnd();\n return `${body}\\n\\n${formatTrailer(trailer)}\\n`;\n}\n\nexport { AttributionTypeSchema };\nexport type { AttributionType };\n"],"mappings":";;;;;;AAEO,IAAM,iBAAiB;AAEvB,IAAM,2BAA2B;AAExC,IAAM,YAAY,iBAAE,OAAO;AAAA,EACzB,SAAS,iBAAE,QAAQ,wBAAwB;AAAA,EAC3C,QAAQ,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,QAAQ,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,WAAW,iBAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACvC,CAAC;AAEM,IAAM,yBAAyB,UAAU,OAAO;AAAA,EACrD,MAAM,iBAAE,QAAQ,cAAc;AAAA,EAC9B,iBAAiB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC9C,UAAU,iBAAE,OAAO,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EACtC,MAAM,iBAAE,OAAO,EAAE,IAAI,EAAE;AAAA,EACvB,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,aAAa;AACpD,CAAC;AAGM,IAAM,uBAAuB,UAAU,OAAO;AAAA,EACnD,MAAM,iBAAE,QAAQ,YAAY;AAAA,EAC5B,aAAa,iBAAE,KAAK,CAAC,aAAa,YAAY,gBAAgB,CAAC;AAAA,EAC/D,cAAc,iBAAE,OAAO,EAAE,YAAY;AAAA,EACrC,mBAAmB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAChD,WAAW,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,YAAY,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACzC,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,aAAa;AACpD,CAAC;AAGM,IAAM,wBAAwB,iBAAE,KAAK,CAAC,cAAc,WAAW,UAAU,CAAC;AAG1E,IAAM,8BAA8B,UAAU,OAAO;AAAA,EAC1D,MAAM,iBAAE,QAAQ,mBAAmB;AAAA,EACnC,WAAW,iBAAE,OAAO,EAAE,MAAM,oBAAoB,kBAAkB;AAAA,EAClE,MAAM,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,QAAQ,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,OAAO,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,QAAQ,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,SAAS,iBAAE,OAAO,EAAE,YAAY;AAAA,EAChC,WAAW,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,iBAAiB;AAAA,EACjB,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,aAAa;AACpD,CAAC;AAGM,IAAM,0BAA0B,UAAU,OAAO;AAAA,EACtD,MAAM,iBAAE,QAAQ,eAAe;AAAA,EAC/B,OAAO,iBAAE,OAAO,EAAE,IAAI;AAAA,EACtB,UAAU,iBAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACpC,MAAM,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,aAAa;AACpD,CAAC;AAGM,IAAM,oBAAoB,iBAAE,KAAK,CAAC,iBAAiB,gBAAgB,CAAC;AAGpE,IAAM,sBAAsB,UAAU,OAAO;AAAA,EAClD,MAAM,iBAAE,QAAQ,WAAW;AAAA,EAC3B,OAAO,iBAAE,OAAO,EAAE,IAAI;AAAA,EACtB,UAAU,iBAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACpC,MAAM,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA;AAAA,EAEtB,gBAAgB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACpC,aAAa;AAAA,EACb,UAAU,iBAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EACpC,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,aAAa;AACpD,CAAC;AAGM,IAAM,iBAAiB,iBAAE,mBAAmB,QAAQ;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AASM,SAAS,iBAAiB,OAAqB;AACpD,QAAM,IAAI,MAAM,6BAA6B,KAAK,UAAU,KAAK,CAAC,EAAE;AACtE;;;ACrEO,SAAS,qBACd,WACA,OACM;AACN,QAAM,YAAY;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACvC,WAAW,MAAM;AAAA,IACjB,MAAM,MAAM;AAAA,IACZ,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,SAAS,MAAM;AAAA,IACf,WAAW,MAAM;AAAA,IACjB,iBAAiB,MAAM;AAAA,IACvB,WAAW,MAAM;AAAA,EACnB;AACA,QAAM,SAAS,4BAA4B,UAAU,SAAS;AAC9D,MAAI,CAAC,OAAO,QAAS;AACrB,YAAU,QAAQ,gBAAgB,OAAO,IAAI;AAC/C;;;AC7BO,SAAS,iBAAiB,WAA2B,OAAgC;AAC1F,QAAM,YAAY;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACvC,OAAO,MAAM;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM;AAAA,IACZ,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,EACxE;AACA,QAAM,SAAS,wBAAwB,UAAU,SAAS;AAC1D,MAAI,CAAC,OAAO,QAAS;AACrB,YAAU,QAAQ,gBAAgB,OAAO,IAAI;AAC/C;;;ACXO,SAAS,aAAa,WAA2B,OAA4B;AAClF,QAAM,YAAY;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACvC,OAAO,MAAM;AAAA,IACb,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM;AAAA,IACZ,gBAAgB,MAAM;AAAA,IACtB,aAAa,MAAM;AAAA,IACnB,UAAU,MAAM;AAAA,IAChB,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,EACxE;AACA,QAAM,SAAS,oBAAoB,UAAU,SAAS;AACtD,MAAI,CAAC,OAAO,QAAS;AACrB,YAAU,QAAQ,gBAAgB,OAAO,IAAI;AAC/C;;;ACxBO,SAAS,cAAc,WAA2B,OAA6B;AACpF,QAAM,YAAY;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACvC,aAAa,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,IACpB,mBAAmB,MAAM;AAAA,IACzB,WAAW,MAAM;AAAA,IACjB,YAAY,MAAM;AAAA,IAClB,WAAW,MAAM;AAAA,EACnB;AACA,QAAM,SAAS,qBAAqB,UAAU,SAAS;AACvD,MAAI,CAAC,OAAO,QAAS;AACrB,YAAU,QAAQ,gBAAgB,OAAO,IAAI;AAC/C;;;ACfO,SAAS,gBAAgB,WAA2B,OAA+B;AACxF,QAAM,YAAY;AAAA,IAChB,SAAS;AAAA,IACT,MAAM;AAAA,IACN,QAAQ,MAAM;AAAA,IACd,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM,aAAa,KAAK,IAAI;AAAA,IACvC,iBAAiB,MAAM;AAAA,IACvB,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,EACnB;AACA,QAAM,SAAS,uBAAuB,UAAU,SAAS;AACzD,MAAI,CAAC,OAAO,QAAS;AACrB,YAAU,QAAQ,gBAAgB,OAAO,IAAI;AAC/C;;;AC9BO,IAAM,wBAAwB;AAE9B,IAAM,mBAAmB,iBAAE,OAAO;AAAA,EACvC,OAAO,iBAAE,OAAO;AAAA,EAChB,OAAO,iBAAE,OAAO;AAClB,CAAC;AAGM,IAAM,wBAAwB,iBAAE,OAAO;AAAA,EAC5C,QAAQ,iBAAE,OAAO;AAAA,EACjB,OAAO,iBAAE,OAAO;AAAA,EAChB,aAAa,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AACvD,CAAC;AAGM,IAAM,yBAAyB,iBAAE,OAAO;AAAA,EAC7C,QAAQ,iBAAE,OAAO;AAAA,EACjB,MAAM,iBAAE,OAAO;AAAA,EACf,YAAY,iBAAE,KAAK,CAAC,OAAO,QAAQ,OAAO,CAAC;AAAA,EAC3C,QAAQ,iBAAE,MAAM,qBAAqB;AACvC,CAAC;AAGM,IAAM,0BAA0B,iBAAE,OAAO;AAAA,EAC9C,cAAc,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,iBAAiB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC9C,UAAU,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACvC,QAAQ,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,WAAW,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,mBAAmB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAClD,CAAC;AAGM,IAAM,6BAA6B,iBAAE,OAAO;AAAA,EACjD,QAAQ,iBAAE,OAAO;AAAA,EACjB,iBAAiB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACrC,gBAAgB,iBAAE,OAAO,EAAE,SAAS;AAAA,EACpC,cAAc,iBAAE,OAAO,EAAE,YAAY;AAAA,EACrC,cAAc,iBAAE,OAAO,EAAE,YAAY;AAAA,EACrC,YAAY,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EACnC,YAAY,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACzC,QAAQ,iBAAE,KAAK,CAAC,UAAU,UAAU,gBAAgB,gBAAgB,mBAAmB,CAAC;AAC1F,CAAC;AAGM,IAAM,uBAAuB,iBAAE,OAAO;AAAA,EAC3C,OAAO,iBAAE,OAAO;AAAA,EAChB,MAAM,iBAAE;AAAA,IACN,iBAAE,OAAO;AAAA,MACP,KAAK,iBAAE,OAAO;AAAA,MACd,OAAO,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,MACpC,SAAS,iBAAE,OAAO,EAAE,YAAY;AAAA,MAChC,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAC7C,cAAc,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAChD,aAAa,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IACjD,CAAC;AAAA,EACH;AACF,CAAC;AAGM,IAAM,kBAAkB,iBAAE,OAAO;AAAA,EACtC,cAAc,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,gBAAgB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC7C,cAAc,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC3C,SAAS,iBAAE,OAAO,EAAE,YAAY;AAAA,EAChC,QAAQ,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,iBAAiB,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAChD,CAAC;AAGM,IAAM,gBAAgB,iBAAE,OAAO;AAAA,EACpC,iBAAiB,iBAAE,OAAO,EAAE,YAAY;AAAA,EACxC,iBAAiB,iBAAE,OAAO,EAAE,YAAY;AAAA,EACxC,2BAA2B,iBAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAG;AAAA,EAC/D,oBAAoB,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,EAC3C,0BAA0B,iBAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAC9D,CAAC;AAGM,IAAM,mBAAmB,iBAAE,OAAO;AAAA,EACvC,MAAM,iBAAE,OAAO;AAAA,EACf,SAAS,iBAAE,OAAO;AAAA,EAClB,SAAS,iBAAE,OAAO,iBAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAGM,IAAM,kBAAkB,iBAAE,OAAO;AAAA,EACtC,eAAe,iBAAE,QAAQ,qBAAqB;AAAA,EAC9C,aAAa,iBAAE,OAAO;AAAA,EACtB,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ,iBAAE,OAAO;AAAA,IACf,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,EACzB,CAAC;AAAA,EACD,QAAQ;AAAA,EACR,YAAY,iBAAE,MAAM,0BAA0B;AAAA,EAC9C,QAAQ,iBAAE,MAAM,oBAAoB;AAAA,EACpC,UAAU,iBAAE,MAAM,gBAAgB;AACpC,CAAC;;;ACnGM,IAAM,yBAAyB;AAE/B,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB,iBAAE,OAAO;AAAA,EACzC,SAAS,iBAAE,QAAQ,sBAAsB;AAAA,EACzC,QAAQ,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,WAAW,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,OAAO,iBAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,QAAQ,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACrC,SAAS,iBAAE,OAAO,EAAE,YAAY;AAAA,EAChC,WAAW,iBAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EACxC,iBAAiB;AAAA,EACjB,eAAe,iBAAE,OAAO,EAAE,IAAI,CAAC;AACjC,CAAC;AAGD,IAAM,UAA8C;AAAA,EAClD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAAS,cAAc,SAA+B;AAC3D,QAAM,QAAQ;AAAA,IACZ,GAAG,QAAQ,OAAO,KAAK,QAAQ,OAAO;AAAA,IACtC,GAAG,QAAQ,MAAM,KAAK,QAAQ,MAAM;AAAA,IACpC,GAAG,QAAQ,SAAS,KAAK,QAAQ,SAAS;AAAA,IAC1C,GAAG,QAAQ,KAAK,KAAK,QAAQ,KAAK;AAAA,IAClC,GAAG,QAAQ,MAAM,KAAK,QAAQ,MAAM;AAAA,IACpC,GAAG,QAAQ,OAAO,KAAK,QAAQ,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACjD,GAAG,QAAQ,SAAS,KAAK,QAAQ,SAAS;AAAA,IAC1C,GAAG,QAAQ,eAAe,KAAK,QAAQ,eAAe;AAAA,IACtD,GAAG,QAAQ,aAAa,KAAK,QAAQ,aAAa;AAAA,IAClD;AAAA,IACA,mBAAmB,iBAAiB;AAAA,EACtC;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAMA,IAAM,iBAAkD;AAAA,EACtD,CAAC,QAAQ,OAAO,GAAG;AAAA,EACnB,CAAC,QAAQ,MAAM,GAAG;AAAA,EAClB,CAAC,QAAQ,OAAO,GAAG;AAAA,EACnB,CAAC,QAAQ,SAAS,GAAG;AACvB;AAuDO,SAAS,mBAAmB,eAAgC;AACjE,SAAO,cAAc,SAAS,GAAG,QAAQ,MAAM,GAAG;AACpD;AAEO,SAAS,uBAAuB,iBAAyB,SAA+B;AAC7F,QAAM,OAAO,gBAAgB,QAAQ;AACrC,SAAO,GAAG,IAAI;AAAA;AAAA,EAAO,cAAc,OAAO,CAAC;AAAA;AAC7C;","names":[]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createOneShotPool
|
|
4
|
+
} from "./chunk-ZFKJAYAN.js";
|
|
5
|
+
|
|
6
|
+
// src/shared/capabilities/git-pool.ts
|
|
7
|
+
var GIT_POOL_TTL_MS = 5e3;
|
|
8
|
+
var GIT_POOL_MAX_CONCURRENT = 4;
|
|
9
|
+
var poolsByCwd = /* @__PURE__ */ new Map();
|
|
10
|
+
function getGitPool(cwd, deps) {
|
|
11
|
+
const existing = poolsByCwd.get(cwd);
|
|
12
|
+
if (existing !== void 0) return existing;
|
|
13
|
+
const pool = createOneShotPool(
|
|
14
|
+
{ ttlMs: GIT_POOL_TTL_MS, maxConcurrent: GIT_POOL_MAX_CONCURRENT },
|
|
15
|
+
deps ?? {}
|
|
16
|
+
);
|
|
17
|
+
poolsByCwd.set(cwd, pool);
|
|
18
|
+
return pool;
|
|
19
|
+
}
|
|
20
|
+
async function gitExec(cwd, args) {
|
|
21
|
+
const result = await getGitPool(cwd).exec(["git", ...args], { cwd });
|
|
22
|
+
return result.value.trim();
|
|
23
|
+
}
|
|
24
|
+
async function gitExecSafe(cwd, args) {
|
|
25
|
+
try {
|
|
26
|
+
return await gitExec(cwd, args);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function invalidateGitPool(cwd) {
|
|
32
|
+
poolsByCwd.get(cwd)?.invalidate();
|
|
33
|
+
}
|
|
34
|
+
var _testing = {
|
|
35
|
+
/** Dispose all pools and clear the registry. Used in test beforeEach to prevent cross-test cache pollution. */
|
|
36
|
+
clearAllPools: () => {
|
|
37
|
+
for (const pool of poolsByCwd.values()) {
|
|
38
|
+
pool.dispose();
|
|
39
|
+
}
|
|
40
|
+
poolsByCwd.clear();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
getGitPool,
|
|
46
|
+
gitExec,
|
|
47
|
+
gitExecSafe,
|
|
48
|
+
invalidateGitPool,
|
|
49
|
+
_testing
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=chunk-4T2OQAVL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/capabilities/git-pool.ts"],"sourcesContent":["import { createOneShotPool, type OneShotPool, type OneShotPoolDeps } from '@shipyard/local-runtime';\n\n/**\n * Per-cwd OneShotPool instances for caching high-frequency git reads.\n *\n * Each unique working directory gets its own pool so cache entries are scoped\n * to a repo — a `git rev-parse HEAD` for /home/user/repo-a is never mistaken\n * for /home/user/repo-b. The pool handles TTL expiry and in-flight coalescing,\n * eliminating repeated subprocess spawns within the same 5-second window.\n *\n * Write operations (add, commit, push, reset, checkout) are never routed here.\n * Only reads whose staleness within a 5-second window is tolerable belong in\n * this pool: HEAD resolution, branch detection, remote URL, default-branch ref.\n */\n\nconst GIT_POOL_TTL_MS = 5_000;\nconst GIT_POOL_MAX_CONCURRENT = 4;\n\nconst poolsByCwd = new Map<string, OneShotPool>();\n\n/**\n * Return the per-cwd OneShotPool, creating it on first access.\n *\n * Exposed as a named export so callers that need DI (tests, mocks) can\n * bypass the module-level map and inject a fake pool directly.\n */\nexport function getGitPool(cwd: string, deps?: OneShotPoolDeps): OneShotPool {\n const existing = poolsByCwd.get(cwd);\n if (existing !== undefined) return existing;\n const pool = createOneShotPool(\n { ttlMs: GIT_POOL_TTL_MS, maxConcurrent: GIT_POOL_MAX_CONCURRENT },\n deps ?? {}\n );\n poolsByCwd.set(cwd, pool);\n return pool;\n}\n\n/**\n * Run a git read command in the given cwd, returning trimmed stdout.\n * Throws on non-zero exit (same semantics as `runWithTimeout`).\n *\n * Results are cached for 5 seconds; concurrent calls with the same\n * command string coalesce to a single subprocess.\n */\nexport async function gitExec(cwd: string, args: readonly string[]): Promise<string> {\n const result = await getGitPool(cwd).exec(['git', ...args], { cwd });\n return result.value.trim();\n}\n\n/**\n * Like `gitExec` but returns null on any error instead of throwing.\n * Mirrors the try/catch-returning-null pattern common in git helpers.\n */\nexport async function gitExecSafe(cwd: string, args: readonly string[]): Promise<string | null> {\n try {\n return await gitExec(cwd, args);\n } catch {\n return null;\n }\n}\n\n/**\n * Invalidate all cached results for a cwd — e.g. after a commit or branch\n * switch. Callers that write to the repo should invoke this so the next\n * read reflects the updated state rather than the stale 5s cache.\n */\nexport function invalidateGitPool(cwd: string): void {\n poolsByCwd.get(cwd)?.invalidate();\n}\n\nexport const _testing = {\n /** Dispose all pools and clear the registry. Used in test beforeEach to prevent cross-test cache pollution. */\n clearAllPools: () => {\n for (const pool of poolsByCwd.values()) {\n pool.dispose();\n }\n poolsByCwd.clear();\n },\n};\n"],"mappings":";;;;;;AAeA,IAAM,kBAAkB;AACxB,IAAM,0BAA0B;AAEhC,IAAM,aAAa,oBAAI,IAAyB;AAQzC,SAAS,WAAW,KAAa,MAAqC;AAC3E,QAAM,WAAW,WAAW,IAAI,GAAG;AACnC,MAAI,aAAa,OAAW,QAAO;AACnC,QAAM,OAAO;AAAA,IACX,EAAE,OAAO,iBAAiB,eAAe,wBAAwB;AAAA,IACjE,QAAQ,CAAC;AAAA,EACX;AACA,aAAW,IAAI,KAAK,IAAI;AACxB,SAAO;AACT;AASA,eAAsB,QAAQ,KAAa,MAA0C;AACnF,QAAM,SAAS,MAAM,WAAW,GAAG,EAAE,KAAK,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,IAAI,CAAC;AACnE,SAAO,OAAO,MAAM,KAAK;AAC3B;AAMA,eAAsB,YAAY,KAAa,MAAiD;AAC9F,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK,IAAI;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOO,SAAS,kBAAkB,KAAmB;AACnD,aAAW,IAAI,GAAG,GAAG,WAAW;AAClC;AAEO,IAAM,WAAW;AAAA;AAAA,EAEtB,eAAe,MAAM;AACnB,eAAW,QAAQ,WAAW,OAAO,GAAG;AACtC,WAAK,QAAQ;AAAA,IACf;AACA,eAAW,MAAM;AAAA,EACnB;AACF;","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/services/credentials/vault-crypto.ts
|
|
4
|
+
async function generateVaultKey() {
|
|
5
|
+
const key = new Uint8Array(32);
|
|
6
|
+
globalThis.crypto.getRandomValues(key);
|
|
7
|
+
return Buffer.from(key).toString("hex");
|
|
8
|
+
}
|
|
9
|
+
async function importKey(hexKey) {
|
|
10
|
+
const keyBytes = Buffer.from(hexKey, "hex");
|
|
11
|
+
if (keyBytes.length !== 32)
|
|
12
|
+
throw new Error(`Invalid vault key length: expected 32 bytes, got ${keyBytes.length}`);
|
|
13
|
+
return globalThis.crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, [
|
|
14
|
+
"encrypt",
|
|
15
|
+
"decrypt"
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
async function encryptCredential(plaintext, hexKey) {
|
|
19
|
+
const key = await importKey(hexKey);
|
|
20
|
+
const iv = new Uint8Array(12);
|
|
21
|
+
globalThis.crypto.getRandomValues(iv);
|
|
22
|
+
const encoded = new TextEncoder().encode(plaintext);
|
|
23
|
+
const encrypted = await globalThis.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
|
|
24
|
+
return {
|
|
25
|
+
ciphertext: Buffer.from(encrypted).toString("base64"),
|
|
26
|
+
iv: Buffer.from(iv).toString("base64")
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function decryptCredential(ciphertext, iv, hexKey) {
|
|
30
|
+
const key = await importKey(hexKey);
|
|
31
|
+
const ivBytes = Buffer.from(iv, "base64");
|
|
32
|
+
const encrypted = Buffer.from(ciphertext, "base64");
|
|
33
|
+
const decrypted = await globalThis.crypto.subtle.decrypt(
|
|
34
|
+
{ name: "AES-GCM", iv: ivBytes },
|
|
35
|
+
key,
|
|
36
|
+
encrypted
|
|
37
|
+
);
|
|
38
|
+
return new TextDecoder().decode(decrypted);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
generateVaultKey,
|
|
43
|
+
encryptCredential,
|
|
44
|
+
decryptCredential
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=chunk-5ER6ZHA2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/credentials/vault-crypto.ts"],"sourcesContent":["export { encryptCredential, decryptCredential, generateVaultKey };\n\nasync function generateVaultKey(): Promise<string> {\n const key = new Uint8Array(32);\n globalThis.crypto.getRandomValues(key);\n return Buffer.from(key).toString('hex');\n}\n\nasync function importKey(hexKey: string): Promise<CryptoKey> {\n const keyBytes = Buffer.from(hexKey, 'hex');\n if (keyBytes.length !== 32)\n throw new Error(`Invalid vault key length: expected 32 bytes, got ${keyBytes.length}`);\n return globalThis.crypto.subtle.importKey('raw', keyBytes, { name: 'AES-GCM' }, false, [\n 'encrypt',\n 'decrypt',\n ]);\n}\n\nasync function encryptCredential(\n plaintext: string,\n hexKey: string\n): Promise<{ ciphertext: string; iv: string }> {\n const key = await importKey(hexKey);\n const iv = new Uint8Array(12);\n globalThis.crypto.getRandomValues(iv);\n const encoded = new TextEncoder().encode(plaintext);\n const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);\n return {\n ciphertext: Buffer.from(encrypted).toString('base64'),\n iv: Buffer.from(iv).toString('base64'),\n };\n}\n\nasync function decryptCredential(ciphertext: string, iv: string, hexKey: string): Promise<string> {\n const key = await importKey(hexKey);\n const ivBytes = Buffer.from(iv, 'base64');\n const encrypted = Buffer.from(ciphertext, 'base64');\n const decrypted = await globalThis.crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: ivBytes },\n key,\n encrypted\n );\n return new TextDecoder().decode(decrypted);\n}\n"],"mappings":";;;AAEA,eAAe,mBAAoC;AACjD,QAAM,MAAM,IAAI,WAAW,EAAE;AAC7B,aAAW,OAAO,gBAAgB,GAAG;AACrC,SAAO,OAAO,KAAK,GAAG,EAAE,SAAS,KAAK;AACxC;AAEA,eAAe,UAAU,QAAoC;AAC3D,QAAM,WAAW,OAAO,KAAK,QAAQ,KAAK;AAC1C,MAAI,SAAS,WAAW;AACtB,UAAM,IAAI,MAAM,oDAAoD,SAAS,MAAM,EAAE;AACvF,SAAO,WAAW,OAAO,OAAO,UAAU,OAAO,UAAU,EAAE,MAAM,UAAU,GAAG,OAAO;AAAA,IACrF;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAe,kBACb,WACA,QAC6C;AAC7C,QAAM,MAAM,MAAM,UAAU,MAAM;AAClC,QAAM,KAAK,IAAI,WAAW,EAAE;AAC5B,aAAW,OAAO,gBAAgB,EAAE;AACpC,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,SAAS;AAClD,QAAM,YAAY,MAAM,WAAW,OAAO,OAAO,QAAQ,EAAE,MAAM,WAAW,GAAG,GAAG,KAAK,OAAO;AAC9F,SAAO;AAAA,IACL,YAAY,OAAO,KAAK,SAAS,EAAE,SAAS,QAAQ;AAAA,IACpD,IAAI,OAAO,KAAK,EAAE,EAAE,SAAS,QAAQ;AAAA,EACvC;AACF;AAEA,eAAe,kBAAkB,YAAoB,IAAY,QAAiC;AAChG,QAAM,MAAM,MAAM,UAAU,MAAM;AAClC,QAAM,UAAU,OAAO,KAAK,IAAI,QAAQ;AACxC,QAAM,YAAY,OAAO,KAAK,YAAY,QAAQ;AAClD,QAAM,YAAY,MAAM,WAAW,OAAO,OAAO;AAAA,IAC/C,EAAE,MAAM,WAAW,IAAI,QAAQ;AAAA,IAC/B;AAAA,IACA;AAAA,EACF;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,SAAS;AAC3C;","names":[]}
|