@primitive.ai/prim 0.1.0-alpha.74 → 0.1.0-alpha.76

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/index.js CHANGED
@@ -149,85 +149,22 @@ import { fileURLToPath as fileURLToPath2 } from "url";
149
149
  import { Command } from "commander";
150
150
  import updateNotifier from "update-notifier";
151
151
 
152
- // src/commands/activation.ts
153
- async function applyActivation(active) {
154
- const root = gitToplevel();
155
- if (!root) {
156
- process.stderr.write(
157
- `[prim] not a git repository \u2014 run \`prim ${active ? "enable" : "disable"}\` inside a repo
158
- `
159
- );
160
- process.exit(1);
161
- }
162
- let phase = active ? "post-commit hook coverage" : "local deactivation";
152
+ // src/lib/confirmation.ts
153
+ function isNonInteractive(globals) {
154
+ return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
155
+ }
156
+ async function askConfirmation(question, output = process.stdout) {
157
+ if (!process.stdin.isTTY) return false;
158
+ const { createInterface } = await import("readline/promises");
159
+ const rl = createInterface({ input: process.stdin, output });
163
160
  try {
164
- let binding;
165
- let postCommitHook;
166
- let postRewriteHook;
167
- if (active) {
168
- postCommitHook = ensureEffectivePostCommitHook(root).path;
169
- try {
170
- postRewriteHook = ensureEffectivePostRewriteHook(root).path;
171
- } catch (error) {
172
- const detail = error instanceof Error ? error.message : String(error);
173
- process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
174
- `);
175
- }
176
- phase = "repository binding";
177
- binding = await bindRepository(root);
178
- phase = "local activation";
179
- }
180
- setRepoActive(root, active);
181
- await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
182
- if (binding?.status === "unbound") {
183
- process.stderr.write(`[prim] Prim is enabled locally in ${root}
184
- `);
185
- process.stderr.write(
186
- "[prim] repository is not connected to Primitive; Moves still ingest into the team graph without repository-specific file attribution, Conflict Gate verification, or commit correlation\n"
187
- );
188
- process.stderr.write(
189
- `[prim] ask an organization owner or administrator to grant Primitive's GitHub App access to this repository through Primitive's GitHub App onboarding; binding retries automatically at the next agent SessionStart
190
- `
191
- );
192
- } else {
193
- process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
194
- `);
195
- }
196
- printJson({
197
- active,
198
- repo: root,
199
- ...binding ? {
200
- bindingStatus: binding.status,
201
- repositoryFullName: binding.repositoryFullName,
202
- ...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
203
- } : {},
204
- ...postCommitHook ? { postCommitHook } : {},
205
- ...postRewriteHook ? { postRewriteHook } : {}
206
- });
207
- } catch (err) {
208
- const detail = err instanceof Error ? err.message : String(err);
209
- process.stderr.write(
210
- active ? `[prim] failed to enable prim during ${phase}: ${detail}
211
- ` : `[prim] failed to disable prim: ${detail}
212
- `
213
- );
214
- process.exit(1);
161
+ const answer = await rl.question(`${question} [y/N] `);
162
+ const normalized = answer.trim().toLowerCase();
163
+ return normalized === "y" || normalized === "yes";
164
+ } finally {
165
+ rl.close();
215
166
  }
216
167
  }
217
- function registerActivationCommands(program2) {
218
- program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action(() => applyActivation(true));
219
- program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(() => applyActivation(false));
220
- }
221
-
222
- // src/commands/auth.ts
223
- import { execFile } from "child_process";
224
- import { createHash, randomBytes as randomBytes2 } from "crypto";
225
- import { existsSync, readFileSync } from "fs";
226
- import { createServer } from "http";
227
- import { platform } from "os";
228
-
229
- // src/auth/api-keys.ts
230
- import { randomBytes } from "crypto";
231
168
 
232
169
  // src/generated/cli-http-v1.validators.js
233
170
  var import_ucs2length = __toESM(require_ucs2length(), 1);
@@ -9961,7 +9898,114 @@ function isGitHubInstallIntentStatusResponse(value) {
9961
9898
  }
9962
9899
  }
9963
9900
 
