@primitive.ai/prim 0.1.0-alpha.92 → 0.1.0-alpha.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -10
- package/SKILL.md +5 -2
- package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
- package/dist/{chunk-26P57LAR.js → chunk-AXQQMX2U.js} +203 -9
- package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
- package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
- package/dist/chunk-FXF4JWK2.js +162 -0
- package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
- package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
- package/dist/{chunk-5HRR4AOR.js → chunk-IMK4JKTA.js} +1 -1
- package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
- package/dist/{chunk-ADUTW66W.js → chunk-O7YAW7G6.js} +6 -6
- package/dist/{chunk-5AFRMOPX.js → chunk-OH3EZXYX.js} +1 -1
- package/dist/{chunk-KFXF2V5G.js → chunk-OTHKG76J.js} +1 -1
- package/dist/chunk-OYHYCK4K.js +13 -0
- package/dist/{chunk-FKXFUUWJ.js → chunk-TYAHC25F.js} +1 -1
- package/dist/{chunk-5GYQ4J3D.js → chunk-UKUAAAXE.js} +2 -2
- package/dist/{chunk-LGGMJYY4.js → chunk-WUXFLE7X.js} +720 -369
- package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
- package/dist/daemon/server.js +372 -123
- package/dist/hook-runtime/daemon/server.js +372 -123
- package/dist/hook-runtime/hooks/post-commit.js +192 -58
- package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
- package/dist/hook-runtime/hooks/post-tool-use.js +150 -67
- package/dist/hook-runtime/hooks/pre-tool-use.js +87 -15
- package/dist/hook-runtime/hooks/prim-hook.js +197 -107
- package/dist/hook-runtime/hooks/session-end.js +53 -5
- package/dist/hook-runtime/hooks/session-start.js +880 -280
- package/dist/hook-runtime/index.js +4008 -2438
- package/dist/hook-runtime/statusline-main.js +23 -1
- package/dist/hooks/post-commit.js +15 -8
- package/dist/hooks/post-rewrite.js +11 -8
- package/dist/hooks/post-tool-use.js +13 -15
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +10 -12
- package/dist/hooks/prim-hook.js +17 -14
- package/dist/hooks/session-end.js +4 -4
- package/dist/hooks/session-start.js +35 -45
- package/dist/index.js +1401 -620
- package/dist/statusline-main.js +23 -1
- package/package.json +1 -1
- package/dist/chunk-BTNNEH2W.js +0 -79
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Install the Primitive CLI and activate passive decision capture for this reposit
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
That single command runs the whole install and current-repository activation in one
|
|
78
|
-
process (auth → session hooks → daemon → git hooks →
|
|
78
|
+
process (auth → session hooks → daemon → skill → activate this repository and wire its git hooks → welcome). The detailed
|
|
79
79
|
step-by-step the agent follows — and the manual fallback — live in
|
|
80
80
|
[`setup.md`](./setup.md).
|
|
81
81
|
|
|
@@ -89,12 +89,17 @@ prim setup --agent cursor # Same, for Cursor IDE and Cursor CLI
|
|
|
89
89
|
prim setup --agent codex # Same, for OpenAI Codex
|
|
90
90
|
prim setup --agent hermes # Same, for Hermes Agent (global-only config)
|
|
91
91
|
prim setup --no-daemon # Stop it and persistently opt out of supervised delivery
|
|
92
|
+
prim setup --global-hooks-path # Also route every repository's git hooks through prim
|
|
92
93
|
```
|
|
93
94
|
|
|
94
|
-
Orchestrates auth → session hooks → supervised daemon →
|
|
95
|
+
Orchestrates auth → session hooks → supervised daemon → git hooks (`--scope project`, or `--global-hooks-path`) → skill → activate this repo (which wires its git hooks) → capture-health gate → welcome,
|
|
95
96
|
re-running each underlying command so every step behaves exactly as if run by
|
|
96
97
|
hand (including the browser login). Idempotent — safe to re-run.
|
|
97
98
|
|
|
99
|
+
Setup wires the current repository's git hooks and never changes git's global
|
|
100
|
+
config on its own: a global `core.hooksPath` reroutes every repository on the
|
|
101
|
+
machine. Pass `--global-hooks-path` to opt in; `prim --yes setup` does not.
|
|
102
|
+
|
|
98
103
|
### Uninstall
|
|
99
104
|
|
|
100
105
|
```bash
|
|
@@ -238,13 +243,39 @@ installation.
|
|
|
238
243
|
|
|
239
244
|
A supervised long-lived companion process that continuously drains captured
|
|
240
245
|
Moves, accelerates decision-graph reads, and powers the "team: N online"
|
|
241
|
-
presence count. `prim setup` requires it to
|
|
242
|
-
`--no-daemon` opt-out is
|
|
246
|
+
presence count. `prim setup` requires it to be live on the current version with
|
|
247
|
+
an authenticated, healthy heartbeat unless the explicit `--no-daemon` opt-out is
|
|
248
|
+
supplied. Hooks still fail soft if it later degrades.
|
|
249
|
+
|
|
250
|
+
`prim daemon start` (and `restart`/`ensure`) does not gate on delivery. While
|
|
251
|
+
Moves are past the 30s delivery SLA it reports them as draining in the
|
|
252
|
+
background when the daemon's last sweep recorded no failure, or when the drains
|
|
253
|
+
that failed acknowledged Moves before failing; another bucket delivering in the
|
|
254
|
+
same sweep does not count. Journal buckets the daemon holds back (unbound,
|
|
255
|
+
another organization, identity unavailable) read as delivery held back, and a
|
|
256
|
+
failure with no progress reads as delivery failing; both are a ⚠ warning,
|
|
257
|
+
still exit 0. Its JSON adds `draining`, `deliveryRetained`, `deliveryFailing`,
|
|
258
|
+
and `ingestion` in those states. Start's JSON `healthy` means the start
|
|
259
|
+
succeeded, so it can differ from `daemon status --json`, which stays degraded
|
|
260
|
+
until the backlog drains. Setup's own health check reports a backlog the
|
|
261
|
+
daemon is draining as a warning and fails on held-back buckets and on failures
|
|
262
|
+
recorded without progress; standalone `prim doctor` still fails a missed SLA.
|
|
243
263
|
|
|
244
264
|
```bash
|
|
245
265
|
prim daemon start # start (stop / restart / status)
|
|
246
266
|
```
|
|
247
267
|
|
|
268
|
+
Ordinary commands may upgrade an older daemon in the background. When an
|
|
269
|
+
installed CLI runs an attended, allowlisted command (for example `auth`,
|
|
270
|
+
`decisions`, `moves`, `skill`, or an agent's `install`/`status`) on macOS and
|
|
271
|
+
finds the launchd daemon on an older version, or on its own version but unable
|
|
272
|
+
to run (say, after a Node upgrade deleted the binary it pinned), it starts a
|
|
273
|
+
detached `prim daemon ensure`, at most once an hour whatever version it
|
|
274
|
+
targets. It never downgrades, never repoints the daemon at another deployment
|
|
275
|
+
or config root, and never runs from hooks or their background drains, `daemon`,
|
|
276
|
+
`doctor`, `setup` or its steps, `auth clear`, removal commands, a development
|
|
277
|
+
checkout, or as root. Set `PRIM_DAEMON_DRIFT_HEAL=0` to turn it off.
|
|
278
|
+
|
|
248
279
|
### Decisions
|
|
249
280
|
|
|
250
281
|
Read and respond to the decision graph.
|
|
@@ -295,15 +326,52 @@ authorizes one retry for the named decision.
|
|
|
295
326
|
### Hooks
|
|
296
327
|
|
|
297
328
|
```bash
|
|
298
|
-
prim hooks install
|
|
299
|
-
prim hooks
|
|
329
|
+
prim hooks install # Wire the pre-commit, post-commit, and post-rewrite hooks
|
|
330
|
+
prim hooks install --scope user --global-hooks-path # Route every repository through prim (exits 3, with the reason on STDERR, if it installs nothing; 1 if prim's hook runtime is not staged)
|
|
331
|
+
prim hooks snippet post-commit # Print the block that wires one hook, to place by hand
|
|
332
|
+
prim hooks uninstall # Remove prim's blocks (and any hook file prim created)
|
|
300
333
|
```
|
|
301
334
|
|
|
302
335
|
The pre-commit hook checks staged files against the live decision graph
|
|
303
|
-
(warn-only — it never blocks the commit). The post-commit
|
|
304
|
-
commit as
|
|
305
|
-
|
|
306
|
-
|
|
336
|
+
(warn-only — it never blocks the commit). The post-commit and post-rewrite hooks
|
|
337
|
+
record each commit and each amend/rebase as capture boundaries for
|
|
338
|
+
classification. All three act only in repos where prim is active (`prim enable`).
|
|
339
|
+
Supports [Husky](https://typicode.github.io/husky/) — `prim hooks install`
|
|
340
|
+
detects Husky and offers to install into `.husky/`.
|
|
341
|
+
|
|
342
|
+
Each hook file gets one short block, inserted right after the shebang so it runs
|
|
343
|
+
before any `exit` or `exec`. The block names no version and no machine path: it
|
|
344
|
+
runs prim's staged entrypoint (`~/.config/prim/prim-git-hook-v1`) and does
|
|
345
|
+
nothing on a machine without prim, so a tracked `.husky/*` file never changes
|
|
346
|
+
across prim releases. `prim enable` and `prim hooks install` write it once and
|
|
347
|
+
never move or rewrite a current block, reformatted or not. Agent session hooks
|
|
348
|
+
only repair Git-private hook files under `.git` and prim's own hooks dir, and
|
|
349
|
+
never add the pre-commit check. prim never edits a hooks dir outside the
|
|
350
|
+
repository, such as a global `core.hooksPath` every repository shares, unless
|
|
351
|
+
you run `prim hooks install --scope user --global-hooks-path` or answer yes at
|
|
352
|
+
its terminal prompt. In a Husky v8 hook the block goes
|
|
353
|
+
after the `husky.sh` line, so it runs once and honors `HUSKY=0`. `prim doctor`
|
|
354
|
+
only reports; it never rewrites a hook.
|
|
355
|
+
|
|
356
|
+
`prim doctor` also checks evidence rather than file contents: each post-commit
|
|
357
|
+
run that reaches prim leaves a stamp in the checkout's git dir, and doctor's
|
|
358
|
+
`hook-fired` check fails when a local commit made since the hooks were wired
|
|
359
|
+
never reached prim. That catches an `exit` before prim's block, hand wiring
|
|
360
|
+
that broke, or a git client whose environment cannot find prim. The check
|
|
361
|
+
applies in manual mode too: once a repository is enabled, doctor expects your
|
|
362
|
+
own wiring to reach prim. `prim disable` ends the expectation, so commits made
|
|
363
|
+
while disabled are never judged. Turning prim off with `git config prim.active
|
|
364
|
+
false` directly ends it too, once an agent session starts while it is off; if
|
|
365
|
+
doctor still flags a commit made while it was off, run `prim disable` then
|
|
366
|
+
`prim enable`.
|
|
367
|
+
|
|
368
|
+
To wire the hooks yourself (lefthook, a custom Husky layout, …), run
|
|
369
|
+
`git config prim.gitHooks manual` (or `--global`) first: prim then never writes
|
|
370
|
+
a hook file. That setting is the only signal prim honors. It does not guess
|
|
371
|
+
from a hook that already mentions prim, so without it, `prim enable` adds its
|
|
372
|
+
block. Paste the block from `prim hooks snippet <hook>`, or have your hook
|
|
373
|
+
manager run `~/.config/prim/prim-git-hook-v1 <hook> "$@"` (post-rewrite also
|
|
374
|
+
needs Git's stdin).
|
|
307
375
|
|
|
308
376
|
### Presence statusline
|
|
309
377
|
|
|
@@ -338,6 +406,12 @@ prim moves tail # Pretty-print recent journal entries
|
|
|
338
406
|
prim moves flush # Drain the local journals to the server (also runs from hooks)
|
|
339
407
|
```
|
|
340
408
|
|
|
409
|
+
Any other command except `uninstall` and a `setup` that starts the daemon (or
|
|
410
|
+
one of its steps) that finds Moves waiting over a minute (or cannot tell how
|
|
411
|
+
long) hands them to a detached background `prim moves flush` and never waits for
|
|
412
|
+
it. It starts none while a live daemon on the same version and deployment, with
|
|
413
|
+
a healthy heartbeat and no recorded ingestion failure, already owns the drain.
|
|
414
|
+
|
|
341
415
|
### Skill
|
|
342
416
|
|
|
343
417
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -223,7 +223,7 @@ With the daemon running (`npx --yes @primitive.ai/prim daemon start`), `npx --ye
|
|
|
223
223
|
|
|
224
224
|
## The git hooks
|
|
225
225
|
|
|
226
|
-
`npx --yes @primitive.ai/prim hooks install` installs
|
|
226
|
+
`npx --yes @primitive.ai/prim hooks install` installs three git hooks:
|
|
227
227
|
|
|
228
228
|
```
|
|
229
229
|
npx --yes @primitive.ai/prim hooks install # auto-detects Husky and prompts
|
|
@@ -234,8 +234,11 @@ npx --yes @primitive.ai/prim hooks uninstall
|
|
|
234
234
|
|
|
235
235
|
- **pre-commit** -- checks staged files against the live decision graph and prints any active decisions that reference them to stderr. It is **warn-only**: failures (auth, network, backend) or matches never block the commit; a successful `git commit` doesn't prove the check ran clean. When the check can't complete it says so ("not verified" / "truncated") rather than implying all-clear.
|
|
236
236
|
- **post-commit** -- records each commit as a capture boundary so the server can classify the surrounding work into decisions. It never blocks and runs in the background.
|
|
237
|
+
- **post-rewrite** -- records amend/rebase rewrites the same way, in the background.
|
|
237
238
|
|
|
238
|
-
|
|
239
|
+
All three act only where the repo is active (`prim enable`). Each hook file gets one version-stable block right after its shebang; `prim enable` and `hooks install` write it once and never rewrite a current block, while session hooks only repair Git-private files under `.git` (and never add the pre-commit check). prim never edits a hooks dir outside the repository, such as a shared global `core.hooksPath`: `hooks install --scope user` changes git's global hooks only with `--global-hooks-path` or a yes typed at a terminal. That changes every repository, so never pass the flag for the user without asking, and know that `--yes` does not count. `prim doctor`'s `hook-fired` check fails if a local commit since the hooks were wired never reached prim (in manual mode too, once the repo is enabled): surface it, since capture is silently missing. If the user wires the hooks themselves, they must set `git config prim.gitHooks manual` (prim does not infer hand wiring from what a hook mentions); prim then never edits their hook files; `npx --yes @primitive.ai/prim hooks snippet <hook>` prints the block to place.
|
|
240
|
+
|
|
241
|
+
Under `CI=1` (or with `--non-interactive`), `hooks install` fails fast in a Husky repo that Husky has not yet configured, unless `--yes` or `--target` is set; the error names both escapes. `hooks uninstall` removes prim's blocks from `.git/hooks` and `.husky/` alike and leaves the rest of each hook untouched. To suppress the hooks for one commit, use `git commit --no-verify`.
|
|
239
242
|
|
|
240
243
|
These git hooks are separate from the **session hooks** (`claude install` / `cursor install` / `codex install` / `hermes install`) that drive in-session capture (and Conflict Gates, when enabled).
|
|
241
244
|
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
syncDirectory,
|
|
8
8
|
syncFile,
|
|
9
9
|
withFileLockSync
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2GP5UFV7.js";
|
|
11
11
|
|
|
12
12
|
// src/lib/bin-path.ts
|
|
13
13
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -127,6 +127,13 @@ function pinnedHookCommand(bin, args = "") {
|
|
|
127
127
|
}
|
|
128
128
|
var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
|
|
129
129
|
var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
|
|
130
|
+
var PRIM_CONFIG_SHELL_RESOLVER = [
|
|
131
|
+
'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
|
|
132
|
+
'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
|
|
133
|
+
'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
|
|
134
|
+
'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
|
|
135
|
+
"fi; fi; "
|
|
136
|
+
].join("");
|
|
130
137
|
function stableHookCommand(bin, args = "") {
|
|
131
138
|
if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
|
|
132
139
|
throw new Error("invalid stable hook command");
|
|
@@ -136,11 +143,7 @@ function stableHookCommand(bin, args = "") {
|
|
|
136
143
|
// it as inert data during the rolling window so an older uninstall or
|
|
137
144
|
// reinstall can remove the stable entry without invoking npm or PATH.
|
|
138
145
|
`prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
|
|
139
|
-
|
|
140
|
-
'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
|
|
141
|
-
'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
|
|
142
|
-
'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
|
|
143
|
-
"fi; fi; ",
|
|
146
|
+
PRIM_CONFIG_SHELL_RESOLVER,
|
|
144
147
|
`case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
|
|
145
148
|
'exec "$prim_launcher" "$@"'
|
|
146
149
|
].join("");
|
|
@@ -220,6 +223,146 @@ import {
|
|
|
220
223
|
writeFileSync
|
|
221
224
|
} from "fs";
|
|
222
225
|
import { delimiter, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve } from "path";
|
|
226
|
+
|
|
227
|
+
// src/lib/git-hook-contract.ts
|
|
228
|
+
var MANAGED_GIT_HOOK_NAMES = ["pre-commit", "post-commit", "post-rewrite"];
|
|
229
|
+
var GIT_HOOK_ENTRYPOINT_NAME = "prim-git-hook-v1";
|
|
230
|
+
var GIT_HOOK_CONTRACT_VERSION = 1;
|
|
231
|
+
var CONTRACT_LINE_PREFIX = "# prim git hook v";
|
|
232
|
+
var CONTRACT_LINE_RE = /^# prim git hook v([0-9]+)\b/mu;
|
|
233
|
+
function blockMarkers(hook) {
|
|
234
|
+
return { start: `# >>> prim ${hook} hook >>>`, end: `# <<< prim ${hook} hook <<<` };
|
|
235
|
+
}
|
|
236
|
+
function entrypointCommand(hook) {
|
|
237
|
+
const script = [
|
|
238
|
+
PRIM_CONFIG_SHELL_RESOLVER,
|
|
239
|
+
`case "$prim_config" in /) prim_entry=/${GIT_HOOK_ENTRYPOINT_NAME} ;; `,
|
|
240
|
+
`*) prim_entry="$prim_config/${GIT_HOOK_ENTRYPOINT_NAME}" ;; esac; `,
|
|
241
|
+
'[ -x "$prim_entry" ] || exit 0; exec "$prim_entry" "$@"'
|
|
242
|
+
].join("");
|
|
243
|
+
return `/bin/sh -c ${shellQuote(script)} ${GIT_HOOK_ENTRYPOINT_NAME} ${hook} "$@"`;
|
|
244
|
+
}
|
|
245
|
+
function managedHookBlock(hook) {
|
|
246
|
+
const { start, end } = blockMarkers(hook);
|
|
247
|
+
const header = `${CONTRACT_LINE_PREFIX}${GIT_HOOK_CONTRACT_VERSION}: a no-op unless prim is installed and enabled here (see \`prim hooks snippet ${hook}\`).`;
|
|
248
|
+
if (hook !== "post-rewrite") {
|
|
249
|
+
return `${start}
|
|
250
|
+
${header}
|
|
251
|
+
# shellcheck disable=SC2016
|
|
252
|
+
${entrypointCommand(hook)} || :
|
|
253
|
+
${end}`;
|
|
254
|
+
}
|
|
255
|
+
return `${start}
|
|
256
|
+
${header}
|
|
257
|
+
prim_rewrite_stdin=$(mktemp 2>/dev/null) || prim_rewrite_stdin=
|
|
258
|
+
if test -n "\${prim_rewrite_stdin}" && cat >"\${prim_rewrite_stdin}"; then
|
|
259
|
+
# shellcheck disable=SC2016
|
|
260
|
+
${entrypointCommand(hook)} <"\${prim_rewrite_stdin}" || :
|
|
261
|
+
exec <"\${prim_rewrite_stdin}"
|
|
262
|
+
fi
|
|
263
|
+
if test -n "\${prim_rewrite_stdin}"; then rm -f "\${prim_rewrite_stdin}"; fi
|
|
264
|
+
unset prim_rewrite_stdin
|
|
265
|
+
${end}`;
|
|
266
|
+
}
|
|
267
|
+
var OPEN_WORDS = /* @__PURE__ */ new Set(["then", "do", "else", "{", "(", "&&", "||", "|"]);
|
|
268
|
+
function canonicalHookBlock(text) {
|
|
269
|
+
const tokens = [];
|
|
270
|
+
let word = "";
|
|
271
|
+
let quote;
|
|
272
|
+
const separate = () => {
|
|
273
|
+
const last = tokens.at(-1);
|
|
274
|
+
if (last !== void 0 && last !== ";" && !OPEN_WORDS.has(last)) tokens.push(";");
|
|
275
|
+
};
|
|
276
|
+
const flush = () => {
|
|
277
|
+
if (word === "") return;
|
|
278
|
+
const last = tokens.at(-1);
|
|
279
|
+
if (last !== void 0 && /^[0-9]*(?:<|>|>>|<&|>&)$/u.test(last))
|
|
280
|
+
tokens[tokens.length - 1] += word;
|
|
281
|
+
else tokens.push(word);
|
|
282
|
+
word = "";
|
|
283
|
+
};
|
|
284
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
285
|
+
const char = text[index];
|
|
286
|
+
if (quote) {
|
|
287
|
+
word += char;
|
|
288
|
+
if (char === quote) quote = void 0;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (char === "'" || char === '"') {
|
|
292
|
+
quote = char;
|
|
293
|
+
word += char;
|
|
294
|
+
} else if (char === "#" && word === "") {
|
|
295
|
+
const end = text.indexOf("\n", index);
|
|
296
|
+
const comment = text.slice(index, end === -1 ? text.length : end);
|
|
297
|
+
tokens.push(comment.replace(/\s+/gu, " ").trim());
|
|
298
|
+
index = (end === -1 ? text.length : end) - 1;
|
|
299
|
+
} else if (char === "\n" || char === ";") {
|
|
300
|
+
flush();
|
|
301
|
+
if (char === ";" && text[index + 1] === ";") {
|
|
302
|
+
tokens.push(";;");
|
|
303
|
+
index += 1;
|
|
304
|
+
} else {
|
|
305
|
+
separate();
|
|
306
|
+
}
|
|
307
|
+
} else if (/\s/u.test(char)) {
|
|
308
|
+
flush();
|
|
309
|
+
} else {
|
|
310
|
+
word += char;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
flush();
|
|
314
|
+
while (tokens.at(-1) === ";") tokens.pop();
|
|
315
|
+
return tokens.join("\0");
|
|
316
|
+
}
|
|
317
|
+
function hookBlockContractVersion(block) {
|
|
318
|
+
const match = CONTRACT_LINE_RE.exec(block);
|
|
319
|
+
return match?.[1] ? Number(match[1]) : void 0;
|
|
320
|
+
}
|
|
321
|
+
var GIT_HOOK_ENTRYPOINT_CONTENT = `#!/bin/sh
|
|
322
|
+
# prim git hook entrypoint v1 \u2014 managed by prim; do not edit.
|
|
323
|
+
# Run by the "# >>> prim <hook> hook >>>" block in a Git hook file. Acts only
|
|
324
|
+
# where prim is active (git config prim.active true) and never fails the hook.
|
|
325
|
+
case "$0" in */*) prim_dir=\${0%/*} ;; *) exit 0 ;; esac
|
|
326
|
+
prim_launcher="$prim_dir/${STABLE_HOOK_LAUNCHER_NAME}"
|
|
327
|
+
[ -x "$prim_launcher" ] || exit 0
|
|
328
|
+
[ "$(git config --get prim.active 2>/dev/null)" = "true" ] || exit 0
|
|
329
|
+
prim_hook=\${1-}
|
|
330
|
+
if [ "$#" -gt 0 ]; then shift; fi
|
|
331
|
+
case "$prim_hook" in
|
|
332
|
+
pre-commit)
|
|
333
|
+
"$prim_launcher" prim-pre-commit "$@" || :
|
|
334
|
+
;;
|
|
335
|
+
post-commit)
|
|
336
|
+
prim_sha=$(git rev-parse --verify HEAD 2>/dev/null) || exit 0
|
|
337
|
+
case "$prim_sha" in *[!0-9a-f]* | "") exit 0 ;; esac
|
|
338
|
+
case "\${#prim_sha}" in 40 | 64) ;; *) exit 0 ;; esac
|
|
339
|
+
prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
|
|
340
|
+
prim_observed=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_observed=
|
|
341
|
+
(
|
|
342
|
+
trap '' HUP
|
|
343
|
+
trap 'if [ -n "$prim_observed" ]; then rm -f "$prim_observed"; fi' 0
|
|
344
|
+
PRIM_COMMIT_SHA=$prim_sha PRIM_COMMIT_BRANCH=$prim_branch PRIM_COMMIT_OBSERVED_FILE=$prim_observed "$prim_launcher" prim-post-commit
|
|
345
|
+
) </dev/null >/dev/null 2>&1 &
|
|
346
|
+
;;
|
|
347
|
+
post-rewrite)
|
|
348
|
+
case "\${1-}" in amend | rebase) prim_source=$1 ;; *) exit 0 ;; esac
|
|
349
|
+
prim_pairs=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || exit 0
|
|
350
|
+
if ! chmod 600 "$prim_pairs" 2>/dev/null || ! cat >"$prim_pairs"; then
|
|
351
|
+
rm -f "$prim_pairs"
|
|
352
|
+
exit 0
|
|
353
|
+
fi
|
|
354
|
+
prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
|
|
355
|
+
(
|
|
356
|
+
trap '' HUP
|
|
357
|
+
trap 'rm -f "$prim_pairs"' 0
|
|
358
|
+
PRIM_REWRITE_SOURCE=$prim_source PRIM_REWRITE_BRANCH=$prim_branch PRIM_REWRITE_PAIRS_FILE=$prim_pairs "$prim_launcher" prim-post-rewrite
|
|
359
|
+
) </dev/null >/dev/null 2>&1 &
|
|
360
|
+
;;
|
|
361
|
+
esac
|
|
362
|
+
exit 0
|
|
363
|
+
`;
|
|
364
|
+
|
|
365
|
+
// src/lib/hook-runtime.ts
|
|
223
366
|
var HOOK_RUNTIME_SCHEMA_VERSION = 1;
|
|
224
367
|
var DIRECTORY_MODE = 448;
|
|
225
368
|
var DATA_FILE_MODE = 384;
|
|
@@ -263,6 +406,7 @@ function hookRuntimePaths(options = {}) {
|
|
|
263
406
|
return {
|
|
264
407
|
configDir,
|
|
265
408
|
launcher: join2(configDir, STABLE_HOOK_LAUNCHER_NAME),
|
|
409
|
+
gitHookEntrypoint: join2(configDir, GIT_HOOK_ENTRYPOINT_NAME),
|
|
266
410
|
runtimeDir,
|
|
267
411
|
releasesDir: join2(runtimeDir, "releases"),
|
|
268
412
|
current: join2(runtimeDir, "current"),
|
|
@@ -636,6 +780,13 @@ function stageHookRuntime(options = {}) {
|
|
|
636
780
|
mode: LAUNCHER_MODE
|
|
637
781
|
});
|
|
638
782
|
}
|
|
783
|
+
const entrypointCurrent = gitHookEntrypointCurrent(paths);
|
|
784
|
+
if (!entrypointCurrent) {
|
|
785
|
+
atomicWriteFile(paths.gitHookEntrypoint, GIT_HOOK_ENTRYPOINT_CONTENT, {
|
|
786
|
+
ensureParent: true,
|
|
787
|
+
mode: LAUNCHER_MODE
|
|
788
|
+
});
|
|
789
|
+
}
|
|
639
790
|
if (!selectorCurrent) {
|
|
640
791
|
syncDirectory(paths.releasesDir);
|
|
641
792
|
atomicWriteFile(paths.current, `${finalRelease.name}
|
|
@@ -652,13 +803,38 @@ function stageHookRuntime(options = {}) {
|
|
|
652
803
|
now
|
|
653
804
|
);
|
|
654
805
|
return {
|
|
655
|
-
changed: !launcherCurrent || !selectorCurrent,
|
|
806
|
+
changed: !launcherCurrent || !entrypointCurrent || !selectorCurrent,
|
|
656
807
|
releaseDir: finalRelease.dir,
|
|
657
808
|
manifest: finalRelease.manifest,
|
|
658
809
|
paths
|
|
659
810
|
};
|
|
660
811
|
});
|
|
661
812
|
}
|
|
813
|
+
function gitHookEntrypointCurrent(paths) {
|
|
814
|
+
try {
|
|
815
|
+
return readFileSync2(paths.gitHookEntrypoint, "utf8") === GIT_HOOK_ENTRYPOINT_CONTENT;
|
|
816
|
+
} catch {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function inspectGitHookEntrypoint(options = {}) {
|
|
821
|
+
let paths;
|
|
822
|
+
try {
|
|
823
|
+
paths = hookRuntimePaths(options);
|
|
824
|
+
} catch {
|
|
825
|
+
return "invalid";
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
const entrypoint = lstatIfPresent(paths.gitHookEntrypoint);
|
|
829
|
+
if (!entrypoint) return "missing";
|
|
830
|
+
if (!entrypoint.isFile() || (Number(entrypoint.mode) & 64) === 0 || !gitHookEntrypointCurrent(paths) || readFileSync2(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT || !lstatIfPresent(paths.current)?.isFile()) {
|
|
831
|
+
return "invalid";
|
|
832
|
+
}
|
|
833
|
+
return "ready";
|
|
834
|
+
} catch {
|
|
835
|
+
return "invalid";
|
|
836
|
+
}
|
|
837
|
+
}
|
|
662
838
|
function lstatIfPresent(path) {
|
|
663
839
|
try {
|
|
664
840
|
return lstatSync(path);
|
|
@@ -669,6 +845,12 @@ function lstatIfPresent(path) {
|
|
|
669
845
|
}
|
|
670
846
|
}
|
|
671
847
|
function assertOwnedHookRuntime(paths) {
|
|
848
|
+
const entrypoint = lstatIfPresent(paths.gitHookEntrypoint);
|
|
849
|
+
if (entrypoint && (!entrypoint.isFile() || !gitHookEntrypointCurrent(paths))) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`refusing to remove unrecognized Git hook entrypoint at ${paths.gitHookEntrypoint}`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
672
854
|
const launcher = lstatIfPresent(paths.launcher);
|
|
673
855
|
if (launcher) {
|
|
674
856
|
if (!launcher.isFile() || readFileSync2(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT) {
|
|
@@ -708,15 +890,18 @@ function assertOwnedHookRuntime(paths) {
|
|
|
708
890
|
function removeHookRuntime(options = {}) {
|
|
709
891
|
const paths = hookRuntimePaths(options);
|
|
710
892
|
const hadLauncher = lstatIfPresent(paths.launcher) !== void 0;
|
|
893
|
+
const hadEntrypoint = lstatIfPresent(paths.gitHookEntrypoint) !== void 0;
|
|
711
894
|
const hadRuntime = lstatIfPresent(paths.runtimeDir) !== void 0;
|
|
712
|
-
if (!hadLauncher && !hadRuntime) return { changed: false, paths };
|
|
895
|
+
if (!hadLauncher && !hadEntrypoint && !hadRuntime) return { changed: false, paths };
|
|
713
896
|
assertOwnedHookRuntime(paths);
|
|
714
897
|
if (!hadRuntime) {
|
|
898
|
+
rmSync(paths.gitHookEntrypoint, { force: true });
|
|
715
899
|
rmSync(paths.launcher, { force: true });
|
|
716
|
-
return { changed:
|
|
900
|
+
return { changed: true, paths };
|
|
717
901
|
}
|
|
718
902
|
withFileLockSync(paths.selectionLock, () => {
|
|
719
903
|
assertOwnedHookRuntime(paths);
|
|
904
|
+
if (lstatIfPresent(paths.gitHookEntrypoint)) rmSync(paths.gitHookEntrypoint, { force: true });
|
|
720
905
|
if (lstatIfPresent(paths.launcher)) rmSync(paths.launcher, { force: true });
|
|
721
906
|
const quarantined = `${paths.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes(8).toString("hex")}`;
|
|
722
907
|
renameSync(paths.runtimeDir, quarantined);
|
|
@@ -1460,15 +1645,24 @@ export {
|
|
|
1460
1645
|
compareSemver,
|
|
1461
1646
|
binFile,
|
|
1462
1647
|
packageVersion,
|
|
1648
|
+
packageRoot,
|
|
1463
1649
|
pinnedNpxArgs,
|
|
1464
1650
|
pinnedNpxCommand,
|
|
1465
1651
|
pinnedHookCommand,
|
|
1466
1652
|
stableHookCommand,
|
|
1467
1653
|
commandMatchesBin,
|
|
1468
1654
|
hookCommandResolutions,
|
|
1655
|
+
MANAGED_GIT_HOOK_NAMES,
|
|
1656
|
+
GIT_HOOK_CONTRACT_VERSION,
|
|
1657
|
+
blockMarkers,
|
|
1658
|
+
managedHookBlock,
|
|
1659
|
+
canonicalHookBlock,
|
|
1660
|
+
hookBlockContractVersion,
|
|
1469
1661
|
hookRuntimePaths,
|
|
1470
1662
|
inspectHookRuntime,
|
|
1663
|
+
stableNodePath,
|
|
1471
1664
|
stageHookRuntime,
|
|
1665
|
+
inspectGitHookEntrypoint,
|
|
1472
1666
|
assertOwnedHookRuntime,
|
|
1473
1667
|
removeHookRuntime,
|
|
1474
1668
|
projectRoot,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getClient
|
|
3
|
-
|
|
2
|
+
getClient,
|
|
3
|
+
terminalSafeLine
|
|
4
|
+
} from "./chunk-G2VMA2QO.js";
|
|
4
5
|
import {
|
|
5
6
|
gitToplevel,
|
|
6
7
|
githubRepositoryFullName
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
import {
|
|
9
10
|
__commonJS,
|
|
10
11
|
__toESM
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2GP5UFV7.js";
|
|
12
13
|
|
|
13
14
|
// node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/runtime/ucs2length.js
|
|
14
15
|
var require_ucs2length = __commonJS({
|
|
@@ -18331,6 +18332,73 @@ function scheduleCollectScopeRefresh(cwd, collectScopeVersion, dependencies = de
|
|
|
18331
18332
|
scheduledRefreshes.set(cwd, refresh);
|
|
18332
18333
|
}
|
|
18333
18334
|
|
|
18335
|
+
// src/lib/ansi.ts
|
|
18336
|
+
var ANSI_CODES = {
|
|
18337
|
+
yellow: "\x1B[33m",
|
|
18338
|
+
magenta: "\x1B[35m",
|
|
18339
|
+
blue: "\x1B[34m",
|
|
18340
|
+
cyan: "\x1B[36m",
|
|
18341
|
+
green: "\x1B[32m",
|
|
18342
|
+
red: "\x1B[31m",
|
|
18343
|
+
// 256-color approximation of the orange trigger marker (xterm 208).
|
|
18344
|
+
orange: "\x1B[38;5;208m",
|
|
18345
|
+
gray: "\x1B[90m"
|
|
18346
|
+
};
|
|
18347
|
+
var ANSI_RESET = "\x1B[0m";
|
|
18348
|
+
var ANSI_DIM = "\x1B[2m";
|
|
18349
|
+
var ANSI_BOLD = "\x1B[1m";
|
|
18350
|
+
function supportsColor() {
|
|
18351
|
+
if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
|
|
18352
|
+
return false;
|
|
18353
|
+
}
|
|
18354
|
+
return process.stderr.isTTY === true;
|
|
18355
|
+
}
|
|
18356
|
+
function color(text, c) {
|
|
18357
|
+
if (!supportsColor()) {
|
|
18358
|
+
return text;
|
|
18359
|
+
}
|
|
18360
|
+
return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
|
|
18361
|
+
}
|
|
18362
|
+
function dim(text) {
|
|
18363
|
+
if (!supportsColor()) {
|
|
18364
|
+
return text;
|
|
18365
|
+
}
|
|
18366
|
+
return `${ANSI_DIM}${text}${ANSI_RESET}`;
|
|
18367
|
+
}
|
|
18368
|
+
function bold(text) {
|
|
18369
|
+
if (!supportsColor()) {
|
|
18370
|
+
return text;
|
|
18371
|
+
}
|
|
18372
|
+
return `${ANSI_BOLD}${text}${ANSI_RESET}`;
|
|
18373
|
+
}
|
|
18374
|
+
var AREA_COLORS = {
|
|
18375
|
+
auth: "yellow",
|
|
18376
|
+
data: "magenta",
|
|
18377
|
+
mobile: "blue",
|
|
18378
|
+
infra: "cyan",
|
|
18379
|
+
ui: "green",
|
|
18380
|
+
billing: "orange",
|
|
18381
|
+
api: "blue",
|
|
18382
|
+
docs: "gray",
|
|
18383
|
+
testing: "gray"
|
|
18384
|
+
};
|
|
18385
|
+
function colorForArea(area) {
|
|
18386
|
+
if (!area) {
|
|
18387
|
+
return "gray";
|
|
18388
|
+
}
|
|
18389
|
+
return AREA_COLORS[area] ?? "gray";
|
|
18390
|
+
}
|
|
18391
|
+
var MAX_HEALTH_ERROR_LENGTH = 240;
|
|
18392
|
+
function boundedHealthError(value) {
|
|
18393
|
+
if (!value) return void 0;
|
|
18394
|
+
const clean = terminalSafeLine(value);
|
|
18395
|
+
if (!clean) return void 0;
|
|
18396
|
+
return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
|
|
18397
|
+
}
|
|
18398
|
+
function stripAnsi(text) {
|
|
18399
|
+
return text.replace(/\u001b\[[0-9;]*m/g, "");
|
|
18400
|
+
}
|
|
18401
|
+
|
|
18334
18402
|
export {
|
|
18335
18403
|
repoActiveFlag,
|
|
18336
18404
|
isRepoActive,
|
|
@@ -18354,5 +18422,11 @@ export {
|
|
|
18354
18422
|
isGitHubInstallIntentStatusResponse,
|
|
18355
18423
|
cachedCollectScopeAdmits,
|
|
18356
18424
|
fetchAndCacheCollectScope,
|
|
18357
|
-
scheduleCollectScopeRefresh
|
|
18425
|
+
scheduleCollectScopeRefresh,
|
|
18426
|
+
color,
|
|
18427
|
+
dim,
|
|
18428
|
+
bold,
|
|
18429
|
+
colorForArea,
|
|
18430
|
+
boundedHealthError,
|
|
18431
|
+
stripAnsi
|
|
18358
18432
|
};
|