@outerlayer/cli 0.1.1 → 0.2.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +14 -20
  3. package/dist/build-info.json +1 -1
  4. package/dist/{chunk-BQB3U2VD.js → chunk-4TNZMO7V.js} +1874 -3191
  5. package/dist/{chunk-RCQXYLMO.js → chunk-65WXAANR.js} +2 -17
  6. package/dist/{chunk-KKEU6FFR.js → chunk-DCNOXRMV.js} +1 -1
  7. package/dist/{chunk-IWIUYLDR.js → chunk-I3ETLSNE.js} +7 -187
  8. package/dist/{chunk-3VQ4XXQA.js → chunk-NXLLURA4.js} +3 -3
  9. package/dist/{chunk-HNCCD2IX.js → chunk-Q6CL5THG.js} +3 -2
  10. package/dist/chunk-U32VSRLO.js +2225 -0
  11. package/dist/chunk-WZBHBH3J.js +20 -0
  12. package/dist/{cli-EFJIG2VT.js → cli-G7DILYJY.js} +48 -130
  13. package/dist/{context-materialize-J6CRQ7O7.js → context-materialize-6WKT3RBQ.js} +1 -1
  14. package/dist/{emit-artifact-cmd-KPPD3YLO.js → emit-artifact-cmd-UQVT3OKW.js} +6 -4
  15. package/dist/{emit-finding-cmd-VR5VEGXJ.js → emit-finding-cmd-FDOU2OPD.js} +6 -4
  16. package/dist/{emit-result-cmd-YOA5BJKL.js → emit-result-cmd-H2T4C2K3.js} +6 -4
  17. package/dist/{hook-fast-I2XTHDE6.js → hook-fast-J5LCDHUJ.js} +2 -2
  18. package/dist/index.js +2 -2
  19. package/dist/{loop-BWE2BS6J.js → loop-ASZRX3CZ.js} +10 -8
  20. package/dist/{status-JHVSZYC5.js → status-I27IST4L.js} +8 -6
  21. package/dist/{sync-cmd-S7Q6DWU3.js → sync-cmd-BBAUZ5JD.js} +6 -4
  22. package/dist/{watch-NRLXJUST.js → watch-3DTPJETH.js} +6 -4
  23. package/dist/{work-claim-cmd-HXB7K27H.js → work-claim-cmd-5AUNCLS2.js} +8 -6
  24. package/dist/{work-cmd-ABP6XDBP.js → work-cmd-2P4BVX47.js} +7 -5
  25. package/dist/{work-launch-YNNCKIH3.js → work-launch-LT663PB3.js} +2 -1
  26. package/dist/{work-pr-cmd-GOYSPEN2.js → work-pr-cmd-AZQPWH4H.js} +6 -4
  27. package/package.json +1 -1
  28. package/dist/emit-commit-credit-cmd-656BRAQ4.js +0 -121
@@ -1,25 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
+ import { parseTrackerNumber } from './chunk-WZBHBH3J.js';
3
4
  import { mkdirSync, appendFileSync, readFileSync } from 'fs';
4
5
  import { join } from 'path';
5
6
 
6
7
  createRequire(import.meta.url);
7
-
8
- // src/tracker-number.ts
9
- var MAX_TRACKER_NUMBER = Number.MAX_SAFE_INTEGER;
10
- function parseTrackerNumber(raw) {
11
- if (raw === void 0) return void 0;
12
- const text = typeof raw === "number" ? String(raw) : raw;
13
- if (!/^\d+$/.test(text)) return void 0;
14
- const n = Number(text);
15
- if (!Number.isSafeInteger(n) || n <= 0) return void 0;
16
- return n;
17
- }
18
- function trackerNumberError(flag, raw) {
19
- return `invalid ${flag} "${raw}" \u2014 expected a positive integer no larger than ${MAX_TRACKER_NUMBER}`;
20
- }
21
-
22
- // src/work-launch.ts
23
8
  var HOW_TO_CREATE_AN_ITEM = "outerlayer work add";