9901
+ // src/lib/github-install-intent.ts
9902
+ var PROTOCOL_VERSION = 1;
9903
+ var MODE = "install_intent_v1";
9904
+ var START_PATH = "/api/cli/github/install-intents";
9905
+ var POLL_REQUEST_TIMEOUT_MS = 1e4;
9906
+ var MAX_INTENT_LIFETIME_MS = 15 * 6e4;
9907
+ var MAX_SERVER_CLOCK_AHEAD_MS = 3e4;
9908
+ var REPOSITORY_IDENTITY_MIGRATION_REQUIRED = "repository_identity_migration_required";
9909
+ function isRepositoryIdentityMigrationRequiredStatus(value) {
9910
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
9911
+ return false;
9912
+ }
9913
+ const record2 = value;
9914
+ if (record2.failureCode !== REPOSITORY_IDENTITY_MIGRATION_REQUIRED) {
9915
+ return false;
9916
+ }
9917
+ return isGitHubInstallIntentStatusResponse({
9918
+ ...record2,
9919
+ failureCode: "proof_commit_failed"
9920
+ });
9921
+ }
9922
+ function parseGitHubInstallIntentStart(value, now = Date.now()) {
9923
+ if (!isGitHubInstallIntentStartResponse(value) || value.expiresAt <= now || value.expiresAt - now > MAX_INTENT_LIFETIME_MS + MAX_SERVER_CLOCK_AHEAD_MS) {
9924
+ return null;
9925
+ }
9926
+ return value;
9927
+ }
9928
+ function parseGitHubInstallIntentStatus(value, expectedExpiresAt) {
9929
+ if (isGitHubInstallIntentStatusResponse(value)) {
9930
+ return value.expiresAt === expectedExpiresAt ? value : null;
9931
+ }
9932
+ if (!isRepositoryIdentityMigrationRequiredStatus(value) || value.expiresAt !== expectedExpiresAt) {
9933
+ return null;
9934
+ }
9935
+ return value;
9936
+ }
9937
+ async function createGitHubInstallIntent(client, options = {}) {
9938
+ const { now = Date.now, ...requestOptions } = options;
9939
+ const raw = await client.post(START_PATH, void 0, requestOptions);
9940
+ const parsed = parseGitHubInstallIntentStart(raw, now());
9941
+ if (!parsed) throw new Error("server returned an invalid GitHub install-intent response");
9942
+ return parsed;
9943
+ }
9944
+ function defaultSleep(milliseconds, signal) {
9945
+ if (signal.aborted) return Promise.reject(signal.reason);
9946
+ return new Promise((resolvePromise, reject) => {
9947
+ const onAbort = () => {
9948
+ clearTimeout(timer);
9949
+ reject(signal.reason);
9950
+ };
9951
+ const timer = setTimeout(() => {
9952
+ signal.removeEventListener("abort", onAbort);
9953
+ resolvePromise();
9954
+ }, milliseconds);
9955
+ signal.addEventListener("abort", onAbort, { once: true });
9956
+ });
9957
+ }
9958
+ function expiredStatus(start) {
9959
+ return {
9960
+ protocolVersion: PROTOCOL_VERSION,
9961
+ mode: MODE,
9962
+ found: true,
9963
+ status: "expired",
9964
+ expiresAt: start.expiresAt,
9965
+ closedAt: start.expiresAt
9966
+ };
9967
+ }
9968
+ async function pollGitHubInstallIntent(client, start, options = {}) {
9969
+ const now = options.now ?? Date.now;
9970
+ const sleep2 = options.sleep ?? defaultSleep;
9971
+ const signal = options.signal ?? new AbortController().signal;
9972
+ const path = `${START_PATH}/${encodeURIComponent(start.intentId)}`;
9973
+ while (true) {
9974
+ if (signal.aborted) throw signal.reason;
9975
+ const remainingBeforeSleep = start.expiresAt - now();
9976
+ if (remainingBeforeSleep <= 0) return expiredStatus(start);
9977
+ await sleep2(Math.min(start.pollAfterMs, remainingBeforeSleep), signal);
9978
+ if (signal.aborted) throw signal.reason;
9979
+ const remainingBeforeRequest = start.expiresAt - now();
9980
+ if (remainingBeforeRequest <= 0) return expiredStatus(start);
9981
+ let raw;
9982
+ try {
9983
+ raw = await client.get(path, {
9984
+ signal: AbortSignal.any([
9985
+ signal,
9986
+ AbortSignal.timeout(Math.min(POLL_REQUEST_TIMEOUT_MS, remainingBeforeRequest))
9987
+ ])
9988
+ });
9989
+ } catch (error) {
9990
+ if (signal.aborted) throw error;
9991
+ if (now() >= start.expiresAt) return expiredStatus(start);
9992
+ throw error;
9993
+ }
9994
+ const status = parseGitHubInstallIntentStatus(raw, start.expiresAt);
9995
+ if (!status) throw new Error("server returned an invalid GitHub install-intent status");
9996
+ if (status.status !== "pending" && status.status !== "claimed") return status;
9997
+ }
9998
+ }
9999
+
10000
+ // src/commands/auth.ts
10001
+ import { execFile } from "child_process";
10002
+ import { createHash, randomBytes as randomBytes2 } from "crypto";
10003
+ import { existsSync, readFileSync } from "fs";
10004
+ import { createServer } from "http";
10005
+ import { platform } from "os";
10006
+
9964
10007
  // src/auth/api-keys.ts
10008
+ import { randomBytes } from "crypto";
9965
10009
  var USER_API_KEY_TIMEOUT_MS = 1e4;
9966
10010
  var USER_API_KEY_EXIT = {
9967
10011
  ok: 0,
@@ -10850,57 +10894,282 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
10850
10894
  };
10851
10895
  }
10852
10896
 
