@mmnto/cli 1.9.0 → 1.10.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.
Files changed (98) hide show
  1. package/LICENSE +190 -190
  2. package/dist/commands/add-lesson.d.ts.map +1 -1
  3. package/dist/commands/add-lesson.js +5 -1
  4. package/dist/commands/add-lesson.js.map +1 -1
  5. package/dist/commands/anchor.d.ts +2 -0
  6. package/dist/commands/anchor.d.ts.map +1 -0
  7. package/dist/commands/anchor.js +84 -0
  8. package/dist/commands/anchor.js.map +1 -0
  9. package/dist/commands/compile.d.ts.map +1 -1
  10. package/dist/commands/compile.js +4 -1
  11. package/dist/commands/compile.js.map +1 -1
  12. package/dist/commands/config-cmd.d.ts +4 -0
  13. package/dist/commands/config-cmd.d.ts.map +1 -0
  14. package/dist/commands/config-cmd.js +60 -0
  15. package/dist/commands/config-cmd.js.map +1 -0
  16. package/dist/commands/extract-local.d.ts +6 -0
  17. package/dist/commands/extract-local.d.ts.map +1 -0
  18. package/dist/commands/extract-local.js +134 -0
  19. package/dist/commands/extract-local.js.map +1 -0
  20. package/dist/commands/extract-pr.d.ts +19 -0
  21. package/dist/commands/extract-pr.d.ts.map +1 -0
  22. package/dist/commands/extract-pr.js +193 -0
  23. package/dist/commands/extract-pr.js.map +1 -0
  24. package/dist/commands/extract-scan.d.ts +6 -0
  25. package/dist/commands/extract-scan.d.ts.map +1 -0
  26. package/dist/commands/extract-scan.js +100 -0
  27. package/dist/commands/extract-scan.js.map +1 -0
  28. package/dist/commands/extract-shared.d.ts +29 -0
  29. package/dist/commands/extract-shared.d.ts.map +1 -0
  30. package/dist/commands/extract-shared.js +351 -0
  31. package/dist/commands/extract-shared.js.map +1 -0
  32. package/dist/commands/extract-templates.d.ts +1 -0
  33. package/dist/commands/extract-templates.d.ts.map +1 -1
  34. package/dist/commands/extract-templates.js +33 -0
  35. package/dist/commands/extract-templates.js.map +1 -1
  36. package/dist/commands/extract.d.ts +7 -36
  37. package/dist/commands/extract.d.ts.map +1 -1
  38. package/dist/commands/extract.js +44 -588
  39. package/dist/commands/extract.js.map +1 -1
  40. package/dist/commands/extract.test.js +103 -2
  41. package/dist/commands/extract.test.js.map +1 -1
  42. package/dist/commands/init.d.ts +5 -0
  43. package/dist/commands/init.d.ts.map +1 -1
  44. package/dist/commands/init.js +85 -2
  45. package/dist/commands/init.js.map +1 -1
  46. package/dist/commands/init.test.js +54 -1
  47. package/dist/commands/init.test.js.map +1 -1
  48. package/dist/commands/install-hooks.d.ts +17 -7
  49. package/dist/commands/install-hooks.d.ts.map +1 -1
  50. package/dist/commands/install-hooks.js +96 -16
  51. package/dist/commands/install-hooks.js.map +1 -1
  52. package/dist/commands/install-hooks.test.js +137 -22
  53. package/dist/commands/install-hooks.test.js.map +1 -1
  54. package/dist/commands/learn.d.ts +26 -0
  55. package/dist/commands/learn.d.ts.map +1 -0
  56. package/dist/commands/learn.js +325 -0
  57. package/dist/commands/learn.js.map +1 -0
  58. package/dist/commands/learn.test.d.ts +2 -0
  59. package/dist/commands/learn.test.d.ts.map +1 -0
  60. package/dist/commands/learn.test.js +169 -0
  61. package/dist/commands/learn.test.js.map +1 -0
  62. package/dist/commands/shield.d.ts.map +1 -1
  63. package/dist/commands/shield.js +12 -1
  64. package/dist/commands/shield.js.map +1 -1
  65. package/dist/commands/shield.test.js +37 -0
  66. package/dist/commands/shield.test.js.map +1 -1
  67. package/dist/commands/spec.d.ts +6 -0
  68. package/dist/commands/spec.d.ts.map +1 -1
  69. package/dist/commands/spec.js +11 -1
  70. package/dist/commands/spec.js.map +1 -1
  71. package/dist/commands/spec.test.js +25 -1
  72. package/dist/commands/spec.test.js.map +1 -1
  73. package/dist/exemptions/exemption-schema.d.ts +6 -6
  74. package/dist/hooks/auto-context.d.ts.map +1 -1
  75. package/dist/hooks/auto-context.js +2 -19
  76. package/dist/hooks/auto-context.js.map +1 -1
  77. package/dist/index.js +33 -9
  78. package/dist/index.js.map +1 -1
  79. package/dist/orchestrator.test.d.ts +2 -0
  80. package/dist/orchestrator.test.d.ts.map +1 -0
  81. package/dist/orchestrator.test.js +130 -0
  82. package/dist/orchestrator.test.js.map +1 -0
  83. package/dist/schemas/handoff-checkpoint.d.ts +2 -2
  84. package/dist/utils/pilot.d.ts +44 -0
  85. package/dist/utils/pilot.d.ts.map +1 -0
  86. package/dist/utils/pilot.js +141 -0
  87. package/dist/utils/pilot.js.map +1 -0
  88. package/dist/utils/pilot.test.d.ts +2 -0
  89. package/dist/utils/pilot.test.d.ts.map +1 -0
  90. package/dist/utils/pilot.test.js +166 -0
  91. package/dist/utils/pilot.test.js.map +1 -0
  92. package/dist/utils.d.ts +8 -1
  93. package/dist/utils.d.ts.map +1 -1
  94. package/dist/utils.js +27 -15
  95. package/dist/utils.js.map +1 -1
  96. package/dist/utils.test.js +81 -1
  97. package/dist/utils.test.js.map +1 -1
  98. package/package.json +3 -2
