@intend-it/core 4.0.1 → 4.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.
Files changed (3) hide show
  1. package/README.md +24 -12
  2. package/dist/index.js +122 -34
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -20,6 +20,7 @@
20
20
  ### ✨ Key Features
21
21
 
22
22
  - **🧠 AI Code Generation** - Transforms natural language steps into actual code
23
+ - **🌐 Global Context** - Define file-level rules using the `context` keyword
23
24
  - **🔀 Hybrid Orchestration** - Mix AI steps with deterministic function calls
24
25
  - **🔄 Self-Correction Loop** - Validates output and auto-fixes errors
25
26
  - **⚡ Smart Caching (CAS)** - Content-addressable storage for instant rebuilds
@@ -113,29 +114,40 @@ interface AIGeneratorOptions {
113
114
  }
114
115
  ```
115
116
 
116
- ## Caching with CAS
117
+ ## 🔒 Build Determinism & Lockfiles
117
118
 
118
- The compiler includes a Content-Addressable Storage system for caching:
119
+ The core package includes a `LockfileManager` that ensures generated code is deterministic and reusable.
119
120
 
120
121
  ```typescript
121
- import { FileSystemCAS, computeHash } from "@intend-it/core";
122
+ import { LockfileManager, computeHash } from "@intend-it/core";
122
123
 
123
- const cas = new FileSystemCAS("./.intend/store");
124
+ const lockManager = new LockfileManager("./intend.lock");
124
125
 
125
- // Check cache
126
- const hash = computeHash(sourceCode, config);
127
- const cached = await cas.get(hash);
126
+ // Calculate hashes
127
+ const sourceHash = computeHash(intentSource);
128
+ const configHash = computeHash(JSON.stringify(config));
128
129
 
129
- if (cached) {
130
- console.log("Cache hit!");
131
- return cached.code;
130
+ // Try to get from lockfile
131
+ const cachedCode = lockManager.getEntry(relativePath, sourceHash, configHash);
132
+
133
+ if (cachedCode) {
134
+ return cachedCode; // Instant build!
132
135
  }
133
136
 
134
- // Generate and cache
137
+ // Generate new code and save to lockfile
135
138
  const result = await generator.generate(ast);
136
- await cas.put(hash, result.code);
139
+ lockManager.setEntry(relativePath, sourceHash, configHash, result.code, {
140
+ model: "gemini-2.0-flash",
141
+ provider: "gemini"
142
+ });
143
+ lockManager.save();
137
144
  ```
138
145
 
146
+ ### Why a Lockfile?
147
+ - **Determinism**: Prevents minor AI fluctuations from changing your production code.
148
+ - **Performance**: Skips expensive LLM calls for unchanged intentions.
149
+ - **Auditability**: Changes to generated code appear in your git diffs, making it easy to review what the AI has changed.
150
+
139
151
  ## Architecture
140
152
 
141
153
  ```
package/dist/index.js CHANGED
@@ -169173,9 +169173,10 @@ ${request.prompt}`;
169173
169173
  }
169174
169174
  // src/ai/prompt-builder.ts
169175
169175
  class PromptBuilder {
169176
- buildContext(intent, fileImports, importedIntents = []) {
169176
+ buildContext(intent, fileImports, importedIntents = [], fileContext) {
169177
169177
  const returnType = this.formatReturnType(intent.returnType);
169178
169178
  return {
169179
+ fileContext,
169179
169180
  functionName: intent.name,
169180
169181
  parameters: intent.parameters,
169181
169182
  returnType,
@@ -169209,6 +169210,11 @@ class PromptBuilder {
169209
169210
  const lines = [];
169210
169211
  lines.push(`Implement this TypeScript function step:
169211
169212
  `);
169213
+ if (context.fileContext) {
169214
+ lines.push(`GLOBAL CONTEXT / RULES:`);
169215
+ lines.push(context.fileContext);
169216
+ lines.push("");
169217
+ }
169212
169218
  lines.push(`Function: ${context.functionName}`);
169213
169219
  lines.push(`Parameters: ${this.formatParameters(context.parameters)}`);
169214
169220
  lines.push(`Return Type: ${context.returnType}
@@ -169270,6 +169276,11 @@ Generate ONLY the TypeScript code for this step (no explanations, no markdown).`
169270
169276
  const lines = [];
169271
169277
  lines.push(`Implement this complete TypeScript function:
169272
169278
  `);
169279
+ if (context.fileContext) {
169280
+ lines.push(`GLOBAL CONTEXT / RULES:`);
169281
+ lines.push(context.fileContext);
169282
+ lines.push("");
169283
+ }
169273
169284
  lines.push(`Function: ${context.functionName}`);
169274
169285
  lines.push(`Parameters: ${this.formatParameters(context.parameters)}`);
169275
169286
  lines.push(`Return Type: ${context.returnType}
@@ -169297,22 +169308,7 @@ Generate ONLY the TypeScript code for this step (no explanations, no markdown).`
169297
169308
  lines.push("");
169298
169309
  }
169299
169310
  lines.push(`Implementation Steps (in order):`);
169300
- context.steps.forEach((step, i) => {
169301
- let line = `${i + 1}. `;
169302
- if (step.type === "Call") {
169303
- const args = step.args.map((a) => a.type === "String" ? JSON.stringify(a.value) : a.value).join(", ");
169304
- line += `Call: ${step.intent}(${args})`;
169305
- } else {
169306
- line += `"${step.instruction}"`;
169307
- }
169308
- if (step.resultVariable) {
169309
- line += ` => const ${step.resultVariable}`;
169310
- if (step.type === "Step" && step.resultType) {
169311
- line += `: ${step.resultType}`;
169312
- }
169313
- }
169314
- lines.push(line);
169315
- });
169311
+ lines.push(this.formatSteps(context.steps, 0));
169316
169312
  lines.push("");
169317
169313
  if (context.ensures.length > 0) {
169318
169314
  lines.push(`Postconditions (must be true at the end):`);
@@ -169349,27 +169345,58 @@ Return ONLY the function body code (no function signature, no markdown).`);
169349
169345
  }