24
9
  function parseWorkItemEnv(raw) {
25
10
  if (raw === void 0) return { reason: "OUTERLAYER_WORK is not set" };
@@ -93,4 +78,4 @@ function readLaunchSpoolState(home, readImpl = readLaunchSpool) {
93
78
  return { ids, ...malformedLines > 0 ? { malformedLines } : {} };
94
79
  }
95
80
 
96
- export { appendLaunchRecord, launchStatusIsDecisive, launchedItemForSession, launchesPath, parseTrackerNumber, parseWorkItemEnv, readLaunchSpoolState, trackerNumberError };
81
+ export { appendLaunchRecord, launchStatusIsDecisive, launchedItemForSession, launchesPath, parseWorkItemEnv, readLaunchSpoolState };
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { startTimeOf, isSameProcess } from './chunk-OZ7C3XUE.js';
4
4
  import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-JJP7YLMN.js';
5
- import { postJson, WorkCommandError } from './chunk-HNCCD2IX.js';
5
+ import { postJson, WorkCommandError } from './chunk-Q6CL5THG.js';
6
6
  import { relative, dirname, delimiter, join } from 'path';
7
7
  import { existsSync, readFileSync, mkdirSync, writeFileSync, openSync, statSync, closeSync, readdirSync } from 'fs';
8
8
  import { spawn } from 'child_process';
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
3
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
4
- import { existsSync, readFileSync, unlinkSync, readdirSync, mkdirSync, writeFileSync, chmodSync } from 'fs';
4
+ import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, unlinkSync, chmodSync } from 'fs';
5
5
  import { join, isAbsolute, dirname, basename, resolve } from 'path';
6
6
  import { execFileSync } from 'child_process';
7
7
 
@@ -74,137 +74,7 @@ function contextExcludesPresent(gitCommonDir) {
74
74
  if (beginIdx === -1) return false;
75
75
  return BLOCK_LINES.every((l, i) => lines[beginIdx + i] === l);
76
76
  }
