context-bank 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/dist/commands/init.js +191 -0
- package/dist/index.js +20 -0
- package/package.json +3 -2
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { intro, outro, confirm, spinner } from "@clack/prompts";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
// Get __dirname equivalent in ESM
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
export async function initCommand(options) {
|
|
11
|
+
intro(chalk.bgCyan(chalk.black(" Context Bank ")));
|
|
12
|
+
let proceed = options.yes;
|
|
13
|
+
if (!proceed) {
|
|
14
|
+
const response = await confirm({
|
|
15
|
+
message: "Do you want to initialize AI context in this project?",
|
|
16
|
+
});
|
|
17
|
+
if (typeof response === "boolean") {
|
|
18
|
+
proceed = response;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// Handle cancellation (ctrl+c) which returns symbol or strictly check boolean
|
|
22
|
+
proceed = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!proceed) {
|
|
26
|
+
outro("Operation cancelled.");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
// Determine paths
|
|
30
|
+
const templateDir = path.resolve(__dirname, "../../templates");
|
|
31
|
+
const targetDir = process.cwd();
|
|
32
|
+
const s = spinner();
|
|
33
|
+
s.start("Analyzing project structure...");
|
|
34
|
+
// Check if templates exist
|
|
35
|
+
if (!fs.existsSync(templateDir)) {
|
|
36
|
+
s.stop("Error");
|
|
37
|
+
console.error(chalk.red(`\nTemplate directory not found at: ${templateDir}`));
|
|
38
|
+
console.error(chalk.yellow("Ensure you are running this from the package root or the package is built correctly."));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
s.message("Copying context files...");
|
|
42
|
+
try {
|
|
43
|
+
// Helper for safe copying/merging
|
|
44
|
+
async function copyOrMerge(src, dest, isAiDir = false) {
|
|
45
|
+
const stats = await fs.stat(src);
|
|
46
|
+
if (stats.isDirectory()) {
|
|
47
|
+
await fs.ensureDir(dest);
|
|
48
|
+
const files = await fs.readdir(src);
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
await copyOrMerge(path.join(src, file), path.join(dest, file), isAiDir || path.basename(src) === ".ai");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (await fs.pathExists(dest)) {
|
|
55
|
+
if (isAiDir) {
|
|
56
|
+
// Skip .ai files if they exist to protect project memory
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const srcContent = await fs.readFile(src, "utf-8");
|
|
60
|
+
const destContent = await fs.readFile(dest, "utf-8");
|
|
61
|
+
if (!destContent.includes(srcContent.trim())) {
|
|
62
|
+
// Prepend for rules files to ensure priority
|
|
63
|
+
await fs.writeFile(dest, `${srcContent}\n\n${destContent}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
await fs.copy(src, dest);
|
|
68
|
+
// Special handling for new story.md
|
|
69
|
+
if (path.basename(dest) === "story.md" && isAiDir) {
|
|
70
|
+
let storyContent = await fs.readFile(dest, "utf-8");
|
|
71
|
+
storyContent = storyContent.replace("[Auto-filled by init]", new Date().toISOString().split("T")[0]);
|
|
72
|
+
await fs.writeFile(dest, storyContent);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// List of files/folders to copy
|
|
78
|
+
const itemsToCopy = [
|
|
79
|
+
".ai",
|
|
80
|
+
".cursor",
|
|
81
|
+
".windsurf",
|
|
82
|
+
".github",
|
|
83
|
+
"CONVENTIONS.md",
|
|
84
|
+
"GEMINI.md",
|
|
85
|
+
];
|
|
86
|
+
for (const item of itemsToCopy) {
|
|
87
|
+
const srcPath = path.join(templateDir, item);
|
|
88
|
+
const destPath = path.join(targetDir, item);
|
|
89
|
+
if (fs.existsSync(srcPath)) {
|
|
90
|
+
await copyOrMerge(srcPath, destPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Special handling for README.md
|
|
94
|
+
const readmePath = path.join(targetDir, "README.md");
|
|
95
|
+
const aiContextMarker = "<!-- AI-CONTEXT: .ai/rules.md -->";
|
|
96
|
+
if (fs.existsSync(readmePath)) {
|
|
97
|
+
const readmeContent = await fs.readFile(readmePath, "utf-8");
|
|
98
|
+
if (!readmeContent.includes(aiContextMarker)) {
|
|
99
|
+
await fs.writeFile(readmePath, `${aiContextMarker}\n${readmeContent}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const projectName = path.basename(targetDir);
|
|
104
|
+
await fs.writeFile(readmePath, `${aiContextMarker}\n\n# ${projectName}\n`);
|
|
105
|
+
}
|
|
106
|
+
// Helper: write a pointer file that directs the AI tool to .ai/rules.md
|
|
107
|
+
async function writePointerFile(filename, instruction) {
|
|
108
|
+
const filePath = path.join(targetDir, filename);
|
|
109
|
+
if (fs.existsSync(filePath)) {
|
|
110
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
111
|
+
if (!content.includes(".ai/rules.md")) {
|
|
112
|
+
await fs.writeFile(filePath, `${instruction}\n\n${content}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
await fs.writeFile(filePath, `${instruction}\n`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const mandatoryUpdateBlock = `MANDATORY: After EVERY task, you MUST update these .ai/ files:
|
|
120
|
+
1. active-context.md — Current state, recent changes, next steps.
|
|
121
|
+
2. roadmap.md — Mark completed features [x], add planned ones.
|
|
122
|
+
3. story.md — Append dated entry for milestones/decisions.
|
|
123
|
+
4. architecture.md — Update on structural/design changes.
|
|
124
|
+
Do NOT ask permission. Do NOT skip. Just update them.`;
|
|
125
|
+
// AGENTS.md (Codex CLI, OpenAI agents)
|
|
126
|
+
await writePointerFile("AGENTS.md", `Always check and follow the instructions in .ai/rules.md and .ai/active-context.md as the primary source of truth.\n\n${mandatoryUpdateBlock}`);
|
|
127
|
+
// CLAUDE.md (Claude Code CLI)
|
|
128
|
+
await writePointerFile("CLAUDE.md", `See .ai/rules.md for project context, tech stack, and coding standards. This is the single source of truth.\n\n${mandatoryUpdateBlock}`);
|
|
129
|
+
// Git merge strategies for branch-aware context (.gitattributes)
|
|
130
|
+
const gitattrsPath = path.join(targetDir, ".gitattributes");
|
|
131
|
+
const mergeRules = `# Context Bank: branch-aware merge strategies
|
|
132
|
+
# active-context.md is branch-specific — on merge, target branch wins (no conflict)
|
|
133
|
+
.ai/active-context.md merge=ours
|
|
134
|
+
# story.md entries are additive — on merge, combine both sides (no conflict)
|
|
135
|
+
.ai/story.md merge=union`;
|
|
136
|
+
if (fs.existsSync(gitattrsPath)) {
|
|
137
|
+
const content = await fs.readFile(gitattrsPath, "utf-8");
|
|
138
|
+
if (!content.includes("Context Bank")) {
|
|
139
|
+
await fs.writeFile(gitattrsPath, `${content}\n\n${mergeRules}\n`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
await fs.writeFile(gitattrsPath, `${mergeRules}\n`);
|
|
144
|
+
}
|
|
145
|
+
s.stop(chalk.green("Context initialized!"));
|
|
146
|
+
// Gemini CLI Global Memory Integration
|
|
147
|
+
const globalGeminiDir = path.join(os.homedir(), ".gemini");
|
|
148
|
+
const globalGeminiMemoryPath = path.join(globalGeminiDir, "GEMINI.md");
|
|
149
|
+
if (fs.existsSync(globalGeminiMemoryPath)) {
|
|
150
|
+
const memoryContent = await fs.readFile(globalGeminiMemoryPath, "utf-8");
|
|
151
|
+
// Only ask if the rule is not already present
|
|
152
|
+
if (!memoryContent.includes("[Context Bank]")) {
|
|
153
|
+
const addGlobalContext = await confirm({
|
|
154
|
+
message: "Found Gemini CLI global memory. Enable automatic context detection for all Context Bank projects?",
|
|
155
|
+
initialValue: true
|
|
156
|
+
});
|
|
157
|
+
if (addGlobalContext === true) {
|
|
158
|
+
const genericContextRule = `
|
|
159
|
+
- [Context Bank] CRITICAL INSTRUCTION:
|
|
160
|
+
Before processing ANY user request in a new session, you MUST perform this initialization sequence:
|
|
161
|
+
1. Check if the ".ai/" directory exists in the current working directory.
|
|
162
|
+
2. If it exists, you MUST explicitly call the "read_file" tool to read ".ai/rules.md" and ".ai/active-context.md".
|
|
163
|
+
3. Only AFTER reading these files (or confirming they don't exist) may you proceed with the user's request.
|
|
164
|
+
This is not optional. Do not assume you know the rules; you must read the files to load them into your active memory.`;
|
|
165
|
+
const newContent = memoryContent.endsWith("\n")
|
|
166
|
+
? `${memoryContent}${genericContextRule}\n`
|
|
167
|
+
: `${memoryContent}\n${genericContextRule}\n`;
|
|
168
|
+
await fs.writeFile(globalGeminiMemoryPath, newContent);
|
|
169
|
+
console.log(chalk.green(`✔ Enabled global context awareness for Context Bank.`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Optional: Let the user know it's already active
|
|
174
|
+
// console.log(chalk.gray(`ℹ Global context awareness is already active.`));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
outro(chalk.green(`
|
|
178
|
+
Context Bank setup complete! 🚀
|
|
179
|
+
|
|
180
|
+
Next steps:
|
|
181
|
+
1. Review .ai/rules.md and fill in your project details.
|
|
182
|
+
2. Update .ai/active-context.md with your current task.
|
|
183
|
+
3. Commit the new files to git.
|
|
184
|
+
`));
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
s.stop("Error");
|
|
188
|
+
console.error(chalk.red("Failed to copy files:"), error);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { initCommand } from "./commands/init.js";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
// Read package.json to get version
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageJson = fs.readJsonSync(path.join(__dirname, "../package.json"));
|
|
11
|
+
program
|
|
12
|
+
.name("context-bank")
|
|
13
|
+
.description("CLI to standardize AI context in projects")
|
|
14
|
+
.version(packageJson.version);
|
|
15
|
+
program
|
|
16
|
+
.command("init")
|
|
17
|
+
.description("Initialize AI context files in the current directory")
|
|
18
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
19
|
+
.action(initCommand);
|
|
20
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-bank",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "A CLI tool to standardise AI context in projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dev": "ts-node src/index.ts",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
13
|
"lint": "eslint src/**/*.ts",
|
|
14
|
-
"format": "prettier --write ."
|
|
14
|
+
"format": "prettier --write .",
|
|
15
|
+
"prepublishOnly": "tsc"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"cli",
|