@kogu/context-check 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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/dist/analyzer/engine.d.ts +27 -0
  4. package/dist/analyzer/engine.js +64 -0
  5. package/dist/analyzer/engine.js.map +1 -0
  6. package/dist/analyzer/rules/context-overhead.d.ts +21 -0
  7. package/dist/analyzer/rules/context-overhead.js +53 -0
  8. package/dist/analyzer/rules/context-overhead.js.map +1 -0
  9. package/dist/analyzer/rules/duplicate.d.ts +15 -0
  10. package/dist/analyzer/rules/duplicate.js +53 -0
  11. package/dist/analyzer/rules/duplicate.js.map +1 -0
  12. package/dist/analyzer/rules/high-stakes.d.ts +36 -0
  13. package/dist/analyzer/rules/high-stakes.js +96 -0
  14. package/dist/analyzer/rules/high-stakes.js.map +1 -0
  15. package/dist/analyzer/rules/large-file.d.ts +15 -0
  16. package/dist/analyzer/rules/large-file.js +41 -0
  17. package/dist/analyzer/rules/large-file.js.map +1 -0
  18. package/dist/analyzer/rules/mapping.d.ts +29 -0
  19. package/dist/analyzer/rules/mapping.js +51 -0
  20. package/dist/analyzer/rules/mapping.js.map +1 -0
  21. package/dist/analyzer/rules/repetition.d.ts +20 -0
  22. package/dist/analyzer/rules/repetition.js +80 -0
  23. package/dist/analyzer/rules/repetition.js.map +1 -0
  24. package/dist/analyzer/rules/scoped-no-match.d.ts +15 -0
  25. package/dist/analyzer/rules/scoped-no-match.js +46 -0
  26. package/dist/analyzer/rules/scoped-no-match.js.map +1 -0
  27. package/dist/analyzer/run.d.ts +25 -0
  28. package/dist/analyzer/run.js +59 -0
  29. package/dist/analyzer/run.js.map +1 -0
  30. package/dist/analyzer/severity.d.ts +21 -0
  31. package/dist/analyzer/severity.js +39 -0
  32. package/dist/analyzer/severity.js.map +1 -0
  33. package/dist/analyzer/types.d.ts +35 -0
  34. package/dist/analyzer/types.js +17 -0
  35. package/dist/analyzer/types.js.map +1 -0
  36. package/dist/cli/index.d.ts +23 -0
  37. package/dist/cli/index.js +159 -0
  38. package/dist/cli/index.js.map +1 -0
  39. package/dist/config/load.d.ts +11 -0
  40. package/dist/config/load.js +38 -0
  41. package/dist/config/load.js.map +1 -0
  42. package/dist/discovery/adapters/agents.d.ts +8 -0
  43. package/dist/discovery/adapters/agents.js +25 -0
  44. package/dist/discovery/adapters/agents.js.map +1 -0
  45. package/dist/discovery/adapters/artifact.d.ts +21 -0
  46. package/dist/discovery/adapters/artifact.js +40 -0
  47. package/dist/discovery/adapters/artifact.js.map +1 -0
  48. package/dist/discovery/adapters/claude.d.ts +8 -0
  49. package/dist/discovery/adapters/claude.js +25 -0
  50. package/dist/discovery/adapters/claude.js.map +1 -0
  51. package/dist/discovery/adapters/cursor.d.ts +11 -0
  52. package/dist/discovery/adapters/cursor.js +32 -0
  53. package/dist/discovery/adapters/cursor.js.map +1 -0
  54. package/dist/discovery/adapters/index.d.ts +10 -0
  55. package/dist/discovery/adapters/index.js +18 -0
  56. package/dist/discovery/adapters/index.js.map +1 -0
  57. package/dist/discovery/adapters/skill.d.ts +9 -0
  58. package/dist/discovery/adapters/skill.js +26 -0
  59. package/dist/discovery/adapters/skill.js.map +1 -0
  60. package/dist/discovery/adapters/types.d.ts +22 -0
  61. package/dist/discovery/adapters/types.js +9 -0
  62. package/dist/discovery/adapters/types.js.map +1 -0
  63. package/dist/discovery/discover.d.ts +32 -0
  64. package/dist/discovery/discover.js +51 -0
  65. package/dist/discovery/discover.js.map +1 -0
  66. package/dist/discovery/exclusions.d.ts +19 -0
  67. package/dist/discovery/exclusions.js +40 -0
  68. package/dist/discovery/exclusions.js.map +1 -0
  69. package/dist/discovery/scanner.d.ts +31 -0
  70. package/dist/discovery/scanner.js +75 -0
  71. package/dist/discovery/scanner.js.map +1 -0
  72. package/dist/git/history.d.ts +25 -0
  73. package/dist/git/history.js +71 -0
  74. package/dist/git/history.js.map +1 -0
  75. package/dist/git/sufficiency.d.ts +38 -0
  76. package/dist/git/sufficiency.js +98 -0
  77. package/dist/git/sufficiency.js.map +1 -0
  78. package/dist/index.d.ts +8 -0
  79. package/dist/index.js +13 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/output/json.d.ts +58 -0
  82. package/dist/output/json.js +52 -0
  83. package/dist/output/json.js.map +1 -0
  84. package/dist/output/terminal.d.ts +21 -0
  85. package/dist/output/terminal.js +163 -0
  86. package/dist/output/terminal.js.map +1 -0
  87. package/dist/parser/frontmatter.d.ts +26 -0
  88. package/dist/parser/frontmatter.js +63 -0
  89. package/dist/parser/frontmatter.js.map +1 -0
  90. package/dist/parser/markdown.d.ts +23 -0
  91. package/dist/parser/markdown.js +63 -0
  92. package/dist/parser/markdown.js.map +1 -0
  93. package/dist/parser/normalize.d.ts +20 -0
  94. package/dist/parser/normalize.js +35 -0
  95. package/dist/parser/normalize.js.map +1 -0
  96. package/dist/parser/parse.d.ts +34 -0
  97. package/dist/parser/parse.js +35 -0
  98. package/dist/parser/parse.js.map +1 -0
  99. package/dist/parser/plain-text.d.ts +30 -0
  100. package/dist/parser/plain-text.js +52 -0
  101. package/dist/parser/plain-text.js.map +1 -0
  102. package/dist/parser/scope.d.ts +20 -0
  103. package/dist/parser/scope.js +44 -0
  104. package/dist/parser/scope.js.map +1 -0
  105. package/dist/snapshot/diff.d.ts +62 -0
  106. package/dist/snapshot/diff.js +146 -0
  107. package/dist/snapshot/diff.js.map +1 -0
  108. package/dist/snapshot/manager.d.ts +54 -0
  109. package/dist/snapshot/manager.js +131 -0
  110. package/dist/snapshot/manager.js.map +1 -0
  111. package/dist/tokens/estimator.d.ts +12 -0
  112. package/dist/tokens/estimator.js +16 -0
  113. package/dist/tokens/estimator.js.map +1 -0
  114. package/dist/types/configuration.d.ts +47 -0
  115. package/dist/types/configuration.js +9 -0
  116. package/dist/types/configuration.js.map +1 -0
  117. package/dist/types/finding.d.ts +32 -0
  118. package/dist/types/finding.js +14 -0
  119. package/dist/types/finding.js.map +1 -0
  120. package/dist/types/report.d.ts +20 -0
  121. package/dist/types/report.js +9 -0
  122. package/dist/types/report.js.map +1 -0
  123. package/package.json +70 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Terminal renderer (Spec §9, §28).