10853
- // src/commands/claude-install.ts
10854
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
10855
- import { homedir } from "os";
10856
- import { join as join2 } from "path";
10857
-
10858
- // src/lib/hook-runtime.ts
10859
- import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
10860
- import {
10861
- constants,
10862
- accessSync,
10863
- chmodSync,
10864
- copyFileSync,
10865
- existsSync as existsSync2,
10866
- lstatSync,
10867
- mkdirSync,
10868
- mkdtempSync,
10869
- readFileSync as readFileSync2,
10870
- readdirSync,
10871
- realpathSync,
10872
- renameSync,
10873
- rmSync,
10874
- statSync,
10875
- utimesSync,
10876
- writeFileSync
10877
- } from "fs";
10878
- import { dirname, isAbsolute, join, resolve } from "path";
10879
- var HOOK_RUNTIME_SCHEMA_VERSION = 1;
10880
- var DIRECTORY_MODE = 448;
10881
- var DATA_FILE_MODE = 384;
10882
- var LAUNCHER_MODE = 448;
10883
- var RELEASE_PREFIX = "release-";
10884
- var RELEASE_NAME_RE = /^release-[0-9a-f]{64}$/u;
10885
- var MAX_MANIFEST_BYTES = 64 * 1024;
10886
- var MAX_RELEASES = 8;
10887
- var MAX_RELEASE_DELETIONS_PER_STAGE = 8;
10888
- var RETAIN_PREDECESSORS = 2;
10889
- var RELEASE_GRACE_MS = 10 * 60 * 1e3;
10890
- var HOOK_RUNTIME_ENTRIES = {
10891
- // SessionStart self-healing resolves this exact CLI entry to run
10892
- // `daemon ensure`; it must travel with the immutable hook release.
10893
- prim: "dist/index.js",
10894
- "prim-hook": "dist/hooks/prim-hook.js",
10895
- "prim-pre-commit": "dist/hooks/pre-commit.js",
10896
- "prim-post-commit": "dist/hooks/post-commit.js",
10897
- "prim-post-rewrite": "dist/hooks/post-rewrite.js",
10898
- "prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
10899
- "prim-post-tool-use": "dist/hooks/post-tool-use.js",
10900
- "prim-session-start": "dist/hooks/session-start.js",
10901
- "prim-session-end": "dist/hooks/session-end.js",
10902
- "prim-daemon-server": "dist/daemon/server.js",
10903
- "prim-statusline": "dist/statusline-main.js"
10897
+ // src/commands/github.ts
10898
+ var BIND_TIMEOUT_MS = 1e4;
10899
+ var START_TIMEOUT_MS = 1e4;
10900
+ var EXIT_OK2 = 0;
10901
+ var EXIT_FAILURE = 1;
10902
+ var EXIT_UNBOUND = 2;
10903
+ var defaultDependencies2 = {
10904
+ cwd: () => process.cwd(),
10905
+ gitToplevel,
10906
+ bindRepositoryWithClient,
10907
+ getPinnedClient,
10908
+ createInstallIntent: createGitHubInstallIntent,
10909
+ pollInstallIntent: pollGitHubInstallIntent,
10910
+ openBrowser,
10911
+ now: Date.now
10912
+ };
10913
+ function errorMessage(error) {
10914
+ return error instanceof Error ? error.message : String(error);
10915
+ }
10916
+ function reportConnectFailure(error) {
10917
+ const message = errorMessage(error);
10918
+ const human = boundedHealthError(message) ?? "unknown error";
10919
+ process.stderr.write(`[prim] github connect failed: ${human}
10920
+ `);
10921
+ printJson({
10922
+ status: "error",
10923
+ error: message,
10924
+ ...error instanceof HttpError ? { httpStatus: error.status } : {}
10925
+ });
10926
+ process.exitCode = EXIT_FAILURE;
10927
+ }
10928
+ function reportBinding(binding, installAttempted = false) {
10929
+ if (binding.status === "connected") {
10930
+ process.stderr.write(
10931
+ `[prim] repository binding connected for GitHub origin ${binding.repositoryFullName}
10932
+ `
10933
+ );
10934
+ printJson(binding);
10935
+ process.exitCode = EXIT_OK2;
10936
+ return;
10937
+ }
10938
+ process.stderr.write(
10939
+ installAttempted ? `[prim] GitHub installation completed, but ${binding.repositoryFullName} was not granted admin access; repository binding remains unbound
10940
+ ` : `[prim] repository binding unbound for GitHub origin ${binding.repositoryFullName}; starting GitHub App installation
10941
+ `
10942
+ );
10943
+ printJson(binding);
10944
+ process.exitCode = EXIT_UNBOUND;
10945
+ }
10946
+ function terminalInstallError(status) {
10947
+ if (status.status === "failed_terminal") {
10948
+ return new Error(`GitHub installation failed: ${status.failureCode}`);
10949
+ }
10950
+ return new Error(`GitHub installation ${status.status}`);
10951
+ }
10952
+ async function completeInstallIntent(client, start, dependencies, browser) {
10953
+ process.stderr.write(`[prim] complete GitHub App installation at:
10954
+ ${start.browserUrl}
10955
+ `);
10956
+ if (browser) dependencies.openBrowser(start.browserUrl);
10957
+ return dependencies.pollInstallIntent(client, start, {
10958
+ now: dependencies.now
10959
+ });
10960
+ }
10961
+ async function runGithubConnect(dependencies = defaultDependencies2, options = {}) {
10962
+ const root = options.root ?? dependencies.gitToplevel(dependencies.cwd());
10963
+ if (!root) return { kind: "not-a-repo" };
10964
+ try {
10965
+ const client = await dependencies.getPinnedClient({
10966
+ signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
10967
+ });
10968
+ const existing = await dependencies.bindRepositoryWithClient(root, client, {
10969
+ signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
10970
+ });
10971
+ if (existing.status === "connected") {
10972
+ return { kind: "connected", binding: existing };
10973
+ }
10974
+ const start = await dependencies.createInstallIntent(client, {
10975
+ signal: AbortSignal.timeout(START_TIMEOUT_MS),
10976
+ now: dependencies.now
10977
+ });
10978
+ const status = await completeInstallIntent(
10979
+ client,
10980
+ start,
10981
+ dependencies,
10982
+ options.browser ?? true
10983
+ );
10984
+ if (status.status !== "consumed") throw terminalInstallError(status);
10985
+ process.stderr.write(
10986
+ `[prim] GitHub installation verified: ${status.adminRepositoryCount} admin repositories (${status.repositoryCount} total)
10987
+ `
10988
+ );
10989
+ const binding = await dependencies.bindRepositoryWithClient(root, client, {
10990
+ signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
10991
+ });
10992
+ return binding.status === "connected" ? { kind: "connected", binding } : { kind: "unbound", binding, installAttempted: true };
10993
+ } catch (error) {
10994
+ return { kind: "error", error };
10995
+ }
10996
+ }
10997
+ async function performGithubConnect(dependencies = defaultDependencies2, options = {}) {
10998
+ const outcome = await runGithubConnect(dependencies, options);
10999
+ switch (outcome.kind) {
11000
+ case "not-a-repo":
11001
+ reportConnectFailure(
11002
+ new Error("not a git repository \u2014 run `prim github connect` inside a GitHub repository")
11003
+ );
11004
+ return;
11005
+ case "error":
11006
+ reportConnectFailure(outcome.error);
11007
+ return;
11008
+ case "connected":
11009
+ reportBinding(outcome.binding);
11010
+ return;
11011
+ case "unbound":
11012
+ reportBinding(outcome.binding, outcome.installAttempted);
11013
+ return;
11014
+ }
11015
+ }
11016
+ function registerGithubCommands(program2, dependencies = defaultDependencies2) {
11017
+ const github = program2.command("github").description("Manage GitHub repository bindings");
11018
+ github.command("connect").description("Install or reuse the Primitive GitHub App and bind the current GitHub origin").option("--no-browser", "Print the GitHub installation URL without opening a browser").action(async (options, command) => {
11019
+ const globals = command.optsWithGlobals();
11020
+ await performGithubConnect(dependencies, {
11021
+ browser: options.browser && globals.nonInteractive !== true
11022
+ });
11023
+ });
11024
+ }
11025
+
11026
+ // src/commands/activation.ts
11027
+ var CONNECT_PROMPT = "[prim] Connect this repository to Primitive via the GitHub App now?";
11028
+ async function maybeConnectRepository(root, globals) {
11029
+ if (isNonInteractive(globals)) return void 0;
11030
+ const approved = Boolean(globals.yes) || await askConfirmation(CONNECT_PROMPT, process.stderr);
11031
+ if (!approved) return void 0;
11032
+ const outcome = await runGithubConnect(void 0, { root, browser: true });
11033
+ if (outcome.kind === "connected") {
11034
+ process.stderr.write(
11035
+ `[prim] repository binding connected for GitHub origin ${outcome.binding.repositoryFullName}
11036
+ `
11037
+ );
11038
+ return outcome.binding;
11039
+ }
11040
+ if (outcome.kind === "error") {
11041
+ const detail = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
11042
+ process.stderr.write(`[prim] connect could not complete: ${detail}
11043
+ `);
11044
+ }
11045
+ return void 0;
11046
+ }
11047
+ async function applyActivation(active, globals = {}) {
11048
+ const root = gitToplevel();
11049
+ if (!root) {
11050
+ process.stderr.write(
11051
+ `[prim] not a git repository \u2014 run \`prim ${active ? "enable" : "disable"}\` inside a repo
11052
+ `
11053
+ );
11054
+ process.exit(1);
11055
+ }
11056
+ let phase = active ? "post-commit hook coverage" : "local deactivation";
11057
+ try {
11058
+ let binding;
11059
+ let postCommitHook;
11060
+ let postRewriteHook;
11061
+ if (active) {
11062
+ postCommitHook = ensureEffectivePostCommitHook(root).path;
11063
+ try {
11064
+ postRewriteHook = ensureEffectivePostRewriteHook(root).path;
11065
+ } catch (error) {
11066
+ const detail = error instanceof Error ? error.message : String(error);
11067
+ process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
11068
+ `);
11069
+ }
11070
+ phase = "repository binding";
11071
+ binding = await bindRepository(root);
11072
+ phase = "local activation";
11073
+ }
11074
+ setRepoActive(root, active);
11075
+ if (active && binding?.status === "unbound") {
11076
+ const connected = await maybeConnectRepository(root, globals);
11077
+ if (connected) binding = connected;
11078
+ }
11079
+ await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
11080
+ if (binding?.status === "unbound") {
11081
+ process.stderr.write(`[prim] Prim is enabled locally in ${root}
11082
+ `);
11083
+ process.stderr.write(
11084
+ "[prim] repository is not connected to Primitive; Moves still ingest into the team graph without repository-specific file attribution, Conflict Gate verification, or commit correlation\n"
11085
+ );
11086
+ process.stderr.write(
11087
+ `[prim] ask an organization owner or administrator to grant Primitive's GitHub App access to this repository through Primitive's GitHub App onboarding; binding retries automatically at the next agent SessionStart
11088
+ `
11089
+ );
11090
+ } else {
11091
+ process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
11092
+ `);
11093
+ }
11094
+ printJson({
11095
+ active,
11096
+ repo: root,
11097
+ ...binding ? {
11098
+ bindingStatus: binding.status,
11099
+ repositoryFullName: binding.repositoryFullName,
11100
+ ...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
11101
+ } : {},
11102
+ ...postCommitHook ? { postCommitHook } : {},
11103
+ ...postRewriteHook ? { postRewriteHook } : {}
11104
+ });
11105
+ } catch (err) {
11106
+ const detail = err instanceof Error ? err.message : String(err);
11107
+ process.stderr.write(
11108
+ active ? `[prim] failed to enable prim during ${phase}: ${detail}
11109
+ ` : `[prim] failed to disable prim: ${detail}
11110
+ `
11111
+ );
11112
+ process.exit(1);
11113
+ }
11114
+ }
11115
+ function registerActivationCommands(program2) {
11116
+ program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action((_opts, command) => applyActivation(true, command.optsWithGlobals()));
11117
+ program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(
11118
+ (_opts, command) => applyActivation(false, command.optsWithGlobals())
11119
+ );
11120
+ }
11121
+
11122
+ // src/commands/claude-install.ts
11123
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
11124
+ import { homedir } from "os";
11125
+ import { join as join2 } from "path";
11126
+
11127
+ // src/lib/hook-runtime.ts
11128
+ import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
11129
+ import {
11130
+ constants,
11131
+ accessSync,
11132
+ chmodSync,
11133
+ copyFileSync,
11134
+ existsSync as existsSync2,
11135
+ lstatSync,
11136
+ mkdirSync,
11137
+ mkdtempSync,
11138
+ readFileSync as readFileSync2,
11139
+ readdirSync,
11140
+ realpathSync,
11141
+ renameSync,
11142
+ rmSync,
11143
+ statSync,
11144
+ utimesSync,
11145
+ writeFileSync
11146
+ } from "fs";
11147
+ import { dirname, isAbsolute, join, resolve } from "path";
11148
+ var HOOK_RUNTIME_SCHEMA_VERSION = 1;
11149
+ var DIRECTORY_MODE = 448;
11150
+ var DATA_FILE_MODE = 384;
11151
+ var LAUNCHER_MODE = 448;
11152
+ var RELEASE_PREFIX = "release-";
11153
+ var RELEASE_NAME_RE = /^release-[0-9a-f]{64}$/u;
11154
+ var MAX_MANIFEST_BYTES = 64 * 1024;
11155
+ var MAX_RELEASES = 8;
11156
+ var MAX_RELEASE_DELETIONS_PER_STAGE = 8;
11157
+ var RETAIN_PREDECESSORS = 2;
11158
+ var RELEASE_GRACE_MS = 10 * 60 * 1e3;
11159
+ var HOOK_RUNTIME_ENTRIES = {
11160
+ // SessionStart self-healing resolves this exact CLI entry to run
11161
+ // `daemon ensure`; it must travel with the immutable hook release.
11162
+ prim: "dist/index.js",
11163
+ "prim-hook": "dist/hooks/prim-hook.js",
11164
+ "prim-pre-commit": "dist/hooks/pre-commit.js",
11165
+ "prim-post-commit": "dist/hooks/post-commit.js",
11166
+ "prim-post-rewrite": "dist/hooks/post-rewrite.js",
11167
+ "prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
11168
+ "prim-post-tool-use": "dist/hooks/post-tool-use.js",
11169
+ "prim-session-start": "dist/hooks/session-start.js",
11170
+ "prim-session-end": "dist/hooks/session-end.js",
11171
+ "prim-daemon-server": "dist/daemon/server.js",
11172
+ "prim-statusline": "dist/statusline-main.js"
10904
11173
  };
10905
11174
  var SOURCE_ENTRIES = Object.fromEntries(
10906
11175
  Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, target]) => [
@@ -12652,7 +12921,7 @@ function atomicWrite2(path, content, mode, validate) {
12652
12921
  }
12653
12922
  return true;
12654
12923
  }