169350
169346
  return type;
169351
169347
  }
169348
+ formatSteps(steps, indentLevel) {
169349
+ const indent = " ".repeat(indentLevel);
169350
+ const lines = [];
169351
+ steps.forEach((step, i) => {
169352
+ let line = `${indent}${i + 1}. `;
169353
+ if (step.type === "Call") {
169354
+ const args = step.args.map((a) => a.type === "String" ? JSON.stringify(a.value) : a.value).join(", ");
169355
+ line += `Call: ${step.intent}(${args})`;
169356
+ } else {
169357
+ line += `"${step.instruction}"`;
169358
+ }
169359
+ if (step.resultVariable) {
169360
+ line += ` => ${step.variableKind || "const"} ${step.resultVariable}`;
169361
+ if (step.type === "Step" && step.resultType) {
169362
+ line += `: ${step.resultType}`;
169363
+ }
169364
+ }
169365
+ lines.push(line);
169366
+ if (step.type === "Step" && step.nestedBody) {
169367
+ lines.push(`${indent} BLOCK START (This step contains a nested logic block):`);
169368
+ lines.push(this.formatSteps(step.nestedBody.steps, indentLevel + 1));
169369
+ lines.push(`${indent} BLOCK END`);
169370
+ }
169371
+ });
169372
+ return lines.join(`
169373
+ `);
169374
+ }
169352
169375
  buildReviewerSystemPrompt() {
169353
- return `You are a senior code reviewer specializing in detecting logical errors in TypeScript.
169376
+ return `You are a pragmatic senior code reviewer specializing in ensuring TypeScript code is functionally correct and safe.
169377
+
169378
+ Your task is to review generated code for logical errors that would cause runtime failures or violate the business intent.
169354
169379
 
169355
- Your task is to review generated function implementations for subtle bugs that pass syntax validation but are logically incorrect.
169380
+ Rules for Approval:
169381
+ - BE PRAGMATIC: If the code is functionally correct and will not crash, APPROVE it.
169382
+ - DO NOT be pedantic about 'step' names. If a step asks to "Call GenerateID" and the code uses "crypto.randomUUID()", this is CORRECT.
169383
+ - DO NOT fail for unused variables (e.g. intermediate variables used in object construction).
169384
+ - DO NOT fail for minor stylistic choices or variable shadowing unless it causes a bug.
169356
169385
 
169357
- Common issues to detect:
169358
- 1. FUNCTION SHADOWING: An inner function with the same name as the outer function, causing the outer to never be called correctly.
169359
- 2. UNUSED VARIABLES: Variables declared but never used or returned.
169360
- 3. MISSING STEP IMPLEMENTATION: Steps mentioned in requirements but not implemented.
169361
- 4. INFINITE LOOPS / RECURSION: Unintended recursive calls or loops with no exit condition.
169362
- 5. TYPE MISMATCHES: Returning wrong type or assigning incompatible types.
169363
- 6. DEAD CODE: Code that can never be reached.
169386
+ Critical Issues to detect (FAIL only for these):
169387
+ 1. BUGGY SHADOWING: An inner variable/function with the same name as the outer, causing a reference error or calling the wrong logic.
169388
+ 2. INFINITE LOOPS: Loops or recursion with no exit condition.
169389
+ 3. LOGIC GAP: The code completely ignores a requirement (e.g. failing to save a record that was requested).
169390
+ 4. CRASH POTENTIAL: Accessing properties of 'undefined' without checks, or unhandled 'null' returns.
169391
+ 5. TYPE CONTRACT VIOLATION: Returning a type that deviates from the signature.
169364
169392
 
169365
169393
  Response Format:
169366
- - If the code is logically correct, respond with exactly: APPROVED
169367
- - If there are issues, respond with:
169394
+ - If functionally correct and safe code, respond with exactly: APPROVED
169395
+ - If there are CRITICAL ISSUES, respond with:
169368
169396
  ISSUES:
169369
- - [Issue 1 description]
169370
- - [Issue 2 description]
169397
+ - [Critical issue description]
169371
169398
  ...
169372
- FIX: [Brief description of what needs to change]`;
169399
+ FIX: [Short description of required fix]`;
169373
169400
  }
