@mmnto/cli 1.100.0 → 1.101.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/dist/commands/doctor.d.ts +19 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +132 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +1 -0
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/gate-install.d.ts.map +1 -1
- package/dist/commands/gate-install.js +12 -3
- package/dist/commands/gate-install.js.map +1 -1
- package/dist/commands/gate-install.test.js +13 -1
- package/dist/commands/gate-install.test.js.map +1 -1
- package/dist/commands/init-templates.d.ts +57 -5
- package/dist/commands/init-templates.d.ts.map +1 -1
- package/dist/commands/init-templates.js +222 -1
- package/dist/commands/init-templates.js.map +1 -1
- package/dist/commands/init.d.ts +38 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +196 -13
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +13 -0
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-hooks-exit-contract.test.d.ts +2 -0
- package/dist/commands/install-hooks-exit-contract.test.d.ts.map +1 -0
- package/dist/commands/install-hooks-exit-contract.test.js +339 -0
- package/dist/commands/install-hooks-exit-contract.test.js.map +1 -0
- package/dist/commands/install-hooks.d.ts +39 -0
- package/dist/commands/install-hooks.d.ts.map +1 -1
- package/dist/commands/install-hooks.js +130 -22
- package/dist/commands/install-hooks.js.map +1 -1
- package/dist/commands/prepare-wrapper.test.d.ts +2 -0
- package/dist/commands/prepare-wrapper.test.d.ts.map +1 -0
- package/dist/commands/prepare-wrapper.test.js +284 -0
- package/dist/commands/prepare-wrapper.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit-code contract for `totem hook install` (`hooksCommand`) + the managed
|
|
3
|
+
* session-hook bounded drift-repair (mmnto-ai/totem#2410 PR-A, slices 2+3).
|
|
4
|
+
*
|
|
5
|
+
* This is the dedicated falsifying test for the contract #2406 stabilized and
|
|
6
|
+
* strategy#894 froze (Tenet 19). It locks:
|
|
7
|
+
* - exit 0 ⟺ {fresh install, already-current, bounded drift-repair (git hook AND
|
|
8
|
+
* session hook), declared skips (not-a-git-repo, hook-manager-detected)};
|
|
9
|
+
* - exit ≠0 ⟺ genuine failure (a hook write throws → propagates → handleError);
|
|
10
|
+
* - `--check` is exactly 0/1 (all-present-with-marker vs missing/markerless);
|
|
11
|
+
* - a bare install never mutates a non-bounded file (git hook OR session hook);
|
|
12
|
+
* `--force` is the only unbounded write; a legacy markerless-end session hook is
|
|
13
|
+
* never bare-repaired; a no-marker user file is never touched even under --force;
|
|
14
|
+
* - regenerated artifacts always carry marker + end marker (so they self-repair);
|
|
15
|
+
* - the two `overwritten` messages are distinct ("Drift-repaired…" vs
|
|
16
|
+
* "Force-overwritten…").
|
|
17
|
+
*
|
|
18
|
+
* `hooksCommand` reads `process.cwd()` and gates via `process.exit` on `--check`
|
|
19
|
+
* failure, so the exit-code cases drive it under `process.chdir` + a `process.exit`
|
|
20
|
+
* spy (the doctor.test.ts idiom); the session-hook matrix drives the pure
|
|
21
|
+
* `regenerateManagedSessionHooks(cwd, force)` seam directly.
|
|
22
|
+
*/
|
|
23
|
+
import { execSync } from 'node:child_process';
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as os from 'node:os';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
28
|
+
import { cleanTmpDir } from '../test-utils.js';
|
|
29
|
+
import { CLAUDE_PREWRITESHIELD, CLAUDE_SESSION_START, GEMINI_SESSION_START, MANAGED_SESSION_HOOKS, TOTEM_FILE_END, TOTEM_FILE_MARKER, } from './init-templates.js';
|
|
30
|
+
import { hooksCommand, installHooksNonInteractive, regenerateManagedSessionHooks, TOTEM_PREPUSH_END, TOTEM_PREPUSH_MARKER, } from './install-hooks.js';
|
|
31
|
+
// ─── Roster invariant (locked contract, mmnto-ai/totem#2410) ─────────
|
|
32
|
+
describe('MANAGED_SESSION_HOOKS roster invariant', () => {
|
|
33
|
+
it('every entry embeds its own marker AND end marker (so regenerated artifacts self-repair)', () => {
|
|
34
|
+
expect(MANAGED_SESSION_HOOKS.length).toBeGreaterThan(0);
|
|
35
|
+
for (const { rel, content, marker, endMarker } of MANAGED_SESSION_HOOKS) {
|
|
36
|
+
expect(content.includes(marker), `${rel} content is missing its marker`).toBe(true);
|
|
37
|
+
expect(content.includes(endMarker), `${rel} content is missing its end marker`).toBe(true);
|
|
38
|
+
// The marker must OPEN the file (only whitespace before it) — the ownership
|
|
39
|
+
// precondition for bounded drift-repair.
|
|
40
|
+
expect(content.trimStart().startsWith(marker), `${rel} marker must open the file`).toBe(true);
|
|
41
|
+
// The end marker must CLOSE the region (nothing but whitespace after it).
|
|
42
|
+
const endIdx = content.indexOf(endMarker);
|
|
43
|
+
expect(content.slice(endIdx + endMarker.length).trim()).toBe('');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it('covers the six distributed managed artifacts (incl. the PR-B prepare wrapper)', () => {
|
|
47
|
+
const rels = MANAGED_SESSION_HOOKS.map((h) => h.rel).sort();
|
|
48
|
+
expect(rels).toEqual([
|
|
49
|
+
'.claude/hooks/PreWriteShield.cjs',
|
|
50
|
+
'.claude/hooks/SessionStart.cjs',
|
|
51
|
+
'.claude/hooks/gate-wrapper.cjs',
|
|
52
|
+
'.gemini/hooks/BeforeTool.js',
|
|
53
|
+
'.gemini/hooks/SessionStart.js',
|
|
54
|
+
'.totem/prepare.cjs',
|
|
55
|
+
].sort());
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ─── Managed session-hook bounded drift-repair (slice 3) ─────────────
|
|
59
|
+
describe('regenerateManagedSessionHooks — bounded drift-repair matrix', () => {
|
|
60
|
+
let tmpDir;
|
|
61
|
+
const CLAUDE_SS = '.claude/hooks/SessionStart.cjs';
|
|
62
|
+
function writeHook(rel, content) {
|
|
63
|
+
const p = path.join(tmpDir, ...rel.split('/'));
|
|
64
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
65
|
+
fs.writeFileSync(p, content, 'utf-8');
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-2410-sess-'));
|
|
70
|
+
});
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
cleanTmpDir(tmpDir);
|
|
73
|
+
});
|
|
74
|
+
it('does NOT create a missing session hook (regenerate-only-if-present)', async () => {
|
|
75
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
76
|
+
expect(results).toEqual([]);
|
|
77
|
+
expect(fs.existsSync(path.join(tmpDir, ...CLAUDE_SS.split('/')))).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it('returns exists for a present, byte-identical session hook (already current)', async () => {
|
|
80
|
+
writeHook(CLAUDE_SS, CLAUDE_SESSION_START);
|
|
81
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
82
|
+
const r = results.find((x) => x.file === CLAUDE_SS);
|
|
83
|
+
expect(r.action).toBe('exists');
|
|
84
|
+
expect(fs.readFileSync(path.join(tmpDir, ...CLAUDE_SS.split('/')), 'utf-8')).toBe(CLAUDE_SESSION_START);
|
|
85
|
+
});
|
|
86
|
+
it('bare drift-repairs a bounded totem-owned session hook (overwritten, no force)', async () => {
|
|
87
|
+
const p = writeHook(CLAUDE_SS, `${TOTEM_FILE_MARKER}\nstale body\n${TOTEM_FILE_END}\n`);
|
|
88
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
89
|
+
const r = results.find((x) => x.file === CLAUDE_SS);
|
|
90
|
+
expect(r.action).toBe('overwritten');
|
|
91
|
+
// Regenerated to canonical — and canonical carries marker + end marker.
|
|
92
|
+
const written = fs.readFileSync(p, 'utf-8');
|
|
93
|
+
expect(written).toBe(CLAUDE_SESSION_START);
|
|
94
|
+
expect(written.includes(TOTEM_FILE_MARKER)).toBe(true);
|
|
95
|
+
expect(written.includes(TOTEM_FILE_END)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it('does NOT bare-repair a legacy marker-headed session hook missing the end marker', async () => {
|
|
98
|
+
const legacy = `${TOTEM_FILE_MARKER} — Claude Code SessionStart hook\nstale legacy body\n`;
|
|
99
|
+
const p = writeHook(CLAUDE_SS, legacy);
|
|
100
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
101
|
+
const r = results.find((x) => x.file === CLAUDE_SS);
|
|
102
|
+
expect(r.action).toBe('declined');
|
|
103
|
+
// Untouched — takes one `totem hook install --force`.
|
|
104
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(legacy);
|
|
105
|
+
});
|
|
106
|
+
it('--force overwrites a legacy markerless-end session hook (the migration path)', async () => {
|
|
107
|
+
const legacy = `${TOTEM_FILE_MARKER} — Claude Code SessionStart hook\nstale legacy body\n`;
|
|
108
|
+
const p = writeHook(CLAUDE_SS, legacy);
|
|
109
|
+
const results = await regenerateManagedSessionHooks(tmpDir, true);
|
|
110
|
+
const r = results.find((x) => x.file === CLAUDE_SS);
|
|
111
|
+
expect(r.action).toBe('overwritten');
|
|
112
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(CLAUDE_SESSION_START);
|
|
113
|
+
});
|
|
114
|
+
it('does NOT repair a bounded hook with user content AFTER the end marker (bare declines)', async () => {
|
|
115
|
+
const withTrailingUser = `${TOTEM_FILE_MARKER}\nstale\n${TOTEM_FILE_END}\nconsole.log("mine");\n`;
|
|
116
|
+
const p = writeHook(CLAUDE_SS, withTrailingUser);
|
|
117
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
118
|
+
expect(results.find((x) => x.file === CLAUDE_SS).action).toBe('declined');
|
|
119
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(withTrailingUser);
|
|
120
|
+
});
|
|
121
|
+
it('never touches a user-owned file with NO totem marker, even under --force (skipped)', async () => {
|
|
122
|
+
const userOwned = '// my own SessionStart hook\nconsole.log("mine");\n';
|
|
123
|
+
const p = writeHook(CLAUDE_SS, userOwned);
|
|
124
|
+
const bare = await regenerateManagedSessionHooks(tmpDir);
|
|
125
|
+
expect(bare.find((x) => x.file === CLAUDE_SS).action).toBe('skipped');
|
|
126
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(userOwned);
|
|
127
|
+
const forced = await regenerateManagedSessionHooks(tmpDir, true);
|
|
128
|
+
expect(forced.find((x) => x.file === CLAUDE_SS).action).toBe('skipped');
|
|
129
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(userOwned);
|
|
130
|
+
});
|
|
131
|
+
it('never touches a user file that merely QUOTES the marker (marker not at start), even under --force', async () => {
|
|
132
|
+
// A user-owned hook that quotes the marker string in a comment/string is NOT
|
|
133
|
+
// marker-headed (positional gate, mmnto-ai/totem#2413). The old `includes(marker)`
|
|
134
|
+
// gate would treat it as owned and let --force clobber it.
|
|
135
|
+
const quotesMarker = `// my own hook\n// I copied this line: ${TOTEM_FILE_MARKER}\nconsole.log("mine");\n`;
|
|
136
|
+
const p = writeHook(CLAUDE_SS, quotesMarker);
|
|
137
|
+
const bare = await regenerateManagedSessionHooks(tmpDir);
|
|
138
|
+
expect(bare.find((x) => x.file === CLAUDE_SS).action).toBe('skipped');
|
|
139
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(quotesMarker);
|
|
140
|
+
const forced = await regenerateManagedSessionHooks(tmpDir, true);
|
|
141
|
+
expect(forced.find((x) => x.file === CLAUDE_SS).action).toBe('skipped');
|
|
142
|
+
expect(fs.readFileSync(p, 'utf-8')).toBe(quotesMarker);
|
|
143
|
+
});
|
|
144
|
+
it('repairs each vendor family independently (claude + gemini)', async () => {
|
|
145
|
+
writeHook('.claude/hooks/PreWriteShield.cjs', `${TOTEM_FILE_MARKER}\nstale\n${TOTEM_FILE_END}\n`);
|
|
146
|
+
writeHook('.gemini/hooks/SessionStart.js', GEMINI_SESSION_START); // already current
|
|
147
|
+
const results = await regenerateManagedSessionHooks(tmpDir);
|
|
148
|
+
expect(results.find((x) => x.file === '.claude/hooks/PreWriteShield.cjs').action).toBe('overwritten');
|
|
149
|
+
expect(fs.readFileSync(path.join(tmpDir, '.claude', 'hooks', 'PreWriteShield.cjs'), 'utf-8')).toBe(CLAUDE_PREWRITESHIELD);
|
|
150
|
+
expect(results.find((x) => x.file === '.gemini/hooks/SessionStart.js').action).toBe('exists');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ─── hooksCommand exit-code contract (slice 2) ───────────────────────
|
|
154
|
+
describe('hooksCommand exit-code contract', () => {
|
|
155
|
+
let tmpDir;
|
|
156
|
+
let originalCwd;
|
|
157
|
+
let errorSpy;
|
|
158
|
+
let exitSpy;
|
|
159
|
+
const hooksDir = () => path.join(tmpDir, '.git', 'hooks');
|
|
160
|
+
const errorOutput = () => errorSpy.mock.calls
|
|
161
|
+
.map((c) => c.map((a) => String(a)).join(' '))
|
|
162
|
+
.join('\n');
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-2410-cmd-'));
|
|
165
|
+
execSync('git init', { cwd: tmpDir, stdio: 'ignore' });
|
|
166
|
+
originalCwd = process.cwd();
|
|
167
|
+
process.chdir(tmpDir);
|
|
168
|
+
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
169
|
+
// A throwing process.exit surfaces a --check failure as a rejection (the CLI
|
|
170
|
+
// edge would exit non-zero); the install path must never reach it.
|
|
171
|
+
exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
|
|
172
|
+
throw new Error(`process.exit:${code ?? 0}`);
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
afterEach(() => {
|
|
176
|
+
process.chdir(originalCwd);
|
|
177
|
+
vi.restoreAllMocks();
|
|
178
|
+
cleanTmpDir(tmpDir);
|
|
179
|
+
});
|
|
180
|
+
// ── exit 0 classes ──────────────────────────────────────────────
|
|
181
|
+
it('exit 0: fresh install (no throw, no process.exit)', async () => {
|
|
182
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
183
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
184
|
+
expect(fs.existsSync(path.join(hooksDir(), 'pre-push'))).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
it('exit 0: already-current (second install is a no-op)', async () => {
|
|
187
|
+
await hooksCommand({});
|
|
188
|
+
errorSpy.mockClear();
|
|
189
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
190
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
191
|
+
expect(errorOutput()).toContain('already installed');
|
|
192
|
+
});
|
|
193
|
+
it('exit 0: bare git-hook drift-repair prints "Drift-repaired", not "Force-overwritten"', async () => {
|
|
194
|
+
await hooksCommand({});
|
|
195
|
+
// Corrupt pre-push to a stale-but-bounded totem-owned whole file.
|
|
196
|
+
fs.writeFileSync(path.join(hooksDir(), 'pre-push'), `#!/bin/sh\n# ${TOTEM_PREPUSH_MARKER}\nstale\n# ${TOTEM_PREPUSH_END}\n`);
|
|
197
|
+
errorSpy.mockClear();
|
|
198
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
199
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
200
|
+
const out = errorOutput();
|
|
201
|
+
expect(out).toContain('Drift-repaired pre-push hook (totem-owned bounded region).');
|
|
202
|
+
expect(out).not.toContain('Force-overwritten pre-push');
|
|
203
|
+
});
|
|
204
|
+
it('exit 0: bare session-hook drift-repair prints "Drift-repaired"', async () => {
|
|
205
|
+
fs.mkdirSync(path.join(tmpDir, '.claude', 'hooks'), { recursive: true });
|
|
206
|
+
fs.writeFileSync(path.join(tmpDir, '.claude', 'hooks', 'SessionStart.cjs'), `${TOTEM_FILE_MARKER}\nstale\n${TOTEM_FILE_END}\n`);
|
|
207
|
+
errorSpy.mockClear();
|
|
208
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
209
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
210
|
+
expect(errorOutput()).toContain('Drift-repaired .claude/hooks/SessionStart.cjs session hook (totem-owned bounded region).');
|
|
211
|
+
expect(fs.readFileSync(path.join(tmpDir, '.claude', 'hooks', 'SessionStart.cjs'), 'utf-8')).toBe(CLAUDE_SESSION_START);
|
|
212
|
+
});
|
|
213
|
+
it('exit 0: declared skip — not a git repository', async () => {
|
|
214
|
+
const nonGit = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-2410-nogit-'));
|
|
215
|
+
process.chdir(nonGit);
|
|
216
|
+
try {
|
|
217
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
218
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
219
|
+
expect(errorOutput()).toContain('Not a git repository');
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
process.chdir(tmpDir);
|
|
223
|
+
cleanTmpDir(nonGit);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
it('exit 0: declared skip — hook manager detected', async () => {
|
|
227
|
+
fs.mkdirSync(path.join(tmpDir, '.husky'), { recursive: true });
|
|
228
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
229
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
it('hook-manager repo STILL drift-repairs an existing bounded session hook (lc#806 guard)', async () => {
|
|
232
|
+
// A git-hook manager (husky) means git hooks are the manager's job — a declared
|
|
233
|
+
// skip for the git side. But the session hooks are Claude/Gemini artifacts,
|
|
234
|
+
// independent of the manager, so they must still be regenerated: the fix for the
|
|
235
|
+
// hook-manager early-return that otherwise recreates the lc#806 stale class.
|
|
236
|
+
fs.mkdirSync(path.join(tmpDir, '.husky'), { recursive: true });
|
|
237
|
+
const ssPath = path.join(tmpDir, '.claude', 'hooks', 'SessionStart.cjs');
|
|
238
|
+
fs.mkdirSync(path.dirname(ssPath), { recursive: true });
|
|
239
|
+
fs.writeFileSync(ssPath, `${TOTEM_FILE_MARKER}\nstale\n${TOTEM_FILE_END}\n`);
|
|
240
|
+
errorSpy.mockClear();
|
|
241
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
242
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
243
|
+
// The bounded session hook is drift-repaired even though git hooks were skipped.
|
|
244
|
+
expect(fs.readFileSync(ssPath, 'utf-8')).toBe(CLAUDE_SESSION_START);
|
|
245
|
+
expect(errorOutput()).toContain('Drift-repaired .claude/hooks/SessionStart.cjs session hook (totem-owned bounded region).');
|
|
246
|
+
});
|
|
247
|
+
// ── exit ≠0: genuine failure ────────────────────────────────────
|
|
248
|
+
it('exit ≠0: a genuine hook-write failure propagates as a thrown error', async () => {
|
|
249
|
+
// Make the first-installed hook PATH a directory: the installer's read/write of it
|
|
250
|
+
// throws EISDIR — a genuine FS failure that must PROPAGATE (fail-loud, Tenet 4),
|
|
251
|
+
// not be swallowed into a false `installed`. (A directory-at-path fault is
|
|
252
|
+
// cross-platform, unlike an ESM fs spy — vitest cannot redefine fs namespace exports.)
|
|
253
|
+
fs.mkdirSync(path.join(hooksDir(), 'pre-commit'), { recursive: true });
|
|
254
|
+
await expect(hooksCommand({})).rejects.toThrow();
|
|
255
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
// ── --check: exactly 0/1 ────────────────────────────────────────
|
|
258
|
+
it('--check exit 0: all hooks present with markers', async () => {
|
|
259
|
+
await hooksCommand({});
|
|
260
|
+
errorSpy.mockClear();
|
|
261
|
+
await expect(hooksCommand({ check: true })).resolves.toBeUndefined();
|
|
262
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
263
|
+
expect(errorOutput()).toContain('All hooks installed');
|
|
264
|
+
});
|
|
265
|
+
it('--check exit 1: hooks missing (and the remedy names `totem hook install`)', async () => {
|
|
266
|
+
await expect(hooksCommand({ check: true })).rejects.toThrow('process.exit:1');
|
|
267
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
268
|
+
const out = errorOutput();
|
|
269
|
+
expect(out).toContain('Some hooks are missing');
|
|
270
|
+
expect(out).toContain('totem hook install');
|
|
271
|
+
expect(out).not.toContain('Run `totem hooks`');
|
|
272
|
+
});
|
|
273
|
+
it('--check exit 1: hook present but missing the Totem marker', async () => {
|
|
274
|
+
// Install, then strip the marker from one hook (markerless → not counted).
|
|
275
|
+
await hooksCommand({});
|
|
276
|
+
fs.writeFileSync(path.join(hooksDir(), 'pre-push'), '#!/bin/sh\necho "no marker"\n');
|
|
277
|
+
await expect(hooksCommand({ check: true })).rejects.toThrow('process.exit:1');
|
|
278
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
279
|
+
});
|
|
280
|
+
it('--check is read-only: it does NOT mutate a drifted session hook', async () => {
|
|
281
|
+
// Install git hooks so --check passes on the git side (exit 0), then plant a
|
|
282
|
+
// drifted-but-bounded session hook. --check is a verify-only path and returns
|
|
283
|
+
// before session-hook regeneration, so the drifted hook must be left untouched.
|
|
284
|
+
await hooksCommand({});
|
|
285
|
+
const ssPath = path.join(tmpDir, '.claude', 'hooks', 'SessionStart.cjs');
|
|
286
|
+
fs.mkdirSync(path.dirname(ssPath), { recursive: true });
|
|
287
|
+
const drifted = `${TOTEM_FILE_MARKER}\nstale\n${TOTEM_FILE_END}\n`;
|
|
288
|
+
fs.writeFileSync(ssPath, drifted);
|
|
289
|
+
await expect(hooksCommand({ check: true })).resolves.toBeUndefined();
|
|
290
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
291
|
+
expect(fs.readFileSync(ssPath, 'utf-8')).toBe(drifted);
|
|
292
|
+
});
|
|
293
|
+
// ── the two `overwritten` messages are distinct ─────────────────
|
|
294
|
+
it('bare drift-repair and --force overwrite print DISTINCT messages', async () => {
|
|
295
|
+
await hooksCommand({});
|
|
296
|
+
const staleBounded = `#!/bin/sh\n# ${TOTEM_PREPUSH_MARKER}\nstale\n# ${TOTEM_PREPUSH_END}\n`;
|
|
297
|
+
// Bare (no --force) bounded drift-repair.
|
|
298
|
+
fs.writeFileSync(path.join(hooksDir(), 'pre-push'), staleBounded);
|
|
299
|
+
errorSpy.mockClear();
|
|
300
|
+
await hooksCommand({});
|
|
301
|
+
const bareMsg = errorOutput()
|
|
302
|
+
.split('\n')
|
|
303
|
+
.find((l) => l.includes('pre-push hook'));
|
|
304
|
+
// --force overwrite of the same stale-bounded file.
|
|
305
|
+
fs.writeFileSync(path.join(hooksDir(), 'pre-push'), staleBounded);
|
|
306
|
+
errorSpy.mockClear();
|
|
307
|
+
await hooksCommand({ force: true });
|
|
308
|
+
const forceMsg = errorOutput()
|
|
309
|
+
.split('\n')
|
|
310
|
+
.find((l) => l.includes('pre-push hook'));
|
|
311
|
+
expect(bareMsg).toContain('Drift-repaired');
|
|
312
|
+
expect(forceMsg).toContain('Force-overwritten');
|
|
313
|
+
expect(bareMsg).not.toBe(forceMsg);
|
|
314
|
+
});
|
|
315
|
+
// ── bare install never mutates a non-bounded git hook ───────────
|
|
316
|
+
it('bare install never mutates a user hook with an appended totem block (non-bounded)', async () => {
|
|
317
|
+
const userThenTotem = `#!/bin/sh\nrun_my_tests\n# ${TOTEM_PREPUSH_MARKER}\nstale\n# ${TOTEM_PREPUSH_END}\n`;
|
|
318
|
+
fs.mkdirSync(hooksDir(), { recursive: true });
|
|
319
|
+
fs.writeFileSync(path.join(hooksDir(), 'pre-push'), userThenTotem);
|
|
320
|
+
await expect(hooksCommand({})).resolves.toBeUndefined();
|
|
321
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
322
|
+
// User content preserved — only --force may overwrite an unbounded file.
|
|
323
|
+
expect(fs.readFileSync(path.join(hooksDir(), 'pre-push'), 'utf-8')).toBe(userThenTotem);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
// ─── sanity: installHooksNonInteractive still classifies a fresh repo ─
|
|
327
|
+
describe('installHooksNonInteractive (contract sanity)', () => {
|
|
328
|
+
let tmpDir;
|
|
329
|
+
beforeEach(() => {
|
|
330
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-2410-ni-'));
|
|
331
|
+
});
|
|
332
|
+
afterEach(() => {
|
|
333
|
+
cleanTmpDir(tmpDir);
|
|
334
|
+
});
|
|
335
|
+
it('returns null outside a git repo (declared skip, exit 0 at the command edge)', () => {
|
|
336
|
+
expect(installHooksNonInteractive(tmpDir)).toBeNull();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
//# sourceMappingURL=install-hooks-exit-contract.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-hooks-exit-contract.test.js","sourceRoot":"","sources":["../../src/commands/install-hooks-exit-contract.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -99,6 +99,45 @@ export declare function hooksCommand(opts: {
|
|
|
99
99
|
strict?: boolean;
|
|
100
100
|
standard?: boolean;
|
|
101
101
|
}): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* The action taken on one managed session-hook artifact by
|
|
104
|
+
* {@link regenerateManagedSessionHooks}:
|
|
105
|
+
* - `exists` — present, marker-headed, already byte-identical to canonical (no write).
|
|
106
|
+
* - `overwritten` — regenerated: a bare bounded drift-repair OR a `--force` overwrite.
|
|
107
|
+
* - `declined` — marker present but the region is NOT bounded-owned (legacy file
|
|
108
|
+
* with no end marker, or user content after the end marker) and no
|
|
109
|
+
* `--force`: left untouched, takes one `totem hook install --force`.
|
|
110
|
+
* - `skipped` — a user-owned file carrying NO totem marker at all: never touched,
|
|
111
|
+
* not even under `--force`.
|
|
112
|
+
*/
|
|
113
|
+
export type ManagedSessionHookAction = 'exists' | 'overwritten' | 'declined' | 'skipped';
|
|
114
|
+
export interface ManagedSessionHookResult {
|
|
115
|
+
/** Repo-relative path of the artifact. */
|
|
116
|
+
file: string;
|
|
117
|
+
action: ManagedSessionHookAction;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Walk the {@link MANAGED_SESSION_HOOKS} roster and regenerate the whole-file
|
|
121
|
+
* session-hook artifacts (`.claude/hooks/*.cjs`, `.gemini/hooks/*.js`) that EXIST
|
|
122
|
+
* under `cwd`, applying the #2406 bounded-ownership semantics generalized to the
|
|
123
|
+
* JS/CJS hook family:
|
|
124
|
+
*
|
|
125
|
+
* - Missing file → not created (creation is `totem init`'s job); omitted
|
|
126
|
+
* from the results entirely.
|
|
127
|
+
* - Marker + identical → `exists` (already current).
|
|
128
|
+
* - Marker + drifted, bounded totem-owned whole file → bare drift-repair
|
|
129
|
+
* (`overwritten`), or `--force` → `overwritten`.
|
|
130
|
+
* - Marker + drifted, unbounded (legacy no-end-marker / trailing user content):
|
|
131
|
+
* bare → `declined`; `--force` → `overwritten`.
|
|
132
|
+
* - Marker does not OPEN the file (no marker, or a merely-quoted marker) →
|
|
133
|
+
* `skipped` even under `--force` (never clobber a
|
|
134
|
+
* user-owned file).
|
|
135
|
+
*
|
|
136
|
+
* Regenerate-only-if-present, single-writer per invocation. A write failure
|
|
137
|
+
* (perms/FS) PROPAGATES (Tenet 4 — a hook the tool cannot write must fail loud,
|
|
138
|
+
* never silently report success), mirroring `installGitHook`.
|
|
139
|
+
*/
|
|
140
|
+
export declare function regenerateManagedSessionHooks(cwd: string, force?: boolean): Promise<ManagedSessionHookResult[]>;
|
|
102
141
|
/**
|
|
103
142
|
* Silently upgrade the pre-push hook if it was installed by Totem but uses
|
|
104
143
|
* an old format (flag-checking or command-executing) instead of the new
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-hooks.d.ts","sourceRoot":"","sources":["../../src/commands/install-hooks.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"install-hooks.d.ts","sourceRoot":"","sources":["../../src/commands/install-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -651,7 +651,7 @@ export async function hooksCommand(opts) {
|
|
|
651
651
|
console.error('[Totem] All hooks installed.');
|
|
652
652
|
}
|
|
653
653
|
else {
|
|
654
|
-
console.error('[Totem] Some hooks are missing. Run `totem
|
|
654
|
+
console.error('[Totem] Some hooks are missing. Run `totem hook install` to install.');
|
|
655
655
|
process.exit(1);
|
|
656
656
|
}
|
|
657
657
|
return;
|
|
@@ -682,32 +682,140 @@ export async function hooksCommand(opts) {
|
|
|
682
682
|
tier = 'standard';
|
|
683
683
|
}
|
|
684
684
|
const result = installHooksNonInteractive(cwd, opts.force, { tier });
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
685
|
+
// The git-hook summary prints ONLY when git hooks were actually written. A null
|
|
686
|
+
// result means a hook manager (husky/lefthook) was detected and
|
|
687
|
+
// installHooksNonInteractive already printed its guidance — but this MUST NOT
|
|
688
|
+
// early-return: the managed session hooks below are Claude/Gemini artifacts,
|
|
689
|
+
// independent of any git-hook manager, and must still be regenerated (a
|
|
690
|
+
// hook-manager repo otherwise recreates the lc#806 stale-session-hook class).
|
|
691
|
+
if (result) {
|
|
692
|
+
const actions = [
|
|
693
|
+
{ name: 'pre-commit', status: result.preCommit },
|
|
694
|
+
{ name: 'pre-push', status: result.prePush },
|
|
695
|
+
{ name: 'post-merge', status: result.postMerge },
|
|
696
|
+
{ name: 'post-checkout', status: result.postCheckout },
|
|
697
|
+
];
|
|
698
|
+
for (const { name, status } of actions) {
|
|
699
|
+
switch (status) {
|
|
700
|
+
case 'installed':
|
|
701
|
+
console.error(`[Totem] Installed ${name} hook.`);
|
|
702
|
+
break;
|
|
703
|
+
case 'appended':
|
|
704
|
+
console.error(`[Totem] Appended Totem to existing ${name} hook.`);
|
|
705
|
+
break;
|
|
706
|
+
case 'exists':
|
|
707
|
+
console.error(`[Totem] ${name} hook already installed.`);
|
|
708
|
+
break;
|
|
709
|
+
case 'overwritten':
|
|
710
|
+
// `installGitHook` returns `overwritten` for BOTH a forced overwrite and a
|
|
711
|
+
// bare (no-force) drift-repair of a totem-owned bounded region. Print the
|
|
712
|
+
// truthful cause: "Force-overwritten" ONLY when --force was actually passed,
|
|
713
|
+
// "Drift-repaired" for the bare bounded self-repair (mmnto-ai/totem#2410 —
|
|
714
|
+
// fixes the misleading always-"Force-overwritten" message).
|
|
715
|
+
console.error(opts.force
|
|
716
|
+
? `[Totem] Force-overwritten ${name} hook.`
|
|
717
|
+
: `[Totem] Drift-repaired ${name} hook (totem-owned bounded region).`);
|
|
718
|
+
break;
|
|
719
|
+
case 'skipped-non-shell':
|
|
720
|
+
console.error(`[Totem] Warning: ${name} hook uses a non-shell interpreter. Integrate manually.`);
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
688
724
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
725
|
+
// ── Managed session-hook regeneration (mmnto-ai/totem#2410 PR-A slice 3) ──
|
|
726
|
+
// Runs on BOTH the manager and no-manager paths (the `if (result)` above only
|
|
727
|
+
// gates the git-hook summary): the `.claude/hooks/*.cjs` and `.gemini/hooks/*.js`
|
|
728
|
+
// artifacts are Claude/Gemini hooks, not git hooks, so they are regenerated
|
|
729
|
+
// whether or not a git-hook manager (husky/lefthook) is in play. The `--check`
|
|
730
|
+
// and not-a-git-repo paths already returned above, so this never fires for the
|
|
731
|
+
// read-only verify or the honest-skip. Regenerate-only-if-present: creation stays
|
|
732
|
+
// with `totem init`; this verb repairs drift in artifacts the repo already adopted.
|
|
733
|
+
await printManagedSessionHookSummary(gitRoot, opts.force);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Walk the {@link MANAGED_SESSION_HOOKS} roster and regenerate the whole-file
|
|
737
|
+
* session-hook artifacts (`.claude/hooks/*.cjs`, `.gemini/hooks/*.js`) that EXIST
|
|
738
|
+
* under `cwd`, applying the #2406 bounded-ownership semantics generalized to the
|
|
739
|
+
* JS/CJS hook family:
|
|
740
|
+
*
|
|
741
|
+
* - Missing file → not created (creation is `totem init`'s job); omitted
|
|
742
|
+
* from the results entirely.
|
|
743
|
+
* - Marker + identical → `exists` (already current).
|
|
744
|
+
* - Marker + drifted, bounded totem-owned whole file → bare drift-repair
|
|
745
|
+
* (`overwritten`), or `--force` → `overwritten`.
|
|
746
|
+
* - Marker + drifted, unbounded (legacy no-end-marker / trailing user content):
|
|
747
|
+
* bare → `declined`; `--force` → `overwritten`.
|
|
748
|
+
* - Marker does not OPEN the file (no marker, or a merely-quoted marker) →
|
|
749
|
+
* `skipped` even under `--force` (never clobber a
|
|
750
|
+
* user-owned file).
|
|
751
|
+
*
|
|
752
|
+
* Regenerate-only-if-present, single-writer per invocation. A write failure
|
|
753
|
+
* (perms/FS) PROPAGATES (Tenet 4 — a hook the tool cannot write must fail loud,
|
|
754
|
+
* never silently report success), mirroring `installGitHook`.
|
|
755
|
+
*/
|
|
756
|
+
export async function regenerateManagedSessionHooks(cwd, force) {
|
|
757
|
+
// Dynamic-import the roster (large canonical template strings) + the shared
|
|
758
|
+
// ownership helpers so init-templates stays off the CLI cold-start graph
|
|
759
|
+
// (packages/cli lazy-load guideline; mirrors doctor-parity.ts's own import).
|
|
760
|
+
// `isBoundedOwnedFile` is the single shared session-hook ownership checker
|
|
761
|
+
// (mmnto-ai/totem#2413 — was a divergent twin of init's local copy).
|
|
762
|
+
const { MANAGED_SESSION_HOOKS, isBoundedOwnedFile, markerOpensFile } = await import('./init-templates.js');
|
|
763
|
+
const results = [];
|
|
764
|
+
for (const { rel, content, marker, endMarker } of MANAGED_SESSION_HOOKS) {
|
|
765
|
+
const filePath = path.join(cwd, ...rel.split('/'));
|
|
766
|
+
if (!fs.existsSync(filePath)) {
|
|
767
|
+
// Regenerate-only-if-present: never create a session hook the repo opted out of.
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
771
|
+
// Positional ownership gate (mmnto-ai/totem#2413): the marker must OPEN the file.
|
|
772
|
+
// A user-owned file with NO marker — or one that merely QUOTES the marker string in
|
|
773
|
+
// a comment/string — is never touched, not even under --force. (The old
|
|
774
|
+
// `includes(marker)` gate let a quoting user file be clobbered by --force, breaking
|
|
775
|
+
// the "no marker → never touched, even forced" contract.)
|
|
776
|
+
if (!markerOpensFile(existing, marker)) {
|
|
777
|
+
results.push({ file: rel, action: 'skipped' });
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (existing === content) {
|
|
781
|
+
results.push({ file: rel, action: 'exists' });
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
// Content differs. --force overwrites any marker-headed file (bounded or not);
|
|
785
|
+
// a bare run repairs only a bounded totem-OWNED whole file, and declines the rest.
|
|
786
|
+
if (force || isBoundedOwnedFile(existing, marker, endMarker)) {
|
|
787
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
788
|
+
results.push({ file: rel, action: 'overwritten' });
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
results.push({ file: rel, action: 'declined' });
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return results;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Regenerate the managed session hooks and print one summary line per artifact,
|
|
798
|
+
* mirroring the git-hook summary. The `overwritten` line distinguishes a forced
|
|
799
|
+
* overwrite from a bare bounded drift-repair (same truthful split the git-hook
|
|
800
|
+
* summary uses).
|
|
801
|
+
*/
|
|
802
|
+
async function printManagedSessionHookSummary(cwd, force) {
|
|
803
|
+
const results = await regenerateManagedSessionHooks(cwd, force);
|
|
804
|
+
for (const { file, action } of results) {
|
|
805
|
+
switch (action) {
|
|
703
806
|
case 'exists':
|
|
704
|
-
console.error(`[Totem] ${
|
|
807
|
+
console.error(`[Totem] ${file} session hook already current.`);
|
|
705
808
|
break;
|
|
706
809
|
case 'overwritten':
|
|
707
|
-
console.error(
|
|
810
|
+
console.error(force
|
|
811
|
+
? `[Totem] Force-overwritten ${file} session hook.`
|
|
812
|
+
: `[Totem] Drift-repaired ${file} session hook (totem-owned bounded region).`);
|
|
813
|
+
break;
|
|
814
|
+
case 'declined':
|
|
815
|
+
console.error(`[Totem] ${file} session hook has drifted but is not a bounded totem-owned region — run \`totem hook install --force\` to regenerate.`);
|
|
708
816
|
break;
|
|
709
|
-
case 'skipped
|
|
710
|
-
console.error(`[Totem]
|
|
817
|
+
case 'skipped':
|
|
818
|
+
console.error(`[Totem] ${file}: user-owned file (no Totem marker) — left untouched.`);
|
|
711
819
|
break;
|
|
712
820
|
}
|
|
713
821
|
}
|