@marcomigozzi/trailhead 0.9.0 → 0.11.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 +11 -11
- package/README.md +11 -11
- package/bin/lib/codex-projection.js +13 -6
- package/bin/lib/codex-projection.test.js +45 -3
- package/bin/lib/install-verify.js +257 -0
- package/bin/lib/install-verify.test.js +379 -0
- package/bin/trailhead.js +91 -5
- package/bin/trailhead.test.js +588 -4
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/commands/quick.md +1 -1
- package/plugins/trailhead/hooks/lib/commit-message-check.js +58 -2
- package/plugins/trailhead/hooks/lib/commit-message-check.test.js +103 -13
- package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +375 -0
- package/plugins/trailhead/hooks/lib/fixtures/commit-msg-a319156 +57 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +32 -3
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +41 -0
- package/plugins/trailhead/skills/_shared/choices.md +3 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +27 -20
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/principles.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +8 -6
- package/plugins/trailhead/skills/_shared/substrate-commands.md +1 -1
- package/plugins/trailhead/skills/_shared/teamwork.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +13 -4
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +15 -3
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques.md +2 -2
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +11 -10
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +12 -9
- package/plugins/trailhead/templates/trailhead-commit-msg +49 -2
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +171 -0
package/bin/trailhead.js
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
// projection in bin/lib/codex-projection.js (engine text rewrites, prompt
|
|
16
16
|
// generation). With no --codex/--claude flag the host is auto-detected from
|
|
17
17
|
// the CLIs on $PATH; the interactive prompt only fires when that is ambiguous.
|
|
18
|
+
//
|
|
19
|
+
// Post-install check (#182): just before printing the final ✓, both
|
|
20
|
+
// installers run bin/lib/install-verify.js's verifyInstall against what's
|
|
21
|
+
// actually on disk (and, for the registered hook targets, what's actually in
|
|
22
|
+
// the host's registry file). A gap fails the run loudly instead of a silent
|
|
23
|
+
// ✓; a registered target that depends on an env var this process doesn't
|
|
24
|
+
// have set prints as a ⚠ warning alongside the ✓, not a failure.
|
|
18
25
|
|
|
19
26
|
const fs = require('fs');
|
|
20
27
|
const os = require('os');
|
|
@@ -24,6 +31,7 @@ const { spawnSync } = require('child_process');
|
|
|
24
31
|
|
|
25
32
|
const { getHost, configDirFor, codexVersionGate } = require('./lib/host-descriptor.js');
|
|
26
33
|
const { codexLayout, convertToCodex, injectCodexAdapterHeader, codexAgentsYaml, codexClusterAgentsYaml, codexHookEntries, enableCodexHooksFeature, codexAgentTomlPlan, codexVerbSkillPlan, enableCodexMultiAgentV2Feature } = require('./lib/codex-projection.js');
|
|
34
|
+
const { sharedCoreFiles, claudeVerifySpec, codexVerifySpec, verifyInstall, formatVerifyFailure, formatVerifyWarnings } = require('./lib/install-verify.js');
|
|
27
35
|
|
|
28
36
|
const PKG = path.resolve(__dirname, '..');
|
|
29
37
|
const SRC = path.join(PKG, 'plugins', 'trailhead');
|
|
@@ -42,6 +50,28 @@ function engineSkillDirs() {
|
|
|
42
50
|
.sort();
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
// Every subdirectory shipped under plugins/trailhead/skills, with no SKILL.md
|
|
54
|
+
// filter (unlike engineSkillDirs). This is the EXPECTED skill set the
|
|
55
|
+
// post-install check verifies against, so a cluster whose SKILL.md failed to
|
|
56
|
+
// copy is caught as missing rather than silently dropped from the set.
|
|
57
|
+
function shippedSkillDirs() {
|
|
58
|
+
const skillsRoot = path.join(SRC, 'skills');
|
|
59
|
+
if (!fs.existsSync(skillsRoot)) return [];
|
|
60
|
+
return fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
61
|
+
.filter((e) => e.isDirectory())
|
|
62
|
+
.map((e) => e.name)
|
|
63
|
+
.sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The shared-core file list, single-sourced from the source copy of
|
|
67
|
+
// _shared/load-first.md (see install-verify.js's sharedCoreFiles). Read at
|
|
68
|
+
// verify time by each installer; a missing file yields just load-first.md.
|
|
69
|
+
function readSharedCoreFiles() {
|
|
70
|
+
let loadFirstText = '';
|
|
71
|
+
try { loadFirstText = fs.readFileSync(path.join(SRC, 'skills', '_shared', 'load-first.md'), 'utf8'); } catch { /* missing: falls back to just load-first.md */ }
|
|
72
|
+
return sharedCoreFiles(loadFirstText);
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
// Sweep every trailhead-owned skill dir under a skills root, by name: the
|
|
46
76
|
// dispatcher (`trailhead`), the shared core (`_shared`), and every
|
|
47
77
|
// `trailhead-<cluster|verb>` sibling. Clears a prior layout's stale dirs (an
|
|
@@ -222,6 +252,10 @@ const HOOK_FILES = ['trailhead-commit-guard.js', 'trailhead-issue-injection-scan
|
|
|
222
252
|
// recursive sweep: the Claude hooks/lib dir is shared with other plugins, so
|
|
223
253
|
// we must not clobber a co-tenant's lib nor ship our own *.test.js files.
|
|
224
254
|
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcommand.js'];
|
|
255
|
+
// search-guard is Claude-Code-specific (#169): excluded from both the Codex
|
|
256
|
+
// hook copy and the Codex verify spec's expected hookFiles, so a healthy
|
|
257
|
+
// Codex install is never flagged for a script it never ships.
|
|
258
|
+
const CODEX_HOOK_EXCLUDE = ['trailhead-search-guard.js'];
|
|
225
259
|
|
|
226
260
|
// Copy the hook scripts and their runtime libs into a destination hooks dir
|
|
227
261
|
// (Claude's ~/.claude/hooks or Codex's skills/trailhead/hooks). Shared by both
|
|
@@ -232,10 +266,14 @@ const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcomma
|
|
|
232
266
|
// live too, matching skills/commands. Never symlink the whole hooks/ dir: it is
|
|
233
267
|
// shared with other plugins, so only the curated allowlist is touched. rmrf the
|
|
234
268
|
// dest first so a reinstall over an existing file or symlink never hits EEXIST.
|
|
235
|
-
function copyHookScripts(destHooksDir, { useSymlink = false } = {}) {
|
|
269
|
+
function copyHookScripts(destHooksDir, { useSymlink = false, exclude = [] } = {}) {
|
|
236
270
|
ensure(destHooksDir);
|
|
271
|
+
// rmrf the dest first, always: a reinstall that finds its source gone must
|
|
272
|
+
// still remove whatever an earlier install left behind, not just skip the
|
|
273
|
+
// copy and leave a stale file in place.
|
|
237
274
|
const put = (src, dest) => {
|
|
238
275
|
rmrf(dest);
|
|
276
|
+
if (!fs.existsSync(src)) return;
|
|
239
277
|
if (useSymlink) {
|
|
240
278
|
fs.symlinkSync(src, dest, 'file');
|
|
241
279
|
} else {
|
|
@@ -243,7 +281,7 @@ function copyHookScripts(destHooksDir, { useSymlink = false } = {}) {
|
|
|
243
281
|
fs.chmodSync(dest, 0o755);
|
|
244
282
|
}
|
|
245
283
|
};
|
|
246
|
-
for (const f of HOOK_FILES) put(path.join(SRC, 'hooks', f), path.join(destHooksDir, f));
|
|
284
|
+
for (const f of HOOK_FILES.filter((f) => !exclude.includes(f))) put(path.join(SRC, 'hooks', f), path.join(destHooksDir, f));
|
|
247
285
|
if (HOOK_LIB_FILES.length) {
|
|
248
286
|
const libDest = path.join(destHooksDir, 'lib');
|
|
249
287
|
ensure(libDest);
|
|
@@ -384,6 +422,24 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
384
422
|
].some(Boolean);
|
|
385
423
|
writeJSON(P.settings, s);
|
|
386
424
|
|
|
425
|
+
// Verify the install actually landed before reporting success (#182): a
|
|
426
|
+
// filesystem-only check, run just before the ✓ line, that never trusts the
|
|
427
|
+
// copy/write calls above to have succeeded silently.
|
|
428
|
+
const spec = claudeVerifySpec(configDir, {
|
|
429
|
+
skillDirs: shippedSkillDirs(),
|
|
430
|
+
sharedFiles: readSharedCoreFiles(),
|
|
431
|
+
hookFiles: HOOK_FILES,
|
|
432
|
+
hookLibFiles: HOOK_LIB_FILES,
|
|
433
|
+
env: process.env,
|
|
434
|
+
home: os.homedir(),
|
|
435
|
+
});
|
|
436
|
+
const { missing, unverified } = verifyInstall(spec);
|
|
437
|
+
if (missing.length) {
|
|
438
|
+
console.error(formatVerifyFailure('Claude Code', configDir, { missing, unverified }));
|
|
439
|
+
process.exitCode = 1;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
387
443
|
console.log(`✓ trailhead installed for Claude Code → ${configDir}`);
|
|
388
444
|
console.log(` skills → ${path.join(configDir, 'skills')}/ (${skillDirs.join(', ')})${useSymlink ? ' (symlink)' : ''}`);
|
|
389
445
|
console.log(` commands → ${P.commands} (/trailhead:*)`);
|
|
@@ -392,6 +448,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
392
448
|
console.log(` templates → ${P.templates}`);
|
|
393
449
|
console.log('\nRestart or reload your agent to pick up the commands, then run /trailhead to start.');
|
|
394
450
|
console.log('Note: the commit guard now runs on every git commit (conventional + no Co-Authored-By).');
|
|
451
|
+
for (const w of formatVerifyWarnings(unverified)) console.log(w);
|
|
395
452
|
}
|
|
396
453
|
|
|
397
454
|
// --- Codex adapter -----------------------------------------------------------
|
|
@@ -575,10 +632,17 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
575
632
|
const pkgVersion = (readJSON(path.join(SRC, '.claude-plugin', 'plugin.json')).version || '').trim();
|
|
576
633
|
if (pkgVersion) fs.writeFileSync(L.versionFile, pkgVersion + '\n');
|
|
577
634
|
|
|
578
|
-
// Hooks: copy the
|
|
579
|
-
// ~/.codex/hooks.json (same shape as Claude's
|
|
580
|
-
|
|
635
|
+
// Hooks: copy the guard scripts this host actually uses into the skill dir
|
|
636
|
+
// and register them in ~/.codex/hooks.json (same shape as Claude's
|
|
637
|
+
// settings.json hooks block). search-guard is excluded: it is
|
|
638
|
+
// Claude-Code-specific (see codexHookEntries), so it is neither copied nor
|
|
639
|
+
// registered here.
|
|
640
|
+
copyHookScripts(L.hooksScriptsDir, { useSymlink, exclude: CODEX_HOOK_EXCLUDE });
|
|
581
641
|
const h = readJSON(L.hooksJson);
|
|
642
|
+
// Migration: strip a pre-#169 search-guard registration. Its target is
|
|
643
|
+
// always gone now (search-guard is never copied to Codex), so a prior
|
|
644
|
+
// install's stale entry would otherwise fail the post-install check below.
|
|
645
|
+
stripHook(h, 'PreToolUse', 'trailhead-search-guard.js');
|
|
582
646
|
for (const e of codexHookEntries(L.hooksScriptsDir)) addHook(h, e.event, e.matcher, e.command);
|
|
583
647
|
writeJSON(L.hooksJson, h);
|
|
584
648
|
|
|
@@ -629,6 +693,23 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
629
693
|
}
|
|
630
694
|
}
|
|
631
695
|
|
|
696
|
+
// Verify the install actually landed before reporting success (#182),
|
|
697
|
+
// mirroring installClaude's check.
|
|
698
|
+
const spec = codexVerifySpec(configDir, {
|
|
699
|
+
skillDirs: shippedSkillDirs(),
|
|
700
|
+
sharedFiles: readSharedCoreFiles(),
|
|
701
|
+
hookFiles: HOOK_FILES.filter((f) => !CODEX_HOOK_EXCLUDE.includes(f)),
|
|
702
|
+
hookLibFiles: HOOK_LIB_FILES,
|
|
703
|
+
env: process.env,
|
|
704
|
+
home: os.homedir(),
|
|
705
|
+
});
|
|
706
|
+
const { missing, unverified } = verifyInstall(spec);
|
|
707
|
+
if (missing.length) {
|
|
708
|
+
console.error(formatVerifyFailure('Codex', configDir, { missing, unverified }));
|
|
709
|
+
process.exitCode = 1;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
632
713
|
console.log(`✓ trailhead installed for Codex → ${configDir}`);
|
|
633
714
|
console.log(` skill → ${L.skillDir}/ (invoke $trailhead)`);
|
|
634
715
|
console.log(` templates → ${L.templatesDir}/`);
|
|
@@ -653,6 +734,7 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
653
734
|
if (featureManualV2) {
|
|
654
735
|
console.log(` ⚠ could not edit ${L.configToml} automatically, add \`features.multi_agent_v2 = true\` under [features] by hand to let Codex honour the projected model pins.`);
|
|
655
736
|
}
|
|
737
|
+
for (const w of formatVerifyWarnings(unverified)) console.log(w);
|
|
656
738
|
}
|
|
657
739
|
|
|
658
740
|
function uninstallCodex(configDir) {
|
|
@@ -682,6 +764,10 @@ function uninstallCodex(configDir) {
|
|
|
682
764
|
stripHook(h, 'PreToolUse', 'trailhead-commit-guard.js');
|
|
683
765
|
stripHook(h, 'PreToolUse', 'trailhead-secret-guard.js');
|
|
684
766
|
stripHook(h, 'PreToolUse', 'trailhead-install-guard.js');
|
|
767
|
+
// search-guard is no longer projected to Codex (#169: it is
|
|
768
|
+
// Claude-Code-specific), so it is never registered by a current install;
|
|
769
|
+
// this strip is retained only to clean up a stale entry left by a
|
|
770
|
+
// pre-#169 install that did register it.
|
|
685
771
|
stripHook(h, 'PreToolUse', 'trailhead-search-guard.js');
|
|
686
772
|
stripHook(h, 'PreToolUse', 'trailhead-secret-read-guard.js');
|
|
687
773
|
stripHook(h, 'PreToolUse', 'trailhead-body-guard.js');
|