169374
169401
  buildReviewerPrompt(context, code) {
169375
169402
  const lines = [];
@@ -169908,7 +169935,7 @@ class AICodeGenerator {
169908
169935
  }
169909
169936
  }
169910
169937
  }
169911
- const context = this.promptBuilder.buildContext(intent, file.imports, importedIntents);
169938
+ const context = this.promptBuilder.buildContext(intent, file.imports, importedIntents, file.context);
169912
169939
  totalSteps += context.steps.length;
169913
169940
  if (this.mode === "step-by-step") {
169914
169941
  currentCode = await this.generateFull(currentCode, context, intent.name);
@@ -169951,7 +169978,8 @@ class AICodeGenerator {
169951
169978
  console.log(errorMsg.split(`
169952
169979
  `).map((l) => ` ${l}`).join(`
169953
169980
  `));
169954
- console.log(` Retrying with AI auto-correction...
169981
+ console.log(`
169982
+ Retrying with AI auto-correction...
169955
169983
  `);
169956
169984
  prompt += `
169957
169985
 
@@ -169988,7 +170016,8 @@ ${errorMsg}`);
169988
170016
  \uD83D\uDD0D Logic review found issues for ${functionName} (Attempt ${attempts}/${limit}):`);
169989
170017
  console.log(` ${logicIssues.issues.slice(0, 3).join(`
169990
170018
  `)}`);
169991
- console.log(` Retrying with AI auto-correction...
170019
+ console.log(`
170020
+ Retrying with AI auto-correction...
169992
170021
  `);
169993
170022
  prompt += `
169994
170023
 
@@ -170178,6 +170207,64 @@ function computeHash(content, config) {
170178
170207
  hash.update(JSON.stringify(config));
170179
170208
  return hash.digest("hex");
170180
170209
  }
170210
+ // src/lockfile/manager.ts
170211
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
170212
+
170213
+ class LockfileManager {
170214
+ filePath;
170215
+ lockfile;
170216
+ constructor(filePath) {
170217
+ this.filePath = filePath;
170218
+ this.lockfile = this.load();
170219
+ }
170220
+ load() {
170221
+ if (!existsSync2(this.filePath)) {
170222
+ return {
170223
+ version: "1.0",
170224
+ files: {}
170225
+ };
170226
+ }
170227
+ try {
170228
+ const content = readFileSync2(this.filePath, "utf-8");
170229
+ return JSON.parse(content);
170230
+ } catch (error) {
170231
+ console.warn(`Failed to read lockfile at ${this.filePath}. Starting fresh.`);
170232
+ return {
170233
+ version: "1.0",
170234
+ files: {}
170235
+ };
170236
+ }
170237
+ }
170238
+ save() {
170239
+ try {
170240
+ writeFileSync2(this.filePath, JSON.stringify(this.lockfile, null, 2), "utf-8");
170241
+ } catch (error) {
170242
+ throw new Error(`Failed to write lockfile: ${error.message}`);
170243
+ }
170244
+ }
170245
+ getEntry(relativePath, sourceHash, configHash) {
170246
+ const entry = this.lockfile.files[relativePath];
170247
+ if (!entry)
170248
+ return null;
170249
+ if (entry.sourceHash === sourceHash && entry.configHash === configHash) {
170250
+ return entry.generatedCode;
170251
+ }
170252
+ return null;
170253
+ }
170254
+ setEntry(relativePath, sourceHash, configHash, code, metadata) {
170255
+ this.lockfile.files[relativePath] = {
170256
+ sourceHash,
170257
+ configHash,
170258
+ generatedCode: code,
170259
+ model: metadata?.model,
170260
+ provider: metadata?.provider,
170261
+ timestamp: Date.now()
170262
+ };
170263
+ }
170264
+ getLockfile() {
170265
+ return this.lockfile;
170266
+ }
170267
+ }
170181
170268
  export {
170182
170269
  generateTypeScript,
170183
170270
  debugLogger,
@@ -170186,6 +170273,7 @@ export {
170186
170273
  TypeScriptGenerator,
170187
170274
  PromptBuilder,
170188
170275
  OllamaProvider,
170276
+ LockfileManager,
170189
170277
  GeminiProvider,
170190
170278
  GeminiClient,
170191
170279
  FileSystemCAS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intend-it/core",
3
- "version": "4.0.1",
3
+ "version": "4.0.3",
4
4
  "description": "Core compiler and AI integration for the Intend programming language",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@intend-it/parser": "^1.3.1",
38
+ "@intend-it/parser": "^1.3.3",
39
39
  "@google/generative-ai": "^0.21.0"
40
40
  },
41
41
  "devDependencies": {
@@ -43,6 +43,6 @@
43
43
  "typescript": "^5.0.0"
44
44
  },
45
45
  "peerDependencies": {
46
- "@intend-it/parser": ">=1.3.1"
46
+ "@intend-it/parser": ">=1.3.3"
47
47
  }
48
48
  }