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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/LLM_CONTEXT.md +260 -0
  3. package/README.md +949 -0
  4. package/dist/cli/commands/clean.d.ts +15 -0
  5. package/dist/cli/commands/clean.d.ts.map +1 -0
  6. package/dist/cli/commands/clean.js +142 -0
  7. package/dist/cli/commands/clean.js.map +1 -0
  8. package/dist/cli/commands/compare.d.ts +88 -0
  9. package/dist/cli/commands/compare.d.ts.map +1 -0
  10. package/dist/cli/commands/compare.js +519 -0
  11. package/dist/cli/commands/compare.js.map +1 -0
  12. package/dist/cli/commands/context.d.ts +24 -0
  13. package/dist/cli/commands/context.d.ts.map +1 -0
  14. package/dist/cli/commands/context.js +576 -0
  15. package/dist/cli/commands/context.js.map +1 -0
  16. package/dist/cli/commands/init.d.ts +14 -0
  17. package/dist/cli/commands/init.d.ts.map +1 -0
  18. package/dist/cli/commands/init.js +71 -0
  19. package/dist/cli/commands/init.js.map +1 -0
  20. package/dist/cli/commands/validate.d.ts +52 -0
  21. package/dist/cli/commands/validate.d.ts.map +1 -0
  22. package/dist/cli/commands/validate.js +368 -0
  23. package/dist/cli/commands/validate.js.map +1 -0
  24. package/dist/cli/index.d.ts +7 -0
  25. package/dist/cli/index.d.ts.map +1 -0
  26. package/dist/cli/index.js +223 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/cli/stamp.d.ts +7 -0
  29. package/dist/cli/stamp.d.ts.map +1 -0
  30. package/dist/cli/stamp.js +961 -0
  31. package/dist/cli/stamp.js.map +1 -0
  32. package/dist/cli/validate-index.d.ts +7 -0
  33. package/dist/cli/validate-index.d.ts.map +1 -0
  34. package/dist/cli/validate-index.js +55 -0
  35. package/dist/cli/validate-index.js.map +1 -0
  36. package/dist/core/astParser.d.ts +47 -0
  37. package/dist/core/astParser.d.ts.map +1 -0
  38. package/dist/core/astParser.js +417 -0
  39. package/dist/core/astParser.js.map +1 -0
  40. package/dist/core/contractBuilder.d.ts +48 -0
  41. package/dist/core/contractBuilder.d.ts.map +1 -0
  42. package/dist/core/contractBuilder.js +120 -0
  43. package/dist/core/contractBuilder.js.map +1 -0
  44. package/dist/core/manifest.d.ts +75 -0
  45. package/dist/core/manifest.d.ts.map +1 -0
  46. package/dist/core/manifest.js +173 -0
  47. package/dist/core/manifest.js.map +1 -0
  48. package/dist/core/pack.d.ts +190 -0
  49. package/dist/core/pack.d.ts.map +1 -0
  50. package/dist/core/pack.js +438 -0
  51. package/dist/core/pack.js.map +1 -0
  52. package/dist/core/signature.d.ts +47 -0
  53. package/dist/core/signature.d.ts.map +1 -0
  54. package/dist/core/signature.js +208 -0
  55. package/dist/core/signature.js.map +1 -0
  56. package/dist/index.d.ts +27 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +23 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/types/UIFContract.d.ts +112 -0
  61. package/dist/types/UIFContract.d.ts.map +1 -0
  62. package/dist/types/UIFContract.js +36 -0
  63. package/dist/types/UIFContract.js.map +1 -0
  64. package/dist/utils/config.d.ts +34 -0
  65. package/dist/utils/config.d.ts.map +1 -0
  66. package/dist/utils/config.js +62 -0
  67. package/dist/utils/config.js.map +1 -0
  68. package/dist/utils/fsx.d.ts +85 -0
  69. package/dist/utils/fsx.d.ts.map +1 -0
  70. package/dist/utils/fsx.js +181 -0
  71. package/dist/utils/fsx.js.map +1 -0
  72. package/dist/utils/gitignore.d.ts +62 -0
  73. package/dist/utils/gitignore.d.ts.map +1 -0
  74. package/dist/utils/gitignore.js +183 -0
  75. package/dist/utils/gitignore.js.map +1 -0
  76. package/dist/utils/hash.d.ts +73 -0
  77. package/dist/utils/hash.d.ts.map +1 -0
  78. package/dist/utils/hash.js +159 -0
  79. package/dist/utils/hash.js.map +1 -0
  80. package/dist/utils/llmContext.d.ts +34 -0
  81. package/dist/utils/llmContext.d.ts.map +1 -0
  82. package/dist/utils/llmContext.js +136 -0
  83. package/dist/utils/llmContext.js.map +1 -0
  84. package/dist/utils/tokens.d.ts +23 -0
  85. package/dist/utils/tokens.d.ts.map +1 -0
  86. package/dist/utils/tokens.js +29 -0
  87. package/dist/utils/tokens.js.map +1 -0
  88. package/package.json +69 -0
  89. 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