@marcomigozzi/trailhead 0.13.0 → 0.15.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/README.it.md +142 -29
- package/README.md +141 -28
- package/bin/base-preflight-docs.test.js +65 -0
- package/bin/execute-split-docs.test.js +146 -0
- package/bin/isolation-answer-docs.test.js +41 -0
- package/bin/isolation-copy-docs.test.js +28 -0
- package/bin/lib/codex-projection.js +17 -10
- package/bin/lib/codex-projection.test.js +80 -11
- package/bin/lib/install-verify.js +10 -5
- package/bin/lib/install-verify.test.js +51 -0
- package/bin/report-status-docs.test.js +195 -0
- package/bin/resolve-helpers-docs.test.js +36 -0
- package/bin/scope-confirm-skip-docs.test.js +70 -0
- package/bin/trailhead.js +40 -28
- package/bin/trailhead.test.js +503 -6
- package/bin/uat-setup-docs.test.js +65 -0
- package/package.json +4 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +3 -2
- package/plugins/trailhead/agents/trailhead-code-review.md +15 -2
- package/plugins/trailhead/agents/trailhead-codebase-map.md +20 -1
- package/plugins/trailhead/agents/trailhead-debug.md +20 -1
- package/plugins/trailhead/agents/trailhead-executor.md +29 -2
- package/plugins/trailhead/agents/trailhead-fix.md +29 -2
- package/plugins/trailhead/agents/trailhead-plan.md +20 -1
- package/plugins/trailhead/agents/trailhead-research.md +22 -3
- package/plugins/trailhead/agents/trailhead-verify.md +15 -2
- package/plugins/trailhead/commands/panel.md +10 -0
- package/plugins/trailhead/hooks/hooks.json +3 -18
- package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
- package/plugins/trailhead/hooks/lib/body-sources.js +795 -0
- package/plugins/trailhead/hooks/lib/commit-message-check.js +1 -1
- package/plugins/trailhead/hooks/lib/commit-message-check.test.js +3 -2
- package/plugins/trailhead/hooks/lib/commit-msg-module-repo.test.js +116 -0
- package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +100 -9
- package/plugins/trailhead/hooks/lib/dashboard.test.js +409 -0
- package/plugins/trailhead/hooks/lib/fixtures/commit-msg-legacy-js +104 -0
- package/plugins/trailhead/hooks/lib/gh-subcommand.js +92 -10
- package/plugins/trailhead/hooks/lib/gh-subcommand.test.js +55 -0
- package/plugins/trailhead/hooks/lib/isolation-answer.test.js +258 -0
- package/plugins/trailhead/hooks/lib/isolation-copy.test.js +298 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +470 -0
- package/plugins/trailhead/hooks/lib/mockup-link.js +21 -5
- package/plugins/trailhead/hooks/lib/report-status.test.js +366 -0
- package/plugins/trailhead/hooks/lib/shell-scan.js +532 -65
- package/plugins/trailhead/hooks/lib/shell-scan.test.js +434 -0
- package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
- package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
- package/plugins/trailhead/hooks/lib/unmodelled.js +345 -0
- package/plugins/trailhead/hooks/lib/unmodelled.test.js +173 -0
- package/plugins/trailhead/hooks/lib/work-state.test.js +297 -0
- package/plugins/trailhead/hooks/mod/README.md +219 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +606 -0
- package/plugins/trailhead/hooks/mod/map-pane.tsx +832 -0
- package/plugins/trailhead/hooks/mod/register.tsx +293 -0
- package/plugins/trailhead/hooks/mod/work-state-prompt.ts +69 -0
- package/plugins/trailhead/hooks/mod/work-state-view.ts +110 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +140 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +233 -0
- package/plugins/trailhead/hooks/trailhead-body-guard.js +145 -61
- package/plugins/trailhead/hooks/trailhead-body-guard.test.js +224 -3
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +41 -14
- package/plugins/trailhead/hooks/trailhead-commit-guard.test.js +2 -1
- package/plugins/trailhead/hooks/trailhead-install-guard.js +187 -42
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +182 -2
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.js +48 -11
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.test.js +75 -3
- package/plugins/trailhead/hooks/trailhead-search-guard.js +59 -13
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +68 -3
- package/plugins/trailhead/hooks/trailhead-secret-guard.js +541 -57
- package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +1000 -4
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +171 -6
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +170 -0
- package/plugins/trailhead/skills/_shared/choices.md +7 -3
- package/plugins/trailhead/skills/_shared/configuration-reference.md +15 -3
- package/plugins/trailhead/skills/_shared/load-first.md +3 -3
- package/plugins/trailhead/skills/_shared/multi-map.md +3 -3
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +22 -11
- package/plugins/trailhead/skills/_shared/substrate.md +6 -4
- package/plugins/trailhead/skills/_shared/teamwork.md +17 -6
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +17 -9
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +2 -0
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques.md +12 -2
- package/plugins/trailhead/skills/trailhead/SKILL.md +3 -3
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +8 -6
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +16 -13
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +6 -4
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +28 -10
- package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
- package/plugins/trailhead/templates/trailhead-commit-msg +38 -102
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +58 -27
- package/plugins/trailhead/templates/trailhead-commit-msg.cjs +114 -0
- package/plugins/trailhead/templates/trailhead-dashboard.js +340 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +227 -0
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +239 -0
- package/plugins/trailhead/templates/trailhead-issue-body.js +459 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
- package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
- package/plugins/trailhead/templates/trailhead-work-state.js +217 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +1921 -0
- package/plugins/trailhead/tests/map-pane-view.test.ts +853 -0
- package/plugins/trailhead/tests/mod.test.ts +14 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +207 -0
- package/plugins/trailhead/tests/work-state-prompt.test.ts +109 -0
- package/plugins/trailhead/tests/work-state-ui.test.ts +365 -0
- package/plugins/trailhead/tests/work-state-view.test.ts +110 -0
- package/plugins/trailhead/types/index.d.ts +103 -0
|
@@ -1,104 +1,40 @@
|
|
|
1
|
-
#!/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Crash-safe: any unexpected error exits 0 (never wedge a commit).
|
|
15
|
-
//
|
|
16
|
-
// Note: a merge/revert/fixup message already fails the conventional-subject
|
|
17
|
-
// rule below, so it needs no special case; `git commit --amend` keeps whatever
|
|
18
|
-
// trailer the original message carried; `git commit --no-verify` bypasses this
|
|
19
|
-
// hook entirely, as before.
|
|
20
|
-
|
|
21
|
-
const fs = require('fs');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
|
|
24
|
-
const TYPES = 'feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert';
|
|
25
|
-
const CONVENTIONAL = new RegExp(`^(${TYPES})(\\([^)]+\\))?!?:\\s.+`);
|
|
26
|
-
const CO_AUTHORED = /co-authored-by\s*:/i;
|
|
27
|
-
const SCISSORS = '# ------------------------ >8';
|
|
28
|
-
const REFS_LINE = /^\s*refs\s*:(.*)$/i;
|
|
29
|
-
|
|
30
|
-
function messageRegion(raw) {
|
|
31
|
-
const text = String(raw == null ? '' : raw);
|
|
32
|
-
const i = text.indexOf(SCISSORS);
|
|
33
|
-
return i === -1 ? text : text.slice(0, i);
|
|
34
|
-
}
|
|
35
|
-
function subjectOf(raw) {
|
|
36
|
-
for (const line of messageRegion(raw).split('\n')) {
|
|
37
|
-
if (line.trim() === '' || line.startsWith('#')) continue;
|
|
38
|
-
return line.trim();
|
|
39
|
-
}
|
|
40
|
-
return '';
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# trailhead commit-msg hook (git). Thin sh wrapper around the CommonJS logic in
|
|
3
|
+
# trailhead-commit-msg.cjs beside it (#257: an extensionless node script would
|
|
4
|
+
# inherit the "type" of the user repo's package.json, so "type": "module" broke it).
|
|
5
|
+
#
|
|
6
|
+
# Never blocks a commit on a startup failure: node missing, the .cjs missing or
|
|
7
|
+
# unreadable, or node crashing at load all exit 0, but print a one-line "inactive"
|
|
8
|
+
# notice on stderr so the failure is not silent. Only the .cjs's dedicated
|
|
9
|
+
# rule-violation code (3) becomes exit 1, which makes git abort the commit.
|
|
10
|
+
|
|
11
|
+
# Avviso unico su stderr, stesso formato per tutti i casi.
|
|
12
|
+
inactive() {
|
|
13
|
+
echo "trailhead: commit-msg hook inactive ($1); commit rules not enforced." >&2
|
|
41
14
|
}
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function readTicket() {
|
|
69
|
-
try {
|
|
70
|
-
const markerPath = path.join(process.cwd(), '.trailhead', 'session-ticket');
|
|
71
|
-
return parseSessionTicket(fs.readFileSync(markerPath, 'utf8'));
|
|
72
|
-
} catch {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
const file = process.argv[2];
|
|
79
|
-
const raw = file ? fs.readFileSync(file, 'utf8') : '';
|
|
80
|
-
const region = messageRegion(raw);
|
|
81
|
-
|
|
82
|
-
if (CO_AUTHORED.test(region)) {
|
|
83
|
-
process.stderr.write('trailhead: do not add a Co-Authored-By trailer to commits. Remove it and commit again.\n');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const subject = subjectOf(raw);
|
|
87
|
-
if (subject && !CONVENTIONAL.test(subject)) {
|
|
88
|
-
process.stderr.write(`trailhead: commit subject must be Conventional Commits: <type>(<scope>)?: <subject>. Valid types: ${TYPES.replace(/\|/g, ', ')}.\n`);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
if (subject.length > 72) {
|
|
92
|
-
process.stderr.write('trailhead: commit subject must be 72 characters or less.\n');
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const ticket = readTicket();
|
|
97
|
-
if (Number.isInteger(ticket) && ticket > 0 && !hasRefsFor(raw, ticket)) {
|
|
98
|
-
process.stderr.write(`trailhead: this commit is on ticket #${ticket} (per .trailhead/session-ticket) but has no "Refs: #${ticket}" trailer. Add "Refs: #${ticket}" as the last line of the message and commit again. If this commit is not part of ticket #${ticket}, remove the stale .trailhead/session-ticket marker first.\n`);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
process.exit(0); // never wedge a commit on an unexpected error
|
|
103
|
-
}
|
|
104
|
-
process.exit(0);
|
|
16
|
+
case $0 in
|
|
17
|
+
*/*) dir=${0%/*} ;;
|
|
18
|
+
*) dir=. ;;
|
|
19
|
+
esac
|
|
20
|
+
cjs="$dir/trailhead-commit-msg.cjs"
|
|
21
|
+
|
|
22
|
+
if [ ! -r "$cjs" ]; then
|
|
23
|
+
inactive "trailhead-commit-msg.cjs missing or unreadable"
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
27
|
+
inactive "node not found on PATH"
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
node "$cjs" "$@"
|
|
32
|
+
code=$?
|
|
33
|
+
case $code in
|
|
34
|
+
0) exit 0 ;;
|
|
35
|
+
3) exit 1 ;;
|
|
36
|
+
*)
|
|
37
|
+
inactive "trailhead-commit-msg.cjs failed to run, exit $code"
|
|
38
|
+
exit 0
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
// trailhead commit-msg hook sync (self-contained, ships in templates/ so it
|
|
4
4
|
// reaches every install channel: bin/trailhead.js copies templates/ wholesale
|
|
5
|
-
// for Claude/npm and Codex alike). Installs the sibling
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
5
|
+
// for Claude/npm and Codex alike). Installs the sibling templates into the
|
|
6
|
+
// repo's git hooks dir: trailhead-commit-msg (a small sh wrapper, installed as
|
|
7
|
+
// commit-msg) and trailhead-commit-msg.cjs (the logic, installed beside it; a
|
|
8
|
+
// .cjs is always CommonJS, so a "type": "module" repo cannot break it, #257).
|
|
9
|
+
// Upgrades in place when an OLDER trailhead hook (the legacy single-file JS
|
|
10
|
+
// one, or an outdated wrapper/.cjs) is already there, recognised by its own
|
|
11
|
+
// header line, never by trusting the filename, and never touches a foreign
|
|
9
12
|
// (non-trailhead) hook. #185.
|
|
10
13
|
//
|
|
11
14
|
// CLI: node trailhead-commit-msg-sync.js [--repo <dir>] (default cwd)
|
|
@@ -21,13 +24,17 @@ const path = require('path');
|
|
|
21
24
|
const crypto = require('crypto');
|
|
22
25
|
const { execFileSync } = require('child_process');
|
|
23
26
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
// Nome del file di logica installato accanto al wrapper commit-msg.
|
|
28
|
+
const CJS_NAME = 'trailhead-commit-msg.cjs';
|
|
29
|
+
|
|
30
|
+
// La riga riconosciuta di ogni versione dell'hook trailhead: basta che una
|
|
31
|
+
// delle prime 5 righe (trim) inizi con una di queste stringhe. La prima è
|
|
32
|
+
// l'hook JS monofile legacy, la seconda il wrapper sh (layout #257).
|
|
33
|
+
const TRAILHEAD_MARKS = ['// trailhead commit-msg hook (git).', '# trailhead commit-msg hook (git).'];
|
|
27
34
|
|
|
28
35
|
function isTrailheadHook(text) {
|
|
29
36
|
const lines = String(text == null ? '' : text).split('\n').slice(0, 5);
|
|
30
|
-
return lines.some((line) => line.trim().startsWith(
|
|
37
|
+
return lines.some((line) => TRAILHEAD_MARKS.some((mark) => line.trim().startsWith(mark)));
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
// Uniforma i fine riga per il confronto di contenuto: CRLF -> LF, non tocca
|
|
@@ -65,10 +72,36 @@ function resolveHooksTarget({ repoDir, runGit = defaultRunGit }) {
|
|
|
65
72
|
return { hooksDir, hooksPathSet };
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
function
|
|
75
|
+
// Scrittura atomica: temp file unico nella stessa dir, poi rename sul
|
|
76
|
+
// destinazione (mai un truncate in place).
|
|
77
|
+
function writeAtomic(fsMod, hooksDir, finalPath, text) {
|
|
78
|
+
const tmpPath = path.join(hooksDir, `.commit-msg.trailhead-${process.pid}-${crypto.randomBytes(4).toString('hex')}`);
|
|
79
|
+
try {
|
|
80
|
+
fsMod.writeFileSync(tmpPath, text);
|
|
81
|
+
fsMod.chmodSync(tmpPath, 0o755);
|
|
82
|
+
fsMod.renameSync(tmpPath, finalPath);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
try { fsMod.unlinkSync(tmpPath); } catch { /* best effort cleanup */ }
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// true se il file esiste, non è un symlink e ha esattamente il contenuto atteso.
|
|
90
|
+
function matchesTemplate(fsMod, filePath, text) {
|
|
91
|
+
try {
|
|
92
|
+
if (fsMod.lstatSync(filePath).isSymbolicLink()) return false;
|
|
93
|
+
return normalize(fsMod.readFileSync(filePath, 'utf8')) === normalize(text);
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Applica la sincronizzazione vera e propria dentro hooksDir. templateText è il
|
|
100
|
+
// wrapper sh (installato come commit-msg), cjsText la logica (installata come
|
|
101
|
+
// trailhead-commit-msg.cjs). fs è iniettato per testabilità.
|
|
102
|
+
function syncCommitMsgHook({ hooksDir, templateText, cjsText, hooksPathSet, fs: fsMod = fs }) {
|
|
71
103
|
const hookPath = path.join(hooksDir, 'commit-msg');
|
|
104
|
+
const cjsPath = path.join(hooksDir, CJS_NAME);
|
|
72
105
|
|
|
73
106
|
if (!fsMod.existsSync(hookPath)) {
|
|
74
107
|
if (hooksPathSet) {
|
|
@@ -79,8 +112,9 @@ function syncCommitMsgHook({ hooksDir, templateText, hooksPathSet, fs: fsMod = f
|
|
|
79
112
|
};
|
|
80
113
|
}
|
|
81
114
|
fsMod.mkdirSync(hooksDir, { recursive: true });
|
|
82
|
-
|
|
83
|
-
fsMod
|
|
115
|
+
// Prima la logica, poi il wrapper: mai un wrapper senza il suo script.
|
|
116
|
+
writeAtomic(fsMod, hooksDir, cjsPath, cjsText);
|
|
117
|
+
writeAtomic(fsMod, hooksDir, hookPath, templateText);
|
|
84
118
|
return { status: 'installed', hook: hookPath, message: 'installed the trailhead commit-msg hook.' };
|
|
85
119
|
}
|
|
86
120
|
|
|
@@ -94,22 +128,18 @@ function syncCommitMsgHook({ hooksDir, templateText, hooksPathSet, fs: fsMod = f
|
|
|
94
128
|
return { status: 'foreign', hook: hookPath, message: 'a foreign commit-msg hook is present; left untouched.' };
|
|
95
129
|
}
|
|
96
130
|
|
|
97
|
-
|
|
131
|
+
const wrapperCurrent = normalize(existingText) === normalize(templateText);
|
|
132
|
+
const cjsCurrent = matchesTemplate(fsMod, cjsPath, cjsText);
|
|
133
|
+
|
|
134
|
+
if (wrapperCurrent && cjsCurrent) {
|
|
98
135
|
if ((stat.mode & 0o777) !== 0o755) fsMod.chmodSync(hookPath, 0o755);
|
|
99
136
|
return { status: 'current', hook: hookPath, message: 'the trailhead commit-msg hook is already current.' };
|
|
100
137
|
}
|
|
101
138
|
|
|
102
|
-
// Upgrade
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
fsMod.writeFileSync(tmpPath, templateText);
|
|
107
|
-
fsMod.chmodSync(tmpPath, 0o755);
|
|
108
|
-
fsMod.renameSync(tmpPath, hookPath);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
try { fsMod.unlinkSync(tmpPath); } catch { /* best effort cleanup */ }
|
|
111
|
-
throw err;
|
|
112
|
-
}
|
|
139
|
+
// Upgrade: logica per prima, wrapper per ultimo (mai un wrapper senza script).
|
|
140
|
+
if (!cjsCurrent) writeAtomic(fsMod, hooksDir, cjsPath, cjsText);
|
|
141
|
+
if (!wrapperCurrent) writeAtomic(fsMod, hooksDir, hookPath, templateText);
|
|
142
|
+
else if ((stat.mode & 0o777) !== 0o755) fsMod.chmodSync(hookPath, 0o755);
|
|
113
143
|
return { status: 'upgraded', hook: hookPath, message: 'upgraded the trailhead commit-msg hook to the current template.' };
|
|
114
144
|
}
|
|
115
145
|
|
|
@@ -153,7 +183,8 @@ function main(argv, { cwd, stdout } = {}) {
|
|
|
153
183
|
hooksDirForError = target.hooksDir;
|
|
154
184
|
const templatePath = path.join(__dirname, 'trailhead-commit-msg');
|
|
155
185
|
const templateText = fs.readFileSync(templatePath, 'utf8');
|
|
156
|
-
const
|
|
186
|
+
const cjsText = fs.readFileSync(path.join(__dirname, CJS_NAME), 'utf8');
|
|
187
|
+
const result = syncCommitMsgHook({ hooksDir: target.hooksDir, templateText, cjsText, hooksPathSet: target.hooksPathSet, fs });
|
|
157
188
|
writeResult(out, result);
|
|
158
189
|
return 0;
|
|
159
190
|
} catch (err) {
|
|
@@ -163,7 +194,7 @@ function main(argv, { cwd, stdout } = {}) {
|
|
|
163
194
|
}
|
|
164
195
|
}
|
|
165
196
|
|
|
166
|
-
module.exports = { resolveHooksTarget, isTrailheadHook, normalize, syncCommitMsgHook, parseArgs, main };
|
|
197
|
+
module.exports = { CJS_NAME, resolveHooksTarget, isTrailheadHook, normalize, syncCommitMsgHook, parseArgs, main };
|
|
167
198
|
|
|
168
199
|
if (require.main === module) {
|
|
169
200
|
const code = main(process.argv.slice(2), { cwd: process.cwd(), stdout: process.stdout });
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// trailhead commit-msg hook logic (git, CommonJS). Host-independent guardrail:
|
|
4
|
+
// installed beside the sh wrapper .git/hooks/commit-msg (which calls it) at repo
|
|
5
|
+
// first-use so trailhead's commit discipline is enforced on EVERY `git commit`,
|
|
6
|
+
// whatever agent host drives it (Codex has no hook bus; this git hook is how the
|
|
7
|
+
// commit-guard survives there). Decision #16.
|
|
8
|
+
//
|
|
9
|
+
// The .cjs extension is deliberate (#257): Node takes the module format of an
|
|
10
|
+
// extensionless file from the nearest package.json "type", and under .git/hooks/
|
|
11
|
+
// that is the user repo's, so a "type": "module" repo would load this as ESM and
|
|
12
|
+
// fail on require(). A .cjs file is always CommonJS.
|
|
13
|
+
//
|
|
14
|
+
// Self-contained by design: detached from the trailhead install, it inlines the
|
|
15
|
+
// same rules as plugins/trailhead/hooks/lib/commit-message-check.js. That
|
|
16
|
+
// lib's test drives this hook end-to-end so the copies cannot drift.
|
|
17
|
+
//
|
|
18
|
+
// git passes the commit-message file path as argv[2] (the wrapper forwards it).
|
|
19
|
+
// On a violation: print the reason to stderr and exit VIOLATION (3); the wrapper
|
|
20
|
+
// maps exactly that code to exit 1 (git aborts the commit); any code other than
|
|
21
|
+
// 0 or 3 (a crash at load) is reported as the one-line "inactive" notice on
|
|
22
|
+
// stderr and exits 0. Otherwise exit 0. Crash-safe: any unexpected error exits
|
|
23
|
+
// 0 (never wedge a commit).
|
|
24
|
+
//
|
|
25
|
+
// Note: a merge/revert/fixup message already fails the conventional-subject
|
|
26
|
+
// rule below, so it needs no special case; `git commit --amend` keeps whatever
|
|
27
|
+
// trailer the original message carried; `git commit --no-verify` bypasses this
|
|
28
|
+
// hook entirely, as before.
|
|
29
|
+
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const VIOLATION = 3; // codice dedicato, mappato a 1 dal wrapper sh
|
|
34
|
+
const TYPES = 'feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert';
|
|
35
|
+
const CONVENTIONAL = new RegExp(`^(${TYPES})(\\([^)]+\\))?!?:\\s.+`);
|
|
36
|
+
const CO_AUTHORED = /co-authored-by\s*:/i;
|
|
37
|
+
const SCISSORS = '# ------------------------ >8';
|
|
38
|
+
const REFS_LINE = /^\s*refs\s*:(.*)$/i;
|
|
39
|
+
|
|
40
|
+
function messageRegion(raw) {
|
|
41
|
+
const text = String(raw == null ? '' : raw);
|
|
42
|
+
const i = text.indexOf(SCISSORS);
|
|
43
|
+
return i === -1 ? text : text.slice(0, i);
|
|
44
|
+
}
|
|
45
|
+
function subjectOf(raw) {
|
|
46
|
+
for (const line of messageRegion(raw).split('\n')) {
|
|
47
|
+
if (line.trim() === '' || line.startsWith('#')) continue;
|
|
48
|
+
return line.trim();
|
|
49
|
+
}
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Mirrors parseSessionTicket() in commit-message-check.js: parses the first
|
|
54
|
+
// line of a .trailhead/session-ticket marker (`#<n> <title>`).
|
|
55
|
+
function parseSessionTicket(text) {
|
|
56
|
+
const raw = String(text == null ? '' : text).replace(/^/, '');
|
|
57
|
+
const firstLine = raw.split('\n')[0].replace(/\r$/, '');
|
|
58
|
+
const m = firstLine.match(/^#([1-9]\d*)(?:\s|$)/);
|
|
59
|
+
return m ? Number(m[1]) : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Mirrors hasRefsFor() in commit-message-check.js.
|
|
63
|
+
function hasRefsFor(message, n) {
|
|
64
|
+
const target = new RegExp('#' + n + '(?![\\w])');
|
|
65
|
+
for (const line of messageRegion(message).split('\n')) {
|
|
66
|
+
if (line.trim().startsWith('#')) continue;
|
|
67
|
+
const m = line.match(REFS_LINE);
|
|
68
|
+
if (m && target.test(m[1])) return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Reads the session-ticket marker at the worktree top-level. git runs
|
|
74
|
+
// commit-msg from the worktree top-level (linked worktrees included), so
|
|
75
|
+
// process.cwd() is the right root here. Its own try/catch: a read error
|
|
76
|
+
// (marker absent, unreadable, etc.) must never skip the other rules below,
|
|
77
|
+
// it just leaves the Refs rule off.
|
|
78
|
+
function readTicket() {
|
|
79
|
+
try {
|
|
80
|
+
const markerPath = path.join(process.cwd(), '.trailhead', 'session-ticket');
|
|
81
|
+
return parseSessionTicket(fs.readFileSync(markerPath, 'utf8'));
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const file = process.argv[2];
|
|
89
|
+
const raw = file ? fs.readFileSync(file, 'utf8') : '';
|
|
90
|
+
const region = messageRegion(raw);
|
|
91
|
+
|
|
92
|
+
if (CO_AUTHORED.test(region)) {
|
|
93
|
+
process.stderr.write('trailhead: do not add a Co-Authored-By trailer to commits. Remove it and commit again.\n');
|
|
94
|
+
process.exit(VIOLATION);
|
|
95
|
+
}
|
|
96
|
+
const subject = subjectOf(raw);
|
|
97
|
+
if (subject && !CONVENTIONAL.test(subject)) {
|
|
98
|
+
process.stderr.write(`trailhead: commit subject must be Conventional Commits: <type>(<scope>)?: <subject>. Valid types: ${TYPES.replace(/\|/g, ', ')}.\n`);
|
|
99
|
+
process.exit(VIOLATION);
|
|
100
|
+
}
|
|
101
|
+
if (subject.length > 72) {
|
|
102
|
+
process.stderr.write('trailhead: commit subject must be 72 characters or less.\n');
|
|
103
|
+
process.exit(VIOLATION);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const ticket = readTicket();
|
|
107
|
+
if (Number.isInteger(ticket) && ticket > 0 && !hasRefsFor(raw, ticket)) {
|
|
108
|
+
process.stderr.write(`trailhead: this commit is on ticket #${ticket} (per .trailhead/session-ticket) but has no "Refs: #${ticket}" trailer. Add "Refs: #${ticket}" as the last line of the message and commit again. If this commit is not part of ticket #${ticket}, remove the stale .trailhead/session-ticket marker first.\n`);
|
|
109
|
+
process.exit(VIOLATION);
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
process.exit(0); // never wedge a commit on an unexpected error
|
|
113
|
+
}
|
|
114
|
+
process.exit(0);
|