@contextos/core 0.2.3 → 0.4.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/dist/index.js +632 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1963,7 +1963,7 @@ var RateLimiter = class {
|
|
|
1963
1963
|
async waitForSlot() {
|
|
1964
1964
|
const result = await this.checkLimit();
|
|
1965
1965
|
if (!result.allowed) {
|
|
1966
|
-
await new Promise((
|
|
1966
|
+
await new Promise((resolve3) => setTimeout(resolve3, result.waitTime + 100));
|
|
1967
1967
|
return this.waitForSlot();
|
|
1968
1968
|
}
|
|
1969
1969
|
}
|
|
@@ -2091,7 +2091,7 @@ var GeminiClient = class {
|
|
|
2091
2091
|
const waitTime = retryAfter ? parseInt(retryAfter) * 1e3 : 6e4;
|
|
2092
2092
|
if (retryCount < 3) {
|
|
2093
2093
|
console.warn(`Rate limited. Waiting ${waitTime}ms before retry (${retryCount + 1}/3)...`);
|
|
2094
|
-
await new Promise((
|
|
2094
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
2095
2095
|
return this.request(prompt, systemPrompt, retryCount + 1);
|
|
2096
2096
|
}
|
|
2097
2097
|
throw new Error(`Rate limit exceeded after ${retryCount} retries. Please try again later.`);
|
|
@@ -2275,6 +2275,18 @@ Output the compressed version directly.`;
|
|
|
2275
2275
|
tokensSaved: originalTokens - newTokens
|
|
2276
2276
|
};
|
|
2277
2277
|
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Generate code or content from a prompt
|
|
2280
|
+
* Used by AIGenerator for code generation
|
|
2281
|
+
*/
|
|
2282
|
+
async generate(prompt) {
|
|
2283
|
+
const response = await this.request(prompt);
|
|
2284
|
+
return {
|
|
2285
|
+
text: response,
|
|
2286
|
+
tokensUsed: Math.ceil(response.length / 4)
|
|
2287
|
+
// Rough estimate
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2278
2290
|
};
|
|
2279
2291
|
function createGeminiClient() {
|
|
2280
2292
|
const apiKey = process.env.GEMINI_API_KEY;
|
|
@@ -3066,7 +3078,7 @@ var OpenAIAdapter = class {
|
|
|
3066
3078
|
const waitTime = retryAfter ? parseInt(retryAfter) * 1e3 : 6e4;
|
|
3067
3079
|
if (retryCount < 3) {
|
|
3068
3080
|
console.warn(`OpenAI rate limited. Waiting ${waitTime}ms before retry (${retryCount + 1}/3)...`);
|
|
3069
|
-
await new Promise((
|
|
3081
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
3070
3082
|
return this.complete(request, retryCount + 1);
|
|
3071
3083
|
}
|
|
3072
3084
|
return {
|
|
@@ -3166,7 +3178,7 @@ var AnthropicAdapter = class {
|
|
|
3166
3178
|
const waitTime = retryAfter ? parseInt(retryAfter) * 1e3 : 6e4;
|
|
3167
3179
|
if (retryCount < 3) {
|
|
3168
3180
|
console.warn(`Anthropic rate limited. Waiting ${waitTime}ms before retry (${retryCount + 1}/3)...`);
|
|
3169
|
-
await new Promise((
|
|
3181
|
+
await new Promise((resolve3) => setTimeout(resolve3, waitTime));
|
|
3170
3182
|
return this.complete(request, retryCount + 1);
|
|
3171
3183
|
}
|
|
3172
3184
|
return {
|
|
@@ -7578,7 +7590,618 @@ function createLogger(config) {
|
|
|
7578
7590
|
function setGlobalLogger(logger) {
|
|
7579
7591
|
globalLogger = logger;
|
|
7580
7592
|
}
|
|
7593
|
+
|
|
7594
|
+
// src/generator/index.ts
|
|
7595
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync15 } from "fs";
|
|
7596
|
+
import { dirname as dirname5, join as join15, resolve as resolve2, normalize as normalize3 } from "path";
|
|
7597
|
+
|
|
7598
|
+
// src/generator/shadow-fs.ts
|
|
7599
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, readFileSync as readFileSync13, rmSync as rmSync2, renameSync, copyFileSync } from "fs";
|
|
7600
|
+
import { join as join13, dirname as dirname4 } from "path";
|
|
7601
|
+
import { randomUUID } from "crypto";
|
|
7602
|
+
var ShadowFileSystem = class {
|
|
7603
|
+
rootDir;
|
|
7604
|
+
shadowRoot;
|
|
7605
|
+
activeTransactions = /* @__PURE__ */ new Map();
|
|
7606
|
+
constructor(rootDir) {
|
|
7607
|
+
this.rootDir = rootDir;
|
|
7608
|
+
this.shadowRoot = join13(rootDir, ".contextos", ".shadow");
|
|
7609
|
+
}
|
|
7610
|
+
/**
|
|
7611
|
+
* Begin a new transaction
|
|
7612
|
+
*/
|
|
7613
|
+
beginTransaction() {
|
|
7614
|
+
const id = randomUUID();
|
|
7615
|
+
const shadowDir = join13(this.shadowRoot, id);
|
|
7616
|
+
mkdirSync9(shadowDir, { recursive: true });
|
|
7617
|
+
const transaction = {
|
|
7618
|
+
id,
|
|
7619
|
+
shadowDir,
|
|
7620
|
+
files: /* @__PURE__ */ new Map(),
|
|
7621
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
7622
|
+
status: "active"
|
|
7623
|
+
};
|
|
7624
|
+
this.activeTransactions.set(id, transaction);
|
|
7625
|
+
return transaction;
|
|
7626
|
+
}
|
|
7627
|
+
/**
|
|
7628
|
+
* Write file to shadow location
|
|
7629
|
+
*/
|
|
7630
|
+
writeToShadow(txId, relativePath, content) {
|
|
7631
|
+
const tx = this.activeTransactions.get(txId);
|
|
7632
|
+
if (!tx) {
|
|
7633
|
+
return { success: false, error: "Transaction not found" };
|
|
7634
|
+
}
|
|
7635
|
+
if (tx.status !== "active") {
|
|
7636
|
+
return { success: false, error: `Transaction is ${tx.status}` };
|
|
7637
|
+
}
|
|
7638
|
+
try {
|
|
7639
|
+
const shadowPath = join13(tx.shadowDir, relativePath);
|
|
7640
|
+
const shadowDir = dirname4(shadowPath);
|
|
7641
|
+
if (!existsSync14(shadowDir)) {
|
|
7642
|
+
mkdirSync9(shadowDir, { recursive: true });
|
|
7643
|
+
}
|
|
7644
|
+
writeFileSync9(shadowPath, content, "utf-8");
|
|
7645
|
+
const realPath = join13(this.rootDir, relativePath);
|
|
7646
|
+
tx.files.set(realPath, shadowPath);
|
|
7647
|
+
return { success: true, shadowPath };
|
|
7648
|
+
} catch (error) {
|
|
7649
|
+
return {
|
|
7650
|
+
success: false,
|
|
7651
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7652
|
+
};
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
/**
|
|
7656
|
+
* Read file from shadow or real location
|
|
7657
|
+
*/
|
|
7658
|
+
readFile(txId, relativePath) {
|
|
7659
|
+
const tx = this.activeTransactions.get(txId);
|
|
7660
|
+
const realPath = join13(this.rootDir, relativePath);
|
|
7661
|
+
if (tx && tx.files.has(realPath)) {
|
|
7662
|
+
const shadowPath = tx.files.get(realPath);
|
|
7663
|
+
if (existsSync14(shadowPath)) {
|
|
7664
|
+
return readFileSync13(shadowPath, "utf-8");
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
if (existsSync14(realPath)) {
|
|
7668
|
+
return readFileSync13(realPath, "utf-8");
|
|
7669
|
+
}
|
|
7670
|
+
return null;
|
|
7671
|
+
}
|
|
7672
|
+
/**
|
|
7673
|
+
* Commit transaction - atomically move shadow files to real locations
|
|
7674
|
+
*/
|
|
7675
|
+
commit(txId) {
|
|
7676
|
+
const tx = this.activeTransactions.get(txId);
|
|
7677
|
+
if (!tx) {
|
|
7678
|
+
return { success: false, filesCommitted: 0, error: "Transaction not found" };
|
|
7679
|
+
}
|
|
7680
|
+
if (tx.status !== "active") {
|
|
7681
|
+
return { success: false, filesCommitted: 0, error: `Transaction is ${tx.status}` };
|
|
7682
|
+
}
|
|
7683
|
+
let filesCommitted = 0;
|
|
7684
|
+
try {
|
|
7685
|
+
const backups = /* @__PURE__ */ new Map();
|
|
7686
|
+
for (const [realPath, shadowPath] of tx.files) {
|
|
7687
|
+
if (existsSync14(realPath)) {
|
|
7688
|
+
const backupPath = `${realPath}.bak.${txId.slice(0, 8)}`;
|
|
7689
|
+
copyFileSync(realPath, backupPath);
|
|
7690
|
+
backups.set(realPath, backupPath);
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
for (const [realPath, shadowPath] of tx.files) {
|
|
7694
|
+
const targetDir = dirname4(realPath);
|
|
7695
|
+
if (!existsSync14(targetDir)) {
|
|
7696
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
7697
|
+
}
|
|
7698
|
+
renameSync(shadowPath, realPath);
|
|
7699
|
+
filesCommitted++;
|
|
7700
|
+
}
|
|
7701
|
+
for (const backupPath of backups.values()) {
|
|
7702
|
+
if (existsSync14(backupPath)) {
|
|
7703
|
+
rmSync2(backupPath);
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
this.cleanupTransaction(tx);
|
|
7707
|
+
tx.status = "committed";
|
|
7708
|
+
return { success: true, filesCommitted };
|
|
7709
|
+
} catch (error) {
|
|
7710
|
+
tx.status = "rolled_back";
|
|
7711
|
+
return {
|
|
7712
|
+
success: false,
|
|
7713
|
+
filesCommitted,
|
|
7714
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7715
|
+
};
|
|
7716
|
+
}
|
|
7717
|
+
}
|
|
7718
|
+
/**
|
|
7719
|
+
* Rollback transaction - discard all shadow files
|
|
7720
|
+
*/
|
|
7721
|
+
rollback(txId) {
|
|
7722
|
+
const tx = this.activeTransactions.get(txId);
|
|
7723
|
+
if (!tx) {
|
|
7724
|
+
return { success: false, error: "Transaction not found" };
|
|
7725
|
+
}
|
|
7726
|
+
try {
|
|
7727
|
+
this.cleanupTransaction(tx);
|
|
7728
|
+
tx.status = "rolled_back";
|
|
7729
|
+
return { success: true };
|
|
7730
|
+
} catch (error) {
|
|
7731
|
+
return {
|
|
7732
|
+
success: false,
|
|
7733
|
+
error: error instanceof Error ? error.message : String(error)
|
|
7734
|
+
};
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
/**
|
|
7738
|
+
* Cleanup transaction resources
|
|
7739
|
+
*/
|
|
7740
|
+
cleanupTransaction(tx) {
|
|
7741
|
+
if (existsSync14(tx.shadowDir)) {
|
|
7742
|
+
rmSync2(tx.shadowDir, { recursive: true, force: true });
|
|
7743
|
+
}
|
|
7744
|
+
this.activeTransactions.delete(tx.id);
|
|
7745
|
+
}
|
|
7746
|
+
/**
|
|
7747
|
+
* Get transaction status
|
|
7748
|
+
*/
|
|
7749
|
+
getTransaction(txId) {
|
|
7750
|
+
return this.activeTransactions.get(txId);
|
|
7751
|
+
}
|
|
7752
|
+
/**
|
|
7753
|
+
* List files in transaction
|
|
7754
|
+
*/
|
|
7755
|
+
listTransactionFiles(txId) {
|
|
7756
|
+
const tx = this.activeTransactions.get(txId);
|
|
7757
|
+
if (!tx) return [];
|
|
7758
|
+
return Array.from(tx.files.keys()).map(
|
|
7759
|
+
(p) => p.replace(this.rootDir + "/", "").replace(this.rootDir + "\\", "")
|
|
7760
|
+
);
|
|
7761
|
+
}
|
|
7762
|
+
};
|
|
7763
|
+
function createShadowFS(rootDir) {
|
|
7764
|
+
return new ShadowFileSystem(rootDir);
|
|
7765
|
+
}
|
|
7766
|
+
|
|
7767
|
+
// src/context/negative-context.ts
|
|
7768
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
7769
|
+
import { join as join14 } from "path";
|
|
7770
|
+
var NegativeContextManager = class {
|
|
7771
|
+
contextIgnorePath;
|
|
7772
|
+
rules = [];
|
|
7773
|
+
constructor(rootDir) {
|
|
7774
|
+
this.contextIgnorePath = join14(rootDir, ".contextos", ".contextignore");
|
|
7775
|
+
this.load();
|
|
7776
|
+
}
|
|
7777
|
+
/**
|
|
7778
|
+
* Load rules from .contextignore
|
|
7779
|
+
*/
|
|
7780
|
+
load() {
|
|
7781
|
+
if (!existsSync15(this.contextIgnorePath)) {
|
|
7782
|
+
this.rules = [];
|
|
7783
|
+
return;
|
|
7784
|
+
}
|
|
7785
|
+
try {
|
|
7786
|
+
const content = readFileSync14(this.contextIgnorePath, "utf-8");
|
|
7787
|
+
const lines = content.split("\n").filter((line) => line.trim() && !line.startsWith("#"));
|
|
7788
|
+
this.rules = lines.map((line) => {
|
|
7789
|
+
const match = line.match(/^(\w+):(.+?)\|(.+)$/);
|
|
7790
|
+
if (match) {
|
|
7791
|
+
return {
|
|
7792
|
+
category: match[1],
|
|
7793
|
+
rule: match[2].trim(),
|
|
7794
|
+
reason: match[3].trim(),
|
|
7795
|
+
learnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7796
|
+
};
|
|
7797
|
+
}
|
|
7798
|
+
return {
|
|
7799
|
+
rule: line.trim(),
|
|
7800
|
+
reason: "User defined",
|
|
7801
|
+
learnedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7802
|
+
category: "pattern"
|
|
7803
|
+
};
|
|
7804
|
+
});
|
|
7805
|
+
} catch {
|
|
7806
|
+
this.rules = [];
|
|
7807
|
+
}
|
|
7808
|
+
}
|
|
7809
|
+
/**
|
|
7810
|
+
* Save rules to .contextignore
|
|
7811
|
+
*/
|
|
7812
|
+
save() {
|
|
7813
|
+
const header = `# ContextOS Negative Context Rules
|
|
7814
|
+
# Format: CATEGORY:RULE|REASON
|
|
7815
|
+
# Categories: library, pattern, file, approach
|
|
7816
|
+
# These rules are injected into AI prompts to prevent repeated mistakes
|
|
7817
|
+
|
|
7818
|
+
`;
|
|
7819
|
+
const content = this.rules.map(
|
|
7820
|
+
(r) => `${r.category}:${r.rule}|${r.reason}`
|
|
7821
|
+
).join("\n");
|
|
7822
|
+
writeFileSync10(this.contextIgnorePath, header + content, "utf-8");
|
|
7823
|
+
}
|
|
7824
|
+
/**
|
|
7825
|
+
* Add a new negative rule
|
|
7826
|
+
*/
|
|
7827
|
+
addRule(rule) {
|
|
7828
|
+
if (this.rules.some((r) => r.rule === rule.rule)) {
|
|
7829
|
+
return;
|
|
7830
|
+
}
|
|
7831
|
+
this.rules.push({
|
|
7832
|
+
...rule,
|
|
7833
|
+
learnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7834
|
+
});
|
|
7835
|
+
this.save();
|
|
7836
|
+
}
|
|
7837
|
+
/**
|
|
7838
|
+
* Learn from an error
|
|
7839
|
+
*/
|
|
7840
|
+
learnFromError(errorMessage, context) {
|
|
7841
|
+
const patterns = [
|
|
7842
|
+
// Library version issues
|
|
7843
|
+
{
|
|
7844
|
+
regex: /Module not found.*['"](.+?)['"]/i,
|
|
7845
|
+
category: "library",
|
|
7846
|
+
template: (match) => ({
|
|
7847
|
+
rule: `Do not use ${match[1]}`,
|
|
7848
|
+
reason: `Module not found: ${match[1]}`
|
|
7849
|
+
})
|
|
7850
|
+
},
|
|
7851
|
+
// Deprecated API
|
|
7852
|
+
{
|
|
7853
|
+
regex: /deprecated.*['"](.+?)['"]/i,
|
|
7854
|
+
category: "approach",
|
|
7855
|
+
template: (match) => ({
|
|
7856
|
+
rule: `Avoid deprecated API: ${match[1]}`,
|
|
7857
|
+
reason: "API is deprecated"
|
|
7858
|
+
})
|
|
7859
|
+
},
|
|
7860
|
+
// Permission denied
|
|
7861
|
+
{
|
|
7862
|
+
regex: /EACCES.*['"](.+?)['"]/i,
|
|
7863
|
+
category: "file",
|
|
7864
|
+
template: (match) => ({
|
|
7865
|
+
rule: `Cannot write to ${match[1]}`,
|
|
7866
|
+
reason: "Permission denied"
|
|
7867
|
+
})
|
|
7868
|
+
},
|
|
7869
|
+
// Type errors
|
|
7870
|
+
{
|
|
7871
|
+
regex: /Type '(.+?)' is not assignable/i,
|
|
7872
|
+
category: "pattern",
|
|
7873
|
+
template: (match) => ({
|
|
7874
|
+
rule: `Check type compatibility for ${match[1]}`,
|
|
7875
|
+
reason: "Type mismatch error"
|
|
7876
|
+
})
|
|
7877
|
+
}
|
|
7878
|
+
];
|
|
7879
|
+
for (const pattern of patterns) {
|
|
7880
|
+
const match = errorMessage.match(pattern.regex);
|
|
7881
|
+
if (match) {
|
|
7882
|
+
const { rule, reason } = pattern.template(match);
|
|
7883
|
+
const newRule = {
|
|
7884
|
+
rule,
|
|
7885
|
+
reason,
|
|
7886
|
+
category: pattern.category,
|
|
7887
|
+
learnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7888
|
+
};
|
|
7889
|
+
this.addRule(newRule);
|
|
7890
|
+
return newRule;
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
return null;
|
|
7894
|
+
}
|
|
7895
|
+
/**
|
|
7896
|
+
* Get all rules
|
|
7897
|
+
*/
|
|
7898
|
+
getRules() {
|
|
7899
|
+
return [...this.rules];
|
|
7900
|
+
}
|
|
7901
|
+
/**
|
|
7902
|
+
* Get rules by category
|
|
7903
|
+
*/
|
|
7904
|
+
getRulesByCategory(category) {
|
|
7905
|
+
return this.rules.filter((r) => r.category === category);
|
|
7906
|
+
}
|
|
7907
|
+
/**
|
|
7908
|
+
* Generate prompt injection text
|
|
7909
|
+
*/
|
|
7910
|
+
generatePromptInjection() {
|
|
7911
|
+
if (this.rules.length === 0) {
|
|
7912
|
+
return "";
|
|
7913
|
+
}
|
|
7914
|
+
const lines = [
|
|
7915
|
+
"",
|
|
7916
|
+
"# IMPORTANT CONSTRAINTS (Learned from previous failures)",
|
|
7917
|
+
"Do NOT violate these rules:",
|
|
7918
|
+
""
|
|
7919
|
+
];
|
|
7920
|
+
for (const rule of this.rules) {
|
|
7921
|
+
lines.push(`- \u274C ${rule.rule} (Reason: ${rule.reason})`);
|
|
7922
|
+
}
|
|
7923
|
+
lines.push("");
|
|
7924
|
+
return lines.join("\n");
|
|
7925
|
+
}
|
|
7926
|
+
/**
|
|
7927
|
+
* Clear all rules
|
|
7928
|
+
*/
|
|
7929
|
+
clear() {
|
|
7930
|
+
this.rules = [];
|
|
7931
|
+
this.save();
|
|
7932
|
+
}
|
|
7933
|
+
/**
|
|
7934
|
+
* Remove a specific rule
|
|
7935
|
+
*/
|
|
7936
|
+
removeRule(ruleText) {
|
|
7937
|
+
const index = this.rules.findIndex((r) => r.rule === ruleText);
|
|
7938
|
+
if (index !== -1) {
|
|
7939
|
+
this.rules.splice(index, 1);
|
|
7940
|
+
this.save();
|
|
7941
|
+
return true;
|
|
7942
|
+
}
|
|
7943
|
+
return false;
|
|
7944
|
+
}
|
|
7945
|
+
};
|
|
7946
|
+
function createNegativeContextManager(rootDir) {
|
|
7947
|
+
return new NegativeContextManager(rootDir);
|
|
7948
|
+
}
|
|
7949
|
+
|
|
7950
|
+
// src/generator/index.ts
|
|
7951
|
+
var MAX_DEPTH = 3;
|
|
7952
|
+
var AIGenerator = class {
|
|
7953
|
+
config = null;
|
|
7954
|
+
gemini = null;
|
|
7955
|
+
openai = null;
|
|
7956
|
+
anthropic = null;
|
|
7957
|
+
rootDir = process.cwd();
|
|
7958
|
+
shadowFS = null;
|
|
7959
|
+
negativeContext = null;
|
|
7960
|
+
constructor(projectDir) {
|
|
7961
|
+
this.rootDir = projectDir || process.cwd();
|
|
7962
|
+
}
|
|
7963
|
+
/**
|
|
7964
|
+
* Initialize the generator
|
|
7965
|
+
*/
|
|
7966
|
+
async initialize() {
|
|
7967
|
+
try {
|
|
7968
|
+
this.config = loadConfig(this.rootDir);
|
|
7969
|
+
this.rootDir = this.config.rootDir;
|
|
7970
|
+
} catch {
|
|
7971
|
+
}
|
|
7972
|
+
if (isGeminiAvailable()) {
|
|
7973
|
+
this.gemini = createGeminiClient();
|
|
7974
|
+
}
|
|
7975
|
+
if (isOpenAIAvailable()) {
|
|
7976
|
+
this.openai = createOpenAIAdapter();
|
|
7977
|
+
}
|
|
7978
|
+
if (isAnthropicAvailable()) {
|
|
7979
|
+
this.anthropic = createAnthropicAdapter();
|
|
7980
|
+
}
|
|
7981
|
+
if (!this.gemini && !this.openai && !this.anthropic) {
|
|
7982
|
+
throw new Error("No AI API key found. Set GEMINI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY environment variable.");
|
|
7983
|
+
}
|
|
7984
|
+
this.shadowFS = createShadowFS(this.rootDir);
|
|
7985
|
+
this.negativeContext = createNegativeContextManager(this.rootDir);
|
|
7986
|
+
}
|
|
7987
|
+
/**
|
|
7988
|
+
* Generate code from a prompt
|
|
7989
|
+
*/
|
|
7990
|
+
async generate(prompt, options = {}) {
|
|
7991
|
+
const model = options.model || "auto";
|
|
7992
|
+
const depth = options.depth || 0;
|
|
7993
|
+
if (depth >= MAX_DEPTH) {
|
|
7994
|
+
return {
|
|
7995
|
+
success: false,
|
|
7996
|
+
files: [],
|
|
7997
|
+
tokensUsed: 0,
|
|
7998
|
+
error: `Maximum recursion depth (${MAX_DEPTH}) exceeded. Provide a heuristic summary instead.`
|
|
7999
|
+
};
|
|
8000
|
+
}
|
|
8001
|
+
const context = await this.buildContext();
|
|
8002
|
+
const negativeRules = this.negativeContext?.generatePromptInjection() || "";
|
|
8003
|
+
const fullPrompt = this.createPrompt(prompt, context + negativeRules);
|
|
8004
|
+
let response;
|
|
8005
|
+
let tokensUsed = 0;
|
|
8006
|
+
try {
|
|
8007
|
+
if (model === "gemini" || model === "auto" && this.gemini) {
|
|
8008
|
+
if (!this.gemini) throw new Error("Gemini not available");
|
|
8009
|
+
const result = await this.gemini.generate(fullPrompt);
|
|
8010
|
+
response = result.text;
|
|
8011
|
+
tokensUsed = result.tokensUsed;
|
|
8012
|
+
} else if (model === "openai" || model === "auto" && this.openai) {
|
|
8013
|
+
if (!this.openai) throw new Error("OpenAI not available");
|
|
8014
|
+
const result = await this.openai.complete({
|
|
8015
|
+
systemPrompt: "You are an expert software developer. Generate clean, production-ready code.",
|
|
8016
|
+
userMessage: fullPrompt,
|
|
8017
|
+
maxTokens: 8e3
|
|
8018
|
+
});
|
|
8019
|
+
response = result.content;
|
|
8020
|
+
tokensUsed = result.tokensUsed.total;
|
|
8021
|
+
} else if (model === "anthropic" || model === "auto" && this.anthropic) {
|
|
8022
|
+
if (!this.anthropic) throw new Error("Anthropic not available");
|
|
8023
|
+
const result = await this.anthropic.complete({
|
|
8024
|
+
systemPrompt: "You are an expert software developer. Generate clean, production-ready code.",
|
|
8025
|
+
userMessage: fullPrompt,
|
|
8026
|
+
maxTokens: 8e3,
|
|
8027
|
+
model: "claude-3-5-sonnet-20240620"
|
|
8028
|
+
// Default to 3.5 Sonnet
|
|
8029
|
+
});
|
|
8030
|
+
response = result.content;
|
|
8031
|
+
tokensUsed = result.tokensUsed.total;
|
|
8032
|
+
} else {
|
|
8033
|
+
throw new Error("No AI model available");
|
|
8034
|
+
}
|
|
8035
|
+
} catch (error) {
|
|
8036
|
+
return {
|
|
8037
|
+
success: false,
|
|
8038
|
+
files: [],
|
|
8039
|
+
tokensUsed: 0,
|
|
8040
|
+
error: error instanceof Error ? error.message : String(error)
|
|
8041
|
+
};
|
|
8042
|
+
}
|
|
8043
|
+
const files = this.parseResponse(response);
|
|
8044
|
+
const maxFiles = options.maxFiles || 20;
|
|
8045
|
+
if (files.length > maxFiles) {
|
|
8046
|
+
return {
|
|
8047
|
+
success: false,
|
|
8048
|
+
files: [],
|
|
8049
|
+
tokensUsed,
|
|
8050
|
+
error: `Too many files generated (${files.length}). Max: ${maxFiles}`
|
|
8051
|
+
};
|
|
8052
|
+
}
|
|
8053
|
+
if (options.dryRun) {
|
|
8054
|
+
return {
|
|
8055
|
+
success: true,
|
|
8056
|
+
files,
|
|
8057
|
+
tokensUsed
|
|
8058
|
+
};
|
|
8059
|
+
}
|
|
8060
|
+
const writtenFiles = await this.writeFiles(files, options);
|
|
8061
|
+
return {
|
|
8062
|
+
success: true,
|
|
8063
|
+
files: writtenFiles,
|
|
8064
|
+
tokensUsed
|
|
8065
|
+
};
|
|
8066
|
+
}
|
|
8067
|
+
/**
|
|
8068
|
+
* Build context from project files
|
|
8069
|
+
*/
|
|
8070
|
+
async buildContext() {
|
|
8071
|
+
const parts = [];
|
|
8072
|
+
const prdPaths = ["prd.md", "PRD.md", "docs/prd.md", "README.md"];
|
|
8073
|
+
for (const prdPath of prdPaths) {
|
|
8074
|
+
const fullPath = join15(this.rootDir, prdPath);
|
|
8075
|
+
if (existsSync16(fullPath)) {
|
|
8076
|
+
const content = readFileSync15(fullPath, "utf-8");
|
|
8077
|
+
parts.push(`## ${prdPath}
|
|
8078
|
+
|
|
8079
|
+
${content}`);
|
|
8080
|
+
break;
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
8083
|
+
if (this.config) {
|
|
8084
|
+
parts.push(`## Project Info
|
|
8085
|
+
- Name: ${this.config.context.project.name}
|
|
8086
|
+
- Language: ${this.config.context.project.language}
|
|
8087
|
+
- Description: ${this.config.context.project.description || "N/A"}
|
|
8088
|
+
`);
|
|
8089
|
+
}
|
|
8090
|
+
return parts.join("\n\n---\n\n");
|
|
8091
|
+
}
|
|
8092
|
+
/**
|
|
8093
|
+
* Create the full AI prompt
|
|
8094
|
+
*/
|
|
8095
|
+
createPrompt(userPrompt, context) {
|
|
8096
|
+
return `# Project Context
|
|
8097
|
+
|
|
8098
|
+
${context}
|
|
8099
|
+
|
|
8100
|
+
---
|
|
8101
|
+
|
|
8102
|
+
# Task
|
|
8103
|
+
|
|
8104
|
+
${userPrompt}
|
|
8105
|
+
|
|
8106
|
+
---
|
|
8107
|
+
|
|
8108
|
+
# Instructions
|
|
8109
|
+
|
|
8110
|
+
Generate the necessary code files. Use this EXACT format for each file:
|
|
8111
|
+
|
|
8112
|
+
\`\`\`path/to/filename.ext
|
|
8113
|
+
// file content here
|
|
8114
|
+
\`\`\`
|
|
8115
|
+
|
|
8116
|
+
IMPORTANT:
|
|
8117
|
+
- Put the FULL FILE PATH in the code block language tag (e.g. \`\`\`src/index.ts)
|
|
8118
|
+
- Generate complete, working code - no placeholders
|
|
8119
|
+
- Include all necessary imports
|
|
8120
|
+
- Follow best practices for the language
|
|
8121
|
+
- One file per code block
|
|
8122
|
+
|
|
8123
|
+
Generate the files now:`;
|
|
8124
|
+
}
|
|
8125
|
+
/**
|
|
8126
|
+
* Parse AI response into file objects
|
|
8127
|
+
*/
|
|
8128
|
+
parseResponse(response) {
|
|
8129
|
+
const files = [];
|
|
8130
|
+
const codeBlockRegex = /```([^\n`]+)\n([\s\S]*?)```/g;
|
|
8131
|
+
let match;
|
|
8132
|
+
while ((match = codeBlockRegex.exec(response)) !== null) {
|
|
8133
|
+
const pathOrLang = match[1].trim();
|
|
8134
|
+
const content = match[2];
|
|
8135
|
+
if (pathOrLang.includes("/") || pathOrLang.includes("\\") || /\.\w+$/.test(pathOrLang)) {
|
|
8136
|
+
const filePath = pathOrLang.replace(/\\/g, "/");
|
|
8137
|
+
const ext = filePath.split(".").pop() || "";
|
|
8138
|
+
const language = this.getLanguageFromExtension(ext);
|
|
8139
|
+
const fullPath = join15(this.rootDir, filePath);
|
|
8140
|
+
const isNew = !existsSync16(fullPath);
|
|
8141
|
+
files.push({
|
|
8142
|
+
path: filePath,
|
|
8143
|
+
content: content.trim(),
|
|
8144
|
+
isNew,
|
|
8145
|
+
language
|
|
8146
|
+
});
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
return files;
|
|
8150
|
+
}
|
|
8151
|
+
/**
|
|
8152
|
+
* Get language from file extension
|
|
8153
|
+
*/
|
|
8154
|
+
getLanguageFromExtension(ext) {
|
|
8155
|
+
const map = {
|
|
8156
|
+
ts: "typescript",
|
|
8157
|
+
tsx: "typescript",
|
|
8158
|
+
js: "javascript",
|
|
8159
|
+
jsx: "javascript",
|
|
8160
|
+
py: "python",
|
|
8161
|
+
rs: "rust",
|
|
8162
|
+
go: "go",
|
|
8163
|
+
java: "java",
|
|
8164
|
+
md: "markdown",
|
|
8165
|
+
json: "json",
|
|
8166
|
+
yaml: "yaml",
|
|
8167
|
+
yml: "yaml",
|
|
8168
|
+
css: "css",
|
|
8169
|
+
html: "html"
|
|
8170
|
+
};
|
|
8171
|
+
return map[ext] || ext;
|
|
8172
|
+
}
|
|
8173
|
+
/**
|
|
8174
|
+
* Write files to disk
|
|
8175
|
+
*/
|
|
8176
|
+
async writeFiles(files, options) {
|
|
8177
|
+
const written = [];
|
|
8178
|
+
for (const file of files) {
|
|
8179
|
+
const fullPath = resolve2(this.rootDir, file.path);
|
|
8180
|
+
const normalizedPath = normalize3(fullPath);
|
|
8181
|
+
if (!normalizedPath.startsWith(normalize3(this.rootDir))) {
|
|
8182
|
+
console.warn(`Skipping file outside project root: ${file.path}`);
|
|
8183
|
+
continue;
|
|
8184
|
+
}
|
|
8185
|
+
const dir = dirname5(fullPath);
|
|
8186
|
+
if (!existsSync16(dir)) {
|
|
8187
|
+
mkdirSync10(dir, { recursive: true });
|
|
8188
|
+
}
|
|
8189
|
+
if (!file.isNew && options.backupBeforeOverwrite) {
|
|
8190
|
+
const backupPath = `${fullPath}.bak`;
|
|
8191
|
+
const existingContent = readFileSync15(fullPath, "utf-8");
|
|
8192
|
+
writeFileSync11(backupPath, existingContent);
|
|
8193
|
+
}
|
|
8194
|
+
writeFileSync11(fullPath, file.content, "utf-8");
|
|
8195
|
+
written.push(file);
|
|
8196
|
+
}
|
|
8197
|
+
return written;
|
|
8198
|
+
}
|
|
8199
|
+
};
|
|
8200
|
+
function createAIGenerator(projectDir) {
|
|
8201
|
+
return new AIGenerator(projectDir);
|
|
8202
|
+
}
|
|
7581
8203
|
export {
|
|
8204
|
+
AIGenerator,
|
|
7582
8205
|
ASTParser,
|
|
7583
8206
|
AnalyticsCollector,
|
|
7584
8207
|
AnalyticsConfigSchema,
|
|
@@ -7612,6 +8235,7 @@ export {
|
|
|
7612
8235
|
Logger,
|
|
7613
8236
|
MODEL_SPECIFIC_ADDENDUM,
|
|
7614
8237
|
MetaSchema,
|
|
8238
|
+
NegativeContextManager,
|
|
7615
8239
|
OpenAIAdapter,
|
|
7616
8240
|
PluginManager,
|
|
7617
8241
|
PluginRegistry,
|
|
@@ -7621,6 +8245,7 @@ export {
|
|
|
7621
8245
|
RLMEngine,
|
|
7622
8246
|
RLM_BASE_SYSTEM_PROMPT,
|
|
7623
8247
|
ScopeManager,
|
|
8248
|
+
ShadowFileSystem,
|
|
7624
8249
|
StackConfigSchema,
|
|
7625
8250
|
SupportedLanguageSchema,
|
|
7626
8251
|
TeamSync,
|
|
@@ -7632,6 +8257,7 @@ export {
|
|
|
7632
8257
|
calculateCost,
|
|
7633
8258
|
checkHealth,
|
|
7634
8259
|
chunkCode,
|
|
8260
|
+
createAIGenerator,
|
|
7635
8261
|
createAnthropicAdapter,
|
|
7636
8262
|
createBlackboard,
|
|
7637
8263
|
createContextAPI,
|
|
@@ -7639,6 +8265,7 @@ export {
|
|
|
7639
8265
|
createGeminiClient,
|
|
7640
8266
|
createInitialUserMessage,
|
|
7641
8267
|
createLogger,
|
|
8268
|
+
createNegativeContextManager,
|
|
7642
8269
|
createObservationMessage,
|
|
7643
8270
|
createOpenAIAdapter,
|
|
7644
8271
|
createPluginManager,
|
|
@@ -7648,6 +8275,7 @@ export {
|
|
|
7648
8275
|
createSandbox,
|
|
7649
8276
|
createSandboxContext,
|
|
7650
8277
|
createScopeManager,
|
|
8278
|
+
createShadowFS,
|
|
7651
8279
|
createSubAgentResultMessage,
|
|
7652
8280
|
createTrainingDataCollector,
|
|
7653
8281
|
createWatchdog,
|