@johnfaleke/fathom 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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +285 -0
  3. package/dist/ai/provider.d.ts +20 -0
  4. package/dist/ai/provider.d.ts.map +1 -0
  5. package/dist/ai/provider.js +2 -0
  6. package/dist/ai/provider.js.map +1 -0
  7. package/dist/checks/dependencies.d.ts +3 -0
  8. package/dist/checks/dependencies.d.ts.map +1 -0
  9. package/dist/checks/dependencies.js +106 -0
  10. package/dist/checks/dependencies.js.map +1 -0
  11. package/dist/checks/env-vars.d.ts +3 -0
  12. package/dist/checks/env-vars.d.ts.map +1 -0
  13. package/dist/checks/env-vars.js +76 -0
  14. package/dist/checks/env-vars.js.map +1 -0
  15. package/dist/checks/index.d.ts +4 -0
  16. package/dist/checks/index.d.ts.map +1 -0
  17. package/dist/checks/index.js +5 -0
  18. package/dist/checks/index.js.map +1 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +123 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/commands/check.d.ts +4 -0
  24. package/dist/commands/check.d.ts.map +1 -0
  25. package/dist/commands/check.js +32 -0
  26. package/dist/commands/check.js.map +1 -0
  27. package/dist/commands/diff.d.ts +4 -0
  28. package/dist/commands/diff.d.ts.map +1 -0
  29. package/dist/commands/diff.js +30 -0
  30. package/dist/commands/diff.js.map +1 -0
  31. package/dist/commands/init.d.ts +2 -0
  32. package/dist/commands/init.d.ts.map +1 -0
  33. package/dist/commands/init.js +16 -0
  34. package/dist/commands/init.js.map +1 -0
  35. package/dist/commands/status.d.ts +7 -0
  36. package/dist/commands/status.d.ts.map +1 -0
  37. package/dist/commands/status.js +74 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/core/git.d.ts +4 -0
  40. package/dist/core/git.d.ts.map +1 -0
  41. package/dist/core/git.js +40 -0
  42. package/dist/core/git.js.map +1 -0
  43. package/dist/core/project.d.ts +3 -0
  44. package/dist/core/project.d.ts.map +1 -0
  45. package/dist/core/project.js +59 -0
  46. package/dist/core/project.js.map +1 -0
  47. package/dist/core/runner.d.ts +3 -0
  48. package/dist/core/runner.d.ts.map +1 -0
  49. package/dist/core/runner.js +8 -0
  50. package/dist/core/runner.js.map +1 -0
  51. package/dist/core/storage.d.ts +17 -0
  52. package/dist/core/storage.d.ts.map +1 -0
  53. package/dist/core/storage.js +84 -0
  54. package/dist/core/storage.js.map +1 -0
  55. package/dist/index.d.ts +7 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +5 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/render/terminal.d.ts +5 -0
  60. package/dist/render/terminal.d.ts.map +1 -0
  61. package/dist/render/terminal.js +120 -0
  62. package/dist/render/terminal.js.map +1 -0
  63. package/dist/types.d.ts +61 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +3 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +50 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John Faleke
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # Fathom
2
+
3
+ > AI can generate the work. Fathom helps you understand the work.
4
+
5
+ Fathom is a local-first CLI for project Work State. It gives developers a deterministic, evidence-backed view of what is happening in a repo: what is in progress, what has been completed, what changed, and what may be silently broken.
6
+
7
+ ## Why Fathom exists
8
+
9
+ Most project tooling answers either:
10
+
11
+ - which files changed, or
12
+ - what an AI thinks is happening.
13
+
14
+ Fathom does neither blindly. It focuses on observable evidence and clearly labelled findings:
15
+
16
+ - missing environment variables
17
+ - undeclared dependencies
18
+ - stale project state
19
+ - changed files grouped by project area
20
+ - a machine-readable snapshot of project health
21
+
22
+ ## Quick start
23
+
24
+ Install Fathom globally:
25
+
26
+ ```bash
27
+ npm install --global @johnfaleke/fathom
28
+ ```
29
+
30
+ Then run it from any project workspace:
31
+
32
+ ```bash
33
+ cd /path/to/your-project
34
+ fathom init
35
+ fathom check
36
+ fathom status
37
+ ```
38
+
39
+ The package installs the `fathom` command. You do not need to clone this
40
+ repository into the project being inspected.
41
+
42
+ For repository development, install dependencies locally instead:
43
+
44
+ ```bash
45
+ npm install
46
+ ```
47
+
48
+ Initialize a project:
49
+
50
+ ```bash
51
+ npm run fathom -- init
52
+ ```
53
+
54
+ Set your current work and task state:
55
+
56
+ ```bash
57
+ npm run fathom -- set --current "Ship OAuth integration" --complete "Login API, API docs" --incomplete "Webhook retry handling, Missing README"
58
+ ```
59
+
60
+ Check the project for inconsistencies:
61
+
62
+ ```bash
63
+ npm run fathom -- check
64
+ npm run fathom -- check --json
65
+ ```
66
+
67
+ Review project state:
68
+
69
+ ```bash
70
+ npm run fathom -- status
71
+ npm run fathom -- status --json
72
+ ```
73
+
74
+ See what meaningfully changed:
75
+
76
+ ```bash
77
+ npm run fathom -- diff
78
+ npm run fathom -- diff --json
79
+ ```
80
+
81
+ After building:
82
+
83
+ ```bash
84
+ npm run build
85
+ node dist/cli.js init
86
+ node dist/cli.js status
87
+ ```
88
+
89
+ ## Use Fathom in another project
90
+
91
+ Fathom operates on the current working directory. Build it once, then run the
92
+ CLI while your other project is the working directory.
93
+
94
+ From PowerShell on Windows:
95
+
96
+ ```powershell
97
+ Set-Location C:\path\to\Fathom
98
+ npm run build
99
+ Set-Location C:\path\to\your-project
100
+ node C:\path\to\Fathom\dist\cli.js init
101
+ node C:\path\to\Fathom\dist\cli.js check
102
+ node C:\path\to\Fathom\dist\cli.js status
103
+ ```
104
+
105
+ From macOS or Linux:
106
+
107
+ ```bash
108
+ cd /path/to/Fathom
109
+ npm run build
110
+ cd /path/to/your-project
111
+ node /path/to/Fathom/dist/cli.js init
112
+ node /path/to/Fathom/dist/cli.js check
113
+ node /path/to/Fathom/dist/cli.js status
114
+ ```
115
+
116
+ Initialization creates `.fathom/` and `.env.example` in the target project.
117
+ The generated `.fathom/` directory is local project state; commit it only if
118
+ you want to share that state with collaborators. Use `--json` for automation:
119
+
120
+ ```bash
121
+ node /path/to/Fathom/dist/cli.js check --json
122
+ node /path/to/Fathom/dist/cli.js status --json
123
+ node /path/to/Fathom/dist/cli.js diff --json
124
+ ```
125
+
126
+ To use the `fathom` command directly during local development, run `npm link`
127
+ once from the Fathom directory. Then open another project and run:
128
+
129
+ ```bash
130
+ fathom init
131
+ fathom check
132
+ fathom status
133
+ ```
134
+
135
+ The direct command works because Fathom always inspects the directory where
136
+ the command is run, not the directory where Fathom itself is installed.
137
+
138
+ ## Open-source project site
139
+
140
+ The static project site lives in [`site/`](./site/index.html). Open
141
+ `site/index.html` directly in a browser, or serve the repository with any static
142
+ file server while developing. It includes the product overview, installation
143
+ guide, command reference, check interface, Work State schema, contribution links,
144
+ and support link.
145
+
146
+ The machine-readable [`llms.txt`](./llms.txt) file is a concise text reference for
147
+ AI tools, search systems, and integrations that need to understand Fathom without
148
+ parsing the full README or landing page.
149
+
150
+ ## Package releases
151
+
152
+ The published package name is `@johnfaleke/fathom`; the installed command remains
153
+ `fathom`. Releases are published automatically by GitHub Actions when a maintainer
154
+ pushes a semantic version tag:
155
+
156
+ ```bash
157
+ git tag v0.1.0
158
+ git push origin v0.1.0
159
+ ```
160
+
161
+ Before the first release, the npm package owner must configure GitHub Actions as a
162
+ trusted publisher for `@johnfaleke/fathom` on npm. Future v0.2 and later tags will
163
+ then build, test, and publish automatically. A version tag does not happen by
164
+ itself; it is the deliberate release switch.
165
+
166
+ ## Commands
167
+
168
+ | Command | Purpose |
169
+ |---------|---------|
170
+ | `fathom init` | Create `.fathom/` for state, config, and event history |
171
+ | `fathom set --current ...` | Update current work and task lists |
172
+ | `fathom status` | Show the current project state |
173
+ | `fathom diff` | Show semantic file changes and project impact |
174
+ | `fathom check` | Detect inconsistent or forgotten wiring |
175
+
176
+ Add `--json` to any of the reporting commands for machine-readable output.
177
+
178
+ ## Example status output
179
+
180
+ ```text
181
+ FATHOM STATUS
182
+
183
+ Current work: Ship OAuth integration
184
+ Progress: 1 complete, 1 remaining
185
+
186
+ Completed:
187
+ ✓ Login API
188
+
189
+ Remaining:
190
+ • Webhook retry handling
191
+
192
+ Findings:
193
+ ⚠ left-pad is imported but not declared in package.json
194
+
195
+ Changes:
196
+ 1 file(s) changed
197
+ ~ src/app.ts
198
+
199
+ Attention:
200
+ 1 thing(s) are worth reviewing.
201
+ ```
202
+
203
+ ## Example JSON output
204
+
205
+ ```json
206
+ {
207
+ "version": 1,
208
+ "updatedAt": "2026-09-20T00:00:00.000Z",
209
+ "root": "/path/to/project",
210
+ "currentWork": "Ship OAuth integration",
211
+ "completed": [
212
+ { "id": "completed-login-api", "title": "Login API", "status": "completed" }
213
+ ],
214
+ "incomplete": [
215
+ { "id": "incomplete-webhook-retry-handling", "title": "Webhook retry handling", "status": "incomplete" }
216
+ ],
217
+ "findings": [
218
+ {
219
+ "id": "deps.undeclared-import",
220
+ "category": "dependencies",
221
+ "severity": "warning",
222
+ "message": "left-pad is imported but not declared in package.json",
223
+ "evidence": []
224
+ }
225
+ ],
226
+ "changes": {
227
+ "filesChanged": 1,
228
+ "summary": ["~ src/app.ts"]
229
+ },
230
+ "attention": 1
231
+ }
232
+ ```
233
+
234
+ ## Built-in checks
235
+
236
+ The current v0.1 checks are deterministic and evidence-based:
237
+
238
+ - **config.env-var-missing** — `process.env.X` used but not present in `.env.example`
239
+ - **deps.undeclared-import** — package imported but not declared in `package.json`
240
+
241
+ ## Project configuration guidance
242
+
243
+ Fathom uses a local `.fathom/config.json` file and respects ignore rules. A good default config is deliberately conservative:
244
+
245
+ ```json
246
+ {
247
+ "version": 1,
248
+ "ignore": [
249
+ "node_modules",
250
+ "dist",
251
+ ".git",
252
+ ".fathom",
253
+ "coverage",
254
+ ".next"
255
+ ]
256
+ }
257
+ ```
258
+
259
+ For more guidance, see [docs/configuration.md](./docs/configuration.md).
260
+
261
+ ## AI interpretation layer (optional)
262
+
263
+ Fathom intentionally keeps deterministic checks separate from interpretation. If you want AI assistance later, it should be opt-in and use a clean provider interface instead of hard coding a single model provider.
264
+
265
+ ```ts
266
+ export interface AIProvider {
267
+ id: string;
268
+ name: string;
269
+ interpret(input: AIInterpretationRequest): Promise<AIInterpretationResult>;
270
+ }
271
+ ```
272
+
273
+ This keeps the core product honest: evidence stays evidence, and model output remains clearly labelled as interpretation.
274
+
275
+ See [docs/ai-provider.md](./docs/ai-provider.md) for the provider surface and integration conventions.
276
+
277
+ ## Docs and roadmap
278
+
279
+ - [FATHOM.md](./FATHOM.md) — vision, principles, and roadmap
280
+ - [docs/configuration.md](./docs/configuration.md) — config and ignore recommendations
281
+ - [docs/ai-provider.md](./docs/ai-provider.md) — optional provider abstraction for interpretation
282
+
283
+ ## License
284
+
285
+ MIT
@@ -0,0 +1,20 @@
1
+ export type ProviderType = "openai" | "anthropic" | "google" | "custom";
2
+ export interface AIInterpretationRequest {
3
+ projectRoot: string;
4
+ workState: unknown;
5
+ prompt: string;
6
+ context?: Record<string, unknown>;
7
+ }
8
+ export interface AIInterpretationResult {
9
+ summary: string;
10
+ confidence: number;
11
+ notes: string[];
12
+ provider: ProviderType;
13
+ }
14
+ export interface AIProvider {
15
+ id: string;
16
+ name: string;
17
+ type: ProviderType;
18
+ interpret(input: AIInterpretationRequest): Promise<AIInterpretationResult>;
19
+ }
20
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC5E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/ai/provider.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const dependencyCheck: Check;
3
+ //# sourceMappingURL=dependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies.d.ts","sourceRoot":"","sources":["../../src/checks/dependencies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,aAAa,CAAC;AAalE,eAAO,MAAM,eAAe,EAAE,KA+E7B,CAAC"}
@@ -0,0 +1,106 @@
1
+ import { builtinModules } from "node:module";
2
+ const NODE_BUILTINS = new Set(builtinModules.flatMap((m) => (m.startsWith("node:") ? [m.slice(5)] : [m])));
3
+ const IMPORT_FROM = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']/g;
4
+ const REQUIRE_CALL = /require\s*\(\s*["']([^"']+)["']\s*\)/g;
5
+ const DYNAMIC_IMPORT = /import\s*\(\s*["']([^"']+)["']\s*\)/g;
6
+ const CODE_EXT = /\.(?:[cm]?[jt]sx?|mjs|cjs)$/;
7
+ export const dependencyCheck = {
8
+ id: "deps.undeclared-import",
9
+ async run(ctx) {
10
+ const pkgRaw = await ctx.readText("package.json");
11
+ if (!pkgRaw)
12
+ return [];
13
+ let pkg;
14
+ try {
15
+ pkg = JSON.parse(pkgRaw);
16
+ }
17
+ catch {
18
+ return [];
19
+ }
20
+ const declared = new Set([
21
+ ...Object.keys(pkg.dependencies ?? {}),
22
+ ...Object.keys(pkg.devDependencies ?? {}),
23
+ ...Object.keys(pkg.peerDependencies ?? {}),
24
+ ...Object.keys(pkg.optionalDependencies ?? {}),
25
+ ]);
26
+ const used = new Map();
27
+ for (const file of ctx.files) {
28
+ if (!CODE_EXT.test(file))
29
+ continue;
30
+ const text = await ctx.readText(file);
31
+ if (!text)
32
+ continue;
33
+ const specs = [
34
+ ...matchAll(text, IMPORT_FROM),
35
+ ...matchAll(text, REQUIRE_CALL),
36
+ ...matchAll(text, DYNAMIC_IMPORT),
37
+ ];
38
+ for (const spec of specs) {
39
+ const name = packageName(spec);
40
+ if (!name)
41
+ continue;
42
+ if (!used.has(name))
43
+ used.set(name, new Set());
44
+ used.get(name).add(file);
45
+ }
46
+ }
47
+ const findings = [];
48
+ for (const [name, files] of used) {
49
+ if (declared.has(name))
50
+ continue;
51
+ const fileList = [...files].sort();
52
+ findings.push({
53
+ id: "deps.undeclared-import",
54
+ category: "dependencies",
55
+ severity: "warning",
56
+ message: `${name} is imported but not declared in package.json`,
57
+ evidence: [
58
+ {
59
+ kind: "import",
60
+ detail: `Imported in ${fileList.length} file(s)`,
61
+ path: fileList[0],
62
+ },
63
+ ...fileList.slice(0, 5).map((path) => ({
64
+ kind: "source",
65
+ path,
66
+ detail: `import of "${name}"`,
67
+ })),
68
+ {
69
+ kind: "manifest",
70
+ path: "package.json",
71
+ detail: `${name} is not in dependencies / devDependencies`,
72
+ },
73
+ ],
74
+ });
75
+ }
76
+ return findings;
77
+ },
78
+ };
79
+ function matchAll(text, re) {
80
+ const out = [];
81
+ const copy = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
82
+ for (const m of text.matchAll(copy)) {
83
+ if (m[1])
84
+ out.push(m[1]);
85
+ }
86
+ return out;
87
+ }
88
+ function packageName(spec) {
89
+ if (spec.startsWith(".") ||
90
+ spec.startsWith("/") ||
91
+ spec.startsWith("node:") ||
92
+ spec.startsWith("#")) {
93
+ return null;
94
+ }
95
+ if (spec.startsWith("@")) {
96
+ const parts = spec.split("/");
97
+ if (parts.length < 2)
98
+ return null;
99
+ return `${parts[0]}/${parts[1]}`;
100
+ }
101
+ const name = spec.split("/")[0] ?? null;
102
+ if (!name || NODE_BUILTINS.has(name))
103
+ return null;
104
+ return name;
105
+ }
106
+ //# sourceMappingURL=dependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../src/checks/dependencies.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const envVarCheck: Check;
3
+ //# sourceMappingURL=env-vars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-vars.d.ts","sourceRoot":"","sources":["../../src/checks/env-vars.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,aAAa,CAAC;AAQlE,eAAO,MAAM,WAAW,EAAE,KA4DzB,CAAC"}
@@ -0,0 +1,76 @@
1
+ /** Detects env reads: dot form and bracket form on process env. */
2
+ const ENV_ACCESS = /process\.env(?:\.([A-Z][A-Z0-9_]*)|\[["']([A-Z][A-Z0-9_]*)["']\])/g;
3
+ const CODE_EXT = /\.(?:[cm]?[jt]sx?|mjs|cjs)$/;
4
+ export const envVarCheck = {
5
+ id: "config.env-var-missing",
6
+ async run(ctx) {
7
+ const used = new Map();
8
+ for (const file of ctx.files) {
9
+ if (!CODE_EXT.test(file))
10
+ continue;
11
+ if (file.includes(".test.") || file.includes(".spec."))
12
+ continue;
13
+ const text = await ctx.readText(file);
14
+ if (!text)
15
+ continue;
16
+ for (const match of text.matchAll(ENV_ACCESS)) {
17
+ const name = match[1] ?? match[2];
18
+ if (!name)
19
+ continue;
20
+ if (!used.has(name))
21
+ used.set(name, new Set());
22
+ used.get(name).add(file);
23
+ }
24
+ }
25
+ if (used.size === 0)
26
+ return [];
27
+ const example = await ctx.readText(".env.example");
28
+ const documented = parseEnvKeys(example ?? "");
29
+ const findings = [];
30
+ for (const [name, files] of used) {
31
+ if (documented.has(name))
32
+ continue;
33
+ const fileList = [...files].sort();
34
+ findings.push({
35
+ id: "config.env-var-missing",
36
+ category: "configuration",
37
+ severity: "warning",
38
+ message: `${name} is used in code but missing from .env.example`,
39
+ evidence: [
40
+ {
41
+ kind: "env-usage",
42
+ detail: `Referenced in ${fileList.length} file(s)`,
43
+ path: fileList[0],
44
+ },
45
+ ...fileList.slice(0, 5).map((path) => ({
46
+ kind: "source",
47
+ path,
48
+ detail: `process.env.${name}`,
49
+ })),
50
+ {
51
+ kind: "missing-doc",
52
+ path: ".env.example",
53
+ detail: example === null
54
+ ? ".env.example does not exist"
55
+ : `${name} is not listed in .env.example`,
56
+ },
57
+ ],
58
+ });
59
+ }
60
+ return findings;
61
+ },
62
+ };
63
+ function parseEnvKeys(contents) {
64
+ const keys = new Set();
65
+ for (const line of contents.split(/\r?\n/)) {
66
+ const trimmed = line.trim();
67
+ if (!trimmed || trimmed.startsWith("#"))
68
+ continue;
69
+ const eq = trimmed.indexOf("=");
70
+ const key = (eq === -1 ? trimmed : trimmed.slice(0, eq)).trim();
71
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
72
+ keys.add(key);
73
+ }
74
+ return keys;
75
+ }
76
+ //# sourceMappingURL=env-vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/checks/env-vars.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,MAAM,UAAU,GACd,oEAAoE,CAAC;AAEvE,MAAM,QAAQ,GAAG,6BAA6B,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,EAAE,EAAE,wBAAwB;IAC5B,KAAK,CAAC,GAAG,CAAC,GAAmB;QAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAC;QAE5C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEjE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;gBAClC,IAAI,CAAC,IAAI;oBAAE,SAAS;gBACpB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAE/B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACnD,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QAE/C,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;YACjC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YACnC,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,wBAAwB;gBAC5B,QAAQ,EAAE,eAAe;gBACzB,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,GAAG,IAAI,gDAAgD;gBAChE,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,WAAW;wBACjB,MAAM,EAAE,iBAAiB,QAAQ,CAAC,MAAM,UAAU;wBAClD,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;qBAClB;oBACD,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBACrC,IAAI,EAAE,QAAQ;wBACd,IAAI;wBACJ,MAAM,EAAE,eAAe,IAAI,EAAE;qBAC9B,CAAC,CAAC;oBACH;wBACE,IAAI,EAAE,aAAa;wBACnB,IAAI,EAAE,cAAc;wBACpB,MAAM,EAAE,OAAO,KAAK,IAAI;4BACtB,CAAC,CAAC,6BAA6B;4BAC/B,CAAC,CAAC,GAAG,IAAI,gCAAgC;qBAC5C;iBACF;aACF,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,SAAS,YAAY,CAAC,QAAgB;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChE,IAAI,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Check } from "../types.js";
2
+ /** Built-in deterministic checks for v0.1. */
3
+ export declare const builtinChecks: Check[];
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAIzC,8CAA8C;AAC9C,eAAO,MAAM,aAAa,EAAE,KAAK,EAAmC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { envVarCheck } from "./env-vars.js";
2
+ import { dependencyCheck } from "./dependencies.js";
3
+ /** Built-in deterministic checks for v0.1. */
4
+ export const builtinChecks = [envVarCheck, dependencyCheck];
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/checks/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,8CAA8C;AAC9C,MAAM,CAAC,MAAM,aAAa,GAAY,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}