logicstamp-context 0.2.6 → 0.3.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/LLM_CONTEXT.md +2 -2
- package/README.md +79 -36
- package/dist/cli/commands/context.d.ts.map +1 -1
- package/dist/cli/commands/context.js +36 -10
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/ignore.d.ts +16 -0
- package/dist/cli/commands/ignore.d.ts.map +1 -0
- package/dist/cli/commands/ignore.js +61 -0
- package/dist/cli/commands/ignore.js.map +1 -0
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +83 -9
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/security.d.ts +37 -0
- package/dist/cli/commands/security.d.ts.map +1 -0
- package/dist/cli/commands/security.js +304 -0
- package/dist/cli/commands/security.js.map +1 -0
- package/dist/cli/handlers/ignoreHandler.d.ts +5 -0
- package/dist/cli/handlers/ignoreHandler.d.ts.map +1 -0
- package/dist/cli/handlers/ignoreHandler.js +23 -0
- package/dist/cli/handlers/ignoreHandler.js.map +1 -0
- package/dist/cli/handlers/securityHandler.d.ts +5 -0
- package/dist/cli/handlers/securityHandler.d.ts.map +1 -0
- package/dist/cli/handlers/securityHandler.js +41 -0
- package/dist/cli/handlers/securityHandler.js.map +1 -0
- package/dist/cli/parser/argumentParser.d.ts +9 -0
- package/dist/cli/parser/argumentParser.d.ts.map +1 -1
- package/dist/cli/parser/argumentParser.js +50 -0
- package/dist/cli/parser/argumentParser.js.map +1 -1
- package/dist/cli/parser/helpText.d.ts +3 -0
- package/dist/cli/parser/helpText.d.ts.map +1 -1
- package/dist/cli/parser/helpText.js +492 -348
- package/dist/cli/parser/helpText.js.map +1 -1
- package/dist/cli/stamp.js +61 -1
- package/dist/cli/stamp.js.map +1 -1
- package/dist/core/pack/builder.d.ts.map +1 -1
- package/dist/core/pack/builder.js +25 -5
- package/dist/core/pack/builder.js.map +1 -1
- package/dist/core/pack/loader.d.ts +14 -0
- package/dist/core/pack/loader.d.ts.map +1 -1
- package/dist/core/pack/loader.js +86 -2
- package/dist/core/pack/loader.js.map +1 -1
- package/dist/core/pack.d.ts.map +1 -1
- package/dist/core/pack.js +15 -2
- package/dist/core/pack.js.map +1 -1
- package/dist/utils/codeSanitizer.d.ts +24 -0
- package/dist/utils/codeSanitizer.d.ts.map +1 -0
- package/dist/utils/codeSanitizer.js +234 -0
- package/dist/utils/codeSanitizer.js.map +1 -0
- package/dist/utils/gitignore.d.ts +30 -2
- package/dist/utils/gitignore.d.ts.map +1 -1
- package/dist/utils/gitignore.js +132 -20
- package/dist/utils/gitignore.js.map +1 -1
- package/dist/utils/secretDetector.d.ts +21 -0
- package/dist/utils/secretDetector.d.ts.map +1 -0
- package/dist/utils/secretDetector.js +145 -0
- package/dist/utils/secretDetector.js.map +1 -0
- package/dist/utils/stampignore.d.ts +49 -0
- package/dist/utils/stampignore.d.ts.map +1 -0
- package/dist/utils/stampignore.js +211 -0
- package/dist/utils/stampignore.js.map +1 -0
- package/package.json +1 -1
- package/schema/logicstamp.context.schema.json +1 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code sanitization utilities
|
|
3
|
+
* Replaces secret values in code with "PRIVATE_DATA" based on security report
|
|
4
|
+
*/
|
|
5
|
+
import { resolve, isAbsolute, normalize } from 'node:path';
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
/**
|
|
8
|
+
* Load security report from file
|
|
9
|
+
*/
|
|
10
|
+
export async function loadSecurityReport(projectRoot) {
|
|
11
|
+
try {
|
|
12
|
+
const reportPath = resolve(projectRoot, 'stamp_security_report.json');
|
|
13
|
+
const content = await readFile(reportPath, 'utf8');
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Report doesn't exist or can't be read - that's okay
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a path for comparison (handles Windows case-insensitivity)
|
|
23
|
+
* Converts to lowercase on Windows and normalizes separators
|
|
24
|
+
*/
|
|
25
|
+
function normalizePathForComparison(path) {
|
|
26
|
+
const normalized = normalize(resolve(path));
|
|
27
|
+
// On Windows, paths are case-insensitive, so lowercase for comparison
|
|
28
|
+
// Also normalize separators to forward slashes for consistency
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
return normalized.toLowerCase().replace(/\\/g, '/');
|
|
31
|
+
}
|
|
32
|
+
return normalized.replace(/\\/g, '/');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get secret matches for a specific file
|
|
36
|
+
*/
|
|
37
|
+
function getSecretMatchesForFile(report, filePath, projectRoot) {
|
|
38
|
+
// Try to match file path - handle both absolute and relative paths
|
|
39
|
+
const absoluteFilePath = isAbsolute(filePath) ? filePath : resolve(projectRoot, filePath);
|
|
40
|
+
const normalizedTargetPath = normalizePathForComparison(absoluteFilePath);
|
|
41
|
+
// Also normalize project roots for comparison
|
|
42
|
+
const normalizedReportRoot = normalizePathForComparison(report.projectRoot);
|
|
43
|
+
const normalizedCurrentRoot = normalizePathForComparison(projectRoot);
|
|
44
|
+
return report.matches.filter(match => {
|
|
45
|
+
// Normalize match file path - try multiple strategies
|
|
46
|
+
let matchFilePath;
|
|
47
|
+
if (isAbsolute(match.file)) {
|
|
48
|
+
// Match file is already absolute
|
|
49
|
+
matchFilePath = match.file;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Match file is relative - try resolving from report's projectRoot first
|
|
53
|
+
matchFilePath = resolve(report.projectRoot, match.file);
|
|
54
|
+
}
|
|
55
|
+
const normalizedMatchPath = normalizePathForComparison(matchFilePath);
|
|
56
|
+
// Direct path match
|
|
57
|
+
if (normalizedMatchPath === normalizedTargetPath) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// If project roots differ, try resolving relative to current projectRoot
|
|
61
|
+
if (normalizedReportRoot !== normalizedCurrentRoot && !isAbsolute(match.file)) {
|
|
62
|
+
// Try resolving the match file relative to current projectRoot
|
|
63
|
+
const alternativePath = resolve(projectRoot, match.file);
|
|
64
|
+
const normalizedAlternative = normalizePathForComparison(alternativePath);
|
|
65
|
+
if (normalizedAlternative === normalizedTargetPath) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// Try making both paths relative to their respective roots and comparing
|
|
69
|
+
// This handles cases where the file structure is the same but roots differ
|
|
70
|
+
try {
|
|
71
|
+
const relativeFromReport = match.file.replace(/^\.\//, '');
|
|
72
|
+
const relativeFromCurrent = filePath.replace(/^\.\//, '');
|
|
73
|
+
if (normalizePathForComparison(relativeFromReport) === normalizePathForComparison(relativeFromCurrent)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore errors in relative path comparison
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Sanitize a line of code by replacing secret values with "PRIVATE_DATA"
|
|
86
|
+
* Uses the match information to accurately identify and replace the secret value
|
|
87
|
+
*/
|
|
88
|
+
function sanitizeLine(line, matches) {
|
|
89
|
+
if (matches.length === 0) {
|
|
90
|
+
return line;
|
|
91
|
+
}
|
|
92
|
+
let sanitized = line;
|
|
93
|
+
// Sort matches by column (right to left) to avoid offset issues when replacing
|
|
94
|
+
const sortedMatches = [...matches].sort((a, b) => b.column - a.column);
|
|
95
|
+
for (const match of sortedMatches) {
|
|
96
|
+
// Extract the secret value from the snippet
|
|
97
|
+
// The snippet typically shows: "context = 'SECRET_VALUE' context"
|
|
98
|
+
// We need to extract the actual secret value
|
|
99
|
+
// For database URLs: postgres://user:password@host (check this FIRST before quoted value matching)
|
|
100
|
+
// Database URLs are often in quotes, so we need to handle them specially
|
|
101
|
+
if (match.type === 'Database URL with Credentials') {
|
|
102
|
+
// Extract the password from the URL pattern: protocol://user:password@host
|
|
103
|
+
const dbUrlMatch = match.snippet.match(/((?:postgres|mysql|mongodb):\/\/[^:]+:)([^@]+)(@)/i);
|
|
104
|
+
if (dbUrlMatch) {
|
|
105
|
+
const password = dbUrlMatch[2];
|
|
106
|
+
// Also try to find the full URL in the line to replace just the password part
|
|
107
|
+
const fullUrlMatch = sanitized.match(/(['"`])((?:postgres|mysql|mongodb):\/\/[^:]+:)([^@]+)(@[^'"`]+)\1/i);
|
|
108
|
+
if (fullUrlMatch) {
|
|
109
|
+
const quote = fullUrlMatch[1];
|
|
110
|
+
const prefix = fullUrlMatch[2];
|
|
111
|
+
const passwordPart = fullUrlMatch[3];
|
|
112
|
+
const suffix = fullUrlMatch[4];
|
|
113
|
+
// Replace the URL with password sanitized
|
|
114
|
+
sanitized = sanitized.replace(fullUrlMatch[0], `${quote}${prefix}PRIVATE_DATA${suffix}${quote}`);
|
|
115
|
+
}
|
|
116
|
+
else if (sanitized.includes(password)) {
|
|
117
|
+
// Fallback: just replace the password if we can't match the full URL
|
|
118
|
+
sanitized = sanitized.replace(password, 'PRIVATE_DATA');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// Try to extract quoted values: 'value', "value", or `value`
|
|
124
|
+
// Handle cases where snippet has extra context (leading/trailing spaces, etc.)
|
|
125
|
+
const quotedValueMatch = match.snippet.match(/[=:]\s*(['"`])([^'"`]+)\1/);
|
|
126
|
+
if (quotedValueMatch) {
|
|
127
|
+
const quote = quotedValueMatch[1];
|
|
128
|
+
const secretValue = quotedValueMatch[2];
|
|
129
|
+
// Replace the secret value in the line, preserving quotes
|
|
130
|
+
// The secretValue might be in the line even if the snippet has different context
|
|
131
|
+
if (secretValue && secretValue.length > 0) {
|
|
132
|
+
const escapedValue = secretValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
+
// Try to find and replace the quoted value in the line
|
|
134
|
+
// Match: quote + secretValue + quote (with word boundaries to avoid partial matches)
|
|
135
|
+
const quotePattern = new RegExp(`${quote}${escapedValue}${quote}`, 'g');
|
|
136
|
+
// Check if pattern matches (don't use test() as it modifies lastIndex)
|
|
137
|
+
if (quotePattern.exec(sanitized) !== null) {
|
|
138
|
+
// Reset regex lastIndex before replace
|
|
139
|
+
quotePattern.lastIndex = 0;
|
|
140
|
+
sanitized = sanitized.replace(quotePattern, `${quote}PRIVATE_DATA${quote}`);
|
|
141
|
+
}
|
|
142
|
+
else if (sanitized.includes(secretValue)) {
|
|
143
|
+
// Fallback: if exact quote match fails, try to replace the value itself
|
|
144
|
+
// This handles cases where quotes might differ or context is different
|
|
145
|
+
sanitized = sanitized.replace(new RegExp(escapedValue, 'g'), 'PRIVATE_DATA');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
// Try to extract unquoted values after = or :
|
|
151
|
+
const unquotedValueMatch = match.snippet.match(/[=:]\s+([a-zA-Z0-9_\-]{16,})/);
|
|
152
|
+
if (unquotedValueMatch) {
|
|
153
|
+
const secretValue = unquotedValueMatch[1];
|
|
154
|
+
if (sanitized.includes(secretValue)) {
|
|
155
|
+
const escapedValue = secretValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
156
|
+
sanitized = sanitized.replace(new RegExp(`\\b${escapedValue}\\b`, 'g'), 'PRIVATE_DATA');
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
// For private keys, replace the entire key block
|
|
161
|
+
if (match.type === 'Private Key' && sanitized.includes('BEGIN')) {
|
|
162
|
+
// Find the private key block and replace content between BEGIN and END
|
|
163
|
+
sanitized = sanitized.replace(/(-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)[\s\S]*?(-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/g, '$1\nPRIVATE_DATA\n$2');
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Fallback: try to find and replace long alphanumeric strings that match the pattern
|
|
167
|
+
// This is less precise but catches edge cases
|
|
168
|
+
const longStringMatch = match.snippet.match(/([a-zA-Z0-9_\-]{20,})/);
|
|
169
|
+
if (longStringMatch) {
|
|
170
|
+
const potentialSecret = longStringMatch[1];
|
|
171
|
+
// Only replace if it's not already been replaced and it's a reasonable length
|
|
172
|
+
if (potentialSecret.length >= 16 && sanitized.includes(potentialSecret) && !sanitized.includes('PRIVATE_DATA')) {
|
|
173
|
+
const escaped = potentialSecret.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
174
|
+
sanitized = sanitized.replace(new RegExp(escaped, 'g'), 'PRIVATE_DATA');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return sanitized;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Sanitize code by replacing secret values with "PRIVATE_DATA"
|
|
182
|
+
* Uses security report to identify which lines contain secrets
|
|
183
|
+
*/
|
|
184
|
+
export function sanitizeCode(code, filePath, report, projectRoot) {
|
|
185
|
+
if (!report || report.matches.length === 0) {
|
|
186
|
+
return {
|
|
187
|
+
sanitized: code,
|
|
188
|
+
secretsReplaced: false,
|
|
189
|
+
filePath,
|
|
190
|
+
matchCount: 0,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// Get secret matches for this specific file
|
|
194
|
+
const fileMatches = getSecretMatchesForFile(report, filePath, projectRoot);
|
|
195
|
+
if (fileMatches.length === 0) {
|
|
196
|
+
// No matches for this file - return code unchanged
|
|
197
|
+
return {
|
|
198
|
+
sanitized: code,
|
|
199
|
+
secretsReplaced: false,
|
|
200
|
+
filePath,
|
|
201
|
+
matchCount: 0,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// Group matches by line number
|
|
205
|
+
const matchesByLine = new Map();
|
|
206
|
+
for (const match of fileMatches) {
|
|
207
|
+
if (!matchesByLine.has(match.line)) {
|
|
208
|
+
matchesByLine.set(match.line, []);
|
|
209
|
+
}
|
|
210
|
+
matchesByLine.get(match.line).push(match);
|
|
211
|
+
}
|
|
212
|
+
// Sanitize each line that has secrets
|
|
213
|
+
const lines = code.split('\n');
|
|
214
|
+
let secretsReplaced = false;
|
|
215
|
+
const sanitizedLines = lines.map((line, index) => {
|
|
216
|
+
const lineNumber = index + 1; // Line numbers are 1-based
|
|
217
|
+
const lineMatches = matchesByLine.get(lineNumber);
|
|
218
|
+
if (lineMatches && lineMatches.length > 0) {
|
|
219
|
+
const sanitized = sanitizeLine(line, lineMatches);
|
|
220
|
+
if (sanitized !== line) {
|
|
221
|
+
secretsReplaced = true;
|
|
222
|
+
}
|
|
223
|
+
return sanitized;
|
|
224
|
+
}
|
|
225
|
+
return line;
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
sanitized: sanitizedLines.join('\n'),
|
|
229
|
+
secretsReplaced,
|
|
230
|
+
filePath,
|
|
231
|
+
matchCount: fileMatches.length,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=codeSanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codeSanitizer.js","sourceRoot":"","sources":["../../src/utils/codeSanitizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Utilities for managing .gitignore files
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
|
-
* Patterns that should be added to .gitignore for LogicStamp context files
|
|
5
|
+
* Patterns that should be added to .gitignore for LogicStamp context & security files
|
|
6
6
|
*/
|
|
7
7
|
export declare const LOGICSTAMP_GITIGNORE_PATTERNS: string[];
|
|
8
8
|
/**
|
|
@@ -17,12 +17,27 @@ export declare function readGitignore(targetDir: string): Promise<string>;
|
|
|
17
17
|
* Check if a pattern exists in .gitignore content
|
|
18
18
|
*/
|
|
19
19
|
export declare function hasPattern(content: string, pattern: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Check if .gitignore has the LogicStamp block header comment
|
|
22
|
+
*/
|
|
23
|
+
export declare function hasLogicStampBlock(content: string): boolean;
|
|
20
24
|
/**
|
|
21
25
|
* Check if .gitignore has LogicStamp patterns
|
|
26
|
+
* This is a legacy function for backward compatibility - checks for key patterns
|
|
22
27
|
*/
|
|
23
28
|
export declare function hasLogicStampPatterns(content: string): boolean;
|
|
24
29
|
/**
|
|
25
|
-
*
|
|
30
|
+
* Get which LogicStamp patterns are missing from .gitignore content
|
|
31
|
+
*/
|
|
32
|
+
export declare function getMissingPatterns(content: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Add LogicStamp patterns to .gitignore content (idempotent patch mode)
|
|
35
|
+
*
|
|
36
|
+
* Behavior:
|
|
37
|
+
* - If LogicStamp block exists: append only missing patterns to the block
|
|
38
|
+
* - If LogicStamp block doesn't exist: add full block at the end
|
|
39
|
+
* - Never duplicates patterns
|
|
40
|
+
* - Preserves user's manual additions
|
|
26
41
|
*/
|
|
27
42
|
export declare function addLogicStampPatterns(content: string): string;
|
|
28
43
|
/**
|
|
@@ -32,11 +47,24 @@ export declare function writeGitignore(targetDir: string, content: string): Prom
|
|
|
32
47
|
/**
|
|
33
48
|
* Add LogicStamp patterns to .gitignore file
|
|
34
49
|
* Creates .gitignore if it doesn't exist
|
|
50
|
+
* Uses idempotent patch mode - only adds missing patterns if block exists
|
|
35
51
|
*/
|
|
36
52
|
export declare function ensureGitignorePatterns(targetDir: string): Promise<{
|
|
37
53
|
added: boolean;
|
|
38
54
|
created: boolean;
|
|
39
55
|
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Ensure a specific pattern is in .gitignore
|
|
58
|
+
* Adds the pattern if it doesn't exist, preserves existing content
|
|
59
|
+
*
|
|
60
|
+
* @param targetDir - Project root directory
|
|
61
|
+
* @param pattern - Pattern to ensure (relative to project root, e.g., "reports/security.json")
|
|
62
|
+
* @returns Result indicating if pattern was added and if .gitignore was created
|
|
63
|
+
*/
|
|
64
|
+
export declare function ensurePatternInGitignore(targetDir: string, pattern: string): Promise<{
|
|
65
|
+
added: boolean;
|
|
66
|
+
created: boolean;
|
|
67
|
+
}>;
|
|
40
68
|
/**
|
|
41
69
|
* Smart .gitignore management with config-based behavior (no prompting)
|
|
42
70
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,eAAO,MAAM,6BAA6B,
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,eAAO,MAAM,6BAA6B,UAQzC,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,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQ9D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAI5D;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkD7D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BtF;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAiB9G;AAED;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA+B/C;AAED;;;;;;;;;;;;;;GAcG;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,CAyBpF"}
|
package/dist/utils/gitignore.js
CHANGED
|
@@ -6,15 +6,16 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { readConfig } from './config.js';
|
|
7
7
|
import { debugError } from './debug.js';
|
|
8
8
|
/**
|
|
9
|
-
* Patterns that should be added to .gitignore for LogicStamp context files
|
|
9
|
+
* Patterns that should be added to .gitignore for LogicStamp context & security files
|
|
10
10
|
*/
|
|
11
11
|
export const LOGICSTAMP_GITIGNORE_PATTERNS = [
|
|
12
|
-
'# LogicStamp context files',
|
|
12
|
+
'# LogicStamp context & security files',
|
|
13
13
|
'context.json',
|
|
14
14
|
'context_*.json',
|
|
15
15
|
'*.uif.json',
|
|
16
16
|
'logicstamp.manifest.json',
|
|
17
17
|
'.logicstamp/',
|
|
18
|
+
'stamp_security_report.json',
|
|
18
19
|
];
|
|
19
20
|
/**
|
|
20
21
|
* Check if .gitignore exists in the given directory
|
|
@@ -44,11 +45,18 @@ export async function readGitignore(targetDir) {
|
|
|
44
45
|
* Check if a pattern exists in .gitignore content
|
|
45
46
|
*/
|
|
46
47
|
export function hasPattern(content, pattern) {
|
|
47
|
-
const lines = content.split(
|
|
48
|
+
const lines = content.split(/\r?\n/).map(line => line.trim());
|
|
48
49
|
return lines.includes(pattern);
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if .gitignore has the LogicStamp block header comment
|
|
53
|
+
*/
|
|
54
|
+
export function hasLogicStampBlock(content) {
|
|
55
|
+
return hasPattern(content, '# LogicStamp context & security files');
|
|
56
|
+
}
|
|
50
57
|
/**
|
|
51
58
|
* Check if .gitignore has LogicStamp patterns
|
|
59
|
+
* This is a legacy function for backward compatibility - checks for key patterns
|
|
52
60
|
*/
|
|
53
61
|
export function hasLogicStampPatterns(content) {
|
|
54
62
|
// Check for the key patterns (ignore the comment line)
|
|
@@ -59,26 +67,94 @@ export function hasLogicStampPatterns(content) {
|
|
|
59
67
|
(hasPattern(content, 'context_*.json') || hasPattern(content, 'context_main.json'));
|
|
60
68
|
}
|
|
61
69
|
/**
|
|
62
|
-
*
|
|
70
|
+
* Get which LogicStamp patterns are missing from .gitignore content
|
|
63
71
|
*/
|
|
64
|
-
export function
|
|
65
|
-
|
|
66
|
-
// Check which patterns are missing
|
|
67
|
-
const missingPatterns = LOGICSTAMP_GITIGNORE_PATTERNS.filter(pattern => {
|
|
68
|
-
if (pattern.startsWith('#'))
|
|
69
|
-
return false; // Always add the comment
|
|
72
|
+
export function getMissingPatterns(content) {
|
|
73
|
+
return LOGICSTAMP_GITIGNORE_PATTERNS.filter(pattern => {
|
|
70
74
|
return !hasPattern(content, pattern);
|
|
71
75
|
});
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find the insertion point for LogicStamp patterns in .gitignore content
|
|
79
|
+
* Returns the index where the LogicStamp block starts, or -1 if not found
|
|
80
|
+
*/
|
|
81
|
+
function findLogicStampBlockIndex(lines) {
|
|
82
|
+
const headerIndex = lines.findIndex(line => line.trim() === '# LogicStamp context & security files');
|
|
83
|
+
return headerIndex;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Find the insertion point for missing LogicStamp patterns
|
|
87
|
+
* Returns the index after the last existing LogicStamp pattern in the block
|
|
88
|
+
*/
|
|
89
|
+
function findLogicStampInsertionPoint(lines, startIndex) {
|
|
90
|
+
// Known LogicStamp patterns (excluding the header comment)
|
|
91
|
+
const knownPatterns = new Set(LOGICSTAMP_GITIGNORE_PATTERNS.filter(p => !p.startsWith('#')).map(p => p.trim()));
|
|
92
|
+
let lastPatternIndex = startIndex; // Start after the header comment
|
|
93
|
+
// Find the last LogicStamp pattern in the block
|
|
94
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
95
|
+
const trimmed = lines[i].trim();
|
|
96
|
+
// If we hit a blank line, stop here (insert before the blank line)
|
|
97
|
+
if (trimmed === '') {
|
|
98
|
+
return i;
|
|
99
|
+
}
|
|
100
|
+
// If we hit a non-LogicStamp pattern (not a comment, not a known pattern), stop here
|
|
101
|
+
if (!trimmed.startsWith('#') && !knownPatterns.has(trimmed)) {
|
|
102
|
+
return i;
|
|
103
|
+
}
|
|
104
|
+
// If this is a LogicStamp pattern (comment or known pattern), update lastPatternIndex
|
|
105
|
+
if (trimmed.startsWith('#') || knownPatterns.has(trimmed)) {
|
|
106
|
+
lastPatternIndex = i + 1; // Insert after this line
|
|
107
|
+
}
|
|
74
108
|
}
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
109
|
+
// Block extends to end of file - insert at the end
|
|
110
|
+
return lastPatternIndex;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Add LogicStamp patterns to .gitignore content (idempotent patch mode)
|
|
114
|
+
*
|
|
115
|
+
* Behavior:
|
|
116
|
+
* - If LogicStamp block exists: append only missing patterns to the block
|
|
117
|
+
* - If LogicStamp block doesn't exist: add full block at the end
|
|
118
|
+
* - Never duplicates patterns
|
|
119
|
+
* - Preserves user's manual additions
|
|
120
|
+
*/
|
|
121
|
+
export function addLogicStampPatterns(content) {
|
|
122
|
+
const lines = content.split(/\r?\n/);
|
|
123
|
+
const hasBlock = hasLogicStampBlock(content);
|
|
124
|
+
const missingPatterns = getMissingPatterns(content);
|
|
125
|
+
// If block exists and all patterns are present, return unchanged
|
|
126
|
+
if (hasBlock && missingPatterns.length === 0) {
|
|
127
|
+
return content;
|
|
79
128
|
}
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
// If block exists but patterns are missing, append only missing ones
|
|
130
|
+
if (hasBlock && missingPatterns.length > 0) {
|
|
131
|
+
const blockStartIndex = findLogicStampBlockIndex(lines);
|
|
132
|
+
const insertIndex = findLogicStampInsertionPoint(lines, blockStartIndex);
|
|
133
|
+
// Insert missing patterns right after the last existing LogicStamp pattern
|
|
134
|
+
const newLines = [...lines];
|
|
135
|
+
const patternsToAdd = missingPatterns.map(p => p.trim());
|
|
136
|
+
// Insert patterns at the insertion point
|
|
137
|
+
newLines.splice(insertIndex, 0, ...patternsToAdd);
|
|
138
|
+
// Preserve original line ending style
|
|
139
|
+
const lineEnding = content.includes('\r\n') ? '\r\n' : '\n';
|
|
140
|
+
return newLines.join(lineEnding) + (content.endsWith(lineEnding) ? '' : lineEnding);
|
|
141
|
+
}
|
|
142
|
+
// Block doesn't exist - add full block at the end
|
|
143
|
+
let newContent = content;
|
|
144
|
+
// Add blank lines before the section if content exists
|
|
145
|
+
if (newContent.length > 0) {
|
|
146
|
+
// Normalize line endings and ensure we end with at least one newline
|
|
147
|
+
const normalized = newContent.replace(/\r\n/g, '\n');
|
|
148
|
+
if (!normalized.endsWith('\n')) {
|
|
149
|
+
newContent = normalized + '\n';
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
newContent = normalized;
|
|
153
|
+
}
|
|
154
|
+
// Add one more blank line if content doesn't already end with one
|
|
155
|
+
if (!newContent.endsWith('\n\n')) {
|
|
156
|
+
newContent += '\n';
|
|
157
|
+
}
|
|
82
158
|
}
|
|
83
159
|
// Add all LogicStamp patterns as a group
|
|
84
160
|
newContent += LOGICSTAMP_GITIGNORE_PATTERNS.join('\n') + '\n';
|
|
@@ -120,14 +196,50 @@ export async function writeGitignore(targetDir, content) {
|
|
|
120
196
|
/**
|
|
121
197
|
* Add LogicStamp patterns to .gitignore file
|
|
122
198
|
* Creates .gitignore if it doesn't exist
|
|
199
|
+
* Uses idempotent patch mode - only adds missing patterns if block exists
|
|
123
200
|
*/
|
|
124
201
|
export async function ensureGitignorePatterns(targetDir) {
|
|
125
202
|
const exists = await gitignoreExists(targetDir);
|
|
126
203
|
const content = await readGitignore(targetDir);
|
|
127
|
-
if
|
|
204
|
+
// Use smart patch mode - will append only missing patterns if block exists
|
|
205
|
+
const newContent = addLogicStampPatterns(content);
|
|
206
|
+
// Check if content actually changed
|
|
207
|
+
const contentChanged = content !== newContent;
|
|
208
|
+
if (!contentChanged) {
|
|
128
209
|
return { added: false, created: false };
|
|
129
210
|
}
|
|
130
|
-
|
|
211
|
+
await writeGitignore(targetDir, newContent);
|
|
212
|
+
return { added: true, created: !exists };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Ensure a specific pattern is in .gitignore
|
|
216
|
+
* Adds the pattern if it doesn't exist, preserves existing content
|
|
217
|
+
*
|
|
218
|
+
* @param targetDir - Project root directory
|
|
219
|
+
* @param pattern - Pattern to ensure (relative to project root, e.g., "reports/security.json")
|
|
220
|
+
* @returns Result indicating if pattern was added and if .gitignore was created
|
|
221
|
+
*/
|
|
222
|
+
export async function ensurePatternInGitignore(targetDir, pattern) {
|
|
223
|
+
const exists = await gitignoreExists(targetDir);
|
|
224
|
+
const content = await readGitignore(targetDir);
|
|
225
|
+
// Normalize pattern (forward slashes, no leading slash)
|
|
226
|
+
const normalizedPattern = pattern.replace(/\\/g, '/').replace(/^\//, '').trim();
|
|
227
|
+
// Check if pattern already exists
|
|
228
|
+
if (hasPattern(content, normalizedPattern)) {
|
|
229
|
+
return { added: false, created: false };
|
|
230
|
+
}
|
|
231
|
+
// Add pattern to .gitignore
|
|
232
|
+
let newContent = content;
|
|
233
|
+
// Ensure content ends with newline
|
|
234
|
+
if (newContent.length > 0 && !newContent.endsWith('\n')) {
|
|
235
|
+
newContent += '\n';
|
|
236
|
+
}
|
|
237
|
+
// Add blank line before pattern if content exists
|
|
238
|
+
if (newContent.length > 0 && !newContent.endsWith('\n\n')) {
|
|
239
|
+
newContent += '\n';
|
|
240
|
+
}
|
|
241
|
+
// Add pattern
|
|
242
|
+
newContent += normalizedPattern + '\n';
|
|
131
243
|
await writeGitignore(targetDir, newContent);
|
|
132
244
|
return { added: true, created: !exists };
|
|
133
245
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple secret detection utilities
|
|
3
|
+
* Detects common patterns for API keys, tokens, passwords, etc.
|
|
4
|
+
*/
|
|
5
|
+
export interface SecretMatch {
|
|
6
|
+
file: string;
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
type: string;
|
|
10
|
+
snippet: string;
|
|
11
|
+
severity: 'high' | 'medium' | 'low';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Scan a file for secrets
|
|
15
|
+
*/
|
|
16
|
+
export declare function scanFileForSecrets(filePath: string, content: string): SecretMatch[];
|
|
17
|
+
/**
|
|
18
|
+
* Filter out false positives (common patterns that look like secrets but aren't)
|
|
19
|
+
*/
|
|
20
|
+
export declare function filterFalsePositives(matches: SecretMatch[]): SecretMatch[];
|
|
21
|
+
//# sourceMappingURL=secretDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretDetector.d.ts","sourceRoot":"","sources":["../../src/utils/secretDetector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;CACrC;AAyFD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CAmCf;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,EAAE,CAyB1E"}
|