12655
- function defaultSleep(ms) {
12924
+ function defaultSleep2(ms) {
12656
12925
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
12657
12926
  }
12658
12927
  function validatePlist(path, timeoutMs2) {
@@ -12971,7 +13240,7 @@ async function ensureMacDaemonLocked(options) {
12971
13240
  PLIST_FILE_MODE,
12972
13241
  options.validatePlist ?? validatePlist
12973
13242
  );
12974
- const sleep2 = options.sleep ?? defaultSleep;
13243
+ const sleep2 = options.sleep ?? defaultSleep2;
12975
13244
  const nowMs = options.nowMs ?? performance.now.bind(performance);
12976
13245
  const deadlineMs = nowMs() + TRANSITION_TIMEOUT_MS;
12977
13246
  const inspect = options.inspectDaemon ?? inspectDaemon;
@@ -13025,7 +13294,7 @@ async function bootoutMacDaemon(options = {}) {
13025
13294
  async function bootoutMacDaemonLocked(options) {
13026
13295
  const runner = options.runner ?? runLaunchctl;
13027
13296
  const inspect = options.inspectDaemon ?? inspectDaemon;
13028
- const sleep2 = options.sleep ?? defaultSleep;
13297
+ const sleep2 = options.sleep ?? defaultSleep2;
13029
13298
  const nowMs = options.nowMs ?? performance.now.bind(performance);
13030
13299
  const deadlineMs = nowMs() + TRANSITION_TIMEOUT_MS;
13031
13300
  const paths = launchdPaths(options);
@@ -13095,7 +13364,7 @@ var READY_POLL_MS2 = 100;
13095
13364
  var READY_PROBE_TIMEOUT_MS2 = 250;
13096
13365
  var HEALTHY_TIMEOUT_MS = 3e4;
13097
13366
  var HEALTHY_POLL_MS = 250;
13098
- var EXIT_OK2 = 0;
13367
+ var EXIT_OK3 = 0;
13099
13368
  var EXIT_NOT_RUNNING = 2;
13100
13369
  var EXIT_BOOTING = 3;
13101
13370
  function formatDaemonLifecycleMessage(message, decisionIngestion) {
@@ -13476,7 +13745,7 @@ function classifyStatus(pidAlive, responding, snapshot, pid) {
13476
13745
  };
13477
13746
  }
13478
13747
  if (!snapshot) {
13479
- return { json: { running: true, responding: true }, exitCode: EXIT_OK2 };
13748
+ return { json: { running: true, responding: true }, exitCode: EXIT_OK3 };
13480
13749
  }
13481
13750
  if (snapshot.healthy === false) {
13482
13751
  return {
@@ -13484,7 +13753,7 @@ function classifyStatus(pidAlive, responding, snapshot, pid) {
13484
13753
  exitCode: EXIT_BOOTING
13485
13754
  };
13486
13755
  }
13487
- return { json: { running: true, responding: true, ...snapshot }, exitCode: EXIT_OK2 };
13756
+ return { json: { running: true, responding: true, ...snapshot }, exitCode: EXIT_OK3 };
13488
13757
  }
13489
13758
  function classifyLaunchdStatus(service, responding, snapshot, disabled) {
13490
13759
  if (!service) {
@@ -13582,7 +13851,7 @@ function classifyLaunchdStatus(service, responding, snapshot, disabled) {
13582
13851
  disabled,
13583
13852
  ...snapshot
13584
13853
  },
13585
- exitCode: EXIT_OK2
13854
+ exitCode: EXIT_OK3
13586
13855
  };
13587
13856
  }
13588
13857
  function formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion, bindingState) {
@@ -13632,7 +13901,7 @@ async function detachedDaemonStatus() {
13632
13901
  writeLiveSnapshot(snapshot);
13633
13902
  }
13634
13903
  console.log(JSON.stringify(json, null, 2));
13635
- if (exitCode !== EXIT_OK2 && !process.exitCode) {
13904
+ if (exitCode !== EXIT_OK3 && !process.exitCode) {
13636
13905
  process.exitCode = exitCode;
13637
13906
  }
13638
13907
  }
@@ -13682,7 +13951,7 @@ async function macDaemonStatus() {
13682
13951
  writeLiveSnapshot(snapshot, true);
13683
13952
  }
13684
13953
  console.log(JSON.stringify(json, null, 2));
13685
- if (exitCode !== EXIT_OK2 && !process.exitCode) process.exitCode = exitCode;
13954
+ if (exitCode !== EXIT_OK3 && !process.exitCode) process.exitCode = exitCode;
13686
13955
  }
13687
13956
  async function daemonStatus() {
13688
13957
  if (process.platform === "darwin") {
@@ -14278,7 +14547,7 @@ var DECISION_LIFECYCLE_EXIT = {
14278
14547
  server: 3,
14279
14548
  notFound: 4
14280
14549
  };
14281
- var defaultDependencies2 = {
14550
+ var defaultDependencies3 = {
14282
14551
  getClient,
14283
14552
  signal: () => AbortSignal.timeout(DECISION_LIFECYCLE_TIMEOUT_MS),
14284
14553
  writeStdout: (value) => console.log(value),
@@ -14491,19 +14760,19 @@ async function executeLifecycle(operation, request, dependencies) {
14491
14760
  return writeFailure2(operation, classifyError2(error), dependencies);
14492
14761
  }
14493
14762
  }
14494
- function publishDecision(id, dependencies = defaultDependencies2) {
14763
+ function publishDecision(id, dependencies = defaultDependencies3) {
14495
14764
  const request = { id };
14496
14765
  return executeLifecycle("publish", request, dependencies);
14497
14766
  }
14498
- function restoreDecision(id, dependencies = defaultDependencies2) {
14767
+ function restoreDecision(id, dependencies = defaultDependencies3) {
14499
14768
  const request = { id };
14500
14769
  return executeLifecycle("restore", request, dependencies);
14501
14770
  }
14502
- function ratifyDecision(id, dependencies = defaultDependencies2) {
14771
+ function ratifyDecision(id, dependencies = defaultDependencies3) {
14503
14772
  const request = { id };
14504
14773
  return executeLifecycle("ratify", request, dependencies);
14505
14774
  }
14506
- function supersedeDecision(id, by, dependencies = defaultDependencies2) {
14775
+ function supersedeDecision(id, by, dependencies = defaultDependencies3) {
14507
14776
  const request = { id, by };
14508
14777
  return executeLifecycle("supersede", request, dependencies);
14509
14778
  }
@@ -15254,26 +15523,9 @@ function formatShowJson(result) {
15254
15523
  return JSON.stringify(result, null, 2);
15255
15524
  }
15256
15525
 
15257
- // src/lib/confirmation.ts
15258
- function isNonInteractive(globals) {
15259
- return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
15260
- }
15261
- async function askConfirmation(question, output = process.stdout) {
15262
- if (!process.stdin.isTTY) return false;
15263
- const { createInterface } = await import("readline/promises");
15264
- const rl = createInterface({ input: process.stdin, output });
15265
- try {
15266
- const answer = await rl.question(`${question} [y/N] `);
15267
- const normalized = answer.trim().toLowerCase();
15268
- return normalized === "y" || normalized === "yes";
15269
- } finally {
15270
- rl.close();
15271
- }
15272
- }
15273
-
15274
15526
  // src/commands/decisions.ts
15275
15527
  var EXIT_NOT_FOUND = 4;
15276
- var EXIT_FAILURE = 1;
15528
+ var EXIT_FAILURE2 = 1;
15277
15529
  var EXIT_USAGE = 2;
15278
15530
  function handleRepairCommandError(error) {
15279
15531
  if (error instanceof RepairProposalNotFoundError) {
@@ -15290,12 +15542,12 @@ function handleRepairCommandError(error) {
15290
15542
  console.error(
15291
15543
  `[prim] ${error instanceof RepairAuthorizationError ? error.message : "Not authorized to review commit repairs; active organization membership and current repository authorization are required"}`
15292
15544
  );
15293
- process.exitCode = EXIT_FAILURE;
15545
+ process.exitCode = EXIT_FAILURE2;
15294
15546
  return true;
15295
15547
  }
15296
15548
  if (error instanceof RepairEndpointVersionError || error instanceof RepairListContractError) {
15297
15549
  console.error(`[prim] ${error.message}`);
15298
- process.exitCode = EXIT_FAILURE;
15550
+ process.exitCode = EXIT_FAILURE2;
15299
15551
  return true;
15300
15552
  }
15301
15553
  return false;
@@ -16927,220 +17179,6 @@ function registerDoctorCommands(program2) {
16927
17179
  });
16928
17180
  }
16929
17181
 
16930
- // src/lib/github-install-intent.ts
16931
- var PROTOCOL_VERSION = 1;
16932
- var MODE = "install_intent_v1";
16933
- var START_PATH = "/api/cli/github/install-intents";
16934
- var POLL_REQUEST_TIMEOUT_MS = 1e4;
16935
- var MAX_INTENT_LIFETIME_MS = 15 * 6e4;
16936
- var REPOSITORY_IDENTITY_MIGRATION_REQUIRED = "repository_identity_migration_required";
16937
- function isRepositoryIdentityMigrationRequiredStatus(value) {
16938
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
16939
- return false;
16940
- }
16941
- const record2 = value;
16942
- if (record2.failureCode !== REPOSITORY_IDENTITY_MIGRATION_REQUIRED) {
16943
- return false;
16944
- }
16945
- return isGitHubInstallIntentStatusResponse({
16946
- ...record2,
16947
- failureCode: "proof_commit_failed"
16948
- });
16949
- }
16950
- function parseGitHubInstallIntentStart(value, now = Date.now()) {
16951
- if (!isGitHubInstallIntentStartResponse(value) || value.expiresAt <= now || value.expiresAt - now > MAX_INTENT_LIFETIME_MS) {
16952
- return null;
16953
- }
16954
- return value;
16955
- }
16956
- function parseGitHubInstallIntentStatus(value, expectedExpiresAt) {
16957
- if (isGitHubInstallIntentStatusResponse(value)) {
16958
- return value.expiresAt === expectedExpiresAt ? value : null;
16959
- }
16960
- if (!isRepositoryIdentityMigrationRequiredStatus(value) || value.expiresAt !== expectedExpiresAt) {
16961
- return null;
16962
- }
16963
- return value;
16964
- }
16965
- async function createGitHubInstallIntent(client, options = {}) {
16966
- const { now = Date.now, ...requestOptions } = options;
16967
- const raw = await client.post(START_PATH, void 0, requestOptions);
16968
- const parsed = parseGitHubInstallIntentStart(raw, now());
16969
- if (!parsed) throw new Error("server returned an invalid GitHub install-intent response");
16970
- return parsed;
16971
- }
16972
- function defaultSleep2(milliseconds, signal) {
16973
- if (signal.aborted) return Promise.reject(signal.reason);
16974
- return new Promise((resolvePromise, reject) => {
16975
- const onAbort = () => {
16976
- clearTimeout(timer);
16977
- reject(signal.reason);
16978
- };
16979
- const timer = setTimeout(() => {
16980
- signal.removeEventListener("abort", onAbort);
16981
- resolvePromise();
16982
- }, milliseconds);
16983
- signal.addEventListener("abort", onAbort, { once: true });
16984
- });
16985
- }
16986
- function expiredStatus(start) {
16987
- return {
16988
- protocolVersion: PROTOCOL_VERSION,
16989
- mode: MODE,
16990
- found: true,
16991
- status: "expired",
16992
- expiresAt: start.expiresAt,
16993
- closedAt: start.expiresAt
16994
- };
16995
- }
16996
- async function pollGitHubInstallIntent(client, start, options = {}) {
16997
- const now = options.now ?? Date.now;
16998
- const sleep2 = options.sleep ?? defaultSleep2;
16999
- const signal = options.signal ?? new AbortController().signal;
17000
- const path = `${START_PATH}/${encodeURIComponent(start.intentId)}`;
17001
- while (true) {
17002
- if (signal.aborted) throw signal.reason;
17003
- const remainingBeforeSleep = start.expiresAt - now();
17004
- if (remainingBeforeSleep <= 0) return expiredStatus(start);
17005
- await sleep2(Math.min(start.pollAfterMs, remainingBeforeSleep), signal);
17006
- if (signal.aborted) throw signal.reason;
17007
- const remainingBeforeRequest = start.expiresAt - now();
17008
- if (remainingBeforeRequest <= 0) return expiredStatus(start);
17009
- let raw;
17010
- try {
17011
- raw = await client.get(path, {
17012
- signal: AbortSignal.any([
17013
- signal,
17014
- AbortSignal.timeout(Math.min(POLL_REQUEST_TIMEOUT_MS, remainingBeforeRequest))
17015
- ])
17016
- });
17017
- } catch (error) {
17018
- if (signal.aborted) throw error;
17019
- if (now() >= start.expiresAt) return expiredStatus(start);
17020
- throw error;
17021
- }
17022
- const status = parseGitHubInstallIntentStatus(raw, start.expiresAt);
17023
- if (!status) throw new Error("server returned an invalid GitHub install-intent status");
17024
- if (status.status !== "pending" && status.status !== "claimed") return status;
17025
- }
17026
- }
17027
-
17028
- // src/commands/github.ts
17029
- var BIND_TIMEOUT_MS = 1e4;
17030
- var START_TIMEOUT_MS = 1e4;
17031
- var EXIT_OK3 = 0;
17032
- var EXIT_FAILURE2 = 1;
17033
- var EXIT_UNBOUND = 2;
17034
- var defaultDependencies3 = {
17035
- cwd: () => process.cwd(),
17036
- gitToplevel,
17037
- bindRepositoryWithClient,
17038
- getPinnedClient,
17039
- createInstallIntent: createGitHubInstallIntent,
17040
- pollInstallIntent: pollGitHubInstallIntent,
17041
- openBrowser,
17042
- now: Date.now
17043
- };
17044
- function errorMessage(error) {
17045
- return error instanceof Error ? error.message : String(error);
17046
- }
17047
- function reportConnectFailure(error) {
17048
- const message = errorMessage(error);
17049
- const human = boundedHealthError(message) ?? "unknown error";
17050
- process.stderr.write(`[prim] github connect failed: ${human}
17051
- `);
17052
- printJson({
17053
- status: "error",
17054
- error: message,
17055
- ...error instanceof HttpError ? { httpStatus: error.status } : {}
17056
- });
17057
- process.exitCode = EXIT_FAILURE2;
17058
- }
17059
- function reportBinding(binding, installAttempted = false) {
17060
- if (binding.status === "connected") {
17061
- process.stderr.write(
17062
- `[prim] repository binding connected for GitHub origin ${binding.repositoryFullName}
17063
- `
17064
- );
17065
- printJson(binding);
17066
- process.exitCode = EXIT_OK3;
17067
- return;
17068
- }
17069
- process.stderr.write(
17070
- installAttempted ? `[prim] GitHub installation completed, but ${binding.repositoryFullName} was not granted admin access; repository binding remains unbound
17071
- ` : `[prim] repository binding unbound for GitHub origin ${binding.repositoryFullName}; starting GitHub App installation
17072
- `
17073
- );
17074
- printJson(binding);
17075
- process.exitCode = EXIT_UNBOUND;
17076
- }
17077
- function terminalInstallError(status) {
17078
- if (status.status === "failed_terminal") {
17079
- return new Error(`GitHub installation failed: ${status.failureCode}`);
17080
- }
17081
- return new Error(`GitHub installation ${status.status}`);
17082
- }
17083
- async function completeInstallIntent(client, start, dependencies, browser) {
17084
- process.stderr.write(`[prim] complete GitHub App installation at:
17085
- ${start.browserUrl}
17086
- `);
17087
- if (browser) dependencies.openBrowser(start.browserUrl);
17088
- return dependencies.pollInstallIntent(client, start, {
17089
- now: dependencies.now
17090
- });
17091
- }
17092
- async function performGithubConnect(dependencies = defaultDependencies3, options = {}) {
17093
- const root = dependencies.gitToplevel(dependencies.cwd());
17094
- if (!root) {
17095
- reportConnectFailure(
17096
- new Error("not a git repository \u2014 run `prim github connect` inside a GitHub repository")
17097
- );
17098
- return;
17099
- }
17100
- try {
17101
- const client = await dependencies.getPinnedClient({
17102
- signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
17103
- });
17104
- const existing = await dependencies.bindRepositoryWithClient(root, client, {
17105
- signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
17106
- });
17107
- if (existing.status === "connected") {
17108
- reportBinding(existing);
17109
- return;
17110
- }
17111
- const start = await dependencies.createInstallIntent(client, {
17112
- signal: AbortSignal.timeout(START_TIMEOUT_MS),
17113
- now: dependencies.now
17114
- });
17115
- const status = await completeInstallIntent(
17116
- client,
17117
- start,
17118
- dependencies,
17119
- options.browser ?? true
17120
- );
17121
- if (status.status !== "consumed") throw terminalInstallError(status);
17122
- process.stderr.write(
17123
- `[prim] GitHub installation verified: ${status.adminRepositoryCount} admin repositories (${status.repositoryCount} total)
17124
- `
17125
- );
17126
- const binding = await dependencies.bindRepositoryWithClient(root, client, {
17127
- signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
17128
- });
17129
- reportBinding(binding, true);
17130
- } catch (error) {
17131
- reportConnectFailure(error);
17132
- }
17133
- }
17134
- function registerGithubCommands(program2, dependencies = defaultDependencies3) {
17135
- const github = program2.command("github").description("Manage GitHub repository bindings");
17136
- github.command("connect").description("Install or reuse the Primitive GitHub App and bind the current GitHub origin").option("--no-browser", "Print the GitHub installation URL without opening a browser").action(async (options, command) => {
17137
- const globals = command.optsWithGlobals();
17138
- await performGithubConnect(dependencies, {
17139
- browser: options.browser && globals.nonInteractive !== true
17140
- });
17141
- });
17142
- }
17143
-
17144
17182
  // src/commands/hooks.ts
17145
17183
  import { execFileSync } from "child_process";
17146
17184
  import {
@@ -17459,14 +17497,9 @@ function writeOwnHooks() {
17459
17497
  mkdirSync4(PRIM_GIT_HOOKS_DIR, { recursive: true });
17460
17498
  }
17461
17499
  for (const spec of HOOKS) {
17462
- const path = resolve3(PRIM_GIT_HOOKS_DIR, spec.hookName);
17463
- if (spec.hookName === POST_COMMIT.hookName) {
17464
- ensurePostCommitHookAtPath(path, globalHookScript(spec));
17465
- } else if (spec.hookName === POST_REWRITE.hookName) {
17466
- writeFileSync3(path, globalHookScript(spec), { mode: 493 });
17467
- } else {
17468
- writeFileSync3(path, globalHookScript(spec), { mode: 493 });
17469
- }
17500
+ writeFileSync3(resolve3(PRIM_GIT_HOOKS_DIR, spec.hookName), globalHookScript(spec), {
17501
+ mode: 493
17502
+ });
17470
17503
  }
17471
17504
  for (const name of PASSTHROUGH_HOOKS) {
17472
17505
  writeFileSync3(resolve3(PRIM_GIT_HOOKS_DIR, name), passThroughScript(name), { mode: 493 });
@@ -18503,6 +18536,7 @@ function registerSetupCommand(program2, dependencies = {}) {
18503
18536
  const agent = agentInput;
18504
18537
  const scope = opts.scope;
18505
18538
  const self = process.argv[1];
18539
+ const globals = program2.optsWithGlobals();
18506
18540
  const run = dependencies.run ?? ((args, capture = false) => {
18507
18541
  const r = spawnSync2(process.execPath, [self, ...args], {
18508
18542
  stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
@@ -18572,7 +18606,12 @@ function registerSetupCommand(program2, dependencies = {}) {
18572
18606
  const enableStep = setupSteps.find((candidate) => candidate.key === "enable");
18573
18607
  if (enableStep) {
18574
18608
  note(`${enableStep.label} \xB7 installing\u2026`);
18575
- const { code } = run(enableStep.args);
18609
+ const enableArgs = [
18610
+ ...enableStep.args,
18611
+ ...globals.yes ? ["--yes"] : [],
18612
+ ...globals.nonInteractive ? ["--non-interactive"] : []
18613
+ ];
18614
+ const { code } = run(enableArgs);
18576
18615
  results[enableStep.key] = code === 0 ? "ok" : enableStep.required ? "failed" : "skipped";
18577
18616
  }
18578
18617
  const healthStep = setupSteps.find((candidate) => candidate.key === "health");