gibi-bot 1.0.0 → 1.1.1
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/.context.json +47 -3
- package/.github/workflows/npm-publish.yml +33 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +3 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +45 -0
- package/DISTRIBUTION.md +9 -1
- package/GEMINI.md +28 -9
- package/README.md +55 -28
- package/commitlint.config.js +3 -0
- package/conductor/code_styleguides/general.md +6 -1
- package/conductor/code_styleguides/ts.md +42 -35
- package/conductor/product-guidelines.md +16 -12
- package/conductor/product.md +12 -7
- package/conductor/setup_state.json +1 -1
- package/conductor/tech-stack.md +4 -1
- package/conductor/tracks/slack_bot_20260107/metadata.json +1 -1
- package/conductor/tracks/slack_bot_20260107/plan.md +6 -1
- package/conductor/tracks/slack_bot_20260107/spec.md +9 -6
- package/conductor/tracks.md +2 -1
- package/conductor/workflow.md +74 -53
- package/dist/agents.js +7 -10
- package/dist/agents.test.js +17 -12
- package/dist/app.js +212 -135
- package/dist/config.js +5 -5
- package/dist/context.js +4 -3
- package/dist/context.test.js +2 -4
- package/eslint.config.mjs +17 -0
- package/jest.config.js +4 -3
- package/nodemon.json +5 -9
- package/package.json +34 -4
- package/release.config.js +22 -0
- package/src/agents.test.ts +62 -57
- package/src/agents.ts +94 -82
- package/src/app.d.ts +1 -1
- package/src/app.ts +298 -178
- package/src/config.ts +48 -48
- package/src/context.test.ts +54 -56
- package/src/context.ts +123 -114
- package/test_gemini.js +13 -9
- package/test_gemini_approval.js +13 -9
- package/test_gemini_write.js +19 -9
- package/tests/context.test.ts +145 -0
- package/tsconfig.json +1 -1
- package/src/app.js +0 -55
- package/src/app.js.map +0 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { ContextManager } from '../src/context';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
// Mock fs to avoid writing to disk
|
|
6
|
+
jest.mock('fs');
|
|
7
|
+
|
|
8
|
+
describe('ContextManager', () => {
|
|
9
|
+
const mockStoragePath = path.join(process.cwd(), '.context.json');
|
|
10
|
+
let contextManager: ContextManager;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
|
|
15
|
+
// Setup fs mock for constructor load()
|
|
16
|
+
(fs.existsSync as jest.Mock).mockReturnValue(false);
|
|
17
|
+
|
|
18
|
+
contextManager = new ContextManager();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('constructor', () => {
|
|
22
|
+
it('should load existing contexts from disk', () => {
|
|
23
|
+
const mockData = {
|
|
24
|
+
'ctx-1': {
|
|
25
|
+
id: 'ctx-1',
|
|
26
|
+
createdAt: new Date('2023-01-01').toISOString(),
|
|
27
|
+
lastActiveAt: new Date('2023-01-02').toISOString(),
|
|
28
|
+
data: { role: 'admin' },
|
|
29
|
+
messages: [],
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
34
|
+
(fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(mockData));
|
|
35
|
+
|
|
36
|
+
// Re-instantiate to trigger load()
|
|
37
|
+
contextManager = new ContextManager();
|
|
38
|
+
|
|
39
|
+
const ctx = contextManager.getContext('ctx-1');
|
|
40
|
+
expect(ctx).toBeDefined();
|
|
41
|
+
expect(ctx.id).toBe('ctx-1');
|
|
42
|
+
expect(ctx.data).toEqual({ role: 'admin' });
|
|
43
|
+
expect(ctx.createdAt).toBeInstanceOf(Date);
|
|
44
|
+
expect(ctx.lastActiveAt).toBeInstanceOf(Date);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle invalid JSON gracefully', () => {
|
|
48
|
+
(fs.existsSync as jest.Mock).mockReturnValue(true);
|
|
49
|
+
(fs.readFileSync as jest.Mock).mockReturnValue('invalid-json');
|
|
50
|
+
|
|
51
|
+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
52
|
+
|
|
53
|
+
contextManager = new ContextManager();
|
|
54
|
+
|
|
55
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
56
|
+
expect.stringContaining('[ContextManager] Failed to load contexts:'),
|
|
57
|
+
expect.any(Error),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
consoleSpy.mockRestore();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getContext', () => {
|
|
65
|
+
it('should create a new context if it does not exist', () => {
|
|
66
|
+
const ctx = contextManager.getContext('new-ctx');
|
|
67
|
+
|
|
68
|
+
expect(ctx.id).toBe('new-ctx');
|
|
69
|
+
expect(ctx.messages).toEqual([]);
|
|
70
|
+
expect(ctx.createdAt).toBeInstanceOf(Date);
|
|
71
|
+
|
|
72
|
+
// Should save to disk when creating new context
|
|
73
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should retrieve an existing context', () => {
|
|
77
|
+
// First create it
|
|
78
|
+
contextManager.getContext('existing-ctx');
|
|
79
|
+
|
|
80
|
+
// Then retrieve it
|
|
81
|
+
const ctx = contextManager.getContext('existing-ctx');
|
|
82
|
+
expect(ctx.id).toBe('existing-ctx');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should update lastActiveAt on retrieval', () => {
|
|
86
|
+
const ctx1 = contextManager.getContext('active-ctx');
|
|
87
|
+
const firstAccess = ctx1.lastActiveAt;
|
|
88
|
+
|
|
89
|
+
// Advance time slightly (mocking Date would be cleaner but checking for change works)
|
|
90
|
+
const ctx2 = contextManager.getContext('active-ctx');
|
|
91
|
+
|
|
92
|
+
expect(ctx2.lastActiveAt.getTime()).toBeGreaterThanOrEqual(firstAccess.getTime());
|
|
93
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('setContextData', () => {
|
|
98
|
+
it('should update context data and save', () => {
|
|
99
|
+
contextManager.setContextData('data-ctx', { key: 'value' });
|
|
100
|
+
|
|
101
|
+
const ctx = contextManager.getContext('data-ctx');
|
|
102
|
+
expect(ctx.data).toEqual({ key: 'value' });
|
|
103
|
+
|
|
104
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
105
|
+
|
|
106
|
+
// Check partial update
|
|
107
|
+
contextManager.setContextData('data-ctx', { key2: 'value2' });
|
|
108
|
+
const ctx2 = contextManager.getContext('data-ctx');
|
|
109
|
+
expect(ctx2.data).toEqual({ key: 'value', key2: 'value2' });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('getAllContexts', () => {
|
|
114
|
+
it('should return all contexts', () => {
|
|
115
|
+
contextManager.getContext('ctx-1');
|
|
116
|
+
contextManager.getContext('ctx-2');
|
|
117
|
+
|
|
118
|
+
const all = contextManager.getAllContexts();
|
|
119
|
+
expect(all).toHaveLength(2);
|
|
120
|
+
expect(all.map((c) => c.id)).toContain('ctx-1');
|
|
121
|
+
expect(all.map((c) => c.id)).toContain('ctx-2');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('clearContext', () => {
|
|
126
|
+
it('should remove context and save', () => {
|
|
127
|
+
contextManager.getContext('ctx-to-delete');
|
|
128
|
+
expect(contextManager.hasContext('ctx-to-delete')).toBe(true);
|
|
129
|
+
|
|
130
|
+
contextManager.clearContext('ctx-to-delete');
|
|
131
|
+
expect(contextManager.hasContext('ctx-to-delete')).toBe(false);
|
|
132
|
+
|
|
133
|
+
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should do nothing if context does not exist', () => {
|
|
137
|
+
contextManager.clearContext('non-existent');
|
|
138
|
+
// Ideally checking that save wasn't called if we tracked "dirty" state properly,
|
|
139
|
+
// but current implementation might not save.
|
|
140
|
+
// Actually implementation calls save only if delete returns true.
|
|
141
|
+
// We'll check hasContext remains false.
|
|
142
|
+
expect(contextManager.hasContext('non-existent')).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
package/tsconfig.json
CHANGED
package/src/app.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
const bolt_1 = require("@slack/bolt");
|
|
37
|
-
const dotenv = __importStar(require("dotenv"));
|
|
38
|
-
dotenv.config();
|
|
39
|
-
const app = new bolt_1.App({
|
|
40
|
-
token: process.env.SLACK_BOT_TOKEN,
|
|
41
|
-
signingSecret: process.env.SLACK_SIGNING_SECRET,
|
|
42
|
-
socketMode: true, // Recommended for local development
|
|
43
|
-
appToken: process.env.SLACK_APP_TOKEN
|
|
44
|
-
});
|
|
45
|
-
// Listens to incoming messages that contain "ping"
|
|
46
|
-
app.message('ping', async ({ message, say }) => {
|
|
47
|
-
// say() sends a message to the channel where the event was triggered
|
|
48
|
-
await say(`pong`);
|
|
49
|
-
});
|
|
50
|
-
(async () => {
|
|
51
|
-
// Start your app
|
|
52
|
-
await app.start(process.env.PORT || 3000);
|
|
53
|
-
console.log('⚡️ Bolt app is running!');
|
|
54
|
-
})();
|
|
55
|
-
//# sourceMappingURL=app.js.map
|
package/src/app.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sCAAkC;AAClC,+CAAiC;AAEjC,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,GAAG,GAAG,IAAI,UAAG,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;IAClC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB;IAC/C,UAAU,EAAE,IAAI,EAAE,oCAAoC;IACtD,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe;CACtC,CAAC,CAAC;AAEH,mDAAmD;AACnD,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;IAC7C,qEAAqE;IACrE,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,CAAC,KAAK,IAAI,EAAE;IACV,iBAAiB;IACjB,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;IAE1C,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;AACzC,CAAC,CAAC,EAAE,CAAC"}
|