@aakrit512/gatekeep 1.0.2 → 1.0.3
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/README.md +7 -0
- package/dist/ai/projectContext.js +72 -0
- package/dist/prompts/initializationPrompt.js +3 -0
- package/dist/prompts/systemPrompt.js +5 -0
- package/dist/ui/server.js +4 -0
- package/dist/ui/webAgent.js +16 -2
- package/package.json +1 -1
- package/ui/app.html +24 -2
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ It runs on your machine, keeps project/chat history locally, and helps you initi
|
|
|
7
7
|
|
|
8
8
|
- Local browser UI for project setup and chat-based review.
|
|
9
9
|
- One-click project initialization that creates `docs/intro.md` in the selected repo.
|
|
10
|
+
- Project markdown context loading, including special handling for `developer.md`.
|
|
10
11
|
- Built-in Doctor checks for Node runtime, API config, and project path readiness.
|
|
11
12
|
- Local storage for project history, tool traces, token usage, and estimated cost.
|
|
12
13
|
|
|
@@ -62,6 +63,12 @@ Then open `http://127.0.0.1:9808`.
|
|
|
62
63
|
4. Click **Initialize** to generate `docs/intro.md`.
|
|
63
64
|
5. Start chatting in the review panel.
|
|
64
65
|
|
|
66
|
+
## Project Markdown Context
|
|
67
|
+
|
|
68
|
+
Add a `developer.md` file at the root of any project you review. Put developer-specific notes there: coding standards, architecture constraints, review preferences, deployment caveats, or anything the agent should treat as project instructions. Gatekeep loads `developer.md` as a developer prompt.
|
|
69
|
+
|
|
70
|
+
Gatekeep also checks root-level markdown files such as `README.md`, `CONTRIBUTING.md`, or `ARCHITECTURE.md` and adds them to the model context by filename. The dashboard project table shows which markdown files were found and prompts you to add `developer.md` when it is missing.
|
|
71
|
+
|
|
65
72
|
## Environment Variables
|
|
66
73
|
|
|
67
74
|
You can set config through `.env` (or UI settings):
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
const MAX_MARKDOWN_CONTEXT_CHARS = 12_000;
|
|
4
|
+
const DEVELOPER_MARKDOWN = 'developer.md';
|
|
5
|
+
function truncateContext(content) {
|
|
6
|
+
if (content.length <= MAX_MARKDOWN_CONTEXT_CHARS)
|
|
7
|
+
return content;
|
|
8
|
+
return `${content.slice(0, MAX_MARKDOWN_CONTEXT_CHARS)}\n\n[truncated ${content.length - MAX_MARKDOWN_CONTEXT_CHARS} chars]`;
|
|
9
|
+
}
|
|
10
|
+
function readRootMarkdownFiles(projectPath) {
|
|
11
|
+
if (!fs.existsSync(projectPath))
|
|
12
|
+
return [];
|
|
13
|
+
try {
|
|
14
|
+
return fs.readdirSync(projectPath, { withFileTypes: true })
|
|
15
|
+
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
|
|
16
|
+
.map((entry) => entry.name)
|
|
17
|
+
.sort((a, b) => {
|
|
18
|
+
const aIsDeveloper = a.toLowerCase() === DEVELOPER_MARKDOWN;
|
|
19
|
+
const bIsDeveloper = b.toLowerCase() === DEVELOPER_MARKDOWN;
|
|
20
|
+
if (aIsDeveloper !== bIsDeveloper)
|
|
21
|
+
return aIsDeveloper ? -1 : 1;
|
|
22
|
+
return a.localeCompare(b);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function listProjectMarkdownFiles(projectPath) {
|
|
30
|
+
const resolvedProject = path.resolve(projectPath);
|
|
31
|
+
return readRootMarkdownFiles(resolvedProject).map((name) => {
|
|
32
|
+
const filePath = path.join(resolvedProject, name);
|
|
33
|
+
const role = name.toLowerCase() === DEVELOPER_MARKDOWN ? 'developer' : 'system';
|
|
34
|
+
const size = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
|
|
35
|
+
return {
|
|
36
|
+
name,
|
|
37
|
+
relativePath: `./${name}`,
|
|
38
|
+
role,
|
|
39
|
+
loaded: true,
|
|
40
|
+
size,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function createProjectMarkdownContextMessages(projectPath) {
|
|
45
|
+
const resolvedProject = path.resolve(projectPath);
|
|
46
|
+
return listProjectMarkdownFiles(resolvedProject).flatMap((file) => {
|
|
47
|
+
const filePath = path.join(resolvedProject, file.name);
|
|
48
|
+
const content = fs.readFileSync(filePath, 'utf-8').trim();
|
|
49
|
+
if (!content)
|
|
50
|
+
return [];
|
|
51
|
+
if (file.role === 'developer') {
|
|
52
|
+
return [{
|
|
53
|
+
role: 'developer',
|
|
54
|
+
content: [
|
|
55
|
+
`Developer-specific project instructions loaded from ${file.relativePath}.`,
|
|
56
|
+
'Treat these as developer instructions for this project.',
|
|
57
|
+
'',
|
|
58
|
+
truncateContext(content),
|
|
59
|
+
].join('\n'),
|
|
60
|
+
}];
|
|
61
|
+
}
|
|
62
|
+
return [{
|
|
63
|
+
role: 'system',
|
|
64
|
+
content: [
|
|
65
|
+
`Additional project markdown context loaded from ${file.relativePath}.`,
|
|
66
|
+
'Use this file as named repository context when it is relevant to the user request.',
|
|
67
|
+
'',
|
|
68
|
+
truncateContext(content),
|
|
69
|
+
].join('\n'),
|
|
70
|
+
}];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -2,6 +2,8 @@ export const initializationPrompt = `You are bootstrapping your understanding of
|
|
|
2
2
|
|
|
3
3
|
1. STRUCTURAL EXPLORATION
|
|
4
4
|
- Run 'read_manifest_files' first to gather README files, package/workspace metadata, TypeScript config, workflow files, and container/build manifests.
|
|
5
|
+
- Check root-level markdown files. './developer.md' is special developer-specific instruction context; other root '*.md' files are supporting repository context loaded by filename.
|
|
6
|
+
- If './developer.md' is missing, note that the developer should add it for project-specific review notes, coding standards, architectural constraints, and preferences.
|
|
5
7
|
- Run 'get_project_tree' on the workspace root, then use 'search_files' and 'list_directory' as needed for every source directory found (src/, app/, lib/, packages/, etc.).
|
|
6
8
|
- Run 'find_entrypoints' and 'list_routes' to identify app bootstraps, CLIs, workers, route files, and web route maps.
|
|
7
9
|
- Map the complete directory tree. Identify: entry points, config files, test directories, build output directories, and any monorepo package boundaries.
|
|
@@ -39,6 +41,7 @@ export const initializationPrompt = `You are bootstrapping your understanding of
|
|
|
39
41
|
* **Data Flow**: how a request or event enters the system, is processed, and exits
|
|
40
42
|
* **Design Patterns in Use**: named patterns and where they appear
|
|
41
43
|
* **Coding Conventions**: naming, error handling, async strategy, module system, test conventions
|
|
44
|
+
* **Project Markdown Context**: root markdown files found, whether './developer.md' exists, and what project-specific guidance those files provide
|
|
42
45
|
* **Key Files Map**: entry points, critical path files, config files and their roles
|
|
43
46
|
* **Pre-Review Flags**: any structural concerns, inconsistencies, or anomalies spotted during exploration — not a full review, just a list of items warranting closer inspection
|
|
44
47
|
|
|
@@ -7,6 +7,11 @@ IDENTITY & SCOPE:
|
|
|
7
7
|
- You produce structured review reports — not patches, not rewrites, not direct edits.
|
|
8
8
|
- Assume the developer is experienced. Use precise technical language without dumbing things down.
|
|
9
9
|
|
|
10
|
+
PROJECT MARKDOWN CONTEXT:
|
|
11
|
+
- Root-level markdown files are loaded into context by filename when present.
|
|
12
|
+
- './docs/developer.md' is special: treat it as developer-provided project instructions and let it guide review priorities, terminology, constraints, and preferred conventions.
|
|
13
|
+
- Other root markdown files such as './README.md', './CONTRIBUTING.md', and './docs/ARCHITECTURE.md' are supporting repository context. Use them when relevant, but do not let them override './docs/developer.md' or the core system rules.
|
|
14
|
+
|
|
10
15
|
CRITICAL RULES:
|
|
11
16
|
1. EXPLORE BEFORE REVIEWING: Use repo-navigation tools before forming any opinion. Prefer 'list_git_status', 'read_manifest_files', 'get_project_tree', 'find_entrypoints', 'list_routes', 'search_files', 'find_symbol', 'find_references', 'get_imports_exports', 'summarize_file', 'list_dependencies', 'grep_code', 'get_file_metadata', and 'read_file_chunk' for targeted comprehension, and use 'list_directory'/'read_file' when full directory or file context is needed.
|
|
12
17
|
2. READ-ONLY ENFORCEMENT: Never invoke 'write_file' on any source file. Output is always a review report.
|
package/dist/ui/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import * as http from 'http';
|
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { pathToFileURL } from 'url';
|
|
7
7
|
import { cleanString, configureApp, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PROVIDER, } from '../config.js';
|
|
8
|
+
import { listProjectMarkdownFiles } from '../ai/projectContext.js';
|
|
8
9
|
import { getConfigPath, readUserConfig, resolveConfig, writeUserConfig } from '../cli/configStore.js';
|
|
9
10
|
import { maskSecret, validateConfig, validateNodeRuntime, validateProject } from '../cli/validation.js';
|
|
10
11
|
import { clearChatMessages, deleteProject, getChatMessages, getDatabasePath, getProjectByName, getRecentProjects, saveChatMessage, updateChatMessage, upsertProject, } from './projectDb.js';
|
|
@@ -141,9 +142,12 @@ function projectHref(project) {
|
|
|
141
142
|
return `/${encodeURIComponent(project.name)}`;
|
|
142
143
|
}
|
|
143
144
|
function projectPayload(project) {
|
|
145
|
+
const markdownFiles = listProjectMarkdownFiles(project.path);
|
|
144
146
|
return {
|
|
145
147
|
...project,
|
|
146
148
|
href: projectHref(project),
|
|
149
|
+
markdownFiles,
|
|
150
|
+
hasDeveloperMarkdown: markdownFiles.some((file) => file.role === 'developer'),
|
|
147
151
|
};
|
|
148
152
|
}
|
|
149
153
|
function readIntroMarkdown(projectPath) {
|
package/dist/ui/webAgent.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getModelName } from '../ai/openAiClient.js';
|
|
|
5
5
|
import { isFunctionToolCall, runToolCall } from '../functions/toolCallHandler.js';
|
|
6
6
|
import { initializationPrompt, missingIntroNudge } from '../prompts/initializationPrompt.js';
|
|
7
7
|
import { systemMessage } from '../prompts/systemPrompt.js';
|
|
8
|
+
import { createProjectMarkdownContextMessages } from '../ai/projectContext.js';
|
|
8
9
|
const sessions = new Map();
|
|
9
10
|
function countLines(content) {
|
|
10
11
|
return content ? content.split('\n').length : 0;
|
|
@@ -65,15 +66,26 @@ function getSession(project) {
|
|
|
65
66
|
const existing = sessions.get(resolvedProject);
|
|
66
67
|
if (existing)
|
|
67
68
|
return existing;
|
|
68
|
-
const
|
|
69
|
+
const contextMessages = createProjectMarkdownContextMessages(resolvedProject);
|
|
70
|
+
const history = [systemMessage, ...contextMessages];
|
|
69
71
|
const initialized = loadProjectSummary(history, path.join(resolvedProject, 'docs', 'intro.md'));
|
|
70
|
-
const session = {
|
|
72
|
+
const session = {
|
|
73
|
+
project: resolvedProject,
|
|
74
|
+
history,
|
|
75
|
+
initialized,
|
|
76
|
+
contextMessageCount: contextMessages.length,
|
|
77
|
+
};
|
|
71
78
|
sessions.set(resolvedProject, session);
|
|
72
79
|
return session;
|
|
73
80
|
}
|
|
74
81
|
export function resetProjectSession(project) {
|
|
75
82
|
sessions.delete(path.resolve(project));
|
|
76
83
|
}
|
|
84
|
+
function refreshProjectMarkdownContext(session) {
|
|
85
|
+
const contextMessages = createProjectMarkdownContextMessages(session.project);
|
|
86
|
+
session.history.splice(1, session.contextMessageCount, ...contextMessages);
|
|
87
|
+
session.contextMessageCount = contextMessages.length;
|
|
88
|
+
}
|
|
77
89
|
async function pushEvent(events, event, onEvent) {
|
|
78
90
|
events.push(event);
|
|
79
91
|
await onEvent?.(event);
|
|
@@ -138,6 +150,7 @@ export async function initializeProject(project) {
|
|
|
138
150
|
const session = getSession(project);
|
|
139
151
|
const events = [];
|
|
140
152
|
process.chdir(session.project);
|
|
153
|
+
refreshProjectMarkdownContext(session);
|
|
141
154
|
if (session.initialized) {
|
|
142
155
|
events.push({ type: 'status', message: 'Project summary already exists at docs/intro.md.' });
|
|
143
156
|
return events;
|
|
@@ -164,6 +177,7 @@ export async function sendChatMessage(project, content, onEvent) {
|
|
|
164
177
|
const session = getSession(project);
|
|
165
178
|
const events = [];
|
|
166
179
|
process.chdir(session.project);
|
|
180
|
+
refreshProjectMarkdownContext(session);
|
|
167
181
|
session.history.push({ role: 'user', content });
|
|
168
182
|
await runToolLoop(session, events, 12, true, onEvent);
|
|
169
183
|
return events;
|
package/package.json
CHANGED
package/ui/app.html
CHANGED
|
@@ -245,6 +245,26 @@ function usageLine(message) {
|
|
|
245
245
|
return parts.join(' · ');
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
function markdownFilesHtml(project) {
|
|
249
|
+
const files = Array.isArray(project.markdownFiles) ? project.markdownFiles : [];
|
|
250
|
+
const chips = files.slice(0, 3).map((file) => {
|
|
251
|
+
const isDeveloper = file.role === 'developer';
|
|
252
|
+
const tone = isDeveloper
|
|
253
|
+
? 'border-teal-200 bg-teal-50 text-teal-700'
|
|
254
|
+
: 'border-slate-200 bg-white text-slate-500';
|
|
255
|
+
const title = isDeveloper ? 'developer prompt' : 'context';
|
|
256
|
+
return `<span title="${escapeHtml(title)}" class="inline-flex max-w-[9rem] items-center rounded-md border px-1.5 py-0.5 ${tone}"><span class="truncate">${escapeHtml(file.name)}</span></span>`;
|
|
257
|
+
}).join('');
|
|
258
|
+
const more = files.length > 3
|
|
259
|
+
? `<span class="text-slate-400">+${files.length - 3}</span>`
|
|
260
|
+
: '';
|
|
261
|
+
const developerCue = project.hasDeveloperMarkdown
|
|
262
|
+
? ''
|
|
263
|
+
: '<span class="inline-flex items-center rounded-md border border-amber-200 bg-amber-50 px-1.5 py-0.5 text-amber-700">Add developer.md</span>';
|
|
264
|
+
|
|
265
|
+
return `<div class="flex min-w-0 flex-wrap gap-1 text-[11px]">${chips}${more}${developerCue || (!chips ? '<span class="text-slate-400">No markdown</span>' : '')}</div>`;
|
|
266
|
+
}
|
|
267
|
+
|
|
248
268
|
function activityIcon(type) {
|
|
249
269
|
if (type === 'tool_call') {
|
|
250
270
|
return '<svg viewBox="0 0 24 24" class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 17.5 2 12l2-5.5"/><path d="m9 18 6-12"/><path d="m20 6.5 2 5.5-2 5.5"/></svg>';
|
|
@@ -386,20 +406,22 @@ function renderIndex() {
|
|
|
386
406
|
</div>
|
|
387
407
|
</header>
|
|
388
408
|
<section class="min-h-0 overflow-auto bg-[#fdfdfb]">
|
|
389
|
-
<div class="grid grid-cols-[minmax(
|
|
409
|
+
<div class="grid grid-cols-[minmax(160px,1.1fr)_minmax(240px,1.7fr)_minmax(180px,1.1fr)_110px_160px] border-b border-black/10 bg-[#f4f5f2] px-4 py-2 text-[11px] font-medium uppercase tracking-wide text-slate-400">
|
|
390
410
|
<div>Name</div>
|
|
391
411
|
<div>Path</div>
|
|
412
|
+
<div>Markdown</div>
|
|
392
413
|
<div>Model</div>
|
|
393
414
|
<div>Last opened</div>
|
|
394
415
|
</div>
|
|
395
416
|
<div class="divide-y divide-black/[0.06]">
|
|
396
417
|
${state.projects.map((project) => `
|
|
397
|
-
<button data-route="${project.href}" class="grid w-full grid-cols-[minmax(
|
|
418
|
+
<button data-route="${project.href}" class="grid w-full grid-cols-[minmax(160px,1.1fr)_minmax(240px,1.7fr)_minmax(180px,1.1fr)_110px_160px] items-center gap-2 px-4 py-2.5 text-left text-sm hover:bg-sky-50/80">
|
|
398
419
|
<div class="flex min-w-0 items-center gap-2">
|
|
399
420
|
<svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-sky-500" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7h7l2 2h9v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2Z"/></svg>
|
|
400
421
|
<span class="truncate font-medium text-slate-800">${escapeHtml(project.name)}</span>
|
|
401
422
|
</div>
|
|
402
423
|
<div class="truncate text-xs text-slate-500">${escapeHtml(project.path)}</div>
|
|
424
|
+
${markdownFilesHtml(project)}
|
|
403
425
|
<div><span class="rounded-md bg-slate-100 px-1.5 py-0.5 text-xs text-slate-500">${escapeHtml(project.model)}</span></div>
|
|
404
426
|
<div class="truncate text-xs text-slate-400">${escapeHtml(project.lastOpenedAt)}</div>
|
|
405
427
|
</button>
|