77
- var POST_REWRITE_MARKER = "# outerlayer post-rewrite journal";
78
- var POST_REWRITE_VERSION = `${POST_REWRITE_MARKER} v1`;
79
- var POST_REWRITE_HOOK_SCRIPT = `#!/bin/sh
80
- ${POST_REWRITE_VERSION}
81
- # Appends exact old->new commit pairs (amend/rebase, incl. squashes) to the
82
- # OuterLayer spool so rewritten history stays creditable to the recorded
83
- # sessions that produced it. Always exits 0: journaling must never break a
84
- # rebase.
85
- # On a machine without OuterLayer (this hook can be committed into a repo
86
- # and run for every contributor) there is nothing to journal and no reason
87
- # to create ~/.outerlayer: bow out before touching the filesystem.
88
- [ -n "$OUTERLAYER_SPOOL_DIR" ] || [ -d "$HOME/.outerlayer" ] || exit 0
89
- SPOOL_DIR="\${OUTERLAYER_SPOOL_DIR:-$HOME/.outerlayer/spool}"
90
- mkdir -p "$SPOOL_DIR" 2>/dev/null || exit 0
91
- FILE="$SPOOL_DIR/rewrites.jsonl"
92
- if [ -f "$FILE" ] && [ "$(wc -c < "$FILE" 2>/dev/null || echo 0)" -gt 5242880 ]; then
93
- mv -f "$FILE" "$FILE.1" 2>/dev/null || true
94
- fi
95
- REPO="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
96
- if [ -z "$REPO" ]; then
97
- REPO="$(git rev-parse --git-common-dir 2>/dev/null)" || exit 0
98
- case "$REPO" in /*) ;; *) REPO="$PWD/$REPO" ;; esac
99
- fi
100
- TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
101
- awk -v repo="$REPO" -v ts="$TS" -v src="$1" '
102
- NF >= 2 && $1 ~ /^[0-9a-f]+$/ && $2 ~ /^[0-9a-f]+$/ {
103
- pairs = pairs (n ? "," : "") "[\\"" $1 "\\",\\"" $2 "\\"]"; n++
104
- }
105
- END {
106
- if (n) {
107
- gsub(/\\\\/, "\\\\\\\\", repo); gsub(/"/, "\\\\\\"", repo)
108
- printf "{\\"t\\":\\"%s\\",\\"repo\\":\\"%s\\",\\"source\\":\\"%s\\",\\"pairs\\":[%s]}\\n", ts, repo, src, pairs
109
- }
110
- }
111
- ' >> "$FILE" 2>/dev/null || true
112
- exit 0
113
- `;
114
- var POST_COMMIT_HOOK_SCRIPT = `#!/bin/sh
115
- ${POST_REWRITE_VERSION}
116
- # Appends every commit's SHA to the OuterLayer spool the moment it exists,
117
- # so a session whose transcript lost git's porcelain line (piped or muted
118
- # output) can still be credited at sync time. Always exits 0.
119
- # On a machine without OuterLayer (this hook can be committed into a repo
120
- # and run for every contributor) there is nothing to journal and no reason
121
- # to create ~/.outerlayer: bow out before touching the filesystem.
122
- [ -n "$OUTERLAYER_SPOOL_DIR" ] || [ -d "$HOME/.outerlayer" ] || exit 0
123
- SPOOL_DIR="\${OUTERLAYER_SPOOL_DIR:-$HOME/.outerlayer/spool}"
124
- mkdir -p "$SPOOL_DIR" 2>/dev/null || exit 0
125
- FILE="$SPOOL_DIR/commits.jsonl"
126
- if [ -f "$FILE" ] && [ "$(wc -c < "$FILE" 2>/dev/null || echo 0)" -gt 5242880 ]; then
127
- mv -f "$FILE" "$FILE.1" 2>/dev/null || true
128
- fi
129
- REPO="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
130
- if [ -z "$REPO" ]; then
131
- REPO="$(git rev-parse --git-common-dir 2>/dev/null)" || exit 0
132
- case "$REPO" in /*) ;; *) REPO="$PWD/$REPO" ;; esac
133
- fi
134
- SHA="$(git rev-parse HEAD 2>/dev/null)" || exit 0
135
- TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
136
- case "$SHA" in
137
- *[!0-9a-f]*) exit 0 ;;
138
- esac
139
- REPO_ESCAPED=$(printf '%s' "$REPO" | sed -e 's/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g')
140
- printf '{"t":"%s","repo":"%s","sha":"%s"}
141
- ' "$TS" "$REPO_ESCAPED" "$SHA" >> "$FILE" 2>/dev/null || true
142
- exit 0
143
- `;
144
- var PREPARE_COMMIT_MSG_HOOK_SCRIPT = `#!/bin/sh
145
- ${POST_REWRITE_VERSION}
146
- # Appends "OuterLayer-Session: <id>" to commits made during a recorded agent
147
- # session, so the commit itself carries the session identity through squash
148
- # and cross-machine rebases (the server-side trailer tier parses it). Stamps
149
- # NOTHING outside a session: the agent env marker is required, and the id
150
- # comes from the shell's own session variable \u2014 exact, because the commit
151
- # process is a child of the session that issued it \u2014 with the spool journal
152
- # as the fallback for harnesses that don't export one. Always exits 0.
153
- # The journal-fallback selection rules mirror detectActiveSession in
154
- # packages/cli/src/session-detect.ts \u2014 keep them in step.
155
- [ -n "$OUTERLAYER_NO_TRAILER" ] && exit 0
156
- [ -n "$CLAUDECODE" ] || exit 0
157
- MSG_FILE="$1"
158
- [ -f "$MSG_FILE" ] || exit 0
159
- grep -qi '^outerlayer-session:' "$MSG_FILE" 2>/dev/null && exit 0
160
- SID="$CLAUDE_CODE_SESSION_ID"
161
- case "$SID" in
162
- *[!0-9a-fA-F-]*) SID="" ;;
163
- esac
164
- if [ -n "$SID" ]; then
165
- printf '\\nOuterLayer-Session: %s\\n' "$SID" >> "$MSG_FILE" 2>/dev/null || true
166
- exit 0
167
- fi
168
- SPOOL_DIR="\${OUTERLAYER_SPOOL_DIR:-$HOME/.outerlayer/spool}"
169
- EVENTS="$SPOOL_DIR/events.jsonl"
170
- [ -f "$EVENTS" ] || exit 0
171
- TOP="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
172
- [ -n "$TOP" ] || exit 0
173
- TOP_REAL="$(cd "$TOP" 2>/dev/null && pwd -P)" || TOP_REAL="$TOP"
174
- CUTOFF="$(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo 1970-01-01T00:00:00Z)"
175
- SID="$(tail -c 65536 "$EVENTS" 2>/dev/null | awk -v top="$TOP" -v topr="$TOP_REAL" -v cutoff="$CUTOFF" '
176
- function field(line, key, re, v) {
177
- re = "\\"" key "\\":\\"[^\\"]*\\""
178
- if (match(line, re) == 0) return ""
179
- v = substr(line, RSTART, RLENGTH)
180
- sub("^\\"" key "\\":\\"", "", v); sub("\\"$", "", v)
181
- return v
182
- }
183
- # macOS spells one directory two ways (/var is a symlink to /private/var);
184
- # git reports the resolved form while the session recorded the launch
185
- # spelling, so both sides normalize before comparing.
186
- function norm(p) { sub(/^\\/private\\//, "/", p); return p }
187
- function cwdMatches(c, cn, tn, rn) {
188
- cn = norm(c); tn = norm(top); rn = norm(topr)
189
- return cn == tn || cn == rn || index(cn, tn "/") == 1 || index(cn, rn "/") == 1
190
- }
191
- /^\\{/ {
192
- sid = field($0, "sessionId"); if (sid == "") next
193
- ev = field($0, "event")
194
- if (ev == "SessionEnd") { ended[sid] = 1; next }
195
- t = field($0, "t"); c = field($0, "cwd")
196
- if (t >= cutoff && cwdMatches(c)) { lastAt[sid] = NR }
197
- }
198
- END {
199
- best = ""; bestNr = 0
200
- for (sid in lastAt) if (!ended[sid] && lastAt[sid] > bestNr) { best = sid; bestNr = lastAt[sid] }
201
- if (best != "" && best ~ /^[0-9a-fA-F-]+$/) print best
202
- }
203
- ')"
204
- [ -n "$SID" ] || exit 0
205
- printf '\\nOuterLayer-Session: %s\\n' "$SID" >> "$MSG_FILE" 2>/dev/null || true
206
- exit 0
207
- `;
77
+ var JOURNAL_HOOK_MARKER = "# outerlayer post-rewrite journal";
208
78
  var PRE_COMMIT_GUARD_MARKER = "# outerlayer materialized-context commit guard";
