@outerlayer/cli 0.1.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -0
- package/README.md +238 -77
- package/dist/agent-setup-IFH3FB5X.js +197 -0
- package/dist/build-TZUUTRGH.js +9 -0
- package/dist/build-info.json +1 -1
- package/dist/check-WQP2CZDW.js +94 -0
- package/dist/chunk-2E54Z3P5.js +232 -0
- package/dist/chunk-3TT7RQYB.js +55 -0
- package/dist/chunk-3YPKXE5L.js +1683 -0
- package/dist/{chunk-HNCCD2IX.js → chunk-5DLBAXY5.js} +209 -61
- package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
- package/dist/chunk-5Y3QQRUH.js +300 -0
- package/dist/chunk-6Y63SXFO.js +383 -0
- package/dist/chunk-774HEQL6.js +178 -0
- package/dist/chunk-7A6RVXPZ.js +2233 -0
- package/dist/chunk-7YIOOFVJ.js +71 -0
- package/dist/chunk-ABIWDUMS.js +104 -0
- package/dist/chunk-AUFG23AA.js +534 -0
- package/dist/chunk-B2G7JHHB.js +42 -0
- package/dist/chunk-BCJNHMZT.js +34 -0
- package/dist/chunk-BCLJSQCV.js +56 -0
- package/dist/chunk-BFESLKPP.js +61 -0
- package/dist/chunk-BJ3KTMDH.js +63 -0
- package/dist/chunk-BKO6JEZI.js +47 -0
- package/dist/chunk-BOLTI6LR.js +37 -0
- package/dist/chunk-CBPPJSAR.js +89 -0
- package/dist/chunk-CBZB6XY6.js +102 -0
- package/dist/{chunk-RCQXYLMO.js → chunk-DVDEBNQJ.js} +18 -18
- package/dist/{work-pr-cmd-GOYSPEN2.js → chunk-DVYHCMB2.js} +10 -25
- package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
- package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
- package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
- package/dist/chunk-F6GFZXZ3.js +60 -0
- package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
- package/dist/chunk-FAJSS2WN.js +1357 -0
- package/dist/chunk-FS2VYGZT.js +1764 -0
- package/dist/chunk-FV2CCGXK.js +9 -0
- package/dist/{chunk-BQB3U2VD.js → chunk-G7FSV7JX.js} +5113 -3729
- package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
- package/dist/chunk-HE2D4EY5.js +83 -0
- package/dist/chunk-L2DBRKRA.js +94 -0
- package/dist/chunk-LF6MLJCY.js +38 -0
- package/dist/chunk-M5POMKKW.js +1051 -0
- package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
- package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
- package/dist/chunk-N6LURUEF.js +64 -0
- package/dist/chunk-NYC54VBD.js +1084 -0
- package/dist/chunk-OGFGZQA3.js +23 -0
- package/dist/chunk-QOZKPLVJ.js +226 -0
- package/dist/chunk-RA3O54FC.js +30 -0
- package/dist/chunk-TBT347UY.js +41 -0
- package/dist/{chunk-3VQ4XXQA.js → chunk-UFFXNXLQ.js} +115 -71
- package/dist/chunk-USO2DKBD.js +421 -0
- package/dist/chunk-W4SQCOZU.js +206 -0
- package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
- package/dist/chunk-WO2BXCTQ.js +83 -0
- package/dist/chunk-WZBHBH3J.js +20 -0
- package/dist/{chunk-IWIUYLDR.js → chunk-XCCLFVXM.js} +118 -189
- package/dist/{context-materialize-J6CRQ7O7.js → chunk-XDDW4FRS.js} +134 -26
- package/dist/chunk-YYYXRJUW.js +101 -0
- package/dist/chunk-ZM2IMMYN.js +76 -0
- package/dist/chunk-ZMYPLFG3.js +71 -0
- package/dist/chunk-ZNA27WEV.js +470 -0
- package/dist/{cli-EFJIG2VT.js → cli-NHUXABYH.js} +597 -965
- package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
- package/dist/config-XVJYZ4IQ.js +9 -0
- package/dist/connect-cmd-OOG4TPU7.js +16 -0
- package/dist/context-adopt-UMA4O5NY.js +105 -0
- package/dist/context-materialize-G2FUFC72.js +19 -0
- package/dist/dist-RW4UPPC2.js +6 -0
- package/dist/docker-NEGDLU6D.js +7 -0
- package/dist/doctor-53F2PYY7.js +43 -0
- package/dist/doctor-UFVL4PZY.js +426 -0
- package/dist/{emit-artifact-cmd-KPPD3YLO.js → emit-artifact-cmd-KTYW2TN2.js} +25 -19
- package/dist/emit-cmd-DCERAU27.js +9 -0
- package/dist/emit-criteria-cmd-VAUUSAYD.js +166 -0
- package/dist/{emit-finding-cmd-VR5VEGXJ.js → emit-finding-cmd-UR4ID45Z.js} +35 -30
- package/dist/{emit-result-cmd-YOA5BJKL.js → emit-result-cmd-FDTUKGKE.js} +35 -61
- package/dist/exec-client-4XGXV3XN.js +8 -0
- package/dist/guest-init-GENRHNP7.js +8 -0
- package/dist/hook-fast-EYENPK6F.js +9 -0
- package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
- package/dist/host-key-KDYZ5ECC.js +7 -0
- package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
- package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/init-DXBCOAJK.js +36 -0
- package/dist/init-YEXK35CF.js +221 -0
- package/dist/init-cmd-NHR7PRSQ.js +135 -0
- package/dist/install-cmd-7WXOIFDO.js +55 -0
- package/dist/lima-XN65D7GN.js +55 -0
- package/dist/login-browser-7XGSV7MA.js +10 -0
- package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
- package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
- package/dist/loop-VZOLL4WQ.js +43 -0
- package/dist/machine-WSG52J75.js +35 -0
- package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
- package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-JPTP5FMS.js} +20 -9
- package/dist/paths-OYKMVYJP.js +6 -0
- package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
- package/dist/real-deps-SU24ZA2K.js +21 -0
- package/dist/relay-L76HDX72.js +46 -0
- package/dist/settings-J2652U5N.js +7 -0
- package/dist/starter-pack-LIZYMKYQ.js +10 -0
- package/dist/{status-JHVSZYC5.js → status-2UZKKNB7.js} +31 -12
- package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
- package/dist/sync-cmd-VPPYCNNM.js +28 -0
- package/dist/version-BWM6VLDI.js +6 -0
- package/dist/{watch-NRLXJUST.js → watch-5C4ZBBGO.js} +20 -7
- package/dist/{work-claim-cmd-HXB7K27H.js → work-claim-cmd-LSO2B2EX.js} +34 -19
- package/dist/work-cmd-UOQO2AD4.js +17 -0
- package/dist/work-comment-cmd-WF3EOLAE.js +106 -0
- package/dist/{work-launch-YNNCKIH3.js → work-launch-YI4CJDAP.js} +2 -1
- package/dist/work-open-pr-cmd-4SSQZMYG.js +156 -0
- package/dist/work-pr-cmd-6AN6RRQJ.js +16 -0
- package/package.json +13 -3
- package/skill-pack/maintained/amend/SKILL.md +104 -0
- package/skill-pack/maintained/emitting-evidence/SKILL.md +99 -0
- package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
- package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
- package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
- package/skill-pack/template/build/SKILL.md +193 -0
- package/skill-pack/template/build/references/agent-briefs.md +243 -0
- package/skill-pack/template/build/references/criteria-judge.md +91 -0
- package/skill-pack/template/build/references/evidence.md +42 -0
- package/skill-pack/template/build/references/release.md +74 -0
- package/skill-pack/template/build/references/review-briefs.md +275 -0
- package/skill-pack/template/build/references/review-loop.md +158 -0
- package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
- package/skill-pack/template/spec/SKILL.md +84 -0
- package/skill-pack/template/writing-specs/SKILL.md +134 -0
- package/dist/chunk-JJP7YLMN.js +0 -25
- package/dist/chunk-KKEU6FFR.js +0 -589
- package/dist/chunk-OZ7C3XUE.js +0 -34
- package/dist/chunk-WQ6VGRGZ.js +0 -150
- package/dist/emit-commit-credit-cmd-656BRAQ4.js +0 -121
- package/dist/hook-fast-I2XTHDE6.js +0 -8
- package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
- package/dist/init-PTBITAUO.js +0 -103
- package/dist/login-cmd-IRX6LZT7.js +0 -62
- package/dist/loop-BWE2BS6J.js +0 -646
- package/dist/sync-cmd-S7Q6DWU3.js +0 -15
- package/dist/work-cmd-ABP6XDBP.js +0 -16
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { splitRawFile } from './chunk-EMY4I27X.js';
|
|
4
|
+
import { resolvePackageVersion } from './chunk-RA3O54FC.js';
|
|
5
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
createRequire(import.meta.url);
|
|
10
|
+
var TEMPLATE_SKILLS = ["spec", "writing-specs", "build"];
|
|
11
|
+
var MAINTAINED_SKILLS = ["emitting-evidence", "amend", "reporting-findings", "outerlayer"];
|
|
12
|
+
var TEMPLATE_CLASS_LINE = "<!-- yours to edit. Copied once from the default template by outerlayer init; never overwritten. -->";
|
|
13
|
+
function maintainedClassLine(version) {
|
|
14
|
+
return `<!-- maintained by OuterLayer ${version}. Edits are overwritten on the next outerlayer context emit. -->`;
|
|
15
|
+
}
|
|
16
|
+
function resolvePackRoot(entryPath = fileURLToPath(import.meta.url)) {
|
|
17
|
+
return join(dirname(dirname(entryPath)), "skill-pack");
|
|
18
|
+
}
|
|
19
|
+
function skillDir(skill) {
|
|
20
|
+
return `.outerlayer/skills/${skill}`;
|
|
21
|
+
}
|
|
22
|
+
function renderSkillFile(skillClass, content, version) {
|
|
23
|
+
const line = skillClass === "maintained" ? maintainedClassLine(version) : TEMPLATE_CLASS_LINE;
|
|
24
|
+
const { frontmatterInner, body } = splitRawFile(content);
|
|
25
|
+
if (frontmatterInner === null) return `${line}
|
|
26
|
+
${content}`;
|
|
27
|
+
const block = content.slice(0, content.length - body.length);
|
|
28
|
+
return `${block}${block.endsWith("\n") ? "" : "\n"}${line}
|
|
29
|
+
${body}`;
|
|
30
|
+
}
|
|
31
|
+
function filesUnder(dir, base = dir) {
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
const abs = join(dir, entry.name);
|
|
35
|
+
if (entry.isDirectory()) out.push(...filesUnder(abs, base));
|
|
36
|
+
else if (entry.isFile()) out.push(abs.slice(base.length + 1).split("\\").join("/"));
|
|
37
|
+
}
|
|
38
|
+
return out.sort();
|
|
39
|
+
}
|
|
40
|
+
function orderSkillFiles(relPaths) {
|
|
41
|
+
return [...relPaths.filter((p) => p === "SKILL.md"), ...relPaths.filter((p) => p !== "SKILL.md")];
|
|
42
|
+
}
|
|
43
|
+
function loadClass(packRoot, skillClass, names, version) {
|
|
44
|
+
const files = [];
|
|
45
|
+
for (const skill of names) {
|
|
46
|
+
const dir = join(packRoot, skillClass, skill);
|
|
47
|
+
if (!existsSync(dir)) throw new Error(`skill pack is missing ${skillClass}/${skill} under ${packRoot}`);
|
|
48
|
+
for (const rel of orderSkillFiles(filesUnder(dir))) {
|
|
49
|
+
const raw = readFileSync(join(dir, rel), "utf8");
|
|
50
|
+
files.push({
|
|
51
|
+
skill,
|
|
52
|
+
skillClass,
|
|
53
|
+
path: `${skillDir(skill)}/${rel}`,
|
|
54
|
+
content: rel === "SKILL.md" ? renderSkillFile(skillClass, raw, version) : raw
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
function loadTemplateFiles(packRoot = resolvePackRoot(), version = resolvePackageVersion()) {
|
|
61
|
+
return loadClass(packRoot, "template", TEMPLATE_SKILLS, version);
|
|
62
|
+
}
|
|
63
|
+
function loadMaintainedFiles(packRoot = resolvePackRoot(), version = resolvePackageVersion()) {
|
|
64
|
+
return loadClass(packRoot, "maintained", MAINTAINED_SKILLS, version);
|
|
65
|
+
}
|
|
66
|
+
function writeMaintainedFiles(repoRoot, files) {
|
|
67
|
+
const created = [];
|
|
68
|
+
const rewritten = [];
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const abs = join(repoRoot, file.path);
|
|
71
|
+
const exists = existsSync(abs);
|
|
72
|
+
if (exists && readFileSync(abs, "utf8") === file.content) continue;
|
|
73
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
74
|
+
writeFileSync(abs, file.content);
|
|
75
|
+
(exists ? rewritten : created).push(file.path);
|
|
76
|
+
}
|
|
77
|
+
return { created, rewritten };
|
|
78
|
+
}
|
|
79
|
+
function maintainedFilesForExistingSkills(repoRoot, files) {
|
|
80
|
+
return files.filter((file) => existsSync(join(repoRoot, skillDir(file.skill))));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { MAINTAINED_SKILLS, TEMPLATE_SKILLS, loadMaintainedFiles, loadTemplateFiles, maintainedFilesForExistingSkills, resolvePackRoot, skillDir, writeMaintainedFiles };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
|
|
4
|
+
createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
// src/tracker-number.ts
|
|
7
|
+
var MAX_TRACKER_NUMBER = Number.MAX_SAFE_INTEGER;
|
|
8
|
+
function parseTrackerNumber(raw) {
|
|
9
|
+
if (raw === void 0) return void 0;
|
|
10
|
+
const text = typeof raw === "number" ? String(raw) : raw;
|
|
11
|
+
if (!/^\d+$/.test(text)) return void 0;
|
|
12
|
+
const n = Number(text);
|
|
13
|
+
if (!Number.isSafeInteger(n) || n <= 0) return void 0;
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
function trackerNumberError(flag, raw) {
|
|
17
|
+
return `invalid ${flag} "${raw}" \u2014 expected a positive integer no larger than ${MAX_TRACKER_NUMBER}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { parseTrackerNumber, trackerNumberError };
|
|
@@ -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 {
|
|
4
|
+
import { mkdirSync, chmodSync, writeFileSync, readFileSync, readdirSync, existsSync, unlinkSync } from 'fs';
|
|
5
5
|
import { join, isAbsolute, dirname, basename, resolve } from 'path';
|
|
6
6
|
import { execFileSync } from 'child_process';
|
|
7
7
|
|
|
@@ -74,138 +74,103 @@ 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
|
|
78
|
-
var
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
var HOOKS_DIR_NAME = "git-hooks";
|
|
78
|
+
var CO_AUTHOR_FILE = "co-author";
|
|
79
|
+
var HOOKS = [
|
|
80
|
+
"applypatch-msg",
|
|
81
|
+
"pre-applypatch",
|
|
82
|
+
"post-applypatch",
|
|
83
|
+
"pre-commit",
|
|
84
|
+
"pre-merge-commit",
|
|
85
|
+
"prepare-commit-msg",
|
|
86
|
+
"commit-msg",
|
|
87
|
+
"post-commit",
|
|
88
|
+
"pre-rebase",
|
|
89
|
+
"post-checkout",
|
|
90
|
+
"post-merge",
|
|
91
|
+
"pre-push",
|
|
92
|
+
"pre-receive",
|
|
93
|
+
"update",
|
|
94
|
+
"post-receive",
|
|
95
|
+
"post-update",
|
|
96
|
+
"reference-transaction",
|
|
97
|
+
"pre-auto-gc",
|
|
98
|
+
"post-rewrite",
|
|
99
|
+
"sendemail-validate",
|
|
100
|
+
"post-index-change"
|
|
101
|
+
];
|
|
102
|
+
var READS_STDIN = /* @__PURE__ */ new Set(["pre-push", "pre-receive", "post-receive", "reference-transaction", "post-rewrite"]);
|
|
103
|
+
var PRE_COMMIT_GUARD_MARKER = "# outerlayer materialized-context commit guard";
|
|
104
|
+
var RUN_GUARD = `common=$(git rev-parse --git-common-dir 2>/dev/null)
|
|
105
|
+
guard=$common/hooks/pre-commit
|
|
106
|
+
if [ "$guard" != "$hook" ] && [ -f "$guard" ] && [ -x "$guard" ] &&
|
|
107
|
+
sed -n 2p "$guard" | grep -q '^${PRE_COMMIT_GUARD_MARKER}'; then
|
|
108
|
+
"$guard" "$@" || exit $?
|
|
94
109
|
fi
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
`;
|
|
111
|
+
var ADD_TRAILER = `
|
|
112
|
+
# Every commit of the build names the requester once: a trailer already there is left alone.
|
|
113
|
+
file=\${1-}
|
|
114
|
+
if [ -n "$file" ] && [ -f "$file" ]; then
|
|
115
|
+
git interpret-trailers --in-place --if-exists addIfDifferent --trailer "Co-authored-by: $(cat "$here/${CO_AUTHOR_FILE}")" "$file" ||
|
|
116
|
+
echo "outerlayer: could not add the co-author line to the commit message" >&2
|
|
99
117
|
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
118
|
`;
|
|
114
|
-
var
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
#
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
var HOOK_WRAPPER_MARKER = "# Written by the OuterLayer runner.";
|
|
120
|
+
function script(name) {
|
|
121
|
+
const adds = name === "prepare-commit-msg";
|
|
122
|
+
return `#!/bin/sh
|
|
123
|
+
${HOOK_WRAPPER_MARKER} Runs the repository's own ${name} hook, then
|
|
124
|
+
# ${adds ? "adds the co-author line to the commit message" : "hands its status on"}.
|
|
125
|
+
here=$(dirname "$0")
|
|
126
|
+
|
|
127
|
+
# The hooks path git would use without this directory: cut the list of configuration
|
|
128
|
+
# entries the runner passes just before its own core.hooksPath entry, then ask git.
|
|
129
|
+
n=\${GIT_CONFIG_COUNT-0}
|
|
130
|
+
case $n in '' | *[!0-9]*) n=0 ;; esac
|
|
131
|
+
cut=$n
|
|
132
|
+
i=$n
|
|
133
|
+
while [ "$i" -gt 0 ]; do
|
|
134
|
+
i=$((i - 1))
|
|
135
|
+
eval "key=\\\${GIT_CONFIG_KEY_$i-}"
|
|
136
|
+
eval "value=\\\${GIT_CONFIG_VALUE_$i-}"
|
|
137
|
+
if [ "$key" = core.hooksPath ] && { [ "$value" = "$here" ] || [ "$value" -ef "$here" ]; }; then
|
|
138
|
+
cut=$i
|
|
139
|
+
break
|
|
140
|
+
fi
|
|
141
|
+
done
|
|
142
|
+
path=$(GIT_CONFIG_COUNT=$cut git config --type=path core.hooksPath 2>/dev/null) || path=
|
|
143
|
+
if [ -z "$path" ]; then
|
|
144
|
+
path=$(git rev-parse --git-common-dir 2>/dev/null)/hooks
|
|
128
145
|
fi
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
|
|
147
|
+
hook=$path/${name}
|
|
148
|
+
${name === "pre-commit" ? RUN_GUARD : ""}status=0
|
|
149
|
+
if [ "$path" -ef "$here" ] || [ ! -f "$hook" ] || [ ! -x "$hook" ]; then
|
|
150
|
+
${READS_STDIN.has(name) ? " cat >/dev/null\n" : ""} :
|
|
151
|
+
else
|
|
152
|
+
${adds ? ` "$hook" "$@" || status=$?` : ` exec "$hook" "$@"`}
|
|
133
153
|
fi
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
154
|
+
${adds ? `[ "$status" -eq 0 ] || exit "$status"
|
|
155
|
+
${ADD_TRAILER}` : ""}exit 0
|
|
143
156
|
`;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
157
|
+
}
|
|
158
|
+
function writeCommitHooks(buildDir, coAuthor) {
|
|
159
|
+
const dir = join(buildDir, HOOKS_DIR_NAME);
|
|
160
|
+
mkdirSync(dir, { recursive: true });
|
|
161
|
+
chmodSync(dir, 493);
|
|
162
|
+
for (const name of HOOKS) {
|
|
163
|
+
const path = join(dir, name);
|
|
164
|
+
writeFileSync(path, script(name));
|
|
165
|
+
chmodSync(path, 493);
|
|
202
166
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
167
|
+
const data = join(dir, CO_AUTHOR_FILE);
|
|
168
|
+
writeFileSync(data, `${coAuthor.name} <${coAuthor.email}>
|
|
169
|
+
`);
|
|
170
|
+
chmodSync(data, 420);
|
|
171
|
+
return dir;
|
|
172
|
+
}
|
|
173
|
+
var JOURNAL_HOOK_MARKER = "# outerlayer post-rewrite journal";
|
|
209
174
|
var PRE_COMMIT_GUARD_VERSION = `${PRE_COMMIT_GUARD_MARKER} v1`;
|
|
210
175
|
var PRE_COMMIT_GUARD_BODY_SIGNATURE = "outerlayer: refusing to commit materialized control-plane context";
|
|
211
176
|
var PRE_COMMIT_GUARD_SCRIPT = `#!/bin/sh
|
|
@@ -242,15 +207,10 @@ echo " $REMEDY" >&2
|
|
|
242
207
|
exit 1
|
|
243
208
|
`;
|
|
244
209
|
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
|
-
];
|
|
210
|
+
return [["pre-commit", printableHookScript(PRE_COMMIT_GUARD_SCRIPT)]];
|
|
251
211
|
}
|
|
252
|
-
function printableHookScript(
|
|
253
|
-
const lines =
|
|
212
|
+
function printableHookScript(script2) {
|
|
213
|
+
const lines = script2.split("\n");
|
|
254
214
|
return [lines[0], ...lines.slice(2)].join("\n");
|
|
255
215
|
}
|
|
256
216
|
function git(cwd, args) {
|
|
@@ -276,7 +236,7 @@ function commonHooksDir(cwd) {
|
|
|
276
236
|
if (rel === void 0) return void 0;
|
|
277
237
|
return join(isAbsolute(rel) ? rel : join(cwd, rel), "hooks");
|
|
278
238
|
}
|
|
279
|
-
function installHookFile(dir, name,
|
|
239
|
+
function installHookFile(dir, name, script2, marker) {
|
|
280
240
|
const path = join(dir, name);
|
|
281
241
|
let existing;
|
|
282
242
|
try {
|
|
@@ -286,7 +246,7 @@ function installHookFile(dir, name, script, marker) {
|
|
|
286
246
|
}
|
|
287
247
|
if (existing !== void 0) {
|
|
288
248
|
if (!isOwnRecognitionLine(existing, marker)) return "skipped-foreign";
|
|
289
|
-
if (existing ===
|
|
249
|
+
if (existing === script2) {
|
|
290
250
|
try {
|
|
291
251
|
chmodSync(path, 493);
|
|
292
252
|
} catch {
|
|
@@ -295,36 +255,10 @@ function installHookFile(dir, name, script, marker) {
|
|
|
295
255
|
}
|
|
296
256
|
}
|
|
297
257
|
mkdirSync(dir, { recursive: true });
|
|
298
|
-
writeFileSync(path,
|
|
258
|
+
writeFileSync(path, script2);
|
|
299
259
|
chmodSync(path, 493);
|
|
300
260
|
return existing === void 0 ? "installed" : "updated";
|
|
301
261
|
}
|
|
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
262
|
function removeOurHookFile(path, marker) {
|
|
329
263
|
if (!existsSync(path)) return false;
|
|
330
264
|
let content;
|
|
@@ -341,33 +275,14 @@ function isOwnRecognitionLine(content, marker) {
|
|
|
341
275
|
const secondLine = content.split("\n", 2)[1];
|
|
342
276
|
return secondLine !== void 0 && secondLine.startsWith(marker);
|
|
343
277
|
}
|
|
344
|
-
function
|
|
278
|
+
function removeJournalHooks(cwd) {
|
|
345
279
|
const dir = hooksDir(cwd);
|
|
346
280
|
if (dir === void 0) return { removed: false };
|
|
347
281
|
let removed = false;
|
|
348
282
|
for (const name of ["post-rewrite", "post-commit", "prepare-commit-msg"]) {
|
|
349
|
-
if (removeOurHookFile(join(dir, name),
|
|
283
|
+
if (removeOurHookFile(join(dir, name), JOURNAL_HOOK_MARKER)) removed = true;
|
|
350
284
|
}
|
|
351
|
-
return { removed, path:
|
|
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";
|
|
285
|
+
return { removed, path: dir };
|
|
371
286
|
}
|
|
372
287
|
function installPreCommitGuardHook(cwd) {
|
|
373
288
|
const inRepo = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -417,15 +332,29 @@ function expandHooksPathTilde(hooksPath) {
|
|
|
417
332
|
}
|
|
418
333
|
function ensurePreCommitGuardHook(repoRoot, gitCommonDir) {
|
|
419
334
|
const hooksPath = git(repoRoot, ["config", "core.hooksPath"]) ?? "";
|
|
420
|
-
if (hooksPath !== "") {
|
|
335
|
+
if (hooksPath !== "" && !isRunnerHookWrapper(hooksPath, repoRoot)) {
|
|
421
336
|
const expanded = expandHooksPathTilde(hooksPath);
|
|
422
337
|
const effectivePath = isAbsolute(expanded) ? join(expanded, "pre-commit") : join(repoRoot, expanded, "pre-commit");
|
|
423
338
|
return { status: "skipped-custom-hooks-path", adopted: guardAdoptedAtEffectiveHook(effectivePath) };
|
|
424
339
|
}
|
|
340
|
+
return installPreCommitGuardInGitDir(gitCommonDir);
|
|
341
|
+
}
|
|
342
|
+
function installPreCommitGuardInGitDir(gitCommonDir) {
|
|
425
343
|
const path = join(gitCommonDir, "hooks", "pre-commit");
|
|
426
344
|
const status = installHookFile(join(gitCommonDir, "hooks"), "pre-commit", PRE_COMMIT_GUARD_SCRIPT, PRE_COMMIT_GUARD_MARKER);
|
|
427
345
|
return { status, adopted: status === "skipped-foreign" ? guardBodyPresent(path) : true };
|
|
428
346
|
}
|
|
347
|
+
function isRunnerHookWrapper(hooksPath, repoRoot) {
|
|
348
|
+
const dir = resolve(repoRoot, expandHooksPathTilde(hooksPath));
|
|
349
|
+
if (basename(dir) !== HOOKS_DIR_NAME) return false;
|
|
350
|
+
try {
|
|
351
|
+
const script2 = readFileSync(join(dir, "pre-commit"), "utf8");
|
|
352
|
+
const secondLine = script2.indexOf("\n") + 1;
|
|
353
|
+
return secondLine > 0 && script2.startsWith(HOOK_WRAPPER_MARKER, secondLine);
|
|
354
|
+
} catch {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
429
358
|
function deadHooksState(cwd) {
|
|
430
359
|
const inRepo = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
431
360
|
if (inRepo !== "true") return { dead: false };
|
|
@@ -462,11 +391,11 @@ function preparePackageManagerCommand(repoRoot) {
|
|
|
462
391
|
function huskyPrepareScript(repoRoot) {
|
|
463
392
|
try {
|
|
464
393
|
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
|
|
465
|
-
const
|
|
466
|
-
if (typeof
|
|
394
|
+
const script2 = pkg.scripts?.prepare;
|
|
395
|
+
if (typeof script2 === "string" && /husky/i.test(script2)) return script2;
|
|
467
396
|
} catch {
|
|
468
397
|
}
|
|
469
398
|
return null;
|
|
470
399
|
}
|
|
471
400
|
|
|
472
|
-
export { contextExcludesPresent, deadHooksState, ensureContextExcludes, ensurePreCommitGuardHook, huskyPrepareScript,
|
|
401
|
+
export { HOOKS_DIR_NAME, contextExcludesPresent, deadHooksState, ensureContextExcludes, ensurePreCommitGuardHook, huskyPrepareScript, installPreCommitGuardHook, installPreCommitGuardInGitDir, managedHookScripts, preparePackageManagerCommand, removeJournalHooks, removePreCommitGuardHook, writeCommitHooks };
|