@billwgong/contextlint 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/dist/parser.js ADDED
@@ -0,0 +1,106 @@
1
+ export function estimateTokens(text) {
2
+ const nonAscii = [...text].filter(c => c.charCodeAt(0) > 127).length;
3
+ return Math.ceil((text.length - nonAscii) / 4 + nonAscii);
4
+ }
5
+ export function normalize(text) {
6
+ return text.replace(/^[\s]*(?:[-*+]\s+|\d+[.)]\s+)/, '').trim()
7
+ .replace(/\s+/g, ' ').replace(/[.!。!]+$/, '');
8
+ }
9
+ export function parse(content, source) {
10
+ const lines = content.replace(/^\uFEFF/, '').split(/\r?\n/);
11
+ let start = 0;
12
+ if (lines[0]?.trim() === '---') {
13
+ const close = lines.findIndex((line, i) => i > 0 && line.trim() === '---');
14
+ if (close < 0) {
15
+ source.activation = 'unknown';
16
+ return [];
17
+ }
18
+ const metadata = lines.slice(1, close).join('\n');
19
+ if (source.agents.includes('cursor')) {
20
+ source.activation = /^alwaysApply:\s*true\s*$/m.test(metadata) && !/^globs:\s*\S/m.test(metadata)
21
+ ? 'directory' : 'conditional';
22
+ }
23
+ else if (source.agents.includes('copilot') && /^applyTo:/m.test(metadata)) {
24
+ source.activation = 'conditional';
25
+ }
26
+ else if (metadata.trim())
27
+ source.activation = 'unknown';
28
+ start = close + 1;
29
+ }
30
+ let fence = null;
31
+ let comment = false;
32
+ let quoted = false;
33
+ const rules = [];
34
+ let paragraph = [];
35
+ let paragraphStart = 0;
36
+ const headings = [];
37
+ let conditionalIntro = false;
38
+ const isConditional = (text) => /\b(if|unless|except|when|fallback|example|examples|for example|prefer)\b|如果|除非|例如|示例|优先|失败时|仅在|情况下|当.+时/i.test(text);
39
+ const flush = () => {
40
+ if (!paragraph.length)
41
+ return;
42
+ const text = paragraph.join(' ').trim();
43
+ if (text)
44
+ rules.push({ text, normalized: normalize(text), line: paragraphStart + 1,
45
+ endLine: paragraphStart + paragraph.length, source,
46
+ conditional: headings.some(h => h.conditional) || conditionalIntro || isConditional(text),
47
+ context: headings.map(h => h.text).join(' / ') });
48
+ if (isConditional(text) && /[::]$/.test(text))
49
+ conditionalIntro = true;
50
+ paragraph = [];
51
+ };
52
+ for (let i = start; i < lines.length; i++) {
53
+ const line = lines[i];
54
+ if (/^\s*>/.test(line)) {
55
+ flush();
56
+ quoted = true;
57
+ continue;
58
+ }
59
+ if (quoted && line.trim() && !/^\s{0,3}(?:#{1,6}\s|[-*+]\s|\d+[.)]\s|`{3,}|~{3,})/.test(line))
60
+ continue;
61
+ quoted = false;
62
+ const marker = line.match(/^\s{0,3}(`{3,}|~{3,})/);
63
+ if (marker) {
64
+ flush();
65
+ if (!fence)
66
+ fence = { char: marker[1][0], length: marker[1].length };
67
+ else if (marker[1][0] === fence.char && marker[1].length >= fence.length && /^\s*(?:`+|~+)\s*$/.test(line))
68
+ fence = null;
69
+ continue;
70
+ }
71
+ if (fence)
72
+ continue;
73
+ // Top-level indented code is data, not an instruction. Wrapped list prose is preserved.
74
+ if (/^(?: {4}|\t)/.test(line) && (!paragraph.length || !/^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(paragraph[0]))) {
75
+ flush();
76
+ continue;
77
+ }
78
+ if (comment || line.includes('<!--')) {
79
+ flush();
80
+ comment = !line.includes('-->');
81
+ continue;
82
+ }
83
+ if (/^\s*#/.test(line)) {
84
+ flush();
85
+ const heading = line.match(/^\s*(#+)\s*(.*?)\s*#*$/);
86
+ const level = heading[1].length;
87
+ while (headings.length && headings[headings.length - 1].level >= level)
88
+ headings.pop();
89
+ headings.push({ level, text: heading[2], conditional: isConditional(line) || /conditional|例外/i.test(line) });
90
+ conditionalIntro = false;
91
+ continue;
92
+ }
93
+ if (!line.trim() || /^\s*(>|\||[-*_]{3,}\s*$)/.test(line)) {
94
+ flush();
95
+ continue;
96
+ }
97
+ if (/^\s*(?:[-*+]\s+|\d+[.)]\s+)/.test(line))
98
+ flush();
99
+ if (!paragraph.length)
100
+ paragraphStart = i;
101
+ paragraph.push(line.trim());
102
+ }
103
+ flush();
104
+ return rules;
105
+ }
106
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Keep independent private history. Replace regular output copies atomically, never link targets. */
2
+ export declare function saveHtmlReport(root: string, content: string, requestedPath?: string): Promise<{
3
+ outputPath: string;
4
+ archivePath: string;
5
+ }>;
@@ -0,0 +1,80 @@
1
+ import { mkdir, writeFile, lstat, realpath, rename, rm } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { isWithin } from './safe-io.js';
5
+ async function historyDirectory(root) {
6
+ let directory = await realpath(root);
7
+ for (const part of ['.contextlint', 'reports']) {
8
+ directory = path.join(directory, part);
9
+ try {
10
+ await mkdir(directory, { mode: 0o700 });
11
+ }
12
+ catch (error) {
13
+ if (error.code !== 'EEXIST')
14
+ throw error;
15
+ }
16
+ const info = await lstat(directory);
17
+ if (info.isSymbolicLink() || !info.isDirectory())
18
+ throw new Error('Report history must use real directories, not symbolic links');
19
+ }
20
+ return directory;
21
+ }
22
+ /** Keep independent private history. Replace regular output copies atomically, never link targets. */
23
+ export async function saveHtmlReport(root, content, requestedPath) {
24
+ const directory = await historyDirectory(root);
25
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
26
+ const archivePath = path.join(directory, `report-${stamp}-${randomUUID()}.html`);
27
+ await writeFile(archivePath, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
28
+ const outputPath = requestedPath ? path.resolve(requestedPath) : archivePath;
29
+ if (outputPath !== archivePath) {
30
+ let temporary;
31
+ try {
32
+ const canonicalRoot = await realpath(root);
33
+ let ancestor = path.dirname(outputPath);
34
+ while (true) {
35
+ try {
36
+ const resolved = await realpath(ancestor);
37
+ if (isWithin(path.resolve(root), outputPath) && !isWithin(canonicalRoot, resolved)) {
38
+ throw new Error('Report output directory escapes scan root through a symbolic link');
39
+ }
40
+ break;
41
+ }
42
+ catch (error) {
43
+ if (error.code !== 'ENOENT')
44
+ throw error;
45
+ const parent = path.dirname(ancestor);
46
+ if (parent === ancestor)
47
+ throw error;
48
+ ancestor = parent;
49
+ }
50
+ }
51
+ await mkdir(path.dirname(outputPath), { recursive: true });
52
+ // Resolve the parent once; atomic rename replaces the entry rather than following it.
53
+ const parent = await realpath(path.dirname(outputPath));
54
+ if (isWithin(path.resolve(root), outputPath) && !isWithin(canonicalRoot, parent))
55
+ throw new Error('Report output directory escapes scan root');
56
+ const destination = path.join(parent, path.basename(outputPath));
57
+ try {
58
+ const info = await lstat(destination);
59
+ if (info.isSymbolicLink() || !info.isFile())
60
+ throw new Error('Report output must be a regular file, not a symbolic link');
61
+ }
62
+ catch (error) {
63
+ if (error.code !== 'ENOENT')
64
+ throw error;
65
+ }
66
+ temporary = path.join(parent, `.contextlint-${randomUUID()}.tmp`);
67
+ await writeFile(temporary, content, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
68
+ await rename(temporary, destination);
69
+ }
70
+ catch (error) {
71
+ throw new Error(`Output copy failed (${error instanceof Error ? error.message : String(error)}). Report retained: ${archivePath}`);
72
+ }
73
+ finally {
74
+ if (temporary)
75
+ await rm(temporary, { force: true });
76
+ }
77
+ }
78
+ return { outputPath, archivePath };
79
+ }
80
+ //# sourceMappingURL=report-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-storage.js","sourceRoot":"","sources":["../src/report-storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Report } from './types.js';
2
+ import { type Language } from './i18n.js';
3
+ export declare function render(report: Report, language?: Language): string;
@@ -0,0 +1,29 @@
1
+ import { terminalText } from './safe-io.js';
2
+ import { describeFinding, skipReason, words } from './i18n.js';
3
+ export function render(report, language = 'en') {
4
+ const s = report.summary, w = words(language);
5
+ const lines = ['ContextLint', '', `${w.found} ${s.files} ${w.files} · ~${s.estimatedTokens.toLocaleString('en-US')} ${w.tokens}`,
6
+ `${s.conflicts} ${w.conflicts} · ${s.duplicates} ${w.duplicates} · ${s.brokenReferences} ${w.references}`, ''];
7
+ lines.splice(1, 0, terminalText(report.projectName ?? report.root));
8
+ if (!s.files)
9
+ lines.push(w.noSourcesHeadline, w.noSourcesHint);
10
+ if (report.status === 'limited')
11
+ lines.push(w.limitedNotice);
12
+ for (const source of report.sources)
13
+ lines.push(` ${source.path} ~${source.estimatedTokens} tokens · ${source.ruleCount} ${w.ruleCount} · ${source.agents.join(', ')} · ${source.activation === 'directory' ? w.directory : w.conditional}`);
14
+ for (const finding of report.findings) {
15
+ const description = describeFinding(finding.id, language);
16
+ lines.push('', `[${w[finding.severity]}] ${finding.id} (${finding.agents.join(', ')})`, description.title);
17
+ for (const loc of finding.locations)
18
+ lines.push(` ${loc.path}:${loc.line}${loc.endLine !== loc.line ? '-' + loc.endLine : ''} ${loc.text}`);
19
+ lines.push(` ${w.reason}: ${description.reason}`, ` ${w.fix}: ${description.fix}`);
20
+ }
21
+ if (report.skipped.length) {
22
+ lines.push('', w.skipped + ':');
23
+ for (const item of report.skipped)
24
+ lines.push(` ${item.path}: ${skipReason(item.reason, language)}`);
25
+ }
26
+ lines.push('', w.terminalFooter);
27
+ return lines.map(terminalText).join('\n');
28
+ }
29
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare function isWithin(root: string, target: string): boolean;
2
+ /** Never execute repository content; bound reads and refuse linked/non-regular files. */
3
+ export declare function readLocalFile(root: string, filename: string, limit: number): Promise<{
4
+ text: string;
5
+ bytes: number;
6
+ }>;
7
+ /** Display controls visibly, so untrusted filenames/instructions cannot operate a terminal. */
8
+ export declare function terminalText(value: string): string;
@@ -0,0 +1,41 @@
1
+ import { constants } from 'node:fs';
2
+ import { open, realpath, lstat } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ export function isWithin(root, target) {
5
+ const relative = path.relative(root, target);
6
+ return relative !== '..' && !relative.startsWith('..' + path.sep) && !path.isAbsolute(relative);
7
+ }
8
+ /** Never execute repository content; bound reads and refuse linked/non-regular files. */
9
+ export async function readLocalFile(root, filename, limit) {
10
+ if (!isWithin(root, await realpath(path.dirname(filename))))
11
+ throw new Error('File directory escapes scan root');
12
+ if ((await lstat(filename)).isSymbolicLink())
13
+ throw new Error('Symbolic links are not followed');
14
+ const handle = await open(filename, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
15
+ try {
16
+ const info = await handle.stat();
17
+ if (!info.isFile())
18
+ throw new Error('Not a regular file');
19
+ if (info.size > limit)
20
+ throw new Error('File size limit exceeded');
21
+ const buffer = Buffer.alloc(limit + 1);
22
+ let bytes = 0;
23
+ while (bytes < buffer.length) {
24
+ const chunk = await handle.read(buffer, bytes, buffer.length - bytes, null);
25
+ if (!chunk.bytesRead)
26
+ break;
27
+ bytes += chunk.bytesRead;
28
+ }
29
+ if (bytes > limit)
30
+ throw new Error('File size limit exceeded');
31
+ return { text: buffer.subarray(0, bytes).toString('utf8'), bytes };
32
+ }
33
+ finally {
34
+ await handle.close();
35
+ }
36
+ }
37
+ /** Display controls visibly, so untrusted filenames/instructions cannot operate a terminal. */
38
+ export function terminalText(value) {
39
+ return value.replace(/[\x00-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/g, c => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
40
+ }
41
+ //# sourceMappingURL=safe-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-io.js","sourceRoot":"","sources":["../src/safe-io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,MAAc;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,OAAO,QAAQ,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAClG,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IAC/E,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjH,IAAI,CAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,cAAc,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACjG,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;IACpH,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,IAAI,GAAG,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACvC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,OAAO,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;YAC5E,IAAI,CAAC,KAAK,CAAC,SAAS;gBAAE,MAAM;YAC5B,KAAK,IAAI,KAAK,CAAC,SAAS,CAAC;QAC3B,CAAC;QACD,IAAI,KAAK,GAAG,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACrE,CAAC;YAAS,CAAC;QAAC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IAAC,CAAC;AACrC,CAAC;AAED,+FAA+F;AAC/F,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,iDAAiD,EACpE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Source, Rule, ScanOptions } from './types.js';
2
+ export declare function describe(file: string): Omit<Source, 'bytes' | 'estimatedTokens' | 'ruleCount'> | null;
3
+ export declare function scan(rootInput: string, options: ScanOptions): Promise<{
4
+ root: string;
5
+ sources: Source[];
6
+ rules: Rule[];
7
+ skipped: {
8
+ path: string;
9
+ reason: string;
10
+ }[];
11
+ }>;
@@ -0,0 +1,127 @@
1
+ import { opendir, lstat, stat, realpath } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { isWithin, readLocalFile } from './safe-io.js';
4
+ import { estimateTokens, parse } from './parser.js';
5
+ const ignored = new Set(['.git', '.contextlint', 'node_modules', 'dist', 'build', 'coverage', '.next', '.venv', 'vendor']);
6
+ const slash = (p) => p.split(path.sep).join('/');
7
+ export function describe(file) {
8
+ const parent = path.posix.dirname(file);
9
+ if (path.posix.basename(file) === 'AGENTS.md')
10
+ return { path: file, agents: ['codex', 'copilot'], scope: parent, activation: 'directory' };
11
+ if (path.posix.basename(file) === 'CLAUDE.md')
12
+ return { path: file, agents: ['claude'], scope: parent, activation: 'directory' };
13
+ const match = file.match(/^(?:(.*)\/)?(\.claude\/rules\/.*\.md|\.cursor\/rules\/.*\.(?:mdc|md)|\.github\/copilot-instructions\.md|\.github\/instructions\/.*\.instructions\.md)$/);
14
+ if (!match)
15
+ return null;
16
+ const kind = match[2];
17
+ return { path: file, scope: match[1] || '.', agents: [kind.startsWith('.claude') ? 'claude' : kind.startsWith('.cursor') ? 'cursor' : 'copilot'],
18
+ activation: kind.includes('/rules/') || kind.includes('/instructions/') ? 'conditional' : 'directory' };
19
+ }
20
+ export async function scan(rootInput, options) {
21
+ const root = await realpath(path.resolve(rootInput));
22
+ if (!(await stat(root)).isDirectory())
23
+ throw new Error('Scan target must be a directory');
24
+ const sources = [], rules = [];
25
+ const skipped = [];
26
+ const excludes = (options.exclude ?? []).map(p => p.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, ''));
27
+ if (excludes.some(p => !p || path.isAbsolute(p) || /^[a-z]:/i.test(p) || p.split('/').includes('..') || /[*?]/.test(p))) {
28
+ throw new Error('--exclude requires a relative literal file or directory path (no globs)');
29
+ }
30
+ let entriesVisited = 0, totalBytes = 0, halted = false;
31
+ const limited = (file, reason) => {
32
+ if (skipped.length < 1000)
33
+ skipped.push({ path: file || '.', reason });
34
+ else if (skipped.length === 1000)
35
+ skipped.push({ path: '.', reason: 'Skipped-item detail limit reached; additional paths omitted' });
36
+ };
37
+ async function walk(relative, depth = 0) {
38
+ if (halted)
39
+ return;
40
+ if (depth > 64) {
41
+ limited(relative, 'Directory depth limit reached');
42
+ return;
43
+ }
44
+ const directory = path.join(root, relative);
45
+ const entries = [];
46
+ try {
47
+ const info = await lstat(directory);
48
+ if (info.isSymbolicLink() || !isWithin(root, await realpath(directory))) {
49
+ limited(relative, 'Symbolic links are not followed');
50
+ return;
51
+ }
52
+ const handle = await opendir(directory);
53
+ for await (const entry of handle) {
54
+ if (++entriesVisited > 100000) {
55
+ limited(relative, 'Scan entry limit reached');
56
+ halted = true;
57
+ break;
58
+ }
59
+ entries.push(entry);
60
+ }
61
+ }
62
+ catch (error) {
63
+ if (!relative)
64
+ throw error;
65
+ limited(relative, `Directory could not be read (${error.code ?? 'unknown'})`);
66
+ return;
67
+ }
68
+ entries.sort((a, b) => a.name.localeCompare(b.name, 'en'));
69
+ for (const entry of entries) {
70
+ if (halted)
71
+ break;
72
+ const file = slash(path.join(relative, entry.name));
73
+ if (excludes.some(p => file === p || file.startsWith(p + '/'))) {
74
+ limited(file, 'Explicitly excluded');
75
+ continue;
76
+ }
77
+ if (entry.isSymbolicLink()) {
78
+ limited(file, 'Symbolic links are not followed');
79
+ continue;
80
+ }
81
+ if (entry.isDirectory()) {
82
+ if (!ignored.has(entry.name))
83
+ await walk(file, depth + 1);
84
+ continue;
85
+ }
86
+ const metadata = describe(file);
87
+ if (!entry.isFile() || !metadata || (options.agent && !metadata.agents.includes(options.agent)))
88
+ continue;
89
+ if (sources.length >= 1000 || totalBytes >= 8 * 1024 * 1024 || rules.length >= 10000) {
90
+ limited(file, 'Scan file, byte or rule limit reached');
91
+ halted = true;
92
+ break;
93
+ }
94
+ let content, bytes;
95
+ try {
96
+ const read = await readLocalFile(root, path.join(root, file), Math.min(1024 * 1024, 8 * 1024 * 1024 - totalBytes));
97
+ content = read.text;
98
+ bytes = read.bytes;
99
+ }
100
+ catch (error) {
101
+ const message = error.message;
102
+ limited(file, message === 'File size limit exceeded' ? 'Instruction file exceeds 1 MiB or remaining scan byte limit' : `Instruction file could not be read (${error.code ?? message})`);
103
+ continue;
104
+ }
105
+ totalBytes += bytes;
106
+ const source = { ...metadata, bytes, estimatedTokens: estimateTokens(content), ruleCount: 0 };
107
+ const extracted = parse(content, source);
108
+ const supported = extracted.filter(r => r.text.length <= 8192);
109
+ if (supported.length !== extracted.length)
110
+ limited(file, 'Instruction paragraph exceeds 8192 character limit');
111
+ const remaining = 10000 - rules.length;
112
+ if (supported.length > remaining) {
113
+ limited(file, 'Scan rule limit reached');
114
+ halted = true;
115
+ }
116
+ const accepted = supported.slice(0, remaining);
117
+ source.ruleCount = accepted.length;
118
+ if (source.activation !== 'directory')
119
+ limited(file, 'Activation conditions are not resolved; cross-file comparisons disabled');
120
+ sources.push(source);
121
+ rules.push(...accepted);
122
+ }
123
+ }
124
+ await walk('');
125
+ return { root, sources, rules, skipped };
126
+ }
127
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ export declare const agents: readonly ["claude", "codex", "cursor", "copilot"];
2
+ export type Agent = typeof agents[number];
3
+ export interface Source {
4
+ path: string;
5
+ agents: Agent[];
6
+ scope: string;
7
+ activation: 'directory' | 'conditional' | 'unknown';
8
+ estimatedTokens: number;
9
+ bytes: number;
10
+ ruleCount: number;
11
+ }
12
+ export interface Rule {
13
+ text: string;
14
+ normalized: string;
15
+ line: number;
16
+ endLine: number;
17
+ source: Source;
18
+ conditional: boolean;
19
+ context: string;
20
+ }
21
+ export interface Location {
22
+ path: string;
23
+ line: number;
24
+ endLine: number;
25
+ text: string;
26
+ }
27
+ export interface Finding {
28
+ id: 'DUPLICATE_RULE' | 'PACKAGE_MANAGER_CONFLICT' | 'POLARITY_CONFLICT' | 'BROKEN_REFERENCE';
29
+ severity: 'info' | 'warning';
30
+ confidence: 'high' | 'medium';
31
+ message: string;
32
+ reason: string;
33
+ agents: Agent[];
34
+ locations: Location[];
35
+ }
36
+ export interface Report {
37
+ schemaVersion: '1.0';
38
+ root: string;
39
+ projectName?: string;
40
+ status?: 'complete' | 'limited' | 'no-sources';
41
+ options: {
42
+ agent: Agent | null;
43
+ exclude: string[];
44
+ };
45
+ tokenEstimation: string;
46
+ sources: Source[];
47
+ findings: Finding[];
48
+ skipped: {
49
+ path: string;
50
+ reason: string;
51
+ }[];
52
+ summary: {
53
+ files: number;
54
+ rules: number;
55
+ estimatedTokens: number;
56
+ conflicts: number;
57
+ duplicates: number;
58
+ brokenReferences: number;
59
+ };
60
+ }
61
+ export interface ScanOptions {
62
+ agent?: Agent;
63
+ exclude?: string[];
64
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export const agents = ['claude', 'codex', 'cursor', 'copilot'];
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC"}
@@ -0,0 +1,68 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "ContextLint report v1.0",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schemaVersion", "root", "options", "tokenEstimation", "sources", "findings", "skipped", "summary"],
7
+ "properties": {
8
+ "schemaVersion": { "const": "1.0" },
9
+ "root": { "type": "string" },
10
+ "projectName": { "type": "string", "minLength": 1 },
11
+ "status": { "enum": ["complete", "limited", "no-sources"] },
12
+ "options": {
13
+ "type": "object", "additionalProperties": false, "required": ["agent", "exclude"],
14
+ "properties": {
15
+ "agent": { "enum": [null, "claude", "codex", "cursor", "copilot"] },
16
+ "exclude": { "type": "array", "items": { "type": "string" } }
17
+ }
18
+ },
19
+ "tokenEstimation": { "type": "string" },
20
+ "sources": { "type": "array", "items": { "$ref": "#/$defs/source" } },
21
+ "findings": { "type": "array", "items": { "$ref": "#/$defs/finding" } },
22
+ "skipped": {
23
+ "type": "array", "items": {
24
+ "type": "object", "additionalProperties": false, "required": ["path", "reason"],
25
+ "properties": { "path": { "type": "string" }, "reason": { "type": "string" } }
26
+ }
27
+ },
28
+ "summary": {
29
+ "type": "object", "additionalProperties": false,
30
+ "required": ["files", "rules", "estimatedTokens", "conflicts", "duplicates", "brokenReferences"],
31
+ "properties": {
32
+ "files": { "$ref": "#/$defs/count" }, "rules": { "$ref": "#/$defs/count" },
33
+ "estimatedTokens": { "$ref": "#/$defs/count" }, "conflicts": { "$ref": "#/$defs/count" },
34
+ "duplicates": { "$ref": "#/$defs/count" }, "brokenReferences": { "$ref": "#/$defs/count" }
35
+ }
36
+ }
37
+ },
38
+ "$defs": {
39
+ "count": { "type": "integer", "minimum": 0 },
40
+ "agents": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": ["claude", "codex", "cursor", "copilot"] } },
41
+ "source": {
42
+ "type": "object", "additionalProperties": false,
43
+ "required": ["path", "agents", "scope", "activation", "estimatedTokens", "bytes", "ruleCount"],
44
+ "properties": {
45
+ "path": { "type": "string" }, "agents": { "$ref": "#/$defs/agents" }, "scope": { "type": "string" },
46
+ "activation": { "enum": ["directory", "conditional", "unknown"] },
47
+ "estimatedTokens": { "$ref": "#/$defs/count" }, "bytes": { "$ref": "#/$defs/count" }, "ruleCount": { "$ref": "#/$defs/count" }
48
+ }
49
+ },
50
+ "location": {
51
+ "type": "object", "additionalProperties": false, "required": ["path", "line", "endLine", "text"],
52
+ "properties": {
53
+ "path": { "type": "string" }, "line": { "type": "integer", "minimum": 1 },
54
+ "endLine": { "type": "integer", "minimum": 1 }, "text": { "type": "string" }
55
+ }
56
+ },
57
+ "finding": {
58
+ "type": "object", "additionalProperties": false,
59
+ "required": ["id", "severity", "confidence", "message", "reason", "agents", "locations"],
60
+ "properties": {
61
+ "id": { "enum": ["DUPLICATE_RULE", "PACKAGE_MANAGER_CONFLICT", "POLARITY_CONFLICT", "BROKEN_REFERENCE"] },
62
+ "severity": { "enum": ["info", "warning"] }, "confidence": { "enum": ["high", "medium"] },
63
+ "message": { "type": "string" }, "reason": { "type": "string" }, "agents": { "$ref": "#/$defs/agents" },
64
+ "locations": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/location" } }
65
+ }
66
+ }
67
+ }
68
+ }