3
+ *
4
+ * Human-readable, non-fake-precision output (Spec §4.5). Findings are grouped
5
+ * and summarized; `verbose` expands each finding with explanation. Command
6
+ * output must never say "wasted tokens", "unused" or imply causal impact.
7
+ */
8
+ /** Wraps the normative label in brackets, e.g. INFO -> [INFO]. */
9
+ function label(finding) {
10
+ return `[${finding.label}]`;
11
+ }
12
+ /** Formats a full report for the terminal. */
13
+ export function renderReport(report, verbose = false) {
14
+ const lines = [];
15
+ lines.push("context-check");
16
+ lines.push("");
17
+ lines.push("AI CONFIGURATION REPORT");
18
+ lines.push("");
19
+ const s = report.summary;
20
+ lines.push(`Configuration files ${s.configurationFiles}`);
21
+ lines.push(`Skills ${s.skills}`);
22
+ lines.push(`Estimated context ${formatTokens(s.estimatedTokens)} tokens`);
23
+ lines.push("");
24
+ lines.push("Potential Context Overhead");
25
+ lines.push(`~${formatTokens(s.potentialContextOverhead)} tokens`);
26
+ lines.push("");
27
+ lines.push("FINDINGS");
28
+ if (report.findings.length === 0) {
29
+ lines.push("");
30
+ lines.push("No findings to report.");
31
+ }
32
+ else {
33
+ for (const finding of report.findings) {
34
+ lines.push("");
35
+ lines.push(`${label(finding)} ${finding.title}`);
36
+ if (verbose) {
37
+ lines.push(finding.description);
38
+ if (finding.recommendation)
39
+ lines.push(finding.recommendation);
40
+ }
41
+ }
42
+ }
43
+ lines.push("");
44
+ if (!verbose) {
45
+ lines.push("Run `context-check analyze --verbose` for details.");
46
+ }
47
+ return lines.join("\n");
48
+ }
49
+ /** Formats a token count with thousands separators, e.g. 14820 -> "14,820". */
50
+ export function formatTokens(tokens) {
51
+ return Math.round(tokens).toLocaleString("en-US");
52
+ }
53
+ /**
54
+ * Compact human renderer — answers "how many, how big, what needs attention"
55
+ * at a glance. Pure presentation: it renders the same `AnalysisReport` the
56
+ * terminal renderer uses, adding no analysis behavior.
57
+ */
58
+ export function renderCompactReport(report) {
59
+ const s = report.summary;
60
+ const lines = [];
61
+ lines.push("ContextCheck");
62
+ lines.push("");
63
+ lines.push(`${s.configurationFiles} configuration files`);
64
+ lines.push(`~${formatTokens(s.estimatedTokens)} estimated tokens`);
65
+ lines.push("");
66
+ lines.push(`${report.findings.length} ${plural(report.findings.length, "finding")}`);
67
+ if (report.findings.length > 0) {
68
+ lines.push("");
69
+ for (const finding of report.findings) {
70
+ lines.push(`${label(finding)} ${finding.title}`);
71
+ for (const path of finding.filePaths) {
72
+ lines.push(` ${path}`);
73
+ }
74
+ const detail = compactDetail(finding);
75
+ if (detail)
76
+ lines.push(` ${detail}`);
77
+ }
78
+ }
79
+ else {
80
+ lines.push("");
81
+ lines.push("No findings to report.");
82
+ }
83
+ lines.push("");
84
+ lines.push(`Summary: ${s.configurationFiles} files · ${formatTokens(s.estimatedTokens)} tokens · ` +
85
+ `${report.findings.length} ${plural(report.findings.length, "finding")}`);
86
+ return lines.join("\n");
87
+ }
88
+ /** Simple singula/plural helper for output. */
89
+ function plural(n, word) {
90
+ return n === 1 ? word : `${word}s`;
91
+ }
92
+ /** Short one-line detail per finding for compact output. */
93
+ function compactDetail(finding) {
94
+ switch (finding.type) {
95
+ case "large-file": {
96
+ const m = finding.description.match(/~([\d,]+) tokens/);
97
+ return m ? `~${m[1]} tokens` : "large";
98
+ }
99
+ case "scoped-no-match":
100
+ return "no matching file changes in Git history window";
101
+ case "high-stakes":
102
+ return "review manually before modifying or removing";
103
+ case "duplicate":
104
+ return "identical content across files";
105
+ case "repetition":
106
+ return "section repeated across files";
107
+ case "context-overhead":
108
+ return "potential redundant context";
109
+ default:
110
+ return "";
111
+ }
112
+ }
113
+ /** Renders a snapshot diff in the Snapshot/Diff v1 format. */
114
+ export function renderDiff(diff, beforeLabel = "previous", afterLabel = "current") {
115
+ const lines = [];
116
+ lines.push("ContextCheck Diff");
117
+ lines.push("");
118
+ lines.push(`Snapshot: ${beforeLabel} → ${afterLabel}`);
119
+ lines.push("");
120
+ lines.push("Artifacts");
121
+ const changedFiles = diff.files.filter((f) => f.status !== "unchanged");
122
+ if (changedFiles.length === 0) {
123
+ lines.push(" (no configuration file changes)");
124
+ }
125
+ else {
126
+ for (const file of changedFiles) {
127
+ const mark = file.status === "added" ? "+" : file.status === "removed" ? "-" : "~";
128
+ lines.push(` ${mark} ${file.path}`);
129
+ if (file.status === "changed") {
130
+ lines.push(` ${formatTokens(file.beforeTokens)} → ${formatTokens(file.afterTokens)} tokens`);
131
+ }
132
+ }
133
+ }
134
+ lines.push("");
135
+ lines.push("Context");
136
+ lines.push(` Previous ~${formatTokens(diff.beforeTokens)} tokens`);
137
+ lines.push(` Current ~${formatTokens(diff.afterTokens)} tokens`);
138
+ const deltaSign = diff.delta >= 0 ? "+" : "-";
139
+ lines.push(` Change ${deltaSign}${formatTokens(Math.abs(diff.delta))} tokens`);
140
+ lines.push("");
141
+ lines.push("Findings");
142
+ if (diff.findingChanges.unchanged === 0 && diff.findings.length === 0) {
143
+ lines.push(" (no findings recorded)");
144
+ }
145
+ else {
146
+ if (diff.findingChanges.changed > 0)
147
+ lines.push(` ~ ${diff.findingChanges.changed} changed`);
148
+ if (diff.findingChanges.added > 0)
149
+ lines.push(` + ${diff.findingChanges.added} new`);
150
+ if (diff.findingChanges.removed > 0)
151
+ lines.push(` - ${diff.findingChanges.removed} resolved`);
152
+ if (diff.findingChanges.unchanged > 0)
153
+ lines.push(` = ${diff.findingChanges.unchanged} unchanged`);
154
+ }
155
+ lines.push("");
156
+ const added = changedFiles.filter((f) => f.status === "added").length;
157
+ const changed = changedFiles.filter((f) => f.status === "changed").length;
158
+ const removed = changedFiles.filter((f) => f.status === "removed").length;
159
+ lines.push(`Summary`);
160
+ lines.push(` ${added} added · ${changed} modified · ${removed} removed`);
161
+ return lines.join("\n");
162
+ }
163
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/output/terminal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * YAML frontmatter extraction (Spec §17).
3
+ *
4
+ * Frontmatter is an optional `---`-delimited block at the very start of a
5
+ * file. It is stripped from the body before analysis (Rule 22) and parsed
6
+ * into an object so adapter/analyzer logic can read keys such as `globs`,
7
+ * `alwaysApply` and `description`.
8
+ *
9
+ * The parser never mutates content and tolerates malformed YAML: on a parse
10
+ * error it returns the raw body so the analyzer can still produce partial
11
+ * results (Spec §34).
12
+ */
13
+ export interface FrontmatterResult {
14
+ /** Parsed YAML object, or undefined when there is no valid frontmatter. */
15
+ data?: Record<string, unknown>;
16
+ /** Document body with the frontmatter block removed (CRLF normalized). */
17
+ body: string;
18
+ /** True when a `---` block was present but failed to parse. */
19
+ invalid?: boolean;
20
+ }
21
+ /**
22
+ * Extracts and parses the leading YAML frontmatter block, returning the data
23
+ * and the remaining body. Uses a line-based scan so empty frontmatter blocks
24
+ * and mixed EOL styles are handled consistently.
25
+ */
26
+ export declare function extractFrontmatter(content: string): FrontmatterResult;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * YAML frontmatter extraction (Spec §17).
3
+ *
4
+ * Frontmatter is an optional `---`-delimited block at the very start of a
5
+ * file. It is stripped from the body before analysis (Rule 22) and parsed
6
+ * into an object so adapter/analyzer logic can read keys such as `globs`,
7
+ * `alwaysApply` and `description`.
8
+ *
9
+ * The parser never mutates content and tolerates malformed YAML: on a parse
10
+ * error it returns the raw body so the analyzer can still produce partial
11
+ * results (Spec §34).
12
+ */
13
+ import { parse as parseYaml } from "yaml";
14
+ const DELIMITER = "---";
15
+ /**
16
+ * Extracts and parses the leading YAML frontmatter block, returning the data
17
+ * and the remaining body. Uses a line-based scan so empty frontmatter blocks
18
+ * and mixed EOL styles are handled consistently.
19
+ */
20
+ export function extractFrontmatter(content) {
21
+ const lines = content.split(/\r\n|[\r\n]/);
22
+ if (lines.length === 0)
23
+ return { body: content };
24
+ if (lines[0]?.trim() !== DELIMITER) {
25
+ return { body: content };
26
+ }
27
+ // Find the closing delimiter line. Scan from line 1 onward; the body starts
28
+ // after it. If none is found, treat frontmatter as absent (partial result).
29
+ let closeIndex = -1;
30
+ for (let i = 1; i < lines.length; i++) {
31
+ if (lines[i]?.trim() === DELIMITER) {
32
+ closeIndex = i;
33
+ break;
34
+ }
35
+ }
36
+ if (closeIndex === -1) {
37
+ return { body: content, invalid: true };
38
+ }
39
+ const rawBlock = lines.slice(1, closeIndex).join("\n").trim();
40
+ const body = lines
41
+ .slice(closeIndex + 1)
42
+ .join("\n")
43
+ .replace(/\r\n?/g, "\n");
44
+ if (rawBlock.length === 0) {
45
+ return { body, data: {} };
46
+ }
47
+ try {
48
+ const parsed = parseYaml(rawBlock);
49
+ if (parsed == null) {
50
+ return { body, data: {} };
51
+ }
52
+ return { body, data: isRecord(parsed) ? parsed : {} };
53
+ }
54
+ catch {
55
+ // Malformed YAML: keep the body so analysis can continue (Spec §34).
56
+ return { body, invalid: true };
57
+ }
58
+ }
59
+ /** Whether every key we care about should be treated as an object. */
60
+ function isRecord(value) {
61
+ return typeof value === "object" && value !== null && !Array.isArray(value);
62
+ }
63
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter.js","sourceRoot":"","sources":["../../src/parser/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAW1C,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAEjD,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACnC,UAAU,GAAG,CAAC,CAAC;YACf,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,UAAU,KAAK,CAAC,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,KAAK;SACf,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;SACrB,IAAI,CAAC,IAAI,CAAC;SACV,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE3B,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Markdown section extraction (Spec §18).
3
+ *
4
+ * Splits markdown content into sections keyed by heading. A section is
5
+ * `heading + content` accumulated from a heading down to (but excluding) the
6
+ * next heading of the same or higher level. Repetition detection compares
7
+ * heading + normalized content.
8
+ *
9
+ * Parsing never mutates content. Malformed/heading-less text still produces a
10
+ * usable single-block result so analysis continues (Spec §34).
11
+ */
12
+ export interface MarkdownSection {
13
+ /** The heading text (without the leading `#` markers). */
14
+ heading: string;
15
+ /** Heading level, e.g. 1 for `#`, 2 for `##`. */
16
+ level: number;
17
+ /** Raw section content between this heading and the next heading. */
18
+ content: string;
19
+ }
20
+ /** Extracts sections from a markdown body. */
21
+ export declare function extractMarkdownSections(body: string): MarkdownSection[];
22
+ /** Text before the first heading, if any (e.g. a preamble). */
23
+ export declare function extractPreamble(body: string): string;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Markdown section extraction (Spec §18).
3
+ *
4
+ * Splits markdown content into sections keyed by heading. A section is
5
+ * `heading + content` accumulated from a heading down to (but excluding) the
6
+ * next heading of the same or higher level. Repetition detection compares
7
+ * heading + normalized content.
8
+ *
9
+ * Parsing never mutates content. Malformed/heading-less text still produces a
10
+ * usable single-block result so analysis continues (Spec §34).
11
+ */
12
+ const HEADING_RE = /^(#{1,6})\s+(.+)$/;
13
+ /** Extracts sections from a markdown body. */
14
+ export function extractMarkdownSections(body) {
15
+ const rawLines = body.split(/\r\n|[\r\n]/);
16
+ const sections = [];
17
+ let currentHeading = null;
18
+ let currentLevel = 0;
19
+ let currentHeadingLine = -1;
20
+ for (let i = 0; i < rawLines.length; i++) {
21
+ const line = rawLines[i] ?? "";
22
+ const match = line.match(HEADING_RE);
23
+ if (match) {
24
+ // Close the previous section before opening a new one.
25
+ if (currentHeading !== null) {
26
+ sections.push({
27
+ heading: currentHeading,
28
+ level: currentLevel,
29
+ content: rawLines
30
+ .slice(currentHeadingLine + 1, i)
31
+ .join("\n")
32
+ .trim(),
33
+ });
34
+ }
35
+ currentHeading = (match[2] ?? "").trim();
36
+ currentLevel = (match[1] ?? "").length;
37
+ currentHeadingLine = i;
38
+ continue;
39
+ }
40
+ }
41
+ // Close the trailing section.
42
+ if (currentHeading !== null) {
43
+ sections.push({
44
+ heading: currentHeading,
45
+ level: currentLevel,
46
+ content: rawLines
47
+ .slice(currentHeadingLine + 1)
48
+ .join("\n")
49
+ .trim(),
50
+ });
51
+ }
52
+ return sections;
53
+ }
54
+ /** Text before the first heading, if any (e.g. a preamble). */
55
+ export function extractPreamble(body) {
56
+ const rawLines = body.split(/\r\n|[\r\n]/);
57
+ let end = 0;
58
+ while (end < rawLines.length && !rawLines[end]?.match(HEADING_RE)) {
59
+ end++;
60
+ }
61
+ return rawLines.slice(0, end).join("\n").trim();
62
+ }
63
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/parser/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAEvC,8CAA8C;AAC9C,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAsB,EAAE,CAAC;IAEvC,IAAI,cAAc,GAAkB,IAAI,CAAC;IACzC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,kBAAkB,GAAG,CAAC,CAAC,CAAC;IAE5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAErC,IAAI,KAAK,EAAE,CAAC;YACV,uDAAuD;YACvD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;gBAC5B,QAAQ,CAAC,IAAI,CAAC;oBACZ,OAAO,EAAE,cAAc;oBACvB,KAAK,EAAE,YAAY;oBACnB,OAAO,EAAE,QAAQ;yBACd,KAAK,CAAC,kBAAkB,GAAG,CAAC,EAAE,CAAC,CAAC;yBAChC,IAAI,CAAC,IAAI,CAAC;yBACV,IAAI,EAAE;iBACV,CAAC,CAAC;YACL,CAAC;YAED,cAAc,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACzC,YAAY,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACvC,kBAAkB,GAAG,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,QAAQ,CAAC,IAAI,CAAC;YACZ,OAAO,EAAE,cAAc;YACvB,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE,QAAQ;iBACd,KAAK,CAAC,kBAAkB,GAAG,CAAC,CAAC;iBAC7B,IAAI,CAAC,IAAI,CAAC;iBACV,IAAI,EAAE;SACV,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAC3C,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,OAAO,GAAG,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAClE,GAAG,EAAE,CAAC;IACR,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Body normalization shared by duplicate/repetition detection (Spec §18, §19).
3
+ *
4
+ * Comparison is deterministic and text-based. Normalization:
5
+ * - lowercase
6
+ * - line-ending normalization (CRLF -> LF)
7
+ * - whitespace collapse
8
+ * - markdown formatting normalization (trim lines, drop trailing spaces)
9
+ *
10
+ * The MVP does NOT use semantic similarity (Rule 13).
11
+ */
12
+ /** Strips YAML frontmatter and returns only the document body. */
13
+ export declare function stripFrontmatter(content: string): string;
14
+ /**
15
+ * Normalizes text for comparison: lowercase, LF line endings, collapsed
16
+ * whitespace, trailing-space-trimmed lines.
17
+ */
18
+ export declare function normalizeForComparison(text: string): string;
19
+ /** Computes a stable fingerprint (normalized body) for duplicate detection. */
20
+ export declare function normalizeBody(content: string): string;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Body normalization shared by duplicate/repetition detection (Spec §18, §19).
3
+ *
4
+ * Comparison is deterministic and text-based. Normalization:
5
+ * - lowercase
6
+ * - line-ending normalization (CRLF -> LF)
7
+ * - whitespace collapse
8
+ * - markdown formatting normalization (trim lines, drop trailing spaces)
9
+ *
10
+ * The MVP does NOT use semantic similarity (Rule 13).
11
+ */
12
+ import { extractFrontmatter } from "./frontmatter.js";
13
+ /** Strips YAML frontmatter and returns only the document body. */
14
+ export function stripFrontmatter(content) {
15
+ return extractFrontmatter(content).body;
16
+ }
17
+ /**
18
+ * Normalizes text for comparison: lowercase, LF line endings, collapsed
19
+ * whitespace, trailing-space-trimmed lines.
20
+ */
21
+ export function normalizeForComparison(text) {
22
+ return text
23
+ .replace(/\r\n?/g, "\n")
24
+ .toLowerCase()
25
+ .split("\n")
26
+ .map((line) => line.replace(/\s+/g, " ").trim())
27
+ .join("\n")
28
+ .trim();
29
+ }
30
+ /** Computes a stable fingerprint (normalized body) for duplicate detection. */
31
+ export function normalizeBody(content) {
32
+ const body = stripFrontmatter(content);
33
+ return normalizeForComparison(body);
34
+ }
35
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/parser/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,kEAAkE;AAClE,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,IAAI;SACR,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;SACvB,WAAW,EAAE;SACb,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;SAC/C,IAAI,CAAC,IAAI,CAAC;SACV,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Parsing orchestration (Spec §16, §17).
3
+ *
4
+ * Combines frontmatter, scope, markdown and plain-text parsers to produce a
5
+ * single normalized view of a configuration file's content.
6
+ *
7
+ * The parser never mutates source content. Malformed input degrades to a
8
+ * partial result so the analyzer can continue (Spec §34).
9
+ */
10
+ import type { ConfigurationScope } from "../types/configuration.js";
11
+ import { type MarkdownSection } from "./markdown.js";
12
+ import { type TextBlock } from "./plain-text.js";
13
+ export interface ParsedConfiguration {
14
+ /** Parsed frontmatter data (undefined when none/invalid). */
15
+ data?: Record<string, unknown>;
16
+ /** Document body with frontmatter removed. */
17
+ body: string;
18
+ /** Normalized scope derived from frontmatter. */
19
+ scope?: ConfigurationScope;
20
+ /** Markdown sections (empty for heading-less files). */
21
+ sections: MarkdownSection[];
22
+ /** Plain-text blocks (empty for markdown files with headings). */
23
+ blocks: TextBlock[];
24
+ /** True when frontmatter was present but malformed. */
25
+ invalidFrontmatter: boolean;
26
+ }
27
+ /**
28
+ * Builds a normalized view of content.
29
+ *
30
+ * - Files authored in markdown (instruction, skill, .mdc rules) are split into
31
+ * sections by headings; the plain-text block chunker is used as a fallback
32
+ * when there are no headings (Rule 21).
33
+ */
34
+ export declare function parseConfiguration(content: string): ParsedConfiguration;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parsing orchestration (Spec §16, §17).
3
+ *
4
+ * Combines frontmatter, scope, markdown and plain-text parsers to produce a
5
+ * single normalized view of a configuration file's content.
6
+ *
7
+ * The parser never mutates source content. Malformed input degrades to a
8
+ * partial result so the analyzer can continue (Spec §34).
9
+ */
10
+ import { extractFrontmatter } from "./frontmatter.js";
11
+ import { extractMarkdownSections } from "./markdown.js";
12
+ import { chunkPlainText } from "./plain-text.js";
13
+ import { parseScope } from "./scope.js";
14
+ /**
15
+ * Builds a normalized view of content.
16
+ *
17
+ * - Files authored in markdown (instruction, skill, .mdc rules) are split into
18
+ * sections by headings; the plain-text block chunker is used as a fallback
19
+ * when there are no headings (Rule 21).
20
+ */
21
+ export function parseConfiguration(content) {
22
+ const { data, body, invalid } = extractFrontmatter(content);
23
+ const scope = parseScope(data);
24
+ const sections = extractMarkdownSections(body);
25
+ const hasHeadings = sections.length > 0;
26
+ return {
27
+ data,
28
+ body,
29
+ scope,
30
+ sections,
31
+ blocks: hasHeadings ? [] : chunkPlainText(body),
32
+ invalidFrontmatter: invalid === true,
33
+ };
34
+ }
35
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/parser/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAwB,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAkB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAiBxC;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAE/B,MAAM,QAAQ,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAExC,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,KAAK;QACL,QAAQ;QACR,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC;QAC/C,kBAAkB,EAAE,OAAO,KAAK,IAAI;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Plain-text block chunking (Spec §18 "Plain-text fallback").
3
+ *
4
+ * For files without markdown headings (e.g. `.cursorrules`), fall back to
5
+ * paragraph/block-based chunking:
6
+ * 1. split on blank lines into paragraphs/blocks
7
+ * 2. ignore very short blocks
8
+ * 3. normalize whitespace
9
+ * 4. compare normalized blocks
10
+ *
11
+ * The MVP does NOT use semantic similarity: only exact normalized text blocks
12
+ * are considered repetition candidates (Rule 21).
13
+ */
14
+ /** Blocks shorter than this many characters are ignored as noise. */
15
+ export declare const MIN_BLOCK_CHARS = 8;
16
+ export interface TextBlock {
17
+ /** Whitespace-normalized text used for comparison. */
18
+ normalized: string;
19
+ /** The block's raw text. */
20
+ raw: string;
21
+ /** Starting line index (0-based) in the original content. */
22
+ startLine: number;
23
+ }
24
+ /** Normalizes a block: collapse whitespace and trim. */
25
+ export declare function normalizeBlock(text: string): string;
26
+ /**
27
+ * Splits plain text into blocks separated by one or more blank lines.
28
+ * Empty and very short blocks are dropped per Spec §18 step 2.
29
+ */
30
+ export declare function chunkPlainText(text: string): TextBlock[];
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Plain-text block chunking (Spec §18 "Plain-text fallback").
3
+ *
4
+ * For files without markdown headings (e.g. `.cursorrules`), fall back to
5
+ * paragraph/block-based chunking:
6
+ * 1. split on blank lines into paragraphs/blocks
7
+ * 2. ignore very short blocks
8
+ * 3. normalize whitespace
9
+ * 4. compare normalized blocks
10
+ *
11
+ * The MVP does NOT use semantic similarity: only exact normalized text blocks
12
+ * are considered repetition candidates (Rule 21).
13
+ */
14
+ /** Blocks shorter than this many characters are ignored as noise. */
15
+ export const MIN_BLOCK_CHARS = 8;
16
+ /** Normalizes a block: collapse whitespace and trim. */
17
+ export function normalizeBlock(text) {
18
+ return text.replace(/\s+/g, " ").trim();
19
+ }
20
+ /**
21
+ * Splits plain text into blocks separated by one or more blank lines.
22
+ * Empty and very short blocks are dropped per Spec §18 step 2.
23
+ */
24
+ export function chunkPlainText(text) {
25
+ const lines = text.split(/\r\n|[\r\n]/);
26
+ const blocks = [];
27
+ let buffer = [];
28
+ let bufferStart = 0;
29
+ function flush() {
30
+ if (buffer.length === 0)
31
+ return;
32
+ const raw = buffer.join("\n").trim();
33
+ const normalized = normalizeBlock(raw);
34
+ if (normalized.trim().length >= MIN_BLOCK_CHARS) {
35
+ blocks.push({ normalized, raw, startLine: bufferStart });
36
+ }
37
+ buffer = [];
38
+ }
39
+ for (let i = 0; i < lines.length; i++) {
40
+ const isBlank = (lines[i] ?? "").trim().length === 0;
41
+ if (isBlank) {
42
+ flush();
43
+ continue;
44
+ }
45
+ if (buffer.length === 0)
46
+ bufferStart = i;
47
+ buffer.push(lines[i] ?? "");
48
+ }
49
+ flush();
50
+ return blocks;
51
+ }
52
+ //# sourceMappingURL=plain-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plain-text.js","sourceRoot":"","sources":["../../src/parser/plain-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAWjC,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACxC,MAAM,MAAM,GAAgB,EAAE,CAAC;IAE/B,IAAI,MAAM,GAAa,EAAE,CAAC;IAC1B,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,SAAS,KAAK;QACZ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC,MAAM,IAAI,eAAe,EAAE,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM,GAAG,EAAE,CAAC;IACd,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC;QACrD,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,EAAE,CAAC;YACR,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,WAAW,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,EAAE,CAAC;IACR,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Scope normalization (Spec §16, §17).
3
+ *
4
+ * Reads a parsed frontmatter object and produces a `ConfigurationScope`:
5
+ * - `patterns`: from `globs` (an array of strings, or a single string)
6
+ * - `alwaysApply`: a boolean flag
7
+ *
8
+ * Unknown/malformed values are ignored rather than guessed; the analyzer
9
+ * still treats the artifact as unscoped rather than inventing precision.
10
+ */
11
+ import type { ConfigurationScope } from "../types/configuration.js";
12
+ /** Normalizes `globs` from YAML into a string array. */
13
+ export declare function parsePatterns(value: unknown): string[] | undefined;
14
+ /** Normalizes `alwaysApply` from YAML into a boolean. */
15
+ export declare function parseAlwaysApply(value: unknown): boolean | undefined;
16
+ /**
17
+ * Builds a `ConfigurationScope` from parsed frontmatter data. Returns
18
+ * undefined when no usable scope information is present.
19
+ */
20
+ export declare function parseScope(data: Record<string, unknown> | undefined): ConfigurationScope | undefined;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Scope normalization (Spec §16, §17).
3
+ *
4
+ * Reads a parsed frontmatter object and produces a `ConfigurationScope`:
5
+ * - `patterns`: from `globs` (an array of strings, or a single string)
6
+ * - `alwaysApply`: a boolean flag
7
+ *
8
+ * Unknown/malformed values are ignored rather than guessed; the analyzer
9
+ * still treats the artifact as unscoped rather than inventing precision.
10
+ */
11
+ /** Normalizes `globs` from YAML into a string array. */
12
+ export function parsePatterns(value) {
13
+ if (typeof value === "string" && value.trim().length > 0) {
14
+ return [value.trim()];
15
+ }
16
+ if (Array.isArray(value)) {
17
+ const patterns = value.filter((v) => typeof v === "string" && v.trim().length > 0);
18
+ return patterns.length > 0 ? patterns : undefined;
19
+ }
20
+ return undefined;
21
+ }
22
+ /** Normalizes `alwaysApply` from YAML into a boolean. */
23
+ export function parseAlwaysApply(value) {
24
+ return typeof value === "boolean" ? value : undefined;
25
+ }
26
+ /**
27
+ * Builds a `ConfigurationScope` from parsed frontmatter data. Returns
28
+ * undefined when no usable scope information is present.
29
+ */
30
+ export function parseScope(data) {
31
+ if (!data)
32
+ return undefined;
33
+ const patterns = parsePatterns(data["globs"]);
34
+ const alwaysApply = parseAlwaysApply(data["alwaysApply"]);
35
+ if (patterns === undefined && alwaysApply === undefined)
36
+ return undefined;
37
+ const scope = {};
38
+ if (patterns !== undefined)
39
+ scope.patterns = patterns;
40
+ if (alwaysApply !== undefined)
41
+ scope.alwaysApply = alwaysApply;
42
+ return scope;
43
+ }
44
+ //# sourceMappingURL=scope.js.map