logicstamp-context 0.1.0
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/LICENSE +21 -0
- package/LLM_CONTEXT.md +260 -0
- package/README.md +949 -0
- package/dist/cli/commands/clean.d.ts +15 -0
- package/dist/cli/commands/clean.d.ts.map +1 -0
- package/dist/cli/commands/clean.js +142 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/compare.d.ts +88 -0
- package/dist/cli/commands/compare.d.ts.map +1 -0
- package/dist/cli/commands/compare.js +519 -0
- package/dist/cli/commands/compare.js.map +1 -0
- package/dist/cli/commands/context.d.ts +24 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +576 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/init.d.ts +14 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +71 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +52 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +368 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +223 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/stamp.d.ts +7 -0
- package/dist/cli/stamp.d.ts.map +1 -0
- package/dist/cli/stamp.js +961 -0
- package/dist/cli/stamp.js.map +1 -0
- package/dist/cli/validate-index.d.ts +7 -0
- package/dist/cli/validate-index.d.ts.map +1 -0
- package/dist/cli/validate-index.js +55 -0
- package/dist/cli/validate-index.js.map +1 -0
- package/dist/core/astParser.d.ts +47 -0
- package/dist/core/astParser.d.ts.map +1 -0
- package/dist/core/astParser.js +417 -0
- package/dist/core/astParser.js.map +1 -0
- package/dist/core/contractBuilder.d.ts +48 -0
- package/dist/core/contractBuilder.d.ts.map +1 -0
- package/dist/core/contractBuilder.js +120 -0
- package/dist/core/contractBuilder.js.map +1 -0
- package/dist/core/manifest.d.ts +75 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +173 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/pack.d.ts +190 -0
- package/dist/core/pack.d.ts.map +1 -0
- package/dist/core/pack.js +438 -0
- package/dist/core/pack.js.map +1 -0
- package/dist/core/signature.d.ts +47 -0
- package/dist/core/signature.d.ts.map +1 -0
- package/dist/core/signature.js +208 -0
- package/dist/core/signature.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/types/UIFContract.d.ts +112 -0
- package/dist/types/UIFContract.d.ts.map +1 -0
- package/dist/types/UIFContract.js +36 -0
- package/dist/types/UIFContract.js.map +1 -0
- package/dist/utils/config.d.ts +34 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +62 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/fsx.d.ts +85 -0
- package/dist/utils/fsx.d.ts.map +1 -0
- package/dist/utils/fsx.js +181 -0
- package/dist/utils/fsx.js.map +1 -0
- package/dist/utils/gitignore.d.ts +62 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +183 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hash.d.ts +73 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +159 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/llmContext.d.ts +34 -0
- package/dist/utils/llmContext.d.ts.map +1 -0
- package/dist/utils/llmContext.js +136 -0
- package/dist/utils/llmContext.js.map +1 -0
- package/dist/utils/tokens.d.ts +23 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +29 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +69 -0
- package/schema/logicstamp.context.schema.json +430 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uif Contract 0.3
|
|
3
|
+
*
|
|
4
|
+
* Description: fsx - Presentational component
|
|
5
|
+
*
|
|
6
|
+
* Version (Component Composition):
|
|
7
|
+
* variables: []
|
|
8
|
+
* hooks: []
|
|
9
|
+
* components: []
|
|
10
|
+
* functions: ["deleteSidecar","fileExists","findSidecarFiles","getRelativePath","getSidecarPath","globFiles","normalizeEntryId","readFileWithText","resolvePath"]
|
|
11
|
+
* imports: ["glob","node:fs/promises","node:path"]
|
|
12
|
+
*
|
|
13
|
+
* Logic Signature:
|
|
14
|
+
* props: {}
|
|
15
|
+
* events: {}
|
|
16
|
+
* state: {}
|
|
17
|
+
*
|
|
18
|
+
* Predictions:
|
|
19
|
+
* (none)
|
|
20
|
+
*
|
|
21
|
+
* Hashes (informational only - authoritative values in .uif.json):
|
|
22
|
+
* semantic: uif:80b91273520265259cc8e40e (informational)
|
|
23
|
+
* file: uif:f6ee3958ee861ee956f6b5c9
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* File system utilities for glob patterns and path operations
|
|
27
|
+
*/
|
|
28
|
+
import { glob } from 'glob';
|
|
29
|
+
import { readFile, unlink, stat } from 'node:fs/promises';
|
|
30
|
+
import { resolve, relative, join, normalize, isAbsolute } from 'node:path';
|
|
31
|
+
/**
|
|
32
|
+
* Find files matching glob patterns
|
|
33
|
+
*/
|
|
34
|
+
export async function globFiles(searchPath, extensions = '.tsx,.ts') {
|
|
35
|
+
const extArray = extensions.split(',').map((ext) => ext.trim());
|
|
36
|
+
// Build glob pattern
|
|
37
|
+
const patterns = extArray.map((ext) => {
|
|
38
|
+
if (ext.startsWith('.')) {
|
|
39
|
+
return `**/*${ext}`;
|
|
40
|
+
}
|
|
41
|
+
return `**/*.${ext}`;
|
|
42
|
+
});
|
|
43
|
+
const files = [];
|
|
44
|
+
for (const pattern of patterns) {
|
|
45
|
+
const matches = await glob(pattern, {
|
|
46
|
+
cwd: searchPath,
|
|
47
|
+
absolute: true,
|
|
48
|
+
ignore: [
|
|
49
|
+
'**/node_modules/**',
|
|
50
|
+
'**/dist/**',
|
|
51
|
+
'**/build/**',
|
|
52
|
+
'**/.next/**',
|
|
53
|
+
'**/coverage/**',
|
|
54
|
+
'**/*.test.ts',
|
|
55
|
+
'**/*.test.tsx',
|
|
56
|
+
'**/*.spec.ts',
|
|
57
|
+
'**/*.spec.tsx',
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
files.push(...matches);
|
|
61
|
+
}
|
|
62
|
+
// Remove duplicates and sort
|
|
63
|
+
return [...new Set(files)].sort();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Read file and return path + content
|
|
67
|
+
*/
|
|
68
|
+
export async function readFileWithText(filePath) {
|
|
69
|
+
const text = await readFile(filePath, 'utf8');
|
|
70
|
+
return { path: filePath, text };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Find all sidecar files (*.uif.json) in a directory
|
|
74
|
+
*/
|
|
75
|
+
export async function findSidecarFiles(searchPath) {
|
|
76
|
+
return glob('**/*.uif.json', {
|
|
77
|
+
cwd: searchPath,
|
|
78
|
+
absolute: true,
|
|
79
|
+
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Delete a sidecar file
|
|
84
|
+
*/
|
|
85
|
+
export async function deleteSidecar(sidecarPath) {
|
|
86
|
+
try {
|
|
87
|
+
await unlink(sidecarPath);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
// Ignore if file doesn't exist
|
|
91
|
+
if (err.code !== 'ENOENT') {
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get relative path from base directory
|
|
98
|
+
*/
|
|
99
|
+
export function getRelativePath(from, to) {
|
|
100
|
+
return relative(from, to).replace(/\\/g, '/');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve absolute path
|
|
104
|
+
*/
|
|
105
|
+
export function resolvePath(...paths) {
|
|
106
|
+
return resolve(...paths);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if file exists
|
|
110
|
+
*/
|
|
111
|
+
export async function fileExists(filePath) {
|
|
112
|
+
try {
|
|
113
|
+
await stat(filePath);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get sidecar path for a source file
|
|
122
|
+
*/
|
|
123
|
+
export function getSidecarPath(sourcePath, outRoot) {
|
|
124
|
+
// If sourcePath is absolute, place sidecar next to source file
|
|
125
|
+
// This matches the behavior of writeSidecar()
|
|
126
|
+
if (isAbsolute(sourcePath)) {
|
|
127
|
+
return `${sourcePath}.uif.json`;
|
|
128
|
+
}
|
|
129
|
+
// Otherwise, resolve relative path from outRoot
|
|
130
|
+
return join(outRoot, `${sourcePath}.uif.json`);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Normalize a file path for cross-platform consistency
|
|
134
|
+
* This is the canonical path normalization used throughout the codebase.
|
|
135
|
+
* Rules:
|
|
136
|
+
* - Convert backslashes to forward slashes (POSIX-style)
|
|
137
|
+
* - Use Node's normalize() to resolve .. and . segments
|
|
138
|
+
* - On Windows: preserve drive letter but normalize case
|
|
139
|
+
* - Remove leading ./ if present
|
|
140
|
+
* - Result should match manifest keys exactly
|
|
141
|
+
*
|
|
142
|
+
* This function is the single source of truth for path normalization.
|
|
143
|
+
* All entryId fields in contracts and manifest keys use this normalization.
|
|
144
|
+
*/
|
|
145
|
+
export function normalizeEntryId(entryId) {
|
|
146
|
+
const normalized = normalize(entryId).replace(/\\/g, '/');
|
|
147
|
+
// On Windows, normalize drive letter to lowercase for consistency
|
|
148
|
+
// This ensures manifest keys match across different input formats
|
|
149
|
+
let result = normalized.replace(/^([A-Z]):/, (_, drive) => `${drive.toLowerCase()}:`);
|
|
150
|
+
// Remove leading ./ if present
|
|
151
|
+
result = result.replace(/^\.\//, '');
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get the folder path for a given file (parent directory)
|
|
156
|
+
* Returns normalized folder path
|
|
157
|
+
*/
|
|
158
|
+
export function getFolderPath(filePath) {
|
|
159
|
+
const normalized = normalizeEntryId(filePath);
|
|
160
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
161
|
+
if (lastSlash === -1)
|
|
162
|
+
return '.';
|
|
163
|
+
return normalized.substring(0, lastSlash);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Group files by their containing folder
|
|
167
|
+
* Returns a Map of folderPath -> array of file paths
|
|
168
|
+
*/
|
|
169
|
+
export function groupFilesByFolder(files, projectRoot) {
|
|
170
|
+
const normalizedRoot = normalizeEntryId(projectRoot);
|
|
171
|
+
const folderMap = new Map();
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const folderPath = getFolderPath(file);
|
|
174
|
+
if (!folderMap.has(folderPath)) {
|
|
175
|
+
folderMap.set(folderPath, []);
|
|
176
|
+
}
|
|
177
|
+
folderMap.get(folderPath).push(file);
|
|
178
|
+
}
|
|
179
|
+
return folderMap;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=fsx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsx.js","sourceRoot":"","sources":["../../src/utils/fsx.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for managing .gitignore files
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Patterns that should be added to .gitignore for LogicStamp context files
|
|
6
|
+
*/
|
|
7
|
+
export declare const LOGICSTAMP_GITIGNORE_PATTERNS: string[];
|
|
8
|
+
/**
|
|
9
|
+
* Check if .gitignore exists in the given directory
|
|
10
|
+
*/
|
|
11
|
+
export declare function gitignoreExists(targetDir: string): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Read .gitignore content
|
|
14
|
+
*/
|
|
15
|
+
export declare function readGitignore(targetDir: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a pattern exists in .gitignore content
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasPattern(content: string, pattern: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Check if .gitignore has LogicStamp patterns
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasLogicStampPatterns(content: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Add LogicStamp patterns to .gitignore content
|
|
26
|
+
*/
|
|
27
|
+
export declare function addLogicStampPatterns(content: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Write content to .gitignore
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeGitignore(targetDir: string, content: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Add LogicStamp patterns to .gitignore file
|
|
34
|
+
* Creates .gitignore if it doesn't exist
|
|
35
|
+
*/
|
|
36
|
+
export declare function ensureGitignorePatterns(targetDir: string): Promise<{
|
|
37
|
+
added: boolean;
|
|
38
|
+
created: boolean;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Smart .gitignore management with user prompt and config persistence
|
|
42
|
+
*
|
|
43
|
+
* Behavior:
|
|
44
|
+
* 1. If --skip-gitignore flag: do nothing
|
|
45
|
+
* 2. If config has preference: respect it
|
|
46
|
+
* 3. If patterns already exist: do nothing
|
|
47
|
+
* 4. If TTY (interactive): prompt user once, save preference
|
|
48
|
+
* 5. If non-TTY (CI): do nothing (don't auto-add)
|
|
49
|
+
*
|
|
50
|
+
* @param targetDir - Project root directory
|
|
51
|
+
* @param options - Options to control behavior
|
|
52
|
+
* @returns Result of the operation
|
|
53
|
+
*/
|
|
54
|
+
export declare function smartGitignoreSetup(targetDir: string, options?: {
|
|
55
|
+
skipGitignore?: boolean;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
added: boolean;
|
|
58
|
+
created: boolean;
|
|
59
|
+
prompted: boolean;
|
|
60
|
+
skipped: boolean;
|
|
61
|
+
}>;
|
|
62
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;GAEG;AACH,eAAO,MAAM,6BAA6B,UAOzC,CAAC;AAEF;;GAEG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOzE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOtE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGpE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA0B7D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGtF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAY9G;AA2BD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA6CpF"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for managing .gitignore files
|
|
3
|
+
*/
|
|
4
|
+
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { readConfig, updateConfig } from './config.js';
|
|
7
|
+
/**
|
|
8
|
+
* Patterns that should be added to .gitignore for LogicStamp context files
|
|
9
|
+
*/
|
|
10
|
+
export const LOGICSTAMP_GITIGNORE_PATTERNS = [
|
|
11
|
+
'# LogicStamp context files',
|
|
12
|
+
'context.json',
|
|
13
|
+
'context_*.json',
|
|
14
|
+
'*.uif.json',
|
|
15
|
+
'logicstamp.manifest.json',
|
|
16
|
+
'.logicstamp/',
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Check if .gitignore exists in the given directory
|
|
20
|
+
*/
|
|
21
|
+
export async function gitignoreExists(targetDir) {
|
|
22
|
+
try {
|
|
23
|
+
await access(join(targetDir, '.gitignore'));
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read .gitignore content
|
|
32
|
+
*/
|
|
33
|
+
export async function readGitignore(targetDir) {
|
|
34
|
+
const gitignorePath = join(targetDir, '.gitignore');
|
|
35
|
+
try {
|
|
36
|
+
return await readFile(gitignorePath, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a pattern exists in .gitignore content
|
|
44
|
+
*/
|
|
45
|
+
export function hasPattern(content, pattern) {
|
|
46
|
+
const lines = content.split('\n').map(line => line.trim());
|
|
47
|
+
return lines.includes(pattern);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if .gitignore has LogicStamp patterns
|
|
51
|
+
*/
|
|
52
|
+
export function hasLogicStampPatterns(content) {
|
|
53
|
+
// Check for the key patterns (ignore the comment line)
|
|
54
|
+
const patterns = LOGICSTAMP_GITIGNORE_PATTERNS.filter(p => !p.startsWith('#'));
|
|
55
|
+
// Check if at least the main patterns exist
|
|
56
|
+
// We consider it "has patterns" if context.json and context_*.json are present
|
|
57
|
+
return hasPattern(content, 'context.json') &&
|
|
58
|
+
(hasPattern(content, 'context_*.json') || hasPattern(content, 'context_main.json'));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add LogicStamp patterns to .gitignore content
|
|
62
|
+
*/
|
|
63
|
+
export function addLogicStampPatterns(content) {
|
|
64
|
+
const lines = content.split('\n');
|
|
65
|
+
// Check which patterns are missing
|
|
66
|
+
const missingPatterns = LOGICSTAMP_GITIGNORE_PATTERNS.filter(pattern => {
|
|
67
|
+
if (pattern.startsWith('#'))
|
|
68
|
+
return false; // Always add the comment
|
|
69
|
+
return !hasPattern(content, pattern);
|
|
70
|
+
});
|
|
71
|
+
if (missingPatterns.length === 0 && hasPattern(content, '# LogicStamp context files')) {
|
|
72
|
+
return content; // All patterns already exist
|
|
73
|
+
}
|
|
74
|
+
// Add a blank line before the section if content exists and doesn't end with blank line
|
|
75
|
+
let newContent = content;
|
|
76
|
+
if (newContent.length > 0 && !newContent.endsWith('\n\n') && !newContent.endsWith('\n')) {
|
|
77
|
+
newContent += '\n';
|
|
78
|
+
}
|
|
79
|
+
if (newContent.length > 0 && !newContent.endsWith('\n\n')) {
|
|
80
|
+
newContent += '\n';
|
|
81
|
+
}
|
|
82
|
+
// Add all LogicStamp patterns as a group
|
|
83
|
+
newContent += LOGICSTAMP_GITIGNORE_PATTERNS.join('\n') + '\n';
|
|
84
|
+
return newContent;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Write content to .gitignore
|
|
88
|
+
*/
|
|
89
|
+
export async function writeGitignore(targetDir, content) {
|
|
90
|
+
const gitignorePath = join(targetDir, '.gitignore');
|
|
91
|
+
await writeFile(gitignorePath, content, 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Add LogicStamp patterns to .gitignore file
|
|
95
|
+
* Creates .gitignore if it doesn't exist
|
|
96
|
+
*/
|
|
97
|
+
export async function ensureGitignorePatterns(targetDir) {
|
|
98
|
+
const exists = await gitignoreExists(targetDir);
|
|
99
|
+
const content = await readGitignore(targetDir);
|
|
100
|
+
if (hasLogicStampPatterns(content)) {
|
|
101
|
+
return { added: false, created: false };
|
|
102
|
+
}
|
|
103
|
+
const newContent = addLogicStampPatterns(content);
|
|
104
|
+
await writeGitignore(targetDir, newContent);
|
|
105
|
+
return { added: true, created: !exists };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if running in interactive TTY
|
|
109
|
+
*/
|
|
110
|
+
function isTTY() {
|
|
111
|
+
return process.stdout.isTTY === true && process.stdin.isTTY === true;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Prompt user for Y/N input (only in TTY mode)
|
|
115
|
+
*/
|
|
116
|
+
async function promptYesNo(question) {
|
|
117
|
+
const { createInterface } = await import('node:readline');
|
|
118
|
+
const readline = createInterface({
|
|
119
|
+
input: process.stdin,
|
|
120
|
+
output: process.stdout,
|
|
121
|
+
});
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
readline.question(question, (answer) => {
|
|
124
|
+
readline.close();
|
|
125
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes' || answer === '');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Smart .gitignore management with user prompt and config persistence
|
|
131
|
+
*
|
|
132
|
+
* Behavior:
|
|
133
|
+
* 1. If --skip-gitignore flag: do nothing
|
|
134
|
+
* 2. If config has preference: respect it
|
|
135
|
+
* 3. If patterns already exist: do nothing
|
|
136
|
+
* 4. If TTY (interactive): prompt user once, save preference
|
|
137
|
+
* 5. If non-TTY (CI): do nothing (don't auto-add)
|
|
138
|
+
*
|
|
139
|
+
* @param targetDir - Project root directory
|
|
140
|
+
* @param options - Options to control behavior
|
|
141
|
+
* @returns Result of the operation
|
|
142
|
+
*/
|
|
143
|
+
export async function smartGitignoreSetup(targetDir, options = {}) {
|
|
144
|
+
// If --skip-gitignore flag is set, do nothing
|
|
145
|
+
if (options.skipGitignore) {
|
|
146
|
+
return { added: false, created: false, prompted: false, skipped: true };
|
|
147
|
+
}
|
|
148
|
+
// Check if patterns already exist
|
|
149
|
+
const content = await readGitignore(targetDir);
|
|
150
|
+
if (hasLogicStampPatterns(content)) {
|
|
151
|
+
return { added: false, created: false, prompted: false, skipped: false };
|
|
152
|
+
}
|
|
153
|
+
// Check config for saved preference
|
|
154
|
+
const config = await readConfig(targetDir);
|
|
155
|
+
// If user previously chose to skip, respect that
|
|
156
|
+
if (config.gitignorePreference === 'skipped') {
|
|
157
|
+
return { added: false, created: false, prompted: false, skipped: true };
|
|
158
|
+
}
|
|
159
|
+
// If user previously chose to add, do it without prompting
|
|
160
|
+
if (config.gitignorePreference === 'added') {
|
|
161
|
+
const result = await ensureGitignorePatterns(targetDir);
|
|
162
|
+
return { ...result, prompted: false, skipped: false };
|
|
163
|
+
}
|
|
164
|
+
// No preference saved yet - prompt if interactive
|
|
165
|
+
if (isTTY()) {
|
|
166
|
+
console.log('\n💡 LogicStamp generates large context files that are usually not committed.\n');
|
|
167
|
+
const shouldAdd = await promptYesNo('Add recommended patterns to .gitignore? [Y/n] ');
|
|
168
|
+
if (shouldAdd) {
|
|
169
|
+
// User said yes - add patterns and save preference
|
|
170
|
+
await updateConfig(targetDir, { gitignorePreference: 'added' });
|
|
171
|
+
const result = await ensureGitignorePatterns(targetDir);
|
|
172
|
+
return { ...result, prompted: true, skipped: false };
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// User said no - save preference to never ask again
|
|
176
|
+
await updateConfig(targetDir, { gitignorePreference: 'skipped' });
|
|
177
|
+
return { added: false, created: false, prompted: true, skipped: true };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Non-interactive (CI) - don't auto-add, don't prompt
|
|
181
|
+
return { added: false, created: false, prompted: false, skipped: true };
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uif Contract 0.3
|
|
3
|
+
*
|
|
4
|
+
* Description: hash - Presentational component
|
|
5
|
+
*
|
|
6
|
+
* Version (Component Composition):
|
|
7
|
+
* variables: ["SCHEMA_VERSION"]
|
|
8
|
+
* hooks: []
|
|
9
|
+
* components: []
|
|
10
|
+
* functions: ["bundleHash","fileHash","hashesEqual","isValidHash","semanticHashFromAst","sha256Hex","signatureHash","sortObject","stableStringify","structureHash"]
|
|
11
|
+
* imports: ["../core/astParser.js","../types/UIFContract.js","node:crypto"]
|
|
12
|
+
*
|
|
13
|
+
* Logic Signature:
|
|
14
|
+
* props: {}
|
|
15
|
+
* events: {}
|
|
16
|
+
* state: {}
|
|
17
|
+
*
|
|
18
|
+
* Predictions:
|
|
19
|
+
* (none)
|
|
20
|
+
*
|
|
21
|
+
* Hashes (informational only - authoritative values in .uif.json):
|
|
22
|
+
* semantic: uif:9c632ee89c97f0b44ce6ae5c (informational)
|
|
23
|
+
* file: uif:1f0fa0e2c8958d7fc1696036
|
|
24
|
+
*/
|
|
25
|
+
import type { AstExtract } from '../core/astParser.js';
|
|
26
|
+
import type { LogicSignature, ComponentVersion } from '../types/UIFContract.js';
|
|
27
|
+
/**
|
|
28
|
+
* Generate a hash from raw file content
|
|
29
|
+
* Strips the @uif header block before hashing to prevent self-referential churn
|
|
30
|
+
*/
|
|
31
|
+
export declare function fileHash(content: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Generate a hash from component structure (variables, hooks, components, functions)
|
|
34
|
+
* This changes only when structural composition changes
|
|
35
|
+
*/
|
|
36
|
+
export declare function structureHash(ast: AstExtract): string;
|
|
37
|
+
export declare function structureHash(version: ComponentVersion): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a hash from logic signature (props, events, state)
|
|
40
|
+
* This changes only when the component's API contract changes
|
|
41
|
+
*/
|
|
42
|
+
export declare function signatureHash(signature: LogicSignature): string;
|
|
43
|
+
/**
|
|
44
|
+
* Generate a semantic hash from AST structure and logic signature
|
|
45
|
+
* This hash only changes when the component's structural logic changes,
|
|
46
|
+
* not when comments, formatting, or implementation details change.
|
|
47
|
+
*
|
|
48
|
+
* semanticHash = hash(structureHash + signatureHash + schemaVersion)
|
|
49
|
+
*/
|
|
50
|
+
export declare function semanticHashFromAst(ast: AstExtract, signature: LogicSignature): string;
|
|
51
|
+
/**
|
|
52
|
+
* Compute bundle hash from nodes
|
|
53
|
+
* Input: ordered array of {entryId, semanticHash} plus depth and schemaVersion
|
|
54
|
+
* This provides a stable cache key for LLM context bundles
|
|
55
|
+
*/
|
|
56
|
+
export declare function bundleHash(nodes: Array<{
|
|
57
|
+
entryId: string;
|
|
58
|
+
semanticHash: string;
|
|
59
|
+
}>, depth: number, schemaVersion?: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Stable stringify with sorted keys and arrays
|
|
62
|
+
* Ensures deterministic JSON output for hashing
|
|
63
|
+
*/
|
|
64
|
+
export declare function stableStringify(obj: unknown): string;
|
|
65
|
+
/**
|
|
66
|
+
* Verify if a hash matches the expected format
|
|
67
|
+
*/
|
|
68
|
+
export declare function isValidHash(hash: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Compare two hashes for equality
|
|
71
|
+
*/
|
|
72
|
+
export declare function hashesEqual(hash1: string, hash2: string): boolean;
|
|
73
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhF;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAShD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAAC;AACvD,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAAC;AAYjE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAQ/D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,GAAG,MAAM,CAkBtF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,EACvD,KAAK,EAAE,MAAM,EACb,aAAa,SAAQ,GACpB,MAAM,CAeR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAiBpD;AAwBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjE"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uif Contract 0.3
|
|
3
|
+
*
|
|
4
|
+
* Description: hash - Presentational component
|
|
5
|
+
*
|
|
6
|
+
* Version (Component Composition):
|
|
7
|
+
* variables: ["SCHEMA_VERSION"]
|
|
8
|
+
* hooks: []
|
|
9
|
+
* components: []
|
|
10
|
+
* functions: ["bundleHash","fileHash","hashesEqual","isValidHash","semanticHashFromAst","sha256Hex","signatureHash","sortObject","stableStringify","structureHash"]
|
|
11
|
+
* imports: ["../core/astParser.js","../types/UIFContract.js","node:crypto"]
|
|
12
|
+
*
|
|
13
|
+
* Logic Signature:
|
|
14
|
+
* props: {}
|
|
15
|
+
* events: {}
|
|
16
|
+
* state: {}
|
|
17
|
+
*
|
|
18
|
+
* Predictions:
|
|
19
|
+
* (none)
|
|
20
|
+
*
|
|
21
|
+
* Hashes (informational only - authoritative values in .uif.json):
|
|
22
|
+
* semantic: uif:9c632ee89c97f0b44ce6ae5c (informational)
|
|
23
|
+
* file: uif:1f0fa0e2c8958d7fc1696036
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Hash utilities for content and semantic hashing
|
|
27
|
+
*/
|
|
28
|
+
import { createHash } from 'node:crypto';
|
|
29
|
+
const SCHEMA_VERSION = '0.3';
|
|
30
|
+
/**
|
|
31
|
+
* Generate a hash from raw file content
|
|
32
|
+
* Strips the @uif header block before hashing to prevent self-referential churn
|
|
33
|
+
*/
|
|
34
|
+
export function fileHash(content) {
|
|
35
|
+
// Strip @uif header block to prevent self-referential changes
|
|
36
|
+
const stripped = content.replace(/\/\*\*[\s\S]*?@uif[\s\S]*?\*\/\n*/m, '');
|
|
37
|
+
// Normalize line endings for cross-platform consistency
|
|
38
|
+
const normalized = stripped.replace(/\r\n/g, '\n');
|
|
39
|
+
const hash = createHash('sha256').update(normalized, 'utf8').digest('hex');
|
|
40
|
+
return `uif:${hash.slice(0, 24)}`;
|
|
41
|
+
}
|
|
42
|
+
export function structureHash(astOrVersion) {
|
|
43
|
+
const payload = {
|
|
44
|
+
variables: [...(astOrVersion.variables || [])].sort(),
|
|
45
|
+
hooks: [...(astOrVersion.hooks || [])].sort(),
|
|
46
|
+
components: [...(astOrVersion.components || [])].sort(),
|
|
47
|
+
functions: [...(astOrVersion.functions || [])].sort(),
|
|
48
|
+
};
|
|
49
|
+
return sha256Hex(stableStringify(payload));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate a hash from logic signature (props, events, state)
|
|
53
|
+
* This changes only when the component's API contract changes
|
|
54
|
+
*/
|
|
55
|
+
export function signatureHash(signature) {
|
|
56
|
+
const payload = {
|
|
57
|
+
props: sortObject(signature.props),
|
|
58
|
+
emits: sortObject(signature.emits),
|
|
59
|
+
state: signature.state ? sortObject(signature.state) : undefined,
|
|
60
|
+
};
|
|
61
|
+
return sha256Hex(stableStringify(payload));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generate a semantic hash from AST structure and logic signature
|
|
65
|
+
* This hash only changes when the component's structural logic changes,
|
|
66
|
+
* not when comments, formatting, or implementation details change.
|
|
67
|
+
*
|
|
68
|
+
* semanticHash = hash(structureHash + signatureHash + schemaVersion)
|
|
69
|
+
*/
|
|
70
|
+
export function semanticHashFromAst(ast, signature) {
|
|
71
|
+
// Combine structure and signature hashes with schema version
|
|
72
|
+
const payload = {
|
|
73
|
+
schemaVersion: SCHEMA_VERSION,
|
|
74
|
+
structure: {
|
|
75
|
+
variables: [...ast.variables].sort(),
|
|
76
|
+
hooks: [...ast.hooks].sort(),
|
|
77
|
+
components: [...ast.components].sort(),
|
|
78
|
+
functions: [...ast.functions].sort(),
|
|
79
|
+
},
|
|
80
|
+
signature: {
|
|
81
|
+
props: sortObject(signature.props),
|
|
82
|
+
emits: sortObject(signature.emits),
|
|
83
|
+
state: signature.state ? sortObject(signature.state) : undefined,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return sha256Hex(stableStringify(payload));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compute bundle hash from nodes
|
|
90
|
+
* Input: ordered array of {entryId, semanticHash} plus depth and schemaVersion
|
|
91
|
+
* This provides a stable cache key for LLM context bundles
|
|
92
|
+
*/
|
|
93
|
+
export function bundleHash(nodes, depth, schemaVersion = '0.1') {
|
|
94
|
+
// Sort nodes by entryId for determinism
|
|
95
|
+
const ordered = [...nodes].sort((a, b) => a.entryId.localeCompare(b.entryId));
|
|
96
|
+
const payload = {
|
|
97
|
+
schemaVersion,
|
|
98
|
+
depth,
|
|
99
|
+
nodes: ordered.map(n => ({ entryId: n.entryId, semanticHash: n.semanticHash })),
|
|
100
|
+
};
|
|
101
|
+
const hash = createHash('sha256')
|
|
102
|
+
.update(stableStringify(payload), 'utf8')
|
|
103
|
+
.digest('hex');
|
|
104
|
+
return `uifb:${hash.slice(0, 24)}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Stable stringify with sorted keys and arrays
|
|
108
|
+
* Ensures deterministic JSON output for hashing
|
|
109
|
+
*/
|
|
110
|
+
export function stableStringify(obj) {
|
|
111
|
+
return JSON.stringify(obj, (k, v) => {
|
|
112
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
113
|
+
// Sort object keys
|
|
114
|
+
return Object.keys(v)
|
|
115
|
+
.sort()
|
|
116
|
+
.reduce((o, key) => {
|
|
117
|
+
o[key] = v[key];
|
|
118
|
+
return o;
|
|
119
|
+
}, {});
|
|
120
|
+
}
|
|
121
|
+
if (Array.isArray(v)) {
|
|
122
|
+
// Sort arrays for stability
|
|
123
|
+
return [...v].sort();
|
|
124
|
+
}
|
|
125
|
+
return v;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Sort object keys for stable hashing
|
|
130
|
+
*/
|
|
131
|
+
function sortObject(obj) {
|
|
132
|
+
const sorted = Object.keys(obj)
|
|
133
|
+
.sort()
|
|
134
|
+
.reduce((acc, key) => {
|
|
135
|
+
acc[key] = obj[key];
|
|
136
|
+
return acc;
|
|
137
|
+
}, {});
|
|
138
|
+
return sorted;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Generate SHA256 hash with uif: prefix
|
|
142
|
+
*/
|
|
143
|
+
function sha256Hex(input) {
|
|
144
|
+
const hash = createHash('sha256').update(input, 'utf8').digest('hex');
|
|
145
|
+
return `uif:${hash.slice(0, 24)}`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Verify if a hash matches the expected format
|
|
149
|
+
*/
|
|
150
|
+
export function isValidHash(hash) {
|
|
151
|
+
return /^uif:[a-f0-9]{24}$/.test(hash);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Compare two hashes for equality
|
|
155
|
+
*/
|
|
156
|
+
export function hashesEqual(hash1, hash2) {
|
|
157
|
+
return hash1 === hash2;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=hash.js.map
|