@@ -0,0 +1,130 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { invokeShellOrchestrator } from './utils.js';
7
+ function createMockChild() {
8
+ const child = new EventEmitter();
9
+ child.stdout = new EventEmitter();
10
+ child.stderr = new EventEmitter();
11
+ child.kill = vi.fn();
12
+ return child;
13
+ }
14
+ let mockChild;
15
+ vi.mock('node:child_process', () => ({
16
+ spawn: vi.fn(() => mockChild),
17
+ }));
18
+ const { spawn } = await import('node:child_process');
19
+ const mockedSpawn = vi.mocked(spawn);
20
+ // ─── Tests ───────────────────────────────────────────
21
+ describe('invokeShellOrchestrator', () => {
22
+ let tmpDir;
23
+ const totemDir = '.totem';
24
+ beforeEach(() => {
25
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-orch-'));
26
+ vi.spyOn(console, 'error').mockImplementation(() => { });
27
+ mockChild = createMockChild();
28
+ mockedSpawn.mockClear();
29
+ });
30
+ afterEach(() => {
31
+ fs.rmSync(tmpDir, { recursive: true, force: true });
32
+ vi.restoreAllMocks();
33
+ });
34
+ /** Emit stdout data and close with success */
35
+ function emitSuccess(data) {
36
+ process.nextTick(() => {
37
+ mockChild.stdout.emit('data', Buffer.from(data));
38
+ mockChild.emit('close', 0);
39
+ });
40
+ }
41
+ /** Emit close with non-zero exit code and optional stderr */
42
+ function emitFailure(code, stderr = '') {
43
+ process.nextTick(() => {
44
+ if (stderr)
45
+ mockChild.stderr.emit('data', Buffer.from(stderr));
46
+ mockChild.emit('close', code);
47
+ });
48
+ }
49
+ it('returns raw content when output is not Gemini JSON', async () => {
50
+ emitSuccess(' The answer is 42. ');
51
+ const result = await invokeShellOrchestrator('prompt text', 'echo {file}', 'test-model', tmpDir, 'Test', totemDir);
52
+ expect(result.content).toBe('The answer is 42.');
53
+ expect(result.inputTokens).toBeNull();
54
+ expect(result.outputTokens).toBeNull();
55
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
56
+ });
57
+ it('parses Gemini JSON output and returns structured result', async () => {
58
+ const geminiOutput = JSON.stringify({
59
+ response: 'Gemini says hello.',
60
+ stats: {
61
+ models: {
62
+ 'gemini-2.5-pro': {
63
+ tokens: { input: 100, candidates: 50 },
64
+ api: { totalLatencyMs: 2000 },
65
+ },
66
+ },
67
+ },
68
+ });
69
+ emitSuccess(geminiOutput);
70
+ const result = await invokeShellOrchestrator('prompt', 'gemini -e none < {file}', 'gemini-2.5-pro', tmpDir, 'Test', totemDir);
71
+ expect(result.content).toBe('Gemini says hello.');
72
+ expect(result.inputTokens).toBe(100);
73
+ expect(result.outputTokens).toBe(50);
74
+ expect(result.durationMs).toBe(2000);
75
+ });
76
+ it('substitutes {file} and {model} in command', async () => {
77
+ emitSuccess('ok');
78
+ await invokeShellOrchestrator('prompt', 'llm --model {model} < {file}', 'my-model', tmpDir, 'Test', totemDir);
79
+ const cmd = mockedSpawn.mock.calls[0][0];
80
+ expect(cmd).toContain('my-model');
81
+ expect(cmd).not.toContain('{model}');
82
+ expect(cmd).not.toContain('{file}');
83
+ });
84
+ it('writes prompt to temp file and cleans up after', async () => {
85
+ emitSuccess('result');
86
+ await invokeShellOrchestrator('my prompt content', 'cat {file}', 'model', tmpDir, 'Test', totemDir);
87
+ // Temp file should be cleaned up
88
+ const tempDir = path.join(tmpDir, totemDir, 'temp');
89
+ if (fs.existsSync(tempDir)) {
90
+ const files = fs.readdirSync(tempDir).filter((f) => f.startsWith('totem-test-'));
91
+ expect(files).toHaveLength(0);
92
+ }
93
+ });
94
+ it('throws QuotaError for quota-related failures', async () => {
95
+ emitFailure(1, '429 Too Many Requests quota exceeded');
96
+ try {
97
+ await invokeShellOrchestrator('prompt', 'cmd', 'model', tmpDir, 'Test', totemDir);
98
+ expect.fail('Should have thrown');
99
+ }
100
+ catch (err) {
101
+ expect(err.name).toBe('QuotaError');
102
+ }
103
+ });
104
+ it('throws generic error for non-zero exit code', async () => {
105
+ emitFailure(1, 'something went wrong');
106
+ await expect(invokeShellOrchestrator('prompt', 'cmd', 'model', tmpDir, 'Test', totemDir)).rejects.toThrow('[Totem Error] Shell orchestrator command failed');
107
+ });
108
+ it('throws error on spawn error event', async () => {
109
+ process.nextTick(() => {
110
+ mockChild.emit('error', new Error('command not found'));
111
+ });
112
+ await expect(invokeShellOrchestrator('prompt', 'cmd', 'model', tmpDir, 'Test', totemDir)).rejects.toThrow('command not found');
113
+ });
114
+ it('throws descriptive error for timeout', async () => {
115
+ vi.useFakeTimers();
116
+ // Simulate kill → close (rejection now happens in the close handler)
117
+ mockChild.kill = vi.fn(() => {
118
+ process.nextTick(() => mockChild.emit('close', null));
119
+ });
120
+ // Capture the rejection before advancing timers
121
+ const promise = invokeShellOrchestrator('prompt', 'cmd', 'model', tmpDir, 'Test', totemDir).catch((err) => err);
122
+ await vi.advanceTimersByTimeAsync(180_001);
123
+ const err = await promise;
124
+ expect(err).toBeInstanceOf(Error);
125
+ expect(err.message).toContain('timed out after 180s');
126
+ expect(mockChild.kill).toHaveBeenCalled();
127
+ vi.useRealTimers();
128
+ });
129
+ });
130
+ //# sourceMappingURL=orchestrator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.test.js","sourceRoot":"","sources":["../src/orchestrator.test.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,8 @@ export declare const HandoffCheckpointSchema: z.ZodObject<{
10
10
  remaining: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