209
79
  var PRE_COMMIT_GUARD_VERSION = `${PRE_COMMIT_GUARD_MARKER} v1`;
210
80
  var PRE_COMMIT_GUARD_BODY_SIGNATURE = "outerlayer: refusing to commit materialized control-plane context";
@@ -242,12 +112,7 @@ echo " $REMEDY" >&2
242
112
  exit 1
243
113
  `;
244
114
  function managedHookScripts() {
245
- return [
246
- ["post-rewrite", printableHookScript(POST_REWRITE_HOOK_SCRIPT)],
247
- ["post-commit", printableHookScript(POST_COMMIT_HOOK_SCRIPT)],
248
- ["prepare-commit-msg", printableHookScript(PREPARE_COMMIT_MSG_HOOK_SCRIPT)],
249
- ["pre-commit", printableHookScript(PRE_COMMIT_GUARD_SCRIPT)]
250
- ];
115
+ return [["pre-commit", printableHookScript(PRE_COMMIT_GUARD_SCRIPT)]];
251
116
  }
252
117
  function printableHookScript(script) {
253
118
  const lines = script.split("\n");
@@ -299,32 +164,6 @@ function installHookFile(dir, name, script, marker) {
299
164
  chmodSync(path, 493);
300
165
  return existing === void 0 ? "installed" : "updated";
301
166
  }
302
- function installPostRewriteHook(cwd, opts) {
303
- const stampTrailers = opts?.stampTrailers ?? true;
304
- const inRepo = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
305
- if (inRepo !== "true") return { status: "not-a-repo" };
306
- if ((git(cwd, ["config", "core.hooksPath"]) ?? "") !== "") {
307
- return { status: "skipped-custom-hooks-path" };
308
- }
309
- const dir = hooksDir(cwd);
310
- if (dir === void 0) return { status: "not-a-repo" };
311
- const pairOutcomes = [
312
- installHookFile(dir, "post-rewrite", POST_REWRITE_HOOK_SCRIPT, POST_REWRITE_MARKER),
313
- installHookFile(dir, "post-commit", POST_COMMIT_HOOK_SCRIPT, POST_REWRITE_MARKER)
314
- ];
315
- const outcomes = [...pairOutcomes];
316
- if (stampTrailers) {
317
- outcomes.push(installHookFile(dir, "prepare-commit-msg", PREPARE_COMMIT_MSG_HOOK_SCRIPT, POST_REWRITE_MARKER));
318
- } else {
319
- removeOurHookFile(join(dir, "prepare-commit-msg"), POST_REWRITE_MARKER);
320
- }
321
- const path = join(dir, "post-rewrite");
322
- if (pairOutcomes.every((o) => o === "skipped-foreign")) return { status: "skipped-foreign", path };
323
- if (outcomes.every((o) => o === "unchanged")) return { status: "unchanged", path };
324
- if (outcomes.includes("updated")) return { status: "updated", path };
325
- if (outcomes.includes("installed")) return { status: "installed", path };
326
- return { status: "skipped-foreign", path };
327
- }
328
167
  function removeOurHookFile(path, marker) {
329
168
  if (!existsSync(path)) return false;
330
169
  let content;
@@ -341,33 +180,14 @@ function isOwnRecognitionLine(content, marker) {
341
180
  const secondLine = content.split("\n", 2)[1];
342
181
  return secondLine !== void 0 && secondLine.startsWith(marker);
343
182
  }
344
- function removePostRewriteHook(cwd) {
183
+ function removeJournalHooks(cwd) {
345
184
  const dir = hooksDir(cwd);
346
185
  if (dir === void 0) return { removed: false };
347
186
  let removed = false;
348
187
  for (const name of ["post-rewrite", "post-commit", "prepare-commit-msg"]) {
349
- if (removeOurHookFile(join(dir, name), POST_REWRITE_MARKER)) removed = true;
188
+ if (removeOurHookFile(join(dir, name), JOURNAL_HOOK_MARKER)) removed = true;
350
189
  }
351
- return { removed, path: join(dir, "post-rewrite") };
352
- }
353
- function postRewriteHookPresence(cwd) {
354
- const inRepo = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
355
- if (inRepo !== "true") return "not-a-repo";
356
- if ((git(cwd, ["config", "core.hooksPath"]) ?? "") !== "") return "custom-hooks-path";
357
- const dir = hooksDir(cwd);
358
- if (dir === void 0) return "not-a-repo";
359
- const states = ["post-rewrite", "post-commit"].map((name) => {
360
- let content;
361
- try {
362
- content = readFileSync(join(dir, name), "utf8");
363
- } catch {
364
- return "absent";
365
- }
366
- return content.includes(POST_REWRITE_MARKER) ? "ours" : "foreign";
367
- });
368
- if (states.every((st) => st === "ours")) return "ours";
369
- if (states.includes("foreign")) return "foreign";
370
- return "absent";
190
+ return { removed, path: dir };
371
191
  }
372
192
  function installPreCommitGuardHook(cwd) {
373
193
  const inRepo = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
@@ -469,4 +289,4 @@ function huskyPrepareScript(repoRoot) {
469
289
  return null;
470
290
  }
471
291
 
472
- export { contextExcludesPresent, deadHooksState, ensureContextExcludes, ensurePreCommitGuardHook, huskyPrepareScript, installPostRewriteHook, installPreCommitGuardHook, managedHookScripts, postRewriteHookPresence, preparePackageManagerCommand, removePostRewriteHook, removePreCommitGuardHook };
292
+ export { contextExcludesPresent, deadHooksState, ensureContextExcludes, ensurePreCommitGuardHook, huskyPrepareScript, installPreCommitGuardHook, managedHookScripts, preparePackageManagerCommand, removeJournalHooks, removePreCommitGuardHook };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { deadHooksState, huskyPrepareScript, preparePackageManagerCommand } from './chunk-IWIUYLDR.js';
3
+ import { deadHooksState, huskyPrepareScript, preparePackageManagerCommand } from './chunk-I3ETLSNE.js';
4
4
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
5
5
  import { readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, statSync, appendFileSync, openSync, opendirSync, unlinkSync } from 'fs';
6
6
  import { spawn, execFileSync } from 'child_process';
@@ -385,7 +385,7 @@ function hookErrorsContext(home, writtenBytes, seenBytes) {
385
385
  }
386
386
  async function handleWorkLaunch(sessionId, home, env, spawnImpl = spawnDetached) {
387
387
  if (!sessionId || env.OUTERLAYER_WORK === void 0) return void 0;
388
- const { parseWorkItemEnv, appendLaunchRecord } = await import('./work-launch-YNNCKIH3.js');
388
+ const { parseWorkItemEnv, appendLaunchRecord } = await import('./work-launch-LT663PB3.js');
389
389
  const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
390
390
  if ("reason" in parsed) {
391
391
  logHookError(home, new Error(`OUTERLAYER_WORK ignored: ${parsed.reason}`));
@@ -597,7 +597,7 @@ async function runHookFast(event, home = homeFromEnv(), read = readStdin, env =
597
597
  recordHookErrorsSeen(home, hookErrorsBytes);
598
598
  if (contextPossiblyGoverned(sessionCwd, home)) {
599
599
  try {
600
- const { materializeContext: materialize } = await import('./context-materialize-J6CRQ7O7.js');
600
+ const { materializeContext: materialize } = await import('./context-materialize-6WKT3RBQ.js');
601
601
  const sessionId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
602
602
  const result = await materialize({ cwd: sessionCwd, sessionId, home });
603
603
  materializeMessage = result.message;
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { resolveRepoIdentity, detectActiveSession } from './chunk-BQB3U2VD.js';
4
- import { parseTrackerNumber, trackerNumberError } from './chunk-RCQXYLMO.js';
3
+ import { detectActiveSession } from './chunk-U32VSRLO.js';
4
+ import { resolveRepoIdentity } from './chunk-4TNZMO7V.js';
5
+ import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
5
6
  import { readCloudConfig } from './chunk-TFUIDMOB.js';
6
7
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
7
8