@mmnto/cli 1.35.0 → 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/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/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,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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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { HookRule } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Rule classification per ADR-104 § Convergence + § Target-aware dispatch
|
|
4
|
+
* (strategy-Gemini Q1 binding synthesis, T1930Z 2026-05-11).
|
|
5
|
+
*
|
|
6
|
+
* Two classes:
|
|
7
|
+
* - `spine`: has Rego-shadow representation; formally verified by SMT;
|
|
8
|
+
* eligible for the top ~30 invariant set. Ships in ADR-103's lint-rule
|
|
9
|
+
* pipeline. Not produced by this V1 hook engine.
|
|
10
|
+
* - `interpretive`: ast-grep / regex only; no formal verification obligation;
|
|
11
|
+
* ships outside the core invariant set. All bot-pack hooks in V1 fall here.
|
|
12
|
+
*
|
|
13
|
+
* The hook runtime treats every rule as `interpretive`. The class is named
|
|
14
|
+
* explicitly so the loader's warn-and-ignore signal on a future Spine-Rule
|
|
15
|
+
* promotion attempt (`verification_shadow:` on a hook rule) carries the
|
|
16
|
+
* dispatch contract in its error message rather than relying on prose docs.
|
|
17
|
+
*
|
|
18
|
+
* V2 may promote bot-pack hooks to `spine` via a follow-on ADR; this seam
|
|
19
|
+
* is where that promotion lands.
|
|
20
|
+
*/
|
|
21
|
+
export type RuleClassification = 'spine' | 'interpretive';
|
|
22
|
+
export interface ClassificationResult {
|
|
23
|
+
classification: RuleClassification;
|
|
24
|
+
/**
|
|
25
|
+
* When set, the loader SHOULD emit this string to stderr but continue
|
|
26
|
+
* loading the rule. Empty when no warn-and-ignore signal applies.
|
|
27
|
+
*/
|
|
28
|
+
warning?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Classify a hook rule for V1 dispatch.
|
|
32
|
+
*
|
|
33
|
+
* Always returns `interpretive` per ADR-104 § Target-aware dispatch (hooks
|
|
34
|
+
* are Interpretive Rule class — no formal-verification obligation; PreToolUse
|
|
35
|
+
* payloads are not source code, so the Rego/OPA value proposition is weaker
|
|
36
|
+
* than for lint rules).
|
|
37
|
+
*
|
|
38
|
+
* If the rule carries a `verification_shadow:` block (forward-compat schema
|
|
39
|
+
* permits this for future Spine promotion), the returned result includes a
|
|
40
|
+
* structured warning. Per ADR-104 § Convergence: "the engine MUST
|
|
41
|
+
* warn-and-ignore the block (the rule itself still executes as
|
|
42
|
+
* Interpretive)."
|
|
43
|
+
*/
|
|
44
|
+
export declare function classifyHookRule(rule: HookRule): ClassificationResult;
|
|
45
|
+
//# sourceMappingURL=classification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.d.ts","sourceRoot":"","sources":["../../src/hook/classification.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,cAAc,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,kBAAkB,CAAC;IACnC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,oBAAoB,CAQrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a hook rule for V1 dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Always returns `interpretive` per ADR-104 § Target-aware dispatch (hooks
|
|
5
|
+
* are Interpretive Rule class — no formal-verification obligation; PreToolUse
|
|
6
|
+
* payloads are not source code, so the Rego/OPA value proposition is weaker
|
|
7
|
+
* than for lint rules).
|
|
8
|
+
*
|
|
9
|
+
* If the rule carries a `verification_shadow:` block (forward-compat schema
|
|
10
|
+
* permits this for future Spine promotion), the returned result includes a
|
|
11
|
+
* structured warning. Per ADR-104 § Convergence: "the engine MUST
|
|
12
|
+
* warn-and-ignore the block (the rule itself still executes as
|
|
13
|
+
* Interpretive)."
|
|
14
|
+
*/
|
|
15
|
+
export function classifyHookRule(rule) {
|
|
16
|
+
if (rule.verification_shadow !== undefined) {
|
|
17
|
+
return {
|
|
18
|
+
classification: 'interpretive',
|
|
19
|
+
warning: `[totem:hook-shadow-ignored] ${rule.id}: verification_shadow block ignored in V1 (hooks are Interpretive Rule class); rule still executes`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return { classification: 'interpretive' };
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=classification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.js","sourceRoot":"","sources":["../../src/hook/classification.ts"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAc;IAC7C,IAAI,IAAI,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;QAC3C,OAAO;YACL,cAAc,EAAE,cAAc;YAC9B,OAAO,EAAE,+BAA+B,IAAI,CAAC,EAAE,oGAAoG;SACpJ,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.test.d.ts","sourceRoot":"","sources":["../../src/hook/classification.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { classifyHookRule } from './classification.js';
|
|
3
|
+
const baseRule = {
|
|
4
|
+
id: 'r1',
|
|
5
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
6
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
7
|
+
message: 'm',
|
|
8
|
+
};
|
|
9
|
+
describe('classifyHookRule', () => {
|
|
10
|
+
it('returns interpretive with no warning for a plain hook rule', () => {
|
|
11
|
+
const result = classifyHookRule(baseRule);
|
|
12
|
+
expect(result.classification).toBe('interpretive');
|
|
13
|
+
expect(result.warning).toBeUndefined();
|
|
14
|
+
});
|
|
15
|
+
it('returns interpretive but emits a warn-and-ignore signal when verification_shadow is present', () => {
|
|
16
|
+
const withShadow = {
|
|
17
|
+
...baseRule,
|
|
18
|
+
verification_shadow: { rego: 'package x' },
|
|
19
|
+
};
|
|
20
|
+
const result = classifyHookRule(withShadow);
|
|
21
|
+
expect(result.classification).toBe('interpretive');
|
|
22
|
+
expect(result.warning).toBeDefined();
|
|
23
|
+
expect(result.warning).toContain('[totem:hook-shadow-ignored]');
|
|
24
|
+
expect(result.warning).toContain('r1');
|
|
25
|
+
});
|
|
26
|
+
it('treats verification_shadow: null as present (warns and continues)', () => {
|
|
27
|
+
// null is a JS-truthy distinct from undefined; if the schema admits it the
|
|
28
|
+
// classification helper should still emit the warn-and-ignore signal so
|
|
29
|
+
// the dispatch contract holds for any non-undefined value.
|
|
30
|
+
const withNullShadow = {
|
|
31
|
+
...baseRule,
|
|
32
|
+
id: 'r-null',
|
|
33
|
+
verification_shadow: null,
|
|
34
|
+
};
|
|
35
|
+
const result = classifyHookRule(withNullShadow);
|
|
36
|
+
expect(result.classification).toBe('interpretive');
|
|
37
|
+
expect(result.warning).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=classification.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classification.test.js","sourceRoot":"","sources":["../../src/hook/classification.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGvD,MAAM,QAAQ,GAAa;IACzB,EAAE,EAAE,IAAI;IACR,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;IACxC,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAChD,OAAO,EAAE,GAAG;CACb,CAAC;AAEF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6FAA6F,EAAE,GAAG,EAAE;QACrG,MAAM,UAAU,GAAa;YAC3B,GAAG,QAAQ;YACX,mBAAmB,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE;SAC3C,CAAC;QACF,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,2EAA2E;QAC3E,wEAAwE;QACxE,2DAA2D;QAC3D,MAAM,cAAc,GAAa;YAC/B,GAAG,QAAQ;YACX,EAAE,EAAE,QAAQ;YACZ,mBAAmB,EAAE,IAAI;SAC1B,CAAC;QACF,MAAM,MAAM,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { TotemError } from '@mmnto/totem';
|
|
2
|
+
import { type CompiledHookRule } from './schema.js';
|
|
3
|
+
/**
|
|
4
|
+
* Compiled-hooks manifest loader (ADR-104 § Decision 3 staleness check + a
|
|
5
|
+
* forward-compat warn-and-skip path for an evolving manifest schema).
|
|
6
|
+
*
|
|
7
|
+
* The loader is a pure function over `(manifestPath, installedPackVersions)`.
|
|
8
|
+
* It does NOT read `package.json` itself — callers (typically a
|
|
9
|
+
* bootstrap helper) resolve installed pack versions once and pass them in.
|
|
10
|
+
* Keeps the loader testable in isolation; mirrors the substrate-wiring
|
|
11
|
+
* pattern from `bootstrapEngine` (1.25.0 wiring lesson).
|
|
12
|
+
*
|
|
13
|
+
* Three failure modes are surfaced via the `warnings` array, not by
|
|
14
|
+
* throwing:
|
|
15
|
+
*
|
|
16
|
+
* 1. Manifest file missing — empty result, no warnings (a fresh repo without
|
|
17
|
+
* installed pack hooks is a valid state).
|
|
18
|
+
* 2. Manifest schemaVersion is not the runner's expected version — warn and
|
|
19
|
+
* skip the entire manifest (no hooks loaded). Composes with ADR-104
|
|
20
|
+
* § Decision 4's forward-compat ethos.
|
|
21
|
+
* 3. Pack version drift — for each pack whose installed version differs from
|
|
22
|
+
* the compiled-against version, emit a `[totem:hook-stale]` warning per
|
|
23
|
+
* the format in ADR-104 § Decision 3. Hooks still load (Tenet 4 carve-out:
|
|
24
|
+
* hooks are best-effort; staleness is signal, not a fail-closed condition).
|
|
25
|
+
*
|
|
26
|
+
* Structural errors (corrupt JSON, schema-validation failure on a manifest
|
|
27
|
+
* claiming the supported schemaVersion) populate `errors` and yield an empty
|
|
28
|
+
* hooks array — distinct from a missing manifest.
|
|
29
|
+
*/
|
|
30
|
+
export interface LoadCompiledHooksOptions {
|
|
31
|
+
manifestPath: string;
|
|
32
|
+
installedPackVersions: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
export interface LoadCompiledHooksResult {
|
|
35
|
+
hooks: CompiledHookRule[];
|
|
36
|
+
warnings: string[];
|
|
37
|
+
/**
|
|
38
|
+
* Errors carry the original cause via `Error.cause` so debug consumers
|
|
39
|
+
* can traverse the chain (per the codebase styleguide rule against
|
|
40
|
+
* concatenating `err.message` into new strings — destroys the stack).
|
|
41
|
+
* Callers that just need to log can use `err.message`; debug tooling
|
|
42
|
+
* walks `err.cause` recursively.
|
|
43
|
+
*/
|
|
44
|
+
errors: TotemError[];
|
|
45
|
+
}
|
|
46
|
+
export declare function loadCompiledHooks(options: LoadCompiledHooksOptions): LoadCompiledHooksResult;
|
|
47
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/hook/loader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;OAMG;IACH,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB,CA+F5F"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { TotemError } from '@mmnto/totem';
|
|
3
|
+
import { COMPILED_HOOKS_SCHEMA_VERSION, CompiledHooksManifestSchema, } from './schema.js';
|
|
4
|
+
export function loadCompiledHooks(options) {
|
|
5
|
+
const warnings = [];
|
|
6
|
+
const errors = [];
|
|
7
|
+
// No `fs.existsSync` pre-check: that returns false for any filesystem
|
|
8
|
+
// error (permission denied, symlink loops, EBUSY, etc.), not just ENOENT.
|
|
9
|
+
// Treating those as "missing manifest" would silently swallow real
|
|
10
|
+
// diagnostics. Catch ENOENT explicitly inside the read; everything else
|
|
11
|
+
// surfaces as a HOOKS_LOAD_FAILED entry.
|
|
12
|
+
let raw;
|
|
13
|
+
try {
|
|
14
|
+
raw = fs.readFileSync(options.manifestPath, 'utf8');
|
|
15
|
+
// totem-context: intentional — error captured into diagnostics array (the loader's contract is diagnostics-not-throws per Tenet 4 carve-out for hooks being best-effort)
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err.code === 'ENOENT') {
|
|
19
|
+
// Fresh repo without installed pack hooks is a valid state, not a fault.
|
|
20
|
+
return { hooks: [], warnings, errors };
|
|
21
|
+
}
|
|
22
|
+
errors.push(new TotemError('HOOKS_LOAD_FAILED', `failed to read compiled-hooks manifest at ${options.manifestPath}`, 'verify the file is readable and re-run `totem sync` to regenerate', err));
|
|
23
|
+
return { hooks: [], warnings, errors };
|
|
24
|
+
}
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(raw);
|
|
28
|
+
// totem-context: intentional — error captured into diagnostics array (the loader's contract is diagnostics-not-throws per Tenet 4 carve-out for hooks being best-effort)
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
errors.push(new TotemError('HOOKS_LOAD_FAILED', `compiled-hooks manifest at ${options.manifestPath} is not valid JSON`, 're-run `totem sync` to regenerate the manifest', err));
|
|
32
|
+
return { hooks: [], warnings, errors };
|
|
33
|
+
}
|
|
34
|
+
// Forward-compat: peek at schemaVersion BEFORE invoking the strict z.literal
|
|
35
|
+
// schema validator, so an unknown version surfaces as a warn-and-skip
|
|
36
|
+
// rather than a thrown ZodError. Mirrors the per-pack warn-and-skip pattern
|
|
37
|
+
// for `hooks.yaml :: version` from ADR-104 § Decision 4.
|
|
38
|
+
const peekedVersion = typeof parsed === 'object' && parsed !== null
|
|
39
|
+
? parsed.schemaVersion
|
|
40
|
+
: undefined;
|
|
41
|
+
if (peekedVersion !== COMPILED_HOOKS_SCHEMA_VERSION) {
|
|
42
|
+
warnings.push(`[totem:hook-schema] compiled-hooks manifest schemaVersion ${JSON.stringify(peekedVersion)} unsupported by this runner (expected ${COMPILED_HOOKS_SCHEMA_VERSION})\n → upgrade totem CLI or re-run \`totem sync\` to regenerate`);
|
|
43
|
+
return { hooks: [], warnings, errors };
|
|
44
|
+
}
|
|
45
|
+
const validation = CompiledHooksManifestSchema.safeParse(parsed);
|
|
46
|
+
if (!validation.success) {
|
|
47
|
+
const summary = validation.error.issues
|
|
48
|
+
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
49
|
+
.join('; ');
|
|
50
|
+
errors.push(new TotemError('HOOKS_LOAD_FAILED', `compiled-hooks manifest at ${options.manifestPath} failed schema validation: ${summary}`, 're-run `totem sync` to regenerate the manifest, or upgrade totem CLI if the manifest was authored by a newer version', validation.error));
|
|
51
|
+
return { hooks: [], warnings, errors };
|
|
52
|
+
}
|
|
53
|
+
const manifest = validation.data;
|
|
54
|
+
for (const [packId, compiledVersion] of Object.entries(manifest.sourcePackVersions)) {
|
|
55
|
+
const installedVersion = options.installedPackVersions[packId];
|
|
56
|
+
if (installedVersion === undefined) {
|
|
57
|
+
warnings.push(`[totem:hook-stale] ${packId}: compiled against ${compiledVersion}, not currently installed\n → run \`totem sync\` to refresh .totem/compiled-hooks.json`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (installedVersion !== compiledVersion) {
|
|
61
|
+
warnings.push(`[totem:hook-stale] ${packId}: compiled against ${compiledVersion}, installed ${installedVersion}\n → run \`totem sync\` to refresh .totem/compiled-hooks.json`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { hooks: manifest.hooks, warnings, errors };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/hook/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.test.d.ts","sourceRoot":"","sources":["../../src/hook/loader.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,205 @@
|
|
|
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 } from 'vitest';
|
|
5
|
+
import { loadCompiledHooks } from './loader.js';
|
|
6
|
+
let workDir;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-hook-loader-'));
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
12
|
+
});
|
|
13
|
+
function writeManifest(content) {
|
|
14
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
15
|
+
fs.writeFileSync(manifestPath, JSON.stringify(content), 'utf8');
|
|
16
|
+
return manifestPath;
|
|
17
|
+
}
|
|
18
|
+
const validRule = {
|
|
19
|
+
id: 'r1',
|
|
20
|
+
packId: '@mmnto/pack-bot-coderabbit',
|
|
21
|
+
trigger: { tool: 'bash', pattern: '.*' },
|
|
22
|
+
check: { pattern: 'x', type: 'reject-if-match' },
|
|
23
|
+
message: 'm',
|
|
24
|
+
};
|
|
25
|
+
describe('loadCompiledHooks', () => {
|
|
26
|
+
it('returns an empty result when the manifest file does not exist (fresh repo, ENOENT)', () => {
|
|
27
|
+
const result = loadCompiledHooks({
|
|
28
|
+
manifestPath: path.join(workDir, 'missing.json'),
|
|
29
|
+
installedPackVersions: {},
|
|
30
|
+
});
|
|
31
|
+
expect(result.hooks).toEqual([]);
|
|
32
|
+
expect(result.warnings).toEqual([]);
|
|
33
|
+
expect(result.errors).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
it('surfaces non-ENOENT read errors as HOOKS_LOAD_FAILED (does not pretend the file is missing)', () => {
|
|
36
|
+
// Reading a directory as a file produces EISDIR on POSIX / EBADF or
|
|
37
|
+
// ENOTSUP-flavoured failures on Windows. Whatever the platform-specific
|
|
38
|
+
// errno, it is NOT ENOENT — so the loader must surface it, not silently
|
|
39
|
+
// return the "manifest absent" result that the prior existsSync pre-check
|
|
40
|
+
// would have masked.
|
|
41
|
+
const dirAsManifest = path.join(workDir, 'a-directory');
|
|
42
|
+
fs.mkdirSync(dirAsManifest);
|
|
43
|
+
const result = loadCompiledHooks({
|
|
44
|
+
manifestPath: dirAsManifest,
|
|
45
|
+
installedPackVersions: {},
|
|
46
|
+
});
|
|
47
|
+
expect(result.hooks).toEqual([]);
|
|
48
|
+
expect(result.errors.length).toBe(1);
|
|
49
|
+
const err = result.errors[0];
|
|
50
|
+
expect(err.code).toBe('HOOKS_LOAD_FAILED');
|
|
51
|
+
expect(err.message).toContain('failed to read compiled-hooks manifest');
|
|
52
|
+
expect(err.cause).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
it('records a structural error on invalid JSON and preserves the original SyntaxError via cause', () => {
|
|
55
|
+
const manifestPath = path.join(workDir, 'compiled-hooks.json');
|
|
56
|
+
fs.writeFileSync(manifestPath, '{ not valid json', 'utf8');
|
|
57
|
+
const result = loadCompiledHooks({
|
|
58
|
+
manifestPath,
|
|
59
|
+
installedPackVersions: {},
|
|
60
|
+
});
|
|
61
|
+
expect(result.hooks).toEqual([]);
|
|
62
|
+
expect(result.errors.length).toBe(1);
|
|
63
|
+
const err = result.errors[0];
|
|
64
|
+
expect(err.message).toContain('not valid JSON');
|
|
65
|
+
expect(err.code).toBe('HOOKS_LOAD_FAILED');
|
|
66
|
+
// Original parse error preserved on `.cause` so debug consumers can
|
|
67
|
+
// walk the chain without the stack being collapsed into a string.
|
|
68
|
+
expect(err.cause).toBeInstanceOf(SyntaxError);
|
|
69
|
+
});
|
|
70
|
+
it('warns and skips when schemaVersion is higher than the runner supports', () => {
|
|
71
|
+
const manifestPath = writeManifest({
|
|
72
|
+
schemaVersion: 2,
|
|
73
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
74
|
+
sourcePackVersions: {},
|
|
75
|
+
hooks: [],
|
|
76
|
+
});
|
|
77
|
+
const result = loadCompiledHooks({
|
|
78
|
+
manifestPath,
|
|
79
|
+
installedPackVersions: {},
|
|
80
|
+
});
|
|
81
|
+
expect(result.hooks).toEqual([]);
|
|
82
|
+
expect(result.errors).toEqual([]);
|
|
83
|
+
expect(result.warnings.length).toBe(1);
|
|
84
|
+
expect(result.warnings[0]).toContain('[totem:hook-schema]');
|
|
85
|
+
expect(result.warnings[0]).toContain('schemaVersion 2');
|
|
86
|
+
});
|
|
87
|
+
it('warns and skips when schemaVersion is missing', () => {
|
|
88
|
+
const manifestPath = writeManifest({
|
|
89
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
90
|
+
sourcePackVersions: {},
|
|
91
|
+
hooks: [],
|
|
92
|
+
});
|
|
93
|
+
const result = loadCompiledHooks({
|
|
94
|
+
manifestPath,
|
|
95
|
+
installedPackVersions: {},
|
|
96
|
+
});
|
|
97
|
+
expect(result.hooks).toEqual([]);
|
|
98
|
+
expect(result.warnings.length).toBe(1);
|
|
99
|
+
expect(result.warnings[0]).toContain('[totem:hook-schema]');
|
|
100
|
+
});
|
|
101
|
+
it('records a structural error when the supported-version manifest fails schema validation', () => {
|
|
102
|
+
const manifestPath = writeManifest({
|
|
103
|
+
schemaVersion: 1,
|
|
104
|
+
compiledAt: 'not-a-real-date',
|
|
105
|
+
sourcePackVersions: {},
|
|
106
|
+
hooks: [],
|
|
107
|
+
});
|
|
108
|
+
const result = loadCompiledHooks({
|
|
109
|
+
manifestPath,
|
|
110
|
+
installedPackVersions: {},
|
|
111
|
+
});
|
|
112
|
+
expect(result.hooks).toEqual([]);
|
|
113
|
+
expect(result.errors.length).toBe(1);
|
|
114
|
+
const err = result.errors[0];
|
|
115
|
+
expect(err.message).toContain('schema validation');
|
|
116
|
+
expect(err.code).toBe('HOOKS_LOAD_FAILED');
|
|
117
|
+
// Zod's ZodError preserved as the cause for debug-mode chain traversal.
|
|
118
|
+
expect(err.cause).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
it('returns hooks with no warnings when installed pack versions match compiled versions', () => {
|
|
121
|
+
const manifestPath = writeManifest({
|
|
122
|
+
schemaVersion: 1,
|
|
123
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
124
|
+
sourcePackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
125
|
+
hooks: [validRule],
|
|
126
|
+
});
|
|
127
|
+
const result = loadCompiledHooks({
|
|
128
|
+
manifestPath,
|
|
129
|
+
installedPackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
130
|
+
});
|
|
131
|
+
expect(result.hooks).toHaveLength(1);
|
|
132
|
+
expect(result.warnings).toEqual([]);
|
|
133
|
+
expect(result.errors).toEqual([]);
|
|
134
|
+
});
|
|
135
|
+
it('emits a staleness warning when the installed pack version differs from compiled', () => {
|
|
136
|
+
const manifestPath = writeManifest({
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
139
|
+
sourcePackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
140
|
+
hooks: [validRule],
|
|
141
|
+
});
|
|
142
|
+
const result = loadCompiledHooks({
|
|
143
|
+
manifestPath,
|
|
144
|
+
installedPackVersions: { '@mmnto/pack-bot-coderabbit': '1.1.0' },
|
|
145
|
+
});
|
|
146
|
+
expect(result.hooks).toHaveLength(1);
|
|
147
|
+
expect(result.warnings.length).toBe(1);
|
|
148
|
+
expect(result.warnings[0]).toContain('[totem:hook-stale]');
|
|
149
|
+
expect(result.warnings[0]).toContain('@mmnto/pack-bot-coderabbit');
|
|
150
|
+
expect(result.warnings[0]).toContain('compiled against 1.0.0, installed 1.1.0');
|
|
151
|
+
});
|
|
152
|
+
it('emits a staleness warning when a compiled-against pack is not installed at all', () => {
|
|
153
|
+
const manifestPath = writeManifest({
|
|
154
|
+
schemaVersion: 1,
|
|
155
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
156
|
+
sourcePackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
157
|
+
hooks: [validRule],
|
|
158
|
+
});
|
|
159
|
+
const result = loadCompiledHooks({
|
|
160
|
+
manifestPath,
|
|
161
|
+
installedPackVersions: {},
|
|
162
|
+
});
|
|
163
|
+
expect(result.hooks).toHaveLength(1);
|
|
164
|
+
expect(result.warnings.length).toBe(1);
|
|
165
|
+
expect(result.warnings[0]).toContain('not currently installed');
|
|
166
|
+
});
|
|
167
|
+
it('ignores extra installed packs not in sourcePackVersions (no warning)', () => {
|
|
168
|
+
// A pack installed after the last `totem sync` is benign — its hooks
|
|
169
|
+
// are not yet active, but that is not staleness in the compiled set.
|
|
170
|
+
const manifestPath = writeManifest({
|
|
171
|
+
schemaVersion: 1,
|
|
172
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
173
|
+
sourcePackVersions: { '@mmnto/pack-bot-coderabbit': '1.0.0' },
|
|
174
|
+
hooks: [validRule],
|
|
175
|
+
});
|
|
176
|
+
const result = loadCompiledHooks({
|
|
177
|
+
manifestPath,
|
|
178
|
+
installedPackVersions: {
|
|
179
|
+
'@mmnto/pack-bot-coderabbit': '1.0.0',
|
|
180
|
+
'@mmnto/pack-bot-gemini-code-assist': '1.0.0',
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
expect(result.warnings).toEqual([]);
|
|
184
|
+
});
|
|
185
|
+
it('emits one staleness warning per drifting pack', () => {
|
|
186
|
+
const manifestPath = writeManifest({
|
|
187
|
+
schemaVersion: 1,
|
|
188
|
+
compiledAt: '2026-05-11T18:43:00Z',
|
|
189
|
+
sourcePackVersions: {
|
|
190
|
+
'@mmnto/pack-bot-coderabbit': '1.0.0',
|
|
191
|
+
'@mmnto/pack-bot-gemini-code-assist': '2.0.0',
|
|
192
|
+
},
|
|
193
|
+
hooks: [validRule],
|
|
194
|
+
});
|
|
195
|
+
const result = loadCompiledHooks({
|
|
196
|
+
manifestPath,
|
|
197
|
+
installedPackVersions: {
|
|
198
|
+
'@mmnto/pack-bot-coderabbit': '1.1.0',
|
|
199
|
+
'@mmnto/pack-bot-gemini-code-assist': '2.0.1',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
expect(result.warnings.length).toBe(2);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
//# sourceMappingURL=loader.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.test.js","sourceRoot":"","sources":["../../src/hook/loader.test.ts"],"names":[],"mappings":"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"}
|