@mmnto/cli 1.34.3 → 1.36.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/eject.d.ts.map +1 -1
- package/dist/commands/eject.js +64 -2
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +51 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/hook-run.d.ts +91 -0
- package/dist/commands/hook-run.d.ts.map +1 -0
- package/dist/commands/hook-run.js +149 -0
- package/dist/commands/hook-run.js.map +1 -0
- package/dist/commands/hook-run.test.d.ts +2 -0
- package/dist/commands/hook-run.test.d.ts.map +1 -0
- package/dist/commands/hook-run.test.js +264 -0
- package/dist/commands/hook-run.test.js.map +1 -0
- package/dist/commands/hook-test.d.ts +29 -0
- package/dist/commands/hook-test.d.ts.map +1 -0
- package/dist/commands/hook-test.js +132 -0
- package/dist/commands/hook-test.js.map +1 -0
- package/dist/commands/init-templates.d.ts +11 -0
- package/dist/commands/init-templates.d.ts.map +1 -1
- package/dist/commands/init-templates.js +119 -0
- package/dist/commands/init-templates.js.map +1 -1
- package/dist/commands/init.d.ts +21 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +90 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +91 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/hook/classification.d.ts +45 -0
- package/dist/hook/classification.d.ts.map +1 -0
- package/dist/hook/classification.js +24 -0
- package/dist/hook/classification.js.map +1 -0
- package/dist/hook/classification.test.d.ts +2 -0
- package/dist/hook/classification.test.d.ts.map +1 -0
- package/dist/hook/classification.test.js +40 -0
- package/dist/hook/classification.test.js.map +1 -0
- package/dist/hook/loader.d.ts +47 -0
- package/dist/hook/loader.d.ts.map +1 -0
- package/dist/hook/loader.js +66 -0
- package/dist/hook/loader.js.map +1 -0
- package/dist/hook/loader.test.d.ts +2 -0
- package/dist/hook/loader.test.d.ts.map +1 -0
- package/dist/hook/loader.test.js +205 -0
- package/dist/hook/loader.test.js.map +1 -0
- package/dist/hook/runtime.d.ts +47 -0
- package/dist/hook/runtime.d.ts.map +1 -0
- package/dist/hook/runtime.js +85 -0
- package/dist/hook/runtime.js.map +1 -0
- package/dist/hook/runtime.test.d.ts +2 -0
- package/dist/hook/runtime.test.d.ts.map +1 -0
- package/dist/hook/runtime.test.js +135 -0
- package/dist/hook/runtime.test.js.map +1 -0
- package/dist/hook/schema.d.ts +385 -0
- package/dist/hook/schema.d.ts.map +1 -0
- package/dist/hook/schema.js +164 -0
- package/dist/hook/schema.js.map +1 -0
- package/dist/hook/schema.test.d.ts +2 -0
- package/dist/hook/schema.test.d.ts.map +1 -0
- package/dist/hook/schema.test.js +233 -0
- package/dist/hook/schema.test.js.map +1 -0
- package/dist/hook/test-runner.d.ts +64 -0
- package/dist/hook/test-runner.d.ts.map +1 -0
- package/dist/hook/test-runner.js +57 -0
- package/dist/hook/test-runner.js.map +1 -0
- package/dist/hook/test-runner.test.d.ts +2 -0
- package/dist/hook/test-runner.test.d.ts.map +1 -0
- package/dist/hook/test-runner.test.js +237 -0
- package/dist/hook/test-runner.test.js.map +1 -0
- package/dist/index.js +57 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { executeHookRun, EXIT_ALLOW, PRE_TOOL_USE_BLOCK_EXIT_CODE as EXIT_BLOCK, resolveInstalledPackVersions, } from './hook-run.js';
|
|
6
|
+
/** Schema version no live runner supports — used to assert warn-and-skip on unknown manifest versions. */
|
|
7
|
+
const UNSUPPORTED_SCHEMA_VERSION = 999;
|
|
8
|
+
let workDir;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-hook-run-'));
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
function writeManifest(content) {
|
|
16
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
17
|
+
fs.writeFileSync(manifestPath, JSON.stringify(content), 'utf8');
|
|
18
|
+
return manifestPath;
|
|
19
|
+
}
|
|
20
|
+
const baseRule = {
|
|
21
|
+
id: 'gca-tag-xor-command',
|
|
22
|
+
packId: '@mmnto/pack-bot-gemini-code-assist',
|
|
23
|
+
trigger: { tool: 'bash', pattern: 'gh\\s+(pr|issue)\\s+comment' },
|
|
24
|
+
check: {
|
|
25
|
+
pattern: '(?=.*@gemini-code-assist)(?=.*\\/gemini review)',
|
|
26
|
+
type: 'reject-if-match',
|
|
27
|
+
},
|
|
28
|
+
message: 'GCA tag XOR command — never both; doubling wastes GCA quota.',
|
|
29
|
+
recoveryHint: 'Choose one: @-mention to comment, /gemini review for fresh review.',
|
|
30
|
+
};
|
|
31
|
+
function baseManifest(hooks = [baseRule]) {
|
|
32
|
+
return {
|
|
33
|
+
schemaVersion: 1,
|
|
34
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
35
|
+
sourcePackVersions: {
|
|
36
|
+
'@mmnto/pack-bot-gemini-code-assist': '1.0.0',
|
|
37
|
+
},
|
|
38
|
+
hooks,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
describe('executeHookRun — allow path', () => {
|
|
42
|
+
it('returns exit 0 with no stderr when the manifest file is absent (fresh repo)', () => {
|
|
43
|
+
const result = executeHookRun({
|
|
44
|
+
manifestPath: path.join(workDir, 'missing.json'),
|
|
45
|
+
installedPackVersions: {},
|
|
46
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
47
|
+
});
|
|
48
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
49
|
+
expect(result.stderr).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it('returns exit 0 when no hook trigger matches the payload', () => {
|
|
52
|
+
const manifestPath = writeManifest(baseManifest());
|
|
53
|
+
const result = executeHookRun({
|
|
54
|
+
manifestPath,
|
|
55
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
56
|
+
payload: { tool: 'write', args: 'console.log("hi")' },
|
|
57
|
+
});
|
|
58
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
59
|
+
expect(result.stderr).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
it('returns exit 0 when the trigger matches but the check does not (reject-if-match)', () => {
|
|
62
|
+
const manifestPath = writeManifest(baseManifest());
|
|
63
|
+
const result = executeHookRun({
|
|
64
|
+
manifestPath,
|
|
65
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
66
|
+
payload: {
|
|
67
|
+
tool: 'bash',
|
|
68
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist take a look"',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
72
|
+
expect(result.stderr).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('executeHookRun — reject path (ADR-104 § Decision 1)', () => {
|
|
76
|
+
it('exits 2 with the structured [totem:hook-block] line on the first matching hook', () => {
|
|
77
|
+
const manifestPath = writeManifest(baseManifest());
|
|
78
|
+
const result = executeHookRun({
|
|
79
|
+
manifestPath,
|
|
80
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
81
|
+
payload: {
|
|
82
|
+
tool: 'bash',
|
|
83
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review please"',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
87
|
+
const rejectLine = result.stderr[result.stderr.length - 1];
|
|
88
|
+
expect(rejectLine).toContain('[totem:hook-block]');
|
|
89
|
+
expect(rejectLine).toContain('@mmnto/pack-bot-gemini-code-assist/gca-tag-xor-command');
|
|
90
|
+
expect(rejectLine).toContain('GCA tag XOR command');
|
|
91
|
+
expect(rejectLine).toContain('→ Choose one');
|
|
92
|
+
});
|
|
93
|
+
it('first-match-wins: a later hook that also matches is not evaluated', () => {
|
|
94
|
+
const second = {
|
|
95
|
+
...baseRule,
|
|
96
|
+
id: 'never-reached',
|
|
97
|
+
message: 'should not appear in stderr',
|
|
98
|
+
};
|
|
99
|
+
const manifestPath = writeManifest(baseManifest([baseRule, second]));
|
|
100
|
+
const result = executeHookRun({
|
|
101
|
+
manifestPath,
|
|
102
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
103
|
+
payload: {
|
|
104
|
+
tool: 'bash',
|
|
105
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
109
|
+
expect(result.stderr.some((l) => l.includes('never-reached'))).toBe(false);
|
|
110
|
+
expect(result.stderr.some((l) => l.includes('should not appear'))).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
it('omits the recoveryHint line when the rule has no recoveryHint', () => {
|
|
113
|
+
const ruleWithoutHint = { ...baseRule, recoveryHint: undefined };
|
|
114
|
+
const manifestPath = writeManifest(baseManifest([ruleWithoutHint]));
|
|
115
|
+
const result = executeHookRun({
|
|
116
|
+
manifestPath,
|
|
117
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.0.0' },
|
|
118
|
+
payload: {
|
|
119
|
+
tool: 'bash',
|
|
120
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
124
|
+
const rejectLine = result.stderr[result.stderr.length - 1];
|
|
125
|
+
expect(rejectLine).not.toContain('→');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
describe('executeHookRun — diagnostics ordering', () => {
|
|
129
|
+
it('emits [totem:hook-stale] warnings before evaluating hooks (operator sees context for any rejection)', () => {
|
|
130
|
+
const manifestPath = writeManifest(baseManifest());
|
|
131
|
+
const result = executeHookRun({
|
|
132
|
+
manifestPath,
|
|
133
|
+
installedPackVersions: { '@mmnto/pack-bot-gemini-code-assist': '1.1.0' },
|
|
134
|
+
payload: {
|
|
135
|
+
tool: 'bash',
|
|
136
|
+
args: 'gh pr comment 1 --body "@gemini-code-assist /gemini review"',
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
expect(result.exitCode).toBe(EXIT_BLOCK);
|
|
140
|
+
const staleIdx = result.stderr.findIndex((l) => l.includes('[totem:hook-stale]'));
|
|
141
|
+
const rejectIdx = result.stderr.findIndex((l) => l.includes('[totem:hook-block]'));
|
|
142
|
+
expect(staleIdx).toBeGreaterThanOrEqual(0);
|
|
143
|
+
expect(rejectIdx).toBeGreaterThan(staleIdx);
|
|
144
|
+
});
|
|
145
|
+
it('emits [totem:hook-schema] warning and returns allow when manifest schemaVersion is unsupported', () => {
|
|
146
|
+
const manifestPath = writeManifest({
|
|
147
|
+
...baseManifest(),
|
|
148
|
+
schemaVersion: UNSUPPORTED_SCHEMA_VERSION,
|
|
149
|
+
});
|
|
150
|
+
const result = executeHookRun({
|
|
151
|
+
manifestPath,
|
|
152
|
+
installedPackVersions: {},
|
|
153
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
154
|
+
});
|
|
155
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
156
|
+
expect(result.stderr.some((l) => l.includes('[totem:hook-schema]'))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('emits [totem:hook-error] prefix on structural manifest errors (corrupt JSON)', () => {
|
|
159
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
160
|
+
fs.writeFileSync(manifestPath, '{ not valid json', 'utf8');
|
|
161
|
+
const result = executeHookRun({
|
|
162
|
+
manifestPath,
|
|
163
|
+
installedPackVersions: {},
|
|
164
|
+
payload: { tool: 'bash', args: 'whatever' },
|
|
165
|
+
});
|
|
166
|
+
expect(result.exitCode).toBe(EXIT_ALLOW);
|
|
167
|
+
expect(result.stderr.some((l) => l.startsWith('[totem:hook-error]'))).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
describe('resolveInstalledPackVersions', () => {
|
|
171
|
+
it('returns an empty map when no @mmnto scope directory exists', () => {
|
|
172
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
173
|
+
expect(result).toEqual({});
|
|
174
|
+
});
|
|
175
|
+
it('reads version from each @mmnto/pack-* package.json under node_modules', () => {
|
|
176
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
177
|
+
const packDir = path.join(scopeDir, 'pack-bot-coderabbit');
|
|
178
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
179
|
+
fs.writeFileSync(path.join(packDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-bot-coderabbit', version: '1.2.3' }), 'utf8');
|
|
180
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
181
|
+
expect(result).toEqual({ '@mmnto/pack-bot-coderabbit': '1.2.3' });
|
|
182
|
+
});
|
|
183
|
+
it('skips non-pack-* @mmnto packages (e.g. @mmnto/totem itself)', () => {
|
|
184
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
185
|
+
const corePkg = path.join(scopeDir, 'totem');
|
|
186
|
+
fs.mkdirSync(corePkg, { recursive: true });
|
|
187
|
+
fs.writeFileSync(path.join(corePkg, 'package.json'), JSON.stringify({ name: '@mmnto/totem', version: '1.36.0' }), 'utf8');
|
|
188
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
189
|
+
expect(result).toEqual({});
|
|
190
|
+
});
|
|
191
|
+
it('rethrows non-ENOENT readdirSync errors (e.g. scope-dir-is-a-file)', () => {
|
|
192
|
+
// Make `<projectRoot>/node_modules/@mmnto` a regular file so readdirSync
|
|
193
|
+
// fails with a non-ENOENT errno (ENOTDIR on POSIX, varies on Windows but
|
|
194
|
+
// never ENOENT). The function must surface that fault rather than mask
|
|
195
|
+
// it as "no packs installed."
|
|
196
|
+
const scopePath = path.join(workDir, 'node_modules', '@mmnto');
|
|
197
|
+
fs.mkdirSync(path.dirname(scopePath), { recursive: true });
|
|
198
|
+
fs.writeFileSync(scopePath, 'not a directory', 'utf8');
|
|
199
|
+
expect(() => resolveInstalledPackVersions(workDir)).toThrow();
|
|
200
|
+
});
|
|
201
|
+
it('reads from symlinked pack directories (pnpm/yarn workspaces case)', () => {
|
|
202
|
+
// Regression: Dirent.isDirectory() returns false for symlinks even when
|
|
203
|
+
// the target is a directory. Workspace setups symlink packs into
|
|
204
|
+
// `node_modules/@mmnto/`, so a pre-filter on `entry.isDirectory()` would
|
|
205
|
+
// have silently skipped every workspace-linked pack. The function now
|
|
206
|
+
// drops the type pre-filter and lets the `readFileSync` traversal +
|
|
207
|
+
// try/catch handle it.
|
|
208
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
209
|
+
fs.mkdirSync(scopeDir, { recursive: true });
|
|
210
|
+
// Target directory (the "source" of the pack).
|
|
211
|
+
const sourceDir = path.join(workDir, 'workspace-pack-source');
|
|
212
|
+
fs.mkdirSync(sourceDir);
|
|
213
|
+
fs.writeFileSync(path.join(sourceDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-workspace', version: '2.0.0' }), 'utf8');
|
|
214
|
+
// Symlink it into node_modules/@mmnto/pack-workspace. Skip the test on
|
|
215
|
+
// platforms that do not grant the calling process permission to create
|
|
216
|
+
// directory symlinks (Windows without developer mode / admin).
|
|
217
|
+
try {
|
|
218
|
+
fs.symlinkSync(sourceDir, path.join(scopeDir, 'pack-workspace'), 'dir');
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
if (err.code === 'EPERM')
|
|
222
|
+
return;
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
226
|
+
expect(result).toEqual({ '@mmnto/pack-workspace': '2.0.0' });
|
|
227
|
+
});
|
|
228
|
+
it('rethrows unusual per-pack errors (not in the expected errno set, not SyntaxError)', () => {
|
|
229
|
+
// The per-pack catch suppresses ENOENT/ENOTDIR/EPERM/EACCES (expected
|
|
230
|
+
// dir/permission quirks) and SyntaxError (malformed package.json), but
|
|
231
|
+
// any other exception class is treated as a real fault that surfaces.
|
|
232
|
+
// Mock readFileSync to throw a synthetic non-errno error and assert the
|
|
233
|
+
// function propagates it rather than silently skipping the pack.
|
|
234
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
235
|
+
const packDir = path.join(scopeDir, 'pack-unusual');
|
|
236
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
237
|
+
fs.writeFileSync(path.join(packDir, 'package.json'), JSON.stringify({ name: '@mmnto/pack-unusual', version: '1.0.0' }), 'utf8');
|
|
238
|
+
// `resolveInstalledPackVersions` only calls `readFileSync` for pack
|
|
239
|
+
// package.json files, so a blanket-throw mock exercises the inner catch
|
|
240
|
+
// without affecting other code paths.
|
|
241
|
+
const unusual = new Error('synthetic IO timeout');
|
|
242
|
+
const spy = vi.spyOn(fs, 'readFileSync').mockImplementation(() => {
|
|
243
|
+
throw unusual;
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
expect(() => resolveInstalledPackVersions(workDir)).toThrow(unusual);
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
spy.mockRestore();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
it('skips directory entries with unreadable or malformed package.json without failing the whole scan', () => {
|
|
253
|
+
const scopeDir = path.join(workDir, 'node_modules', '@mmnto');
|
|
254
|
+
const broken = path.join(scopeDir, 'pack-broken');
|
|
255
|
+
fs.mkdirSync(broken, { recursive: true });
|
|
256
|
+
fs.writeFileSync(path.join(broken, 'package.json'), '{ not json', 'utf8');
|
|
257
|
+
const good = path.join(scopeDir, 'pack-good');
|
|
258
|
+
fs.mkdirSync(good, { recursive: true });
|
|
259
|
+
fs.writeFileSync(path.join(good, 'package.json'), JSON.stringify({ name: '@mmnto/pack-good', version: '1.0.0' }), 'utf8');
|
|
260
|
+
const result = resolveInstalledPackVersions(workDir);
|
|
261
|
+
expect(result).toEqual({ '@mmnto/pack-good': '1.0.0' });
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=hook-run.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-run.test.js","sourceRoot":"","sources":["../../src/commands/hook-run.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HookTestResult, HookTestSummary } from '../hook/test-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* `totem hook test [--filter <term>]` — run fixture-based verification for
|
|
4
|
+
* compiled bot-pack hooks (ADR-104 § Convergence).
|
|
5
|
+
*
|
|
6
|
+
* Loads `surface: hooks` fixtures from `.totem/tests/`, evaluates each
|
|
7
|
+
* against the matching compiled hook, and reports per-line failures so
|
|
8
|
+
* authors can iterate on specific payloads. The runner is deterministic
|
|
9
|
+
* Node.js — no LLM calls — mirroring the `totem hook run` contract.
|
|
10
|
+
*
|
|
11
|
+
* `--filter <term>` matches against hook id (case-insensitive substring).
|
|
12
|
+
*/
|
|
13
|
+
export interface HookTestCommandOptions {
|
|
14
|
+
filter?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply `--filter` to a hook-test summary, failing loud when the filter
|
|
18
|
+
* matches nothing despite fixtures being present. Pure helper extracted
|
|
19
|
+
* from `hookTestCommand` so the filter contract is unit-testable without
|
|
20
|
+
* driving the command end-to-end through `process.cwd()` + `loadConfig`.
|
|
21
|
+
*
|
|
22
|
+
* Returns the filtered result slice when the filter matches (or is absent).
|
|
23
|
+
* Throws TEST_FAILED when `--filter` is set, `summary.total > 0`, and the
|
|
24
|
+
* filter matches no fixtures — a typoed filter must never look like a
|
|
25
|
+
* successful zero-test run.
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyFilter(summary: HookTestSummary, filter: string | undefined): HookTestResult[];
|
|
28
|
+
export declare function hookTestCommand(opts: HookTestCommandOptions): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=hook-test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-test.d.ts","sourceRoot":"","sources":["../../src/commands/hook-test.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAW9E;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,cAAc,EAAE,CAYlB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwHjF"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { sanitize, TotemError } from '@mmnto/totem';
|
|
3
|
+
// Static `TotemError` import is intentional: `applyFilter` below is a pure
|
|
4
|
+
// synchronous helper that throws TEST_FAILED on a typoed `--filter`, and an
|
|
5
|
+
// async wrapper would defeat the testability win of extracting it. The
|
|
6
|
+
// per-codebase guideline against top-level heavy internal value imports is
|
|
7
|
+
// applied to `runHookTests` and `resolveInstalledPackVersions` (lazy-loaded
|
|
8
|
+
// inside the command handler below) instead.
|
|
9
|
+
const TAG = 'HookTest';
|
|
10
|
+
/**
|
|
11
|
+
* Apply `--filter` to a hook-test summary, failing loud when the filter
|
|
12
|
+
* matches nothing despite fixtures being present. Pure helper extracted
|
|
13
|
+
* from `hookTestCommand` so the filter contract is unit-testable without
|
|
14
|
+
* driving the command end-to-end through `process.cwd()` + `loadConfig`.
|
|
15
|
+
*
|
|
16
|
+
* Returns the filtered result slice when the filter matches (or is absent).
|
|
17
|
+
* Throws TEST_FAILED when `--filter` is set, `summary.total > 0`, and the
|
|
18
|
+
* filter matches no fixtures — a typoed filter must never look like a
|
|
19
|
+
* successful zero-test run.
|
|
20
|
+
*/
|
|
21
|
+
export function applyFilter(summary, filter) {
|
|
22
|
+
if (!filter)
|
|
23
|
+
return summary.results;
|
|
24
|
+
const term = filter.toLowerCase();
|
|
25
|
+
const filtered = summary.results.filter((r) => r.hookId.toLowerCase().includes(term));
|
|
26
|
+
if (filtered.length === 0 && summary.total > 0) {
|
|
27
|
+
throw new TotemError('TEST_FAILED', `No hook tests matched --filter "${sanitize(filter)}".`, 'Use an existing hook id substring or omit --filter to run all hook tests.');
|
|
28
|
+
}
|
|
29
|
+
return filtered;
|
|
30
|
+
}
|
|
31
|
+
export async function hookTestCommand(opts) {
|
|
32
|
+
const { log, bold, errorColor, success: successColor } = await import('../ui.js');
|
|
33
|
+
const { loadConfig, loadEnv, resolveConfigPath } = await import('../utils.js');
|
|
34
|
+
const { runHookTests } = await import('../hook/test-runner.js');
|
|
35
|
+
const { resolveInstalledPackVersions } = await import('./hook-run.js');
|
|
36
|
+
const cwd = process.cwd();
|
|
37
|
+
loadEnv(cwd);
|
|
38
|
+
const configPath = resolveConfigPath(cwd);
|
|
39
|
+
const config = await loadConfig(configPath);
|
|
40
|
+
const configRoot = path.dirname(configPath);
|
|
41
|
+
const manifestPath = path.join(configRoot, config.totemDir, 'compiled-hooks.json');
|
|
42
|
+
const testsDir = path.join(configRoot, config.totemDir, 'tests');
|
|
43
|
+
const installedPackVersions = resolveInstalledPackVersions(configRoot);
|
|
44
|
+
log.info(TAG, 'Running hook tests...');
|
|
45
|
+
const summary = runHookTests({ manifestPath, testsDir, installedPackVersions });
|
|
46
|
+
// Sanitize every value that flows in from pack-supplied data (manifest
|
|
47
|
+
// contents, fixture paths) before it reaches the terminal — third-party
|
|
48
|
+
// packs are an untrusted boundary and identifiers like `hookId`/`packId`
|
|
49
|
+
// could carry ANSI control sequences that would mangle the operator's
|
|
50
|
+
// shell. The loader-emitted warnings/errors carry our own message text
|
|
51
|
+
// but may interpolate user-controlled paths, so they get sanitized too.
|
|
52
|
+
for (const w of summary.loadWarnings) {
|
|
53
|
+
log.warn(TAG, sanitize(w));
|
|
54
|
+
}
|
|
55
|
+
for (const e of summary.loadErrors) {
|
|
56
|
+
log.error('Totem Error', `${sanitize(e.code)}: ${sanitize(e.message)}`);
|
|
57
|
+
}
|
|
58
|
+
for (const unknown of summary.unknownHooks) {
|
|
59
|
+
const safeFixture = sanitize(path.basename(unknown.fixturePath));
|
|
60
|
+
const safeHookId = sanitize(unknown.hookId);
|
|
61
|
+
log.warn(TAG, `Fixture ${safeFixture} references hook id "${safeHookId}" not present in compiled manifest`);
|
|
62
|
+
}
|
|
63
|
+
const results = applyFilter(summary, opts.filter);
|
|
64
|
+
// "No fixtures" only fires when no fixtures exist at all — neither
|
|
65
|
+
// evaluatable results nor orphans referencing an absent hook. A directory
|
|
66
|
+
// containing only orphan fixtures must fail loud below, not show the
|
|
67
|
+
// "create a fixture" placeholder.
|
|
68
|
+
if (results.length === 0 &&
|
|
69
|
+
summary.total === 0 &&
|
|
70
|
+
summary.unknownHooks.length === 0 &&
|
|
71
|
+
summary.loadErrors.length === 0) {
|
|
72
|
+
log.dim(TAG, `No hook fixtures (surface: hooks) found in ${sanitize(config.totemDir)}/tests/`);
|
|
73
|
+
log.dim(TAG, 'Create a fixture with:');
|
|
74
|
+
log.dim(TAG, '');
|
|
75
|
+
log.dim(TAG, ' ---');
|
|
76
|
+
log.dim(TAG, ' rule: <hook-id>');
|
|
77
|
+
log.dim(TAG, ' surface: hooks');
|
|
78
|
+
log.dim(TAG, ' corpus: fail');
|
|
79
|
+
log.dim(TAG, ' ---');
|
|
80
|
+
log.dim(TAG, '');
|
|
81
|
+
log.dim(TAG, ' ## Should fail');
|
|
82
|
+
log.dim(TAG, ' ```text');
|
|
83
|
+
log.dim(TAG, ' <args payload that should be rejected>');
|
|
84
|
+
log.dim(TAG, ' ```');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const result of results) {
|
|
88
|
+
const label = `${sanitize(result.packId)}/${sanitize(result.hookId)}`;
|
|
89
|
+
if (result.passed) {
|
|
90
|
+
log.success(TAG, `${label} — PASS`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
log.error('Totem Error', `${label} — FAIL`);
|
|
94
|
+
for (const failure of result.failures) {
|
|
95
|
+
const direction = failure.expected === 'reject' ? 'missed reject' : 'false positive';
|
|
96
|
+
console.error(` [${direction}] expected ${failure.expected}, got ${failure.actual}`);
|
|
97
|
+
console.error(` payload: ${sanitize(failure.line.trim())}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const passedCount = results.filter((r) => r.passed).length;
|
|
101
|
+
const failedCount = results.length - passedCount;
|
|
102
|
+
console.error('');
|
|
103
|
+
// Tenet 4: load errors or orphan fixtures must fail loud — they are
|
|
104
|
+
// never "success" states. A corrupt manifest or a fixture pointing at a
|
|
105
|
+
// typoed hook id silently passing would mask broken pack wiring.
|
|
106
|
+
const hasOrphans = summary.unknownHooks.length > 0;
|
|
107
|
+
const hasLoadErrors = summary.loadErrors.length > 0;
|
|
108
|
+
if (failedCount === 0 && !hasOrphans && !hasLoadErrors) {
|
|
109
|
+
const label = successColor(bold('PASS'));
|
|
110
|
+
log.info(TAG, `${label} — ${passedCount} hook test(s) passed`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const label = errorColor(bold('FAIL'));
|
|
114
|
+
const parts = [];
|
|
115
|
+
if (failedCount > 0)
|
|
116
|
+
parts.push(`${failedCount} failed`);
|
|
117
|
+
if (hasOrphans)
|
|
118
|
+
parts.push(`${summary.unknownHooks.length} unknown-hook reference(s)`);
|
|
119
|
+
if (hasLoadErrors)
|
|
120
|
+
parts.push(`${summary.loadErrors.length} manifest load error(s)`);
|
|
121
|
+
parts.push(`${passedCount} passed`);
|
|
122
|
+
log.info(TAG, `${label} — ${parts.join(', ')}`);
|
|
123
|
+
const reasons = [];
|
|
124
|
+
if (failedCount > 0)
|
|
125
|
+
reasons.push(`${failedCount} hook test(s) failed`);
|
|
126
|
+
if (hasOrphans)
|
|
127
|
+
reasons.push(`${summary.unknownHooks.length} fixture(s) reference unknown hook id`);
|
|
128
|
+
if (hasLoadErrors)
|
|
129
|
+
reasons.push(`${summary.loadErrors.length} compiled-hooks manifest load error(s)`);
|
|
130
|
+
throw new TotemError('TEST_FAILED', reasons.join('; ') + '.', 'Fix failing patterns, orphan fixtures, or manifest issues, then re-run `totem hook test`.');
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=hook-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-test.js","sourceRoot":"","sources":["../../src/commands/hook-test.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,17 @@ export declare const CLAUDE_SESSION_START_ENTRY: {
|
|
|
35
35
|
timeout: number;
|
|
36
36
|
}[];
|
|
37
37
|
};
|
|
38
|
+
export declare const SKILL_MARKER_START = "<!-- totem:skill-start -->";
|
|
39
|
+
export declare const SKILL_MARKER_END = "<!-- totem:skill-end -->";
|
|
40
|
+
export declare const SIGNOFF_SKILL_CONTENT: string;
|
|
41
|
+
export declare const REVIEW_REPLY_SKILL_CONTENT = "---\nname: review-reply\ndescription: Unified PR review triage \u2014 fetch, normalize, and batch-action bot comments\n---\n\n<!-- totem:skill-start -->\n\nTriage PR review comments from all bots for PR $ARGUMENTS.\n\n## Phase 1: Fetch & Categorize (Deterministic)\n\nRun the triage command to fetch, normalize, deduplicate, and categorize all bot comments:\n\n```bash\npnpm totem triage-pr $ARGUMENTS\n```\n\nThis outputs a categorized inbox grouped by blast radius (Security \u2192 Architecture \u2192 Convention \u2192 Nits) with cross-bot deduplication already applied. The heavy lifting is done in TypeScript \u2014 no LLM math needed.\n\n**STOP HERE.** Present the output to the user and wait for them to specify actions. Do NOT proceed to Phase 2 until the user replies.\n\n## Phase 2: Execute Actions (Bulk Support)\n\nThe user may type individual IDs (e.g., `fix 4, 11`) OR use bulk actions:\n\n- `fix all security`\n- `defer all nits`\n- `extract all architecture`\n\n### `fix <numbers | category>`\n\nMark items as will-fix. No API calls \u2014 just acknowledge. The user will make code changes next.\n\n### `defer <numbers | category> [ticket]`\n\nAuto-reply on the PR acknowledging the deferral:\n\n- **CodeRabbit items:** Reply inline to each thread with \"Tracked in #NNN\" or \"Deferred \u2014 not blocking for this PR.\"\n- **GCA items:** DO NOT reply inline. Batch ALL GCA responses into ONE issue comment: `@gemini-code-assist` followed by a numbered list addressing each finding. Use `gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --input -` with JSON payload.\n- **SARIF items:** No reply needed (our own tool).\n\n### `nit <numbers | category>`\n\nSame as defer but reply text is \"Acknowledged \u2014 nit / by design.\"\n\n### `extract <numbers | category>`\n\nFor each selected finding, generate a lesson and call `mcp__totem-dev__add_lesson` (or equivalent):\n\n- Use the bot's finding as the lesson body\n- Add relevant tags from the file path and finding category\n- The lesson will automatically get `lifecycle: nursery` treatment\n\n### `done`\n\nPrint summary of actions taken and exit.\n\n## CRITICAL: GCA Reply Protocol\n\n**NEVER reply individually to GCA bot comments.** GCA has a quota and will NOT respond to replies unless they contain `@gemini-code-assist`. Always batch ALL GCA responses into a single PR-level comment using the issue comments API endpoint (`/issues/{pr}/comments`), not the review comments reply endpoint.\n\n<!-- totem:skill-end -->\n";
|
|
42
|
+
export declare const DISTRIBUTED_CLAUDE_SKILLS: readonly [{
|
|
43
|
+
readonly name: "signoff";
|
|
44
|
+
readonly content: string;
|
|
45
|
+
}, {
|
|
46
|
+
readonly name: "review-reply";
|
|
47
|
+
readonly content: "---\nname: review-reply\ndescription: Unified PR review triage — fetch, normalize, and batch-action bot comments\n---\n\n<!-- totem:skill-start -->\n\nTriage PR review comments from all bots for PR $ARGUMENTS.\n\n## Phase 1: Fetch & Categorize (Deterministic)\n\nRun the triage command to fetch, normalize, deduplicate, and categorize all bot comments:\n\n```bash\npnpm totem triage-pr $ARGUMENTS\n```\n\nThis outputs a categorized inbox grouped by blast radius (Security → Architecture → Convention → Nits) with cross-bot deduplication already applied. The heavy lifting is done in TypeScript — no LLM math needed.\n\n**STOP HERE.** Present the output to the user and wait for them to specify actions. Do NOT proceed to Phase 2 until the user replies.\n\n## Phase 2: Execute Actions (Bulk Support)\n\nThe user may type individual IDs (e.g., `fix 4, 11`) OR use bulk actions:\n\n- `fix all security`\n- `defer all nits`\n- `extract all architecture`\n\n### `fix <numbers | category>`\n\nMark items as will-fix. No API calls — just acknowledge. The user will make code changes next.\n\n### `defer <numbers | category> [ticket]`\n\nAuto-reply on the PR acknowledging the deferral:\n\n- **CodeRabbit items:** Reply inline to each thread with \"Tracked in #NNN\" or \"Deferred — not blocking for this PR.\"\n- **GCA items:** DO NOT reply inline. Batch ALL GCA responses into ONE issue comment: `@gemini-code-assist` followed by a numbered list addressing each finding. Use `gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --input -` with JSON payload.\n- **SARIF items:** No reply needed (our own tool).\n\n### `nit <numbers | category>`\n\nSame as defer but reply text is \"Acknowledged — nit / by design.\"\n\n### `extract <numbers | category>`\n\nFor each selected finding, generate a lesson and call `mcp__totem-dev__add_lesson` (or equivalent):\n\n- Use the bot's finding as the lesson body\n- Add relevant tags from the file path and finding category\n- The lesson will automatically get `lifecycle: nursery` treatment\n\n### `done`\n\nPrint summary of actions taken and exit.\n\n## CRITICAL: GCA Reply Protocol\n\n**NEVER reply individually to GCA bot comments.** GCA has a quota and will NOT respond to replies unless they contain `@gemini-code-assist`. Always batch ALL GCA responses into a single PR-level comment using the issue comments API endpoint (`/issues/{pr}/comments`), not the review comments reply endpoint.\n\n<!-- totem:skill-end -->\n";
|
|
48
|
+
}];
|
|
38
49
|
export declare function generateConfig(targets: IngestTarget[], embeddingTier: EmbeddingTier, cwd: string): Promise<string>;
|
|
39
50
|
/**
|
|
40
51
|
* Generate a YAML configuration file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/commands/init-templates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMpE,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,YAAY,kCAAkC,CAAC;AAC5D,eAAO,MAAM,UAAU,gCAAgC,CAAC;AACxD,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AACzE,eAAO,MAAM,eAAe,6CAA6C,CAAC;AAE1E,eAAO,MAAM,eAAe,i6JA4C3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAgB7D,eAAO,MAAM,qBAAqB,6CAA6C,CAAC;AAIhF,eAAO,MAAM,oBAAoB,kwBAiBhC,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAqD9B,CAAC;AAEF,eAAO,MAAM,YAAY,4hBAUxB,CAAC;AAIF,eAAO,MAAM,kBAAkB,waAY9B,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;CAQnC,CAAC;AAkBF,eAAO,MAAM,qBAAqB,QA8EjC,CAAC;AAEF,eAAO,MAAM,2BAA2B;;;;;;CAQvC,CAAC;AA4BF,eAAO,MAAM,oBAAoB,m3CAkChC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;CAQtC,CAAC;
|
|
1
|
+
{"version":3,"file":"init-templates.d.ts","sourceRoot":"","sources":["../../src/commands/init-templates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAMpE,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,YAAY,kCAAkC,CAAC;AAC5D,eAAO,MAAM,UAAU,gCAAgC,CAAC;AACxD,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AACzE,eAAO,MAAM,eAAe,6CAA6C,CAAC;AAE1E,eAAO,MAAM,eAAe,i6JA4C3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAgB7D,eAAO,MAAM,qBAAqB,6CAA6C,CAAC;AAIhF,eAAO,MAAM,oBAAoB,kwBAiBhC,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAqD9B,CAAC;AAEF,eAAO,MAAM,YAAY,4hBAUxB,CAAC;AAIF,eAAO,MAAM,kBAAkB,waAY9B,CAAC;AAEF,eAAO,MAAM,uBAAuB;;;;;;CAQnC,CAAC;AAkBF,eAAO,MAAM,qBAAqB,QA8EjC,CAAC;AAEF,eAAO,MAAM,2BAA2B;;;;;;CAQvC,CAAC;AA4BF,eAAO,MAAM,oBAAoB,m3CAkChC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;CAQtC,CAAC;AAmBF,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAC/D,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAE3D,eAAO,MAAM,qBAAqB,QAkCjC,CAAC;AAEF,eAAO,MAAM,0BAA0B,08EA8DtC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;;;;EAG5B,CAAC;AAIX,wBAAsB,cAAc,CAClC,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,aAAa,EAC5B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAyCjB;AAoDD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,aAAa,EAC5B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,aAAa,EAC5B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,YAAY,EAAE,EACvB,aAAa,EAAE,aAAa,EAC5B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAkBhD"}
|
|
@@ -347,6 +347,125 @@ export const CLAUDE_SESSION_START_ENTRY = {
|
|
|
347
347
|
},
|
|
348
348
|
],
|
|
349
349
|
};
|
|
350
|
+
// ─── Claude Code skill distribution (mmnto-ai/totem#1890 Phase C slice 3) ───
|
|
351
|
+
//
|
|
352
|
+
// Skills are surfaced into consumer repos at `.claude/skills/<name>/SKILL.md`.
|
|
353
|
+
// Marker-based replace: canonical content lives between SKILL_MARKER_START
|
|
354
|
+
// and SKILL_MARKER_END. Re-running `totem init` replaces inside-marker
|
|
355
|
+
// content with the current canonical; content AFTER the end marker is
|
|
356
|
+
// user-customization territory and survives across refreshes.
|
|
357
|
+
//
|
|
358
|
+
// v0.1 ships two skills: signoff and review-reply. Both passed the
|
|
359
|
+
// canonical/customization audit (universal across consumer repos,
|
|
360
|
+
// idempotent on refresh, no `totem <name>` subcommand collision).
|
|
361
|
+
//
|
|
362
|
+
// Source-of-truth is `mmnto-ai/totem:.claude/skills/<name>/SKILL.md`. The
|
|
363
|
+
// `installed-skills-match-source.test.ts` invariant locks these constants
|
|
364
|
+
// against the source files so canonical drift fails CI rather than
|
|
365
|
+
// silently propagating stale skill content to consumers.
|
|
366
|
+
export const SKILL_MARKER_START = '<!-- totem:skill-start -->';
|
|
367
|
+
export const SKILL_MARKER_END = '<!-- totem:skill-end -->';
|
|
368
|
+
export const SIGNOFF_SKILL_CONTENT = `---
|
|
369
|
+
name: signoff
|
|
370
|
+
description: End-of-session — update memory, write journal entry, clean up
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
${SKILL_MARKER_START}
|
|
374
|
+
|
|
375
|
+
End-of-session wrap-up:
|
|
376
|
+
|
|
377
|
+
1. Update \`MEMORY.md\` with any new state (version shipped, tickets closed, key decisions)
|
|
378
|
+
2. Write a journal entry to the substrate journal at \`<substrate>/.journal/totem/<filename>.md\` summarizing today's work. Use \`resolveSubstratePaths(gitRoot).journalRoot\` from \`@mmnto/totem\` to locate the substrate; if \`source === 'none'\`, fall back to repo-local \`.journal/totem/\` and warn (ADR-090 graceful degradation).
|
|
379
|
+
3. Commit + push the substrate journal entry from \`mmnto-ai/totem-substrate\` (NOT this repo — \`.journal/\` is sediment-frozen here per ADR-100). Use rebase-and-retry on the push since other agents may sign off concurrently:
|
|
380
|
+
|
|
381
|
+
\`\`\`bash
|
|
382
|
+
cd <substrate-repo-root>
|
|
383
|
+
git add .journal/totem/<filename>.md
|
|
384
|
+
git commit -m "journal(totem): <slug>"
|
|
385
|
+
pushed=0
|
|
386
|
+
for i in 1 2 3 4 5; do
|
|
387
|
+
git push origin main && { pushed=1; break; }
|
|
388
|
+
git pull --rebase --autostash origin main || { echo "Rebase conflict — manual resolution needed"; break; }
|
|
389
|
+
sleep 1
|
|
390
|
+
done
|
|
391
|
+
[ "$pushed" = 1 ] || { echo "ERROR: substrate push failed — surface to user"; exit 1; }
|
|
392
|
+
\`\`\`
|
|
393
|
+
|
|
394
|
+
**Why the retry loop:** Substrate \`main\` accepts only fast-forward pushes. If a peer push (strategy-Claude, lc-Claude, status-Claude, etc.) lands between your commit and your push, yours fails with \`non-fast-forward\`. Per-agent journal filenames (\`<model>-NNNN-*.md\`) don't collide, so the rebase auto-succeeds without conflict — typically resolves within 1-2 retries. After 5 retries surface failure to the user; that's likely a genuine same-file edit (e.g., two recipients moving the same \`_broadcast/\` file to \`processed/\`) that needs manual resolution.
|
|
395
|
+
|
|
396
|
+
` +
|
|
397
|
+
// totem-context: documentation example — `git branch -D` shown in canonical signoff procedure for human readers; not a runtime invocation in this file
|
|
398
|
+
`4. Clean up stale local branches: \`git branch -vv | grep ': gone]' | awk '{print $1}' | xargs git branch -D\`
|
|
399
|
+
5. Report: what shipped, what's pending, what's next
|
|
400
|
+
${SKILL_MARKER_END}
|
|
401
|
+
`;
|
|
402
|
+
export const REVIEW_REPLY_SKILL_CONTENT = `---
|
|
403
|
+
name: review-reply
|
|
404
|
+
description: Unified PR review triage — fetch, normalize, and batch-action bot comments
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
${SKILL_MARKER_START}
|
|
408
|
+
|
|
409
|
+
Triage PR review comments from all bots for PR $ARGUMENTS.
|
|
410
|
+
|
|
411
|
+
## Phase 1: Fetch & Categorize (Deterministic)
|
|
412
|
+
|
|
413
|
+
Run the triage command to fetch, normalize, deduplicate, and categorize all bot comments:
|
|
414
|
+
|
|
415
|
+
\`\`\`bash
|
|
416
|
+
pnpm totem triage-pr $ARGUMENTS
|
|
417
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
This outputs a categorized inbox grouped by blast radius (Security → Architecture → Convention → Nits) with cross-bot deduplication already applied. The heavy lifting is done in TypeScript — no LLM math needed.
|
|
420
|
+
|
|
421
|
+
**STOP HERE.** Present the output to the user and wait for them to specify actions. Do NOT proceed to Phase 2 until the user replies.
|
|
422
|
+
|
|
423
|
+
## Phase 2: Execute Actions (Bulk Support)
|
|
424
|
+
|
|
425
|
+
The user may type individual IDs (e.g., \`fix 4, 11\`) OR use bulk actions:
|
|
426
|
+
|
|
427
|
+
- \`fix all security\`
|
|
428
|
+
- \`defer all nits\`
|
|
429
|
+
- \`extract all architecture\`
|
|
430
|
+
|
|
431
|
+
### \`fix <numbers | category>\`
|
|
432
|
+
|
|
433
|
+
Mark items as will-fix. No API calls — just acknowledge. The user will make code changes next.
|
|
434
|
+
|
|
435
|
+
### \`defer <numbers | category> [ticket]\`
|
|
436
|
+
|
|
437
|
+
Auto-reply on the PR acknowledging the deferral:
|
|
438
|
+
|
|
439
|
+
- **CodeRabbit items:** Reply inline to each thread with "Tracked in #NNN" or "Deferred — not blocking for this PR."
|
|
440
|
+
- **GCA items:** DO NOT reply inline. Batch ALL GCA responses into ONE issue comment: \`@gemini-code-assist\` followed by a numbered list addressing each finding. Use \`gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --input -\` with JSON payload.
|
|
441
|
+
- **SARIF items:** No reply needed (our own tool).
|
|
442
|
+
|
|
443
|
+
### \`nit <numbers | category>\`
|
|
444
|
+
|
|
445
|
+
Same as defer but reply text is "Acknowledged — nit / by design."
|
|
446
|
+
|
|
447
|
+
### \`extract <numbers | category>\`
|
|
448
|
+
|
|
449
|
+
For each selected finding, generate a lesson and call \`mcp__totem-dev__add_lesson\` (or equivalent):
|
|
450
|
+
|
|
451
|
+
- Use the bot's finding as the lesson body
|
|
452
|
+
- Add relevant tags from the file path and finding category
|
|
453
|
+
- The lesson will automatically get \`lifecycle: nursery\` treatment
|
|
454
|
+
|
|
455
|
+
### \`done\`
|
|
456
|
+
|
|
457
|
+
Print summary of actions taken and exit.
|
|
458
|
+
|
|
459
|
+
## CRITICAL: GCA Reply Protocol
|
|
460
|
+
|
|
461
|
+
**NEVER reply individually to GCA bot comments.** GCA has a quota and will NOT respond to replies unless they contain \`@gemini-code-assist\`. Always batch ALL GCA responses into a single PR-level comment using the issue comments API endpoint (\`/issues/{pr}/comments\`), not the review comments reply endpoint.
|
|
462
|
+
|
|
463
|
+
${SKILL_MARKER_END}
|
|
464
|
+
`;
|
|
465
|
+
export const DISTRIBUTED_CLAUDE_SKILLS = [
|
|
466
|
+
{ name: 'signoff', content: SIGNOFF_SKILL_CONTENT },
|
|
467
|
+
{ name: 'review-reply', content: REVIEW_REPLY_SKILL_CONTENT },
|
|
468
|
+
];
|
|
350
469
|
// ─── Config generation ──────────────────────────────────
|
|
351
470
|
export async function generateConfig(targets, embeddingTier, cwd) {
|
|
352
471
|
const { detectOrchestrator, formatTargets } = await import('./init-detect.js');
|