11
11
  context_hints: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
12
12
  }, "strip", z.ZodTypeAny, {
13
- checkpoint_version: 1;
14
13
  timestamp: string;
14
+ checkpoint_version: 1;
15
15
  branch: string;
16
16
  open_prs: number[];
17
17
  active_files: string[];
@@ -20,8 +20,8 @@ export declare const HandoffCheckpointSchema: z.ZodObject<{
20
20
  remaining: string[];
21
21
  context_hints: string[];
22
22
  }, {
23
- checkpoint_version: 1;
24
23
  timestamp: string;
24
+ checkpoint_version: 1;
25
25
  branch: string;
26
26
  active_files: string[];
27
27
  open_prs?: number[] | undefined;
@@ -0,0 +1,44 @@
1
+ import type { TotemConfig } from '@mmnto/totem';
2
+ export interface PilotState {
3
+ startedAt: string;
4
+ pushCount: number;
5
+ violations: Array<{
6
+ timestamp: string;
7
+ hook: string;
8
+ detail: string;
9
+ }>;
10
+ }
11
+ export interface PilotConfig {
12
+ maxDays: number;
13
+ maxPushes: number;
14
+ }
15
+ /**
16
+ * Resolve the `pilot` field from TotemConfig into a concrete PilotConfig,
17
+ * or null if pilot mode is not enabled.
18
+ */
19
+ export declare function resolvePilotConfig(config: TotemConfig): PilotConfig | null;
20
+ /**
21
+ * Read the pilot state from disk. If the file is missing or invalid,
22
+ * initializes a fresh state and persists it.
23
+ */
24
+ export declare function readPilotState(totemDir: string): PilotState;
25
+ /**
26
+ * Atomically write pilot state to disk (write to tmp, then rename).
27
+ */
28
+ export declare function writePilotState(totemDir: string, state: PilotState): void;
29
+ /**
30
+ * Check whether the pilot period has expired based on elapsed days or push count.
31
+ */
32
+ export declare function isPilotExpired(state: PilotState, config: PilotConfig): {
33
+ expired: boolean;
34
+ reason?: string;
35
+ };
36
+ /**
37
+ * Wrap a hook execution in pilot mode:
38
+ * - If the pilot period has expired, prints an error and returns exit code 1.
39
+ * - If the inner fn succeeds (exit 0), increments pushCount and returns 0.
40
+ * - If the inner fn fails (exit non-zero), logs the violation as a WARNING
41
+ * and returns 0 (warn-only).
42
+ */
43
+ export declare function withPilotMode(hookName: string, totemDir: string, pilotConfig: PilotConfig, fn: () => Promise<number>): Promise<number>;
44
+ //# sourceMappingURL=pilot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pilot.d.ts","sourceRoot":"","sources":["../../src/utils/pilot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI,CAW1E;AA2BD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAoB3D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAUzE;AAID;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,GAClB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAmBvC;AAID;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,WAAW,EACxB,EAAE,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GACxB,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
@@ -0,0 +1,141 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ // ─── Defaults ───────────────────────────────────────────
4
+ const DEFAULT_MAX_DAYS = 14;
5
+ const DEFAULT_MAX_PUSHES = 50;
6
+ // ─── Config resolution ──────────────────────────────────
7
+ /**
8
+ * Resolve the `pilot` field from TotemConfig into a concrete PilotConfig,
9
+ * or null if pilot mode is not enabled.
10
+ */
11
+ export function resolvePilotConfig(config) {
12
+ if (config.pilot === undefined || config.pilot === false)
13
+ return null;
14
+ if (config.pilot === true) {
15
+ return { maxDays: DEFAULT_MAX_DAYS, maxPushes: DEFAULT_MAX_PUSHES };
16
+ }
17
+ return {
18
+ maxDays: config.pilot.maxDays ?? DEFAULT_MAX_DAYS,
19
+ maxPushes: config.pilot.maxPushes ?? DEFAULT_MAX_PUSHES,
20
+ };
21
+ }
22
+ // ─── State persistence ──────────────────────────────────
23
+ function statePath(totemDir) {
24
+ return path.join(totemDir, 'pilot-state.json');
25
+ }
26
+ function isValidState(obj) {
27
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj))
28
+ return false;
29
+ const rec = obj;
30
+ if (typeof rec['startedAt'] !== 'string')
31
+ return false;
32
+ if (typeof rec['pushCount'] !== 'number')
33
+ return false;
34
+ if (!Array.isArray(rec['violations']))
35
+ return false;
36
+ for (const v of rec['violations']) {
37
+ if (!v || typeof v !== 'object' || Array.isArray(v))
38
+ return false;
39
+ const vr = v;
40
+ if (typeof vr['timestamp'] !== 'string')
41
+ return false;
42
+ if (typeof vr['hook'] !== 'string')
43
+ return false;
44
+ if (typeof vr['detail'] !== 'string')
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ /**
50
+ * Read the pilot state from disk. If the file is missing or invalid,
51
+ * initializes a fresh state and persists it.
52
+ */
53
+ export function readPilotState(totemDir) {
54
+ const fp = statePath(totemDir);
55
+ if (fs.existsSync(fp)) {
56
+ try {
57
+ const raw = fs.readFileSync(fp, 'utf-8');
58
+ const parsed = JSON.parse(raw);
59
+ if (isValidState(parsed))
60
+ return parsed;
61
+ }
62
+ catch {
63
+ // Fall through — re-initialize
64
+ }
65
+ }
66
+ const fresh = {
67
+ startedAt: new Date().toISOString(),
68
+ pushCount: 0,
69
+ violations: [],
70
+ };
71
+ writePilotState(totemDir, fresh);
72
+ return fresh;
73
+ }
74
+ /**
75
+ * Atomically write pilot state to disk (write to tmp, then rename).
76
+ */
77
+ export function writePilotState(totemDir, state) {
78
+ const fp = statePath(totemDir);
79
+ const dir = path.dirname(fp);
80
+ if (!fs.existsSync(dir)) {
81
+ fs.mkdirSync(dir, { recursive: true });
82
+ }
83
+ const tmp = fp + '.tmp';
84
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', 'utf-8');
85
+ fs.renameSync(tmp, fp);
86
+ }
87
+ // ─── Expiry check ───────────────────────────────────────
88
+ /**
89
+ * Check whether the pilot period has expired based on elapsed days or push count.
90
+ */
91
+ export function isPilotExpired(state, config) {
92
+ const startMs = new Date(state.startedAt).getTime();
93
+ const elapsedDays = (Date.now() - startMs) / (1000 * 60 * 60 * 24);
94
+ if (elapsedDays > config.maxDays) {
95
+ return {
96
+ expired: true,
97
+ reason: `Pilot period expired: ${Math.floor(elapsedDays)} days elapsed (limit: ${config.maxDays}).`,
98
+ };
99
+ }
100
+ if (state.pushCount >= config.maxPushes) {
101
+ return {
102
+ expired: true,
103
+ reason: `Pilot period expired: ${state.pushCount} pushes reached (limit: ${config.maxPushes}).`,
104
+ };
105
+ }
106
+ return { expired: false };
107
+ }
108
+ // ─── Wrapper ────────────────────────────────────────────
109
+ /**
110
+ * Wrap a hook execution in pilot mode:
111
+ * - If the pilot period has expired, prints an error and returns exit code 1.
112
+ * - If the inner fn succeeds (exit 0), increments pushCount and returns 0.
113
+ * - If the inner fn fails (exit non-zero), logs the violation as a WARNING
114
+ * and returns 0 (warn-only).
115
+ */
116
+ export async function withPilotMode(hookName, totemDir, pilotConfig, fn) {
117
+ const state = readPilotState(totemDir);
118
+ const { expired, reason } = isPilotExpired(state, pilotConfig);
119
+ if (expired) {
120
+ console.error(`[Totem] ${reason} Hooks are now enforced. Run \`totem init\` to reconfigure.`);
121
+ return 1;
122
+ }
123
+ const exitCode = await fn();
124
+ if (exitCode === 0) {
125
+ state.pushCount += 1;
126
+ writePilotState(totemDir, state);
127
+ return 0;
128
+ }
129
+ // Non-zero: log violation, warn instead of block — but still consume budget
130
+ state.pushCount += 1;
131
+ state.violations.push({
132
+ timestamp: new Date().toISOString(),
133
+ hook: hookName,
134
+ detail: `Hook exited with code ${exitCode}`,
135
+ });
136
+ writePilotState(totemDir, state);
137
+ console.error(`[Totem] WARNING: ${hookName} would have blocked (exit ${exitCode}), but pilot mode is active. ` +
138
+ `${state.pushCount}/${pilotConfig.maxPushes} pushes used.`);
139
+ return 0;
140
+ }
141
+ //# sourceMappingURL=pilot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pilot.js","sourceRoot":"","sources":["../../src/utils/pilot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pilot.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pilot.test.d.ts","sourceRoot":"","sources":["../../src/utils/pilot.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,166 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { isPilotExpired, readPilotState, resolvePilotConfig, withPilotMode, writePilotState, } from './pilot.js';
6
+ // ─── Helpers ────────────────────────────────────────────
7
+ function makeConfig(pilot) {
8
+ return {
9
+ targets: [{ glob: '**/*.md', type: 'lesson', strategy: 'markdown-heading' }],
10
+ totemDir: '.totem',
11
+ lanceDir: '.lancedb',
12
+ ignorePatterns: [],
13
+ shieldIgnorePatterns: [],
14
+ contextWarningThreshold: 40_000,
15
+ shieldAutoLearn: false,
16
+ pilot,
17
+ };
18
+ }
19
+ const RM_OPTS = { recursive: true, force: true, maxRetries: 3, retryDelay: 100 };
20
+ // ─── resolvePilotConfig ─────────────────────────────────
21
+ describe('resolvePilotConfig', () => {
22
+ it('returns null when pilot not set', () => {
23
+ expect(resolvePilotConfig(makeConfig())).toBeNull();
24
+ expect(resolvePilotConfig(makeConfig(undefined))).toBeNull();
25
+ });
26
+ it('returns null when pilot is false', () => {
27
+ expect(resolvePilotConfig(makeConfig(false))).toBeNull();
28
+ });
29
+ it('returns defaults for pilot: true', () => {
30
+ const result = resolvePilotConfig(makeConfig(true));
31
+ expect(result).toEqual({ maxDays: 14, maxPushes: 50 });
32
+ });
33
+ it('uses custom values from object form', () => {
34
+ const result = resolvePilotConfig(makeConfig({ maxDays: 7, maxPushes: 20 }));
35
+ expect(result).toEqual({ maxDays: 7, maxPushes: 20 });
36
+ });
37
+ });
38
+ // ─── readPilotState / writePilotState ───────────────────
39
+ describe('readPilotState', () => {
40
+ let tmpDir;
41
+ beforeEach(() => {
42
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-pilot-'));
43
+ });
44
+ afterEach(() => {
45
+ fs.rmSync(tmpDir, RM_OPTS);
46
+ });
47
+ it('initializes missing state file', () => {
48
+ const state = readPilotState(tmpDir);
49
+ expect(state.pushCount).toBe(0);
50
+ expect(state.violations).toEqual([]);
51
+ expect(typeof state.startedAt).toBe('string');
52
+ // File should now exist on disk
53
+ const fp = path.join(tmpDir, 'pilot-state.json');
54
+ expect(fs.existsSync(fp)).toBe(true);
55
+ });
56
+ it('reads existing state file', () => {
57
+ const existing = {
58
+ startedAt: '2026-01-01T00:00:00.000Z',
59
+ pushCount: 5,
60
+ violations: [{ timestamp: '2026-01-02T00:00:00.000Z', hook: 'pre-push', detail: 'fail' }],
61
+ };
62
+ fs.writeFileSync(path.join(tmpDir, 'pilot-state.json'), JSON.stringify(existing), 'utf-8');
63
+ const state = readPilotState(tmpDir);
64
+ expect(state).toEqual(existing);
65
+ });
66
+ it('re-initializes when state file is invalid JSON', () => {
67
+ fs.writeFileSync(path.join(tmpDir, 'pilot-state.json'), 'not json', 'utf-8');
68
+ const state = readPilotState(tmpDir);
69
+ expect(state.pushCount).toBe(0);
70
+ expect(state.violations).toEqual([]);
71
+ });
72
+ it('re-initializes when state file has wrong shape', () => {
73
+ fs.writeFileSync(path.join(tmpDir, 'pilot-state.json'), JSON.stringify({ startedAt: 123 }), 'utf-8');
74
+ const state = readPilotState(tmpDir);
75
+ expect(state.pushCount).toBe(0);
76
+ });
77
+ });
78
+ describe('writePilotState', () => {
79
+ let tmpDir;
80
+ beforeEach(() => {
81
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-pilot-'));
82
+ });
83
+ afterEach(() => {
84
+ fs.rmSync(tmpDir, RM_OPTS);
85
+ });
86
+ it('writes state atomically', () => {
87
+ const state = {
88
+ startedAt: '2026-01-01T00:00:00.000Z',
89
+ pushCount: 10,
90
+ violations: [],
91
+ };
92
+ writePilotState(tmpDir, state);
93
+ const raw = fs.readFileSync(path.join(tmpDir, 'pilot-state.json'), 'utf-8');
94
+ expect(JSON.parse(raw)).toEqual(state);
95
+ // Temp file should not remain
96
+ expect(fs.existsSync(path.join(tmpDir, 'pilot-state.json.tmp'))).toBe(false);
97
+ });
98
+ });
99
+ // ─── isPilotExpired ─────────────────────────────────────
100
+ describe('isPilotExpired', () => {
101
+ it('returns expired when days exceeded', () => {
102
+ const fifteenDaysAgo = new Date(Date.now() - 15 * 24 * 60 * 60 * 1000).toISOString();
103
+ const result = isPilotExpired({ startedAt: fifteenDaysAgo, pushCount: 0, violations: [] }, { maxDays: 14, maxPushes: 50 });
104
+ expect(result.expired).toBe(true);
105
+ expect(result.reason).toMatch(/days elapsed/);
106
+ });
107
+ it('returns expired when pushes exceeded', () => {
108
+ const result = isPilotExpired({ startedAt: new Date().toISOString(), pushCount: 50, violations: [] }, { maxDays: 14, maxPushes: 50 });
109
+ expect(result.expired).toBe(true);
110
+ expect(result.reason).toMatch(/pushes reached/);
111
+ });
112
+ it('returns not expired within bounds', () => {
113
+ const result = isPilotExpired({ startedAt: new Date().toISOString(), pushCount: 10, violations: [] }, { maxDays: 14, maxPushes: 50 });
114
+ expect(result.expired).toBe(false);
115
+ expect(result.reason).toBeUndefined();
116
+ });
117
+ });
118
+ // ─── withPilotMode ──────────────────────────────────────
119
+ describe('withPilotMode', () => {
120
+ let tmpDir;
121
+ beforeEach(() => {
122
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-pilot-'));
123
+ vi.spyOn(console, 'error').mockImplementation(() => { });
124
+ });
125
+ afterEach(() => {
126
+ vi.restoreAllMocks();
127
+ fs.rmSync(tmpDir, RM_OPTS);
128
+ });
129
+ it('returns 0 and logs violation when fn fails', async () => {
130
+ const config = { maxDays: 14, maxPushes: 50 };
131
+ const exitCode = await withPilotMode('pre-push', tmpDir, config, async () => 1);
132
+ expect(exitCode).toBe(0);
133
+ // State should have a violation logged
134
+ const state = readPilotState(tmpDir);
135
+ expect(state.violations).toHaveLength(1);
136
+ expect(state.violations[0].hook).toBe('pre-push');
137
+ expect(state.violations[0].detail).toContain('exit');
138
+ // Warning should have been printed
139
+ expect(console.error).toHaveBeenCalledWith(expect.stringContaining('WARNING'));
140
+ });
141
+ it('increments pushCount when fn succeeds', async () => {
142
+ const config = { maxDays: 14, maxPushes: 50 };
143
+ const exitCode = await withPilotMode('pre-push', tmpDir, config, async () => 0);
144
+ expect(exitCode).toBe(0);
145
+ const state = readPilotState(tmpDir);
146
+ expect(state.pushCount).toBe(1);
147
+ expect(state.violations).toHaveLength(0);
148
+ });
149
+ it('returns 1 when pilot period expired', async () => {
150
+ // Seed an expired state (push count at limit)
151
+ writePilotState(tmpDir, {
152
+ startedAt: new Date().toISOString(),
153
+ pushCount: 50,
154
+ violations: [],
155
+ });
156
+ const config = { maxDays: 14, maxPushes: 50 };
157
+ const fn = vi.fn(async () => 0);
158
+ const exitCode = await withPilotMode('pre-push', tmpDir, config, fn);
159
+ expect(exitCode).toBe(1);
160
+ // fn should NOT have been called
161
+ expect(fn).not.toHaveBeenCalled();
162
+ // Error message should mention expiry
163
+ expect(console.error).toHaveBeenCalledWith(expect.stringContaining('expired'));
164
+ });
165
+ });
166
+ //# sourceMappingURL=pilot.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pilot.test.js","sourceRoot":"","sources":["../../src/utils/pilot.test.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.ts CHANGED
@@ -6,14 +6,21 @@ export declare const IS_WIN: boolean;
6
6
  export declare const GH_TIMEOUT_MS = 15000;
7
7
  /**
8
8
  * Load environment variables from .env file (does not override existing).
9
+ * Uses the `dotenv` library for robust parsing of inline comments, quoted
10
+ * values containing `#`, and other edge cases.
9
11
  */
10
12
  export declare function loadEnv(cwd: string): void;
11
13
  export { CONFIG_FILES };
12
14
  export type ConfigFormat = 'ts' | 'yaml' | 'toml';
15
+ /** Return the global totem directory path (~/.totem/). Accepts override for testing. */
16
+ export declare function getGlobalTotemDir(homeDir?: string): string;
13
17
  /**
14
18
  * Resolve config path by checking the fallback chain: .ts → .yaml → .yml → .toml
19
+ * Falls back to the global ~/.totem/ profile when no local config exists.
15
20
  */
16
- export declare function resolveConfigPath(cwd: string): string;
21
+ export declare function resolveConfigPath(cwd: string, homeDir?: string): string;
22
+ /** Check whether a resolved config path comes from the global ~/.totem/ profile. */
23
+ export declare function isGlobalConfigPath(configPath: string, homeDir?: string): boolean;
17
24
  /**
18
25
  * Load and validate Totem configuration from any supported format.
19
26
  * Routes parsing by file extension: .ts via jiti, .yaml/.yml via yaml, .toml via smol-toml.
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,YAAY,EAKb,MAAM,cAAc,CAAC;AAUtB,+EAA+E;AAC/E,eAAO,MAAM,MAAM,SAA+B,CAAC;AAEnD,yCAAyC;AACzC,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBzC;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuDzE;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA+BhD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAwB,GACjC,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,MAAM,CAaR;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAUnE;AAKD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKxC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAOzD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAqBR;AAID,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAIpD;AAKD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,GAAE,MAAiC,EAC3C,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAqBR;AAID,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAYD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,qGAAqG;IACrG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAChC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA6M9B"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EACL,YAAY,EAKb,MAAM,cAAc,CAAC;AAUtB,+EAA+E;AAC/E,eAAO,MAAM,MAAM,SAA+B,CAAC;AAEnD,yCAAyC;AACzC,eAAO,MAAM,aAAa,QAAS,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAKzC;AAGD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAIhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuDzE;AAGD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA+BhD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAwB,GACjC,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAMD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,GACf,MAAM,CAaR;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAUnE;AAKD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAKxC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAOzD,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EAAE,EACvB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAqBR;AAID,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,EACxB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf;IAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAAE,CAIpD;AAKD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,GAAE,MAAiC,EAC3C,SAAS,CAAC,EAAE,OAAO,GAClB,MAAM,CAqBR;AAID,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAYD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,sBAAsB,CAAC;IAChC,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,qGAAqG;IACrG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAChC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA6M9B"}