@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,19 @@
1
+ /**
2
+ * Discovery boundaries (Spec §15).
3
+ *
4
+ * These directories are never scanned by default. The goal is to avoid
5
+ * finding third-party dependency configuration, generated output and to keep
6
+ * filesystem traversal fast.
7
+ */
8
+ /** Directories skipped during recursive discovery. */
9
+ export declare const EXCLUDED_DIRECTORIES: readonly string[];
10
+ /**
11
+ * Returns whether the given path segment is an excluded directory name.
12
+ * Comparisons are case-insensitive and match at any directory level.
13
+ */
14
+ export declare function isExcludedDirectory(segment: string): boolean;
15
+ /**
16
+ * Returns whether an absolute/relative path (posix-normalized) traverses any
17
+ * excluded directory segment. Used as a second guard for exact matches.
18
+ */
19
+ export declare function traversesExcludedDirectory(filePath: string): boolean;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Discovery boundaries (Spec §15).
3
+ *
4
+ * These directories are never scanned by default. The goal is to avoid
5
+ * finding third-party dependency configuration, generated output and to keep
6
+ * filesystem traversal fast.
7
+ */
8
+ import { posix } from "node:path";
9
+ /** Directories skipped during recursive discovery. */
10
+ export const EXCLUDED_DIRECTORIES = [
11
+ ".git",
12
+ ".contextcheck",
13
+ "node_modules",
14
+ "vendor",
15
+ "dist",
16
+ "build",
17
+ ".next",
18
+ "out",
19
+ "coverage",
20
+ ".cache",
21
+ "tmp",
22
+ "temp",
23
+ ];
24
+ /**
25
+ * Returns whether the given path segment is an excluded directory name.
26
+ * Comparisons are case-insensitive and match at any directory level.
27
+ */
28
+ export function isExcludedDirectory(segment) {
29
+ const lower = segment.toLowerCase();
30
+ return EXCLUDED_DIRECTORIES.includes(lower);
31
+ }
32
+ /**
33
+ * Returns whether an absolute/relative path (posix-normalized) traverses any
34
+ * excluded directory segment. Used as a second guard for exact matches.
35
+ */
36
+ export function traversesExcludedDirectory(filePath) {
37
+ const parts = posix.normalize(filePath).split("/");
38
+ return parts.some((part) => isExcludedDirectory(part));
39
+ }
40
+ //# sourceMappingURL=exclusions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exclusions.js","sourceRoot":"","sources":["../../src/discovery/exclusions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAElC,sDAAsD;AACtD,MAAM,CAAC,MAAM,oBAAoB,GAAsB;IACrD,MAAM;IACN,eAAe;IACf,cAAc;IACd,QAAQ;IACR,MAAM;IACN,OAAO;IACP,OAAO;IACP,KAAK;IACL,UAAU;IACV,QAAQ;IACR,KAAK;IACL,MAAM;CACP,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACpC,OAAO,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Recursive discovery scanner (Spec §14, §15).
3
+ *
4
+ * Walks the repository root, skipping excluded directories at every level,
5
+ * and yields the file paths that should be handed to adapters for parse.
6
+ * This module only discovers — it never decides what a file *is*; that is the
7
+ * adapter's job (Rule 14).
8
+ */
9
+ export interface DiscoveredFile {
10
+ /** Absolute path on disk. */
11
+ absolutePath: string;
12
+ /** Path relative to the scanned root (posix-normalized). */
13
+ relativePath: string;
14
+ }
15
+ /**
16
+ * Recursively walks `rootPath` and returns every file path, skipping the
17
+ * excluded directories defined in Spec §15.
18
+ */
19
+ export declare function walkFiles(rootPath: string): Promise<DiscoveredFile[]>;
20
+ /** Reads a file's content as UTF-8. */
21
+ export declare function readTextFile(absolutePath: string): Promise<string>;
22
+ /**
23
+ * Counts lines in `content`. A trailing line terminator is treated as a line
24
+ * boundary rather than an extra empty line, so `"a\nb\n"` is 2 lines.
25
+ */
26
+ export declare function countLines(content: string): number;
27
+ /** Returns basic file metadata (size + line count). */
28
+ export declare function describeFile(absolutePath: string): Promise<{
29
+ sizeBytes: number;
30
+ lineCount: number;
31
+ }>;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Recursive discovery scanner (Spec §14, §15).
3
+ *
4
+ * Walks the repository root, skipping excluded directories at every level,
5
+ * and yields the file paths that should be handed to adapters for parse.
6
+ * This module only discovers — it never decides what a file *is*; that is the
7
+ * adapter's job (Rule 14).
8
+ */
9
+ import { readdir, readFile, stat } from "node:fs/promises";
10
+ import { join, relative, sep } from "node:path";
11
+ import { isExcludedDirectory } from "./exclusions.js";
12
+ /**
13
+ * Recursively walks `rootPath` and returns every file path, skipping the
14
+ * excluded directories defined in Spec §15.
15
+ */
16
+ export async function walkFiles(rootPath) {
17
+ const results = [];
18
+ async function walk(dir) {
19
+ // readdir withFileTypes avoids a stat call per entry and lets us skip
20
+ // directories cheaply. ENOENT is tolerated to avoid races on deletion.
21
+ let entries;
22
+ try {
23
+ entries = await readdir(dir, { withFileTypes: true });
24
+ }
25
+ catch (err) {
26
+ if (err.code === "ENOENT")
27
+ return;
28
+ throw err;
29
+ }
30
+ for (const entry of entries) {
31
+ const fullPath = join(dir, entry.name);
32
+ if (entry.isDirectory()) {
33
+ if (isExcludedDirectory(entry.name))
34
+ continue;
35
+ await walk(fullPath);
36
+ continue;
37
+ }
38
+ if (entry.isFile()) {
39
+ results.push({
40
+ absolutePath: fullPath,
41
+ relativePath: normalizeRelative(rootPath, fullPath),
42
+ });
43
+ }
44
+ }
45
+ }
46
+ await walk(rootPath);
47
+ return results;
48
+ }
49
+ /** Normalizes a path to be relative to root with forward slashes. */
50
+ function normalizeRelative(rootPath, filePath) {
51
+ const rel = relative(rootPath, filePath);
52
+ return rel.split(sep).join("/");
53
+ }
54
+ /** Reads a file's content as UTF-8. */
55
+ export async function readTextFile(absolutePath) {
56
+ return readFile(absolutePath, "utf8");
57
+ }
58
+ /**
59
+ * Counts lines in `content`. A trailing line terminator is treated as a line
60
+ * boundary rather than an extra empty line, so `"a\nb\n"` is 2 lines.
61
+ */
62
+ export function countLines(content) {
63
+ if (content.length === 0)
64
+ return 0;
65
+ const endsWithBreak = /\r\n|\r|\n$/.test(content);
66
+ const raw = content.split(/\r\n|\r|\n/).length;
67
+ return endsWithBreak ? raw - 1 : raw;
68
+ }
69
+ /** Returns basic file metadata (size + line count). */
70
+ export async function describeFile(absolutePath) {
71
+ const s = await stat(absolutePath);
72
+ const content = await readTextFile(absolutePath);
73
+ return { sizeBytes: s.size, lineCount: countLines(content) };
74
+ }
75
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/discovery/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAStD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB;IAC9C,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,UAAU,IAAI,CAAC,GAAW;QAC7B,sEAAsE;QACtE,uEAAuE;QACvE,IAAI,OAAmC,CAAC;QACxC,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO;YAC7D,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAEvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,IAAI,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC9C,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACnB,OAAO,CAAC,IAAI,CAAC;oBACX,YAAY,EAAE,QAAQ;oBACtB,YAAY,EAAE,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC;iBACpD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,qEAAqE;AACrE,SAAS,iBAAiB,CAAC,QAAgB,EAAE,QAAgB;IAC3D,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACzC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,uCAAuC;AACvC,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,YAAoB;IACrD,OAAO,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACnC,MAAM,aAAa,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC;IAC/C,OAAO,aAAa,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACvC,CAAC;AAED,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,YAAoB;IAIrD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;IACjD,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Changed files over a Git window (Spec §23, §24, §31).
3
+ *
4
+ * Used to check whether a scoped rule's glob patterns ever matched a file
5
+ * change. Only metadata is read — never configuration content — keeping the
6
+ * "AI configuration-specific view over Git" role (Spec §31).
7
+ */
8
+ import { type GitRunner } from "./sufficiency.js";
9
+ /**
10
+ * Returns the set of unique file paths changed within `winDays`, relative to
11
+ * the repo root (posix-normalized). Throws if git fails; callers gate this on
12
+ * history sufficiency first.
13
+ */
14
+ export declare function changedFilesInWindow(rootPath: string, windowDays: number, run?: GitRunner): Promise<string[]>;
15
+ /**
16
+ * Returns whether any changed file path matches a glob pattern relative to the
17
+ * repo root. Used by scoped-rule matching (Spec §23).
18
+ */
19
+ export declare function matchesChangedFile(pattern: string, changedPaths: string[]): boolean;
20
+ /**
21
+ * Minimal glob matcher for the MVP: supports `**` (multi-segment), `*`
22
+ * (within a segment) and exact/prefix matches. Not a full minimatch — enough
23
+ * for deterministic scoped-rule matching.
24
+ */
25
+ export declare function globMatch(pattern: string, path: string): boolean;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Changed files over a Git window (Spec §23, §24, §31).
3
+ *
4
+ * Used to check whether a scoped rule's glob patterns ever matched a file
5
+ * change. Only metadata is read — never configuration content — keeping the
6
+ * "AI configuration-specific view over Git" role (Spec §31).
7
+ */
8
+ import { runGit } from "./sufficiency.js";
9
+ /**
10
+ * Returns the set of unique file paths changed within `winDays`, relative to
11
+ * the repo root (posix-normalized). Throws if git fails; callers gate this on
12
+ * history sufficiency first.
13
+ */
14
+ export async function changedFilesInWindow(rootPath, windowDays, run = runGit) {
15
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1000).toISOString();
16
+ const output = await run(rootPath, [
17
+ "log",
18
+ `--since=${since}`,
19
+ "--name-only",
20
+ "--pretty=format:",
21
+ "HEAD",
22
+ ]);
23
+ const names = output
24
+ .split("\n")
25
+ .map((line) => line.trim())
26
+ .filter(Boolean);
27
+ return [...new Set(names)];
28
+ }
29
+ /**
30
+ * Returns whether any changed file path matches a glob pattern relative to the
31
+ * repo root. Used by scoped-rule matching (Spec §23).
32
+ */
33
+ export function matchesChangedFile(pattern, changedPaths) {
34
+ return changedPaths.some((p) => globMatch(pattern, p));
35
+ }
36
+ /**
37
+ * Minimal glob matcher for the MVP: supports `**` (multi-segment), `*`
38
+ * (within a segment) and exact/prefix matches. Not a full minimatch — enough
39
+ * for deterministic scoped-rule matching.
40
+ */
41
+ export function globMatch(pattern, path) {
42
+ const p = pattern.replace(/^\.\//, "").toLowerCase();
43
+ const t = path.toLowerCase();
44
+ if (p === t)
45
+ return true;
46
+ // Fast path: directory prefix pattern.
47
+ if (!containsGlob(p)) {
48
+ return t.startsWith(p.endsWith("/") ? p : `${p}/`);
49
+ }
50
+ const regex = globToRegExp(p);
51
+ return regex.test(t);
52
+ }
53
+ function containsGlob(s) {
54
+ return s.includes("*") || s.includes("?") || s.includes("[");
55
+ }
56
+ /** Placeholder protecting a segment before the `*`/`.` replacements run. */
57
+ const SEGMENT = "\u0000";
58
+ function globToRegExp(glob) {
59
+ // Protect `**/` first so it cannot be rewritten by later `*`/`.` passes.
60
+ let out = glob.replace(/\*\*\//g, SEGMENT);
61
+ // Escape regex literals, leaving `*` and `?` (and SEGMENT) intact.
62
+ out = out.replace(/[.+^${}()|[\]]/g, "\\$&");
63
+ // Multi-segment `**` and single-segment `*`.
64
+ out = out.replace(/\*\*/g, ".*");
65
+ out = out.replace(/\*/g, "[^/]*");
66
+ out = out.replace(/\?/g, "[^/]");
67
+ // Restore `**/` as an optional zero-or-more path prefix.
68
+ out = out.replace(new RegExp(SEGMENT, "g"), "(?:.*/)?");
69
+ return new RegExp(`^${out}$`);
70
+ }
71
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/git/history.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAkB,MAAM,kBAAkB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAgB,EAChB,UAAkB,EAClB,MAAiB,MAAM;IAEvB,MAAM,KAAK,GAAG,IAAI,IAAI,CACpB,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAC9C,CAAC,WAAW,EAAE,CAAC;IAChB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,EAAE;QACjC,KAAK;QACL,WAAW,KAAK,EAAE;QAClB,aAAa;QACb,kBAAkB;QAClB,MAAM;KACP,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;IAEnB,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,YAAsB;IAEtB,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACrD,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAE7B,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzB,uCAAuC;IACvC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC/D,CAAC;AAED,4EAA4E;AAC5E,MAAM,OAAO,GAAG,QAAQ,CAAC;AAEzB,SAAS,YAAY,CAAC,IAAY;IAChC,yEAAyE;IACzE,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC3C,mEAAmE;IACnE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IAC7C,6CAA6C;IAC7C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACjC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAClC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjC,yDAAyD;IACzD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;IACxD,OAAO,IAAI,MAAM,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Git history sufficiency (Spec §24, Rule 18, Rule 26).
3
+ *
4
+ * Scoped-rule activity analysis MUST be skipped when Git history is
5
+ * insufficient. If we only have a short sample, a "no match" is not evidence
6
+ * that a rule is unused — so we never emit `scoped-no-match`.
7
+ *
8
+ * Insufficient when ANY of:
9
+ * 1. No Git repository present.
10
+ * 2. Repository is a shallow clone (`.git/shallow` exists).
11
+ * 3. There are no commits within the analysis window.
12
+ * 4. Total history is shorter than the window.
13
+ * 5. History cannot be analyzed (e.g. git unavailable).
14
+ */
15
+ export interface GitSufficiencyResult {
16
+ isGitRepo: boolean;
17
+ isShallow: boolean;
18
+ /** Commits within the window. */
19
+ windowCommitCount: number;
20
+ /** Total commits across all history. */
21
+ totalCommitCount: number;
22
+ sufficient: boolean;
23
+ }
24
+ /** Checks for a shallow clone using the presence of `.git/shallow` (Rule 26). */
25
+ export declare function isShallowRepository(rootPath: string): boolean;
26
+ /**
27
+ * Runs a git command, returning stdout. Throws if git fails; callers decide
28
+ * how to degrade.
29
+ */
30
+ export declare function runGit(rootPath: string, args: string[]): Promise<string>;
31
+ /** Optional injected runner to keep tests hermetic. */
32
+ export type GitRunner = (rootPath: string, args: string[]) => Promise<string>;
33
+ /**
34
+ * Determines whether there is enough git history to reason about scoped-rule
35
+ * activity within `windowDays`. Never throws; degrades to `sufficient:false`
36
+ * on any error (Spec §24 case 4).
37
+ */
38
+ export declare function assessGitHistory(rootPath: string, windowDays: number, run?: GitRunner): Promise<GitSufficiencyResult>;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Git history sufficiency (Spec §24, Rule 18, Rule 26).
3
+ *
4
+ * Scoped-rule activity analysis MUST be skipped when Git history is
5
+ * insufficient. If we only have a short sample, a "no match" is not evidence
6
+ * that a rule is unused — so we never emit `scoped-no-match`.
7
+ *
8
+ * Insufficient when ANY of:
9
+ * 1. No Git repository present.
10
+ * 2. Repository is a shallow clone (`.git/shallow` exists).
11
+ * 3. There are no commits within the analysis window.
12
+ * 4. Total history is shorter than the window.
13
+ * 5. History cannot be analyzed (e.g. git unavailable).
14
+ */
15
+ import { existsSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { exec } from "node:child_process";
18
+ import { promisify } from "node:util";
19
+ const execAsync = promisify(exec);
20
+ /** Checks for a shallow clone using the presence of `.git/shallow` (Rule 26). */
21
+ export function isShallowRepository(rootPath) {
22
+ return existsSync(join(rootPath, ".git", "shallow"));
23
+ }
24
+ /**
25
+ * Runs a git command, returning stdout. Throws if git fails; callers decide
26
+ * how to degrade.
27
+ */
28
+ export async function runGit(rootPath, args) {
29
+ const { stdout } = await execAsync(`git ${args.join(" ")}`, {
30
+ cwd: rootPath,
31
+ maxBuffer: 8 * 1024 * 1024,
32
+ });
33
+ return stdout.trim();
34
+ }
35
+ /**
36
+ * Determines whether there is enough git history to reason about scoped-rule
37
+ * activity within `windowDays`. Never throws; degrades to `sufficient:false`
38
+ * on any error (Spec §24 case 4).
39
+ */
40
+ export async function assessGitHistory(rootPath, windowDays, run = runGit) {
41
+ const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1000).toISOString();
42
+ const fallback = (partial) => ({
43
+ isGitRepo: false,
44
+ isShallow: false,
45
+ windowCommitCount: 0,
46
+ totalCommitCount: 0,
47
+ sufficient: false,
48
+ ...partial,
49
+ });
50
+ // 1. Repository present?
51
+ let isRepo = false;
52
+ try {
53
+ const head = await run(rootPath, ["rev-parse", "--is-inside-work-tree"]);
54
+ isRepo = head === "true";
55
+ }
56
+ catch {
57
+ return fallback({ isGitRepo: false });
58
+ }
59
+ if (!isRepo) {
60
+ return fallback({ isGitRepo: false });
61
+ }
62
+ // 2. Shallow clone?
63
+ const isShallow = isShallowRepository(rootPath);
64
+ if (isShallow) {
65
+ return fallback({ isGitRepo: true, isShallow: true });
66
+ }
67
+ // 3 & 4. Commit counts.
68
+ try {
69
+ const total = parseInt((await run(rootPath, ["rev-list", "--count", "HEAD"])) || "0", 10) || 0;
70
+ const window = parseInt((await run(rootPath, [
71
+ "rev-list",
72
+ "--count",
73
+ `--since=${since}`,
74
+ "HEAD",
75
+ ])) || "0", 10) || 0;
76
+ // If there is any history but none within the window, or the window count
77
+ // equals total count (history shorter than the window), treat as
78
+ // insufficient — absence of recent activity is not evidence.
79
+ if (total === 0 || window === 0 || window >= total) {
80
+ return fallback({
81
+ isGitRepo: true,
82
+ totalCommitCount: total,
83
+ windowCommitCount: window,
84
+ });
85
+ }
86
+ return {
87
+ isGitRepo: true,
88
+ isShallow: false,
89
+ windowCommitCount: window,
90
+ totalCommitCount: total,
91
+ sufficient: true,
92
+ };
93
+ }
94
+ catch {
95
+ return fallback({ isGitRepo: true });
96
+ }
97
+ }
98
+ //# sourceMappingURL=sufficiency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sufficiency.js","sourceRoot":"","sources":["../../src/git/sufficiency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * contextcheck entry point (temporary scaffold).
3
+ *
4
+ * Phase 1 - Sprint 1: ensures the TypeScript build pipeline works.
5
+ * The real CLI wiring (commander) lands in Sprint 2.
6
+ */
7
+ export declare const version = "0.1.0";
8
+ export declare function main(): void;
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * contextcheck entry point (temporary scaffold).
3
+ *
4
+ * Phase 1 - Sprint 1: ensures the TypeScript build pipeline works.
5
+ * The real CLI wiring (commander) lands in Sprint 2.
6
+ */
7
+ export const version = "0.1.0";
8
+ export function main() {
9
+ // Placeholder; replaced by the CLI dispatcher in Sprint 2.
10
+ // eslint-disable-next-line no-console
11
+ console.log("contextcheck scaffold ok");
12
+ }
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,MAAM,UAAU,IAAI;IAClB,2DAA2D;IAC3D,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Privacy-safe JSON output (Spec §32, §33; Rule 23, Rule 25).
3
+ *
4
+ * The public JSON model is a separate DTO from the internal
5
+ * `ConfigurationArtifact`. The serializer NEVER emits raw `content`. This
6
+ * guarantee is enforced structurally — the DTO has no `content` field to
7
+ * serialize, so a future CI command cannot accidentally leak configuration
8
+ * into logs (Spec §53).
9
+ */
10
+ import type { ConfigurationArtifact, ConfigurationScope } from "../types/configuration.js";
11
+ import type { Finding } from "../types/finding.js";
12
+ import type { AnalysisReport } from "../types/report.js";
13
+ import type { FileDiff, FindingChange, FindingChangesSummary, SnapshotDiff } from "../snapshot/diff.js";
14
+ import type { Snapshot } from "../snapshot/manager.js";
15
+ /** Privacy-safe artifact DTO (Spec §33). */
16
+ export interface ArtifactJson {
17
+ id: string;
18
+ type: ConfigurationArtifact["type"];
19
+ path: string;
20
+ scope?: ConfigurationScope;
21
+ metadata: ConfigurationArtifact["metadata"];
22
+ }
23
+ export interface AnalysisSummaryJson {
24
+ configurationFiles: number;
25
+ skills: number;
26
+ estimatedTokens: number;
27
+ potentialContextOverhead: number;
28
+ }
29
+ export interface ReportJson {
30
+ summary: AnalysisSummaryJson;
31
+ artifacts: ArtifactJson[];
32
+ findings: Finding[];
33
+ }
34
+ /** Converts an internal artifact into its privacy-safe DTO. */
35
+ export declare function toArtifactJson(artifact: ConfigurationArtifact): ArtifactJson;
36
+ /** Converts a full report into a privacy-safe JSON shape. */
37
+ export declare function toReportJson(report: AnalysisReport): ReportJson;
38
+ /** Serializes a report as formatted, privacy-safe JSON. */
39
+ export declare function serializeReport(report: AnalysisReport): string;
40
+ /** Privacy-safe snapshot diff DTO for CI consumption. */
41
+ export interface DiffJson {
42
+ before: {
43
+ id: string | null;
44
+ createdAt: string | null;
45
+ tokens: number;
46
+ files: number;
47
+ };
48
+ after: {
49
+ tokens: number;
50
+ files: number;
51
+ };
52
+ deltaTokens: number;
53
+ files: FileDiff[];
54
+ findingChanges: FindingChangesSummary;
55
+ findings: FindingChange[];
56
+ }
57
+ /** Serializes a snapshot diff as privacy-safe JSON (no raw content). */
58
+ export declare function serializeDiff(diff: SnapshotDiff, before?: Snapshot | null): string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Privacy-safe JSON output (Spec §32, §33; Rule 23, Rule 25).
3
+ *
4
+ * The public JSON model is a separate DTO from the internal
5
+ * `ConfigurationArtifact`. The serializer NEVER emits raw `content`. This
6
+ * guarantee is enforced structurally — the DTO has no `content` field to
7
+ * serialize, so a future CI command cannot accidentally leak configuration
8
+ * into logs (Spec §53).
9
+ */
10
+ /** Converts an internal artifact into its privacy-safe DTO. */
11
+ export function toArtifactJson(artifact) {
12
+ return {
13
+ id: artifact.id,
14
+ type: artifact.type,
15
+ path: artifact.path,
16
+ scope: artifact.scope,
17
+ metadata: artifact.metadata,
18
+ };
19
+ }
20
+ /** Converts a full report into a privacy-safe JSON shape. */
21
+ export function toReportJson(report) {
22
+ return {
23
+ summary: report.summary,
24
+ artifacts: report.artifacts.map(toArtifactJson),
25
+ findings: report.findings,
26
+ };
27
+ }
28
+ /** Serializes a report as formatted, privacy-safe JSON. */
29
+ export function serializeReport(report) {
30
+ return JSON.stringify(toReportJson(report), null, 2);
31
+ }
32
+ /** Serializes a snapshot diff as privacy-safe JSON (no raw content). */
33
+ export function serializeDiff(diff, before) {
34
+ const dto = {
35
+ before: {
36
+ id: before?.id ?? null,
37
+ createdAt: before?.createdAt ?? null,
38
+ tokens: diff.beforeTokens,
39
+ files: before?.artifacts.length ?? 0,
40
+ },
41
+ after: {
42
+ tokens: diff.afterTokens,
43
+ files: diff.files.filter((f) => f.status !== "removed").length,
44
+ },
45
+ deltaTokens: diff.delta,
46
+ files: diff.files,
47
+ findingChanges: diff.findingChanges,
48
+ findings: diff.findings,
49
+ };
50
+ return JSON.stringify(dto, null, 2);
51
+ }
52
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/output/json.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsCH,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,QAA+B;IAC5D,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;KAC5B,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,MAAsB;IACjD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;AACJ,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,MAAsB;IACpD,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACvD,CAAC;AAoBD,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAC3B,IAAkB,EAClB,MAAwB;IAExB,MAAM,GAAG,GAAa;QACpB,MAAM,EAAE;YACN,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI;YACtB,SAAS,EAAE,MAAM,EAAE,SAAS,IAAI,IAAI;YACpC,MAAM,EAAE,IAAI,CAAC,YAAY;YACzB,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,CAAC;SACrC;QACD,KAAK,EAAE;YACL,MAAM,EAAE,IAAI,CAAC,WAAW;YACxB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;SAC/D;QACD,WAAW,EAAE,IAAI,CAAC,KAAK;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,cAAc,EAAE,IAAI,CAAC,cAAc;QACnC,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,21 @@
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
+ import type { AnalysisReport } from "../types/report.js";
9
+ import type { SnapshotDiff } from "../snapshot/diff.js";
10
+ /** Formats a full report for the terminal. */
11
+ export declare function renderReport(report: AnalysisReport, verbose?: boolean): string;
12
+ /** Formats a token count with thousands separators, e.g. 14820 -> "14,820". */
13
+ export declare function formatTokens(tokens: number): string;
14
+ /**
15
+ * Compact human renderer — answers "how many, how big, what needs attention"
16
+ * at a glance. Pure presentation: it renders the same `AnalysisReport` the
17
+ * terminal renderer uses, adding no analysis behavior.
18
+ */
19
+ export declare function renderCompactReport(report: AnalysisReport): string;
20
+ /** Renders a snapshot diff in the Snapshot/Diff v1 format. */
21
+ export declare function renderDiff(diff: SnapshotDiff, beforeLabel?: string, afterLabel?: string): string;