@jatingargiitk/archmap 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jatin Garg
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 CHANGED
@@ -1,3 +1,223 @@
1
- # Temporary Holding Version
1
+ # archmap
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **Snapshot tests for your architecture.** Deterministic module dependency graphs with lockfile diffing.
4
+
5
+ **✅ 100% Deterministic • 🔒 No API Keys • 🆓 Free**
6
+
7
+ **Your AI agent just made `ui/` import `db/`. archmap noticed.**
8
+
9
+ archmap commits a living map of your modules and flags every PR—human or AI—that adds a new dependency between them.
10
+
11
+ ![archmap demo](docs/demo.gif)
12
+
13
+ ---
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ npx @jatingargiitk/archmap init
19
+ ```
20
+
21
+ This creates:
22
+ - ✅ `.github/workflows/archmap.yml` - GitHub Action
23
+ - ✅ `ARCHITECTURE.md` - Living architecture map ([see ours](./ARCHITECTURE.md))
24
+ - ✅ `archmap.lock.json` - Dependency lockfile
25
+
26
+ The generated workflow:
27
+
28
+ ```yaml
29
+ name: archmap
30
+
31
+ on:
32
+ pull_request:
33
+
34
+ permissions:
35
+ contents: write
36
+ pull-requests: write
37
+
38
+ jobs:
39
+ archmap:
40
+ runs-on: ubuntu-latest
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ fetch-depth: 0
45
+ - uses: jatingargiitk/archmap@v0
46
+ ```
47
+
48
+ ---
49
+
50
+ ## What Problem Does This Solve?
51
+
52
+ **The Problem:** AI agents (and humans) quietly add dependencies that violate your architecture. You discover this months later when the codebase is unmaintainable.
53
+
54
+ **The Solution:** archmap tracks your module-level dependency graph in a lockfile. New cross-module edges trigger a required approval step, making architecture changes visible and intentional.
55
+
56
+ Think of it as snapshot testing, but for your architecture.
57
+
58
+ ---
59
+
60
+ ## Example: Before & After
61
+
62
+ ### Before archmap
63
+
64
+ ```diff
65
+ // src/ui/UserProfile.tsx
66
+ + import { getUserFromDB } from '../db/users'; // Oops! UI directly importing from DB
67
+ ```
68
+
69
+ ✅ Tests pass
70
+ ✅ Linter passes
71
+ ✅ PR approved
72
+ ❌ Architecture silently violated
73
+
74
+ ### After archmap
75
+
76
+ The PR gets a sticky comment showing the new dependency with the exact file and line number where the import was added. Now the reviewer knows exactly what changed architecturally, and the decision is explicit.
77
+
78
+ ---
79
+
80
+ ## Features
81
+
82
+ - ✅ **100% Deterministic** - Same input, same output. No LLM or API key required
83
+ - ✅ **Fast** - Parses with `web-tree-sitter` WASM (TS/JS + Python in v0.1)
84
+ - ✅ **Lockfile Diffing** - Git-friendly JSON lockfile tracks accepted edges
85
+ - ✅ **GitHub Action** - Sticky PR comments show added/removed edges with file locations
86
+ - ✅ **Optional Gate Mode** - Fail CI if unapproved edges are added
87
+ - ✅ **Deny Rules** - `deny: { from: "ui/**", to: "db/**" }`
88
+ - ✅ **Living Docs** - `ARCHITECTURE.md` with Mermaid diagram, auto-regenerated
89
+ - ✅ **Agent Skill** - Works with Claude Code, Cursor, and Codex
90
+
91
+ ---
92
+
93
+ ## CLI Commands
94
+
95
+ ```bash
96
+ npx @jatingargiitk/archmap init # Initialize archmap
97
+ npx @jatingargiitk/archmap render # Generate ARCHITECTURE.md and lock
98
+ npx @jatingargiitk/archmap diff # Show diff against main
99
+ npx @jatingargiitk/archmap check # Check for unapproved changes
100
+ npx @jatingargiitk/archmap accept # Accept current changes
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Configuration
106
+
107
+ Optional `archmap.yml`:
108
+
109
+ ```yaml
110
+ modules:
111
+ roots: [src, packages/*, apps/*]
112
+ depth: 1 # 0 = collapse entire root, 1+ = subfolders
113
+
114
+ ignore:
115
+ - "**/*.test.*"
116
+ - "**/fixtures/**"
117
+
118
+ rules:
119
+ - deny: { from: "ui/**", to: "db/**" }
120
+ - deny: { from: "api/**", to: "ui/**" }
121
+
122
+ mode: comment # or "gate" to fail CI on unapproved edges
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Agent Skill
128
+
129
+ archmap ships with a skill for Claude Code, Cursor, and Codex:
130
+
131
+ **Install:**
132
+ ```bash
133
+ npx skills add jatingargiitk/archmap
134
+ ```
135
+
136
+ The agent will now run `archmap check` before finishing tasks, catching architectural drift automatically.
137
+
138
+ ---
139
+
140
+ ## Comparison
141
+
142
+ | | **archmap** | graphify | CodeBoarding | pr-lens | dependency-cruiser |
143
+ |---|---|---|---|---|---|
144
+ | **Focus** | Architecture guarding | Agent memory | Platform + analysis | PR explainer | Linting |
145
+ | **Deterministic** | ✅ Yes | Hybrid | LLM-driven | LLM-driven | ✅ Yes |
146
+ | **No API Key** | ✅ Yes | ✅ Yes | ❌ Needs key | ❌ Needs key | ✅ Yes |
147
+ | **Lockfile Diffing** | ✅ Yes | ❌ No | ❌ No | ❌ No | ❌ No |
148
+ | **Living Docs** | ✅ Committed | Local HTML | Web platform | Per-PR only | Config file |
149
+ | **Languages** | TS/JS, Python | 40+ | 9 | General | JS/TS only |
150
+ | **Setup** | 1 workflow file | uv + skill | Hosted + telemetry | App or Action | Config wizard |
151
+
152
+ **Use archmap when:**
153
+ - You want snapshot testing for architecture
154
+ - You're reviewing AI-written code
155
+ - You need a deterministic, zero-config solution
156
+
157
+ **Use graphify when:**
158
+ - You need agent context and memory
159
+ - You want queryable knowledge graphs
160
+
161
+ ---
162
+
163
+ ## FAQ
164
+
165
+ ### Does archmap work with monorepos?
166
+
167
+ Yes! Configure `modules.roots` to match your structure:
168
+
169
+ ```yaml
170
+ modules:
171
+ roots: [packages/*, apps/*]
172
+ depth: 0 # Collapse each package into one node
173
+ ```
174
+
175
+ ### What about fork PRs?
176
+
177
+ Fork PRs cannot post comments with the default `GITHUB_TOKEN` due to GitHub security restrictions. The workflow will run the check successfully, but comment posting will be skipped. The same architecture diff is visible in the action logs.
178
+
179
+ ### Does anything leave my runner?
180
+
181
+ No. archmap is 100% local. The optional `explain` mode (not yet implemented) will send structural diffs (JSON) to an LLM, but never your code.
182
+
183
+ ### How is this different from Dependabot?
184
+
185
+ Dependabot tracks external package updates. archmap tracks your internal module architecture.
186
+
187
+ ### Can I use this for Go/Rust/Java?
188
+
189
+ Not yet. v0.1 supports TypeScript/JavaScript and Python. Go is planned for v0.2.
190
+
191
+ ---
192
+
193
+ ## Limitations
194
+
195
+ ### Protected Default Branch
196
+
197
+ If your default branch (e.g., `main`) has branch protection rules enabled, the GitHub Action cannot push `ARCHITECTURE.md` and `archmap.lock.json` updates directly on `push` events.
198
+
199
+ **Workaround:** Either:
200
+ 1. Disable branch protection for the `archmap[bot]` user or GitHub Actions
201
+ 2. Only run archmap on `pull_request` events (recommended)
202
+ 3. Manually run `archmap accept` locally and commit the changes
203
+
204
+ The `pull_request` workflow mode (default) is unaffected by branch protection.
205
+
206
+ ---
207
+
208
+ ## Contributing
209
+
210
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, publishing instructions, and how to regenerate the demo GIF.
211
+
212
+ ---
213
+
214
+ ## License
215
+
216
+ MIT © Jatin Garg
217
+
218
+ ---
219
+
220
+ **Made for developers reviewing AI-written code.**
221
+ Track architecture. Make drift visible. Keep understanding your codebase.
222
+
223
+ Built by [@jatingargiitk](https://x.com/jatingargiitk)
package/action.yml ADDED
@@ -0,0 +1,37 @@
1
+ name: 'archmap'
2
+ description: 'Snapshot tests for your architecture - track module dependencies'
3
+ author: 'Jatin Garg'
4
+
5
+ branding:
6
+ icon: 'map'
7
+ color: 'blue'
8
+
9
+ runs:
10
+ using: 'composite'
11
+ steps:
12
+ - name: Setup Node
13
+ uses: actions/setup-node@v4
14
+ with:
15
+ node-version: '20'
16
+
17
+ - name: Install dependencies
18
+ shell: bash
19
+ run: |
20
+ cd ${{ github.action_path }}
21
+ npm ci --omit=dev
22
+
23
+ - name: Run archmap
24
+ shell: bash
25
+ run: node ${{ github.action_path }}/dist/action.js
26
+ env:
27
+ INPUT_MODE: ${{ inputs.mode }}
28
+ GITHUB_TOKEN: ${{ inputs.github-token }}
29
+
30
+ inputs:
31
+ mode:
32
+ description: 'Mode: comment (default) or gate'
33
+ required: false
34
+ github-token:
35
+ description: 'GitHub token for posting comments'
36
+ required: false
37
+ default: ${{ github.token }}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/action.js ADDED
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildGraph,
4
+ checkRules,
5
+ diffLocks,
6
+ extractPythonImports,
7
+ extractTypeScriptImports,
8
+ findWorkspaces,
9
+ graphToLock,
10
+ loadTsConfig,
11
+ renderDeltaDiagram,
12
+ resolveImports,
13
+ shouldFailCheck,
14
+ writeArchitectureFile,
15
+ writeLockFile
16
+ } from "./chunk-QUFBKU64.js";
17
+
18
+ // src/action.ts
19
+ import * as fs from "fs/promises";
20
+ import { readFileSync } from "fs";
21
+ import * as path from "path";
22
+ import { execSync } from "child_process";
23
+ async function runAction() {
24
+ const githubContext = getGitHubContext();
25
+ const rootDir = process.env.GITHUB_WORKSPACE || process.cwd();
26
+ console.log("\u{1F50D} Analyzing architecture...");
27
+ const config = await loadConfig(rootDir);
28
+ const currentGraph = await analyzeRepo(rootDir, config);
29
+ const currentLock = graphToLock(currentGraph);
30
+ await writeArchitectureFile(
31
+ path.join(rootDir, "ARCHITECTURE.md"),
32
+ currentGraph
33
+ );
34
+ await writeLockFile(path.join(rootDir, "archmap.lock.json"), currentLock);
35
+ if (githubContext.eventName === "pull_request") {
36
+ await handlePullRequest(rootDir, currentGraph, currentLock, config, githubContext);
37
+ } else if (githubContext.eventName === "push") {
38
+ await handlePush(rootDir, githubContext);
39
+ }
40
+ }
41
+ async function handlePullRequest(rootDir, currentGraph, currentLock, config, context) {
42
+ console.log("\u{1F4CA} Computing diff...");
43
+ let baseLock = null;
44
+ try {
45
+ const baseRef = `origin/${context.baseRef}`;
46
+ try {
47
+ execSync(`git rev-parse --verify ${baseRef}`, { cwd: rootDir, stdio: "ignore" });
48
+ } catch {
49
+ console.log(`Fetching base ref ${context.baseRef}...`);
50
+ try {
51
+ execSync(`git fetch --no-tags --depth=1 origin ${context.baseRef}`, { cwd: rootDir, stdio: "pipe" });
52
+ } catch (fetchError) {
53
+ console.log(`\u26A0\uFE0F Could not fetch base ref: ${fetchError.message}`);
54
+ }
55
+ }
56
+ const baseLockContent = execSync(
57
+ `git show ${baseRef}:archmap.lock.json`,
58
+ { cwd: rootDir, encoding: "utf-8" }
59
+ );
60
+ baseLock = JSON.parse(baseLockContent);
61
+ } catch {
62
+ console.log("\u26A0\uFE0F No lock file found in base branch");
63
+ }
64
+ const diff = diffLocks(baseLock, currentLock, currentGraph);
65
+ const violations = checkRules(currentGraph.edges, config);
66
+ const commentBody = buildCommentBody(diff, violations, currentGraph, context);
67
+ if (process.env.GITHUB_TOKEN && context.prNumber) {
68
+ await postOrUpdateComment(context, commentBody);
69
+ } else {
70
+ console.log("Comment preview:");
71
+ console.log(commentBody);
72
+ }
73
+ const shouldFail = shouldFailCheck(diff, violations, config);
74
+ if (shouldFail) {
75
+ console.log("\u274C Check failed");
76
+ process.exit(1);
77
+ } else {
78
+ console.log("\u2705 Check passed");
79
+ }
80
+ }
81
+ async function handlePush(rootDir, context) {
82
+ if (context.ref === `refs/heads/${context.defaultBranch}`) {
83
+ console.log("\u{1F504} Committing updated architecture files...");
84
+ try {
85
+ execSync('git config user.name "archmap[bot]"', { cwd: rootDir });
86
+ execSync('git config user.email "archmap[bot]@users.noreply.github.com"', { cwd: rootDir });
87
+ execSync("git add ARCHITECTURE.md archmap.lock.json", { cwd: rootDir });
88
+ try {
89
+ execSync('git commit -m "chore: update architecture map [skip ci]"', { cwd: rootDir });
90
+ execSync(`git push origin ${context.defaultBranch}`, { cwd: rootDir });
91
+ console.log("\u2705 Architecture files updated");
92
+ } catch {
93
+ console.log("\u2139\uFE0F No changes to commit");
94
+ }
95
+ } catch (error) {
96
+ console.log("\u26A0\uFE0F Could not commit changes:", error.message);
97
+ }
98
+ }
99
+ }
100
+ function buildCommentBody(diff, violations, _graph, context) {
101
+ const lines = [];
102
+ lines.push("## \u{1F5FA}\uFE0F archmap");
103
+ lines.push("");
104
+ const summary = `**${diff.added.length > 0 ? "+" : ""}${diff.added.length} new edge${diff.added.length !== 1 ? "s" : ""}, ${diff.removed.length > 0 ? "-" : ""}${diff.removed.length} removed**`;
105
+ lines.push(summary);
106
+ lines.push("");
107
+ if (violations.length > 0) {
108
+ lines.push("### \u26A0\uFE0F Rule Violations");
109
+ lines.push("");
110
+ for (const violation of violations) {
111
+ lines.push(`- ${violation.message}`);
112
+ lines.push(` - Rule: \`${violation.rule}\``);
113
+ }
114
+ lines.push("");
115
+ }
116
+ if (diff.added.length > 0 || diff.removed.length > 0) {
117
+ const touchedModules = /* @__PURE__ */ new Set();
118
+ for (const edge of diff.added) {
119
+ touchedModules.add(edge.from);
120
+ touchedModules.add(edge.to);
121
+ }
122
+ for (const edge of diff.removed) {
123
+ touchedModules.add(edge.from);
124
+ touchedModules.add(edge.to);
125
+ }
126
+ lines.push("### Changes");
127
+ lines.push("");
128
+ lines.push(renderDeltaDiagram(diff.added, diff.removed, touchedModules));
129
+ lines.push("");
130
+ if (diff.added.length > 0) {
131
+ lines.push("#### Added Dependencies");
132
+ lines.push("");
133
+ for (const edge of diff.added) {
134
+ if (edge.locations && edge.locations.length > 0) {
135
+ const loc = edge.locations[0];
136
+ const [owner, repo] = context.repository.split("/");
137
+ const blobUrl = `https://github.com/${owner}/${repo}/blob/${context.sha}/${loc.file}#L${loc.line}`;
138
+ lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\` ([${loc.file}:${loc.line}](${blobUrl}))`);
139
+ } else {
140
+ lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\``);
141
+ }
142
+ }
143
+ lines.push("");
144
+ }
145
+ if (diff.removed.length > 0) {
146
+ lines.push("#### Removed Dependencies");
147
+ lines.push("");
148
+ for (const edge of diff.removed) {
149
+ lines.push(`- \`${edge.from}\` \u2192 \`${edge.to}\``);
150
+ }
151
+ lines.push("");
152
+ }
153
+ }
154
+ if (diff.newCycles.length > 0) {
155
+ lines.push("### \u26A0\uFE0F New Cycles");
156
+ lines.push("");
157
+ for (const cycle of diff.newCycles) {
158
+ lines.push(`- ${cycle.map((m) => `\`${m}\``).join(" \u2192 ")}`);
159
+ }
160
+ lines.push("");
161
+ }
162
+ if (diff.added.length === 0 && diff.removed.length === 0 && violations.length === 0) {
163
+ lines.push("\u2705 No architectural changes detected");
164
+ } else {
165
+ lines.push("---");
166
+ lines.push("");
167
+ lines.push("To accept these changes, run:");
168
+ lines.push("```bash");
169
+ lines.push("npx @jatingargiitk/archmap accept");
170
+ lines.push("git add ARCHITECTURE.md archmap.lock.json");
171
+ lines.push('git commit -m "chore: update architecture map"');
172
+ lines.push("```");
173
+ }
174
+ lines.push("");
175
+ lines.push("<!-- archmap-comment -->");
176
+ return lines.join("\n");
177
+ }
178
+ async function postOrUpdateComment(context, body) {
179
+ const token = process.env.GITHUB_TOKEN;
180
+ if (!token) return;
181
+ const [owner, repo] = context.repository.split("/");
182
+ const issueNumber = context.prNumber;
183
+ try {
184
+ const existingComments = await fetch(
185
+ `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
186
+ {
187
+ headers: {
188
+ Authorization: `Bearer ${token}`,
189
+ "User-Agent": "archmap"
190
+ }
191
+ }
192
+ );
193
+ const comments = await existingComments.json();
194
+ const existingComment = comments.find(
195
+ (c) => c.body.includes("<!-- archmap-comment -->")
196
+ );
197
+ if (existingComment) {
198
+ await fetch(
199
+ `https://api.github.com/repos/${owner}/${repo}/issues/comments/${existingComment.id}`,
200
+ {
201
+ method: "PATCH",
202
+ headers: {
203
+ Authorization: `Bearer ${token}`,
204
+ "Content-Type": "application/json",
205
+ "User-Agent": "archmap"
206
+ },
207
+ body: JSON.stringify({ body })
208
+ }
209
+ );
210
+ console.log("\u2705 Updated existing comment");
211
+ } else {
212
+ await fetch(
213
+ `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
214
+ {
215
+ method: "POST",
216
+ headers: {
217
+ Authorization: `Bearer ${token}`,
218
+ "Content-Type": "application/json",
219
+ "User-Agent": "archmap"
220
+ },
221
+ body: JSON.stringify({ body })
222
+ }
223
+ );
224
+ console.log("\u2705 Posted new comment");
225
+ }
226
+ } catch (error) {
227
+ console.log("\u26A0\uFE0F Could not post comment:", error.message);
228
+ }
229
+ }
230
+ function getGitHubContext() {
231
+ const eventPath = process.env.GITHUB_EVENT_PATH;
232
+ let event = {};
233
+ if (eventPath) {
234
+ try {
235
+ const eventData = readFileSync(eventPath, "utf-8");
236
+ event = JSON.parse(eventData);
237
+ } catch {
238
+ }
239
+ }
240
+ return {
241
+ eventName: process.env.GITHUB_EVENT_NAME || "",
242
+ repository: process.env.GITHUB_REPOSITORY || "",
243
+ ref: process.env.GITHUB_REF || "",
244
+ sha: process.env.GITHUB_SHA || "",
245
+ actor: process.env.GITHUB_ACTOR || "",
246
+ workflow: process.env.GITHUB_WORKFLOW || "",
247
+ defaultBranch: event.repository?.default_branch || "main",
248
+ baseRef: event.pull_request?.base?.ref || "main",
249
+ prNumber: event.pull_request?.number
250
+ };
251
+ }
252
+ async function analyzeRepo(rootDir, config) {
253
+ const files = await findSourceFiles(rootDir, config.ignore || []);
254
+ const allImports = [];
255
+ const allFiles = [];
256
+ for (const file of files) {
257
+ const fullPath = path.join(rootDir, file);
258
+ const content = await fs.readFile(fullPath, "utf-8");
259
+ let imports;
260
+ if (file.endsWith(".ts") || file.endsWith(".tsx") || file.endsWith(".js") || file.endsWith(".jsx")) {
261
+ imports = await extractTypeScriptImports(file, content);
262
+ } else if (file.endsWith(".py")) {
263
+ imports = await extractPythonImports(file, content);
264
+ } else {
265
+ continue;
266
+ }
267
+ allImports.push(imports);
268
+ allFiles.push(file);
269
+ }
270
+ const tsconfig = await loadTsConfig(rootDir);
271
+ const workspaces = await findWorkspaces(rootDir);
272
+ const resolved = await resolveImports(allImports, {
273
+ rootDir,
274
+ tsconfig: tsconfig || void 0,
275
+ workspaces: workspaces.length > 0 ? workspaces : void 0
276
+ });
277
+ const moduleConfig = config.modules || {};
278
+ const graph = buildGraph(resolved, allFiles, moduleConfig);
279
+ return graph;
280
+ }
281
+ async function findSourceFiles(rootDir, ignore) {
282
+ const files = [];
283
+ const ignorePatterns = [
284
+ "**/node_modules/**",
285
+ "**/dist/**",
286
+ "**/build/**",
287
+ "**/.git/**",
288
+ "**/coverage/**",
289
+ "**/*.test.*",
290
+ "**/*.spec.*",
291
+ "**/__tests__/**",
292
+ ...ignore
293
+ ];
294
+ async function walk(dir) {
295
+ const entries = await fs.readdir(dir, { withFileTypes: true });
296
+ for (const entry of entries) {
297
+ const fullPath = path.join(dir, entry.name);
298
+ const relativePath = path.relative(rootDir, fullPath);
299
+ if (shouldIgnore(relativePath, ignorePatterns)) {
300
+ continue;
301
+ }
302
+ if (entry.isDirectory()) {
303
+ await walk(fullPath);
304
+ } else if (entry.isFile()) {
305
+ if (isSourceFile(entry.name)) {
306
+ files.push(relativePath);
307
+ }
308
+ }
309
+ }
310
+ }
311
+ await walk(rootDir);
312
+ return files;
313
+ }
314
+ function shouldIgnore(filePath, patterns) {
315
+ const normalized = filePath.replace(/\\/g, "/");
316
+ for (const pattern of patterns) {
317
+ const regex = patternToRegex(pattern);
318
+ if (regex.test(normalized)) {
319
+ return true;
320
+ }
321
+ }
322
+ return false;
323
+ }
324
+ function patternToRegex(pattern) {
325
+ const regexStr = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLESTAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLESTAR___/g, ".*");
326
+ return new RegExp(`^${regexStr}$`);
327
+ }
328
+ function isSourceFile(name) {
329
+ return /\.(ts|tsx|js|jsx|py)$/.test(name);
330
+ }
331
+ async function loadConfig(rootDir) {
332
+ const configPath = path.join(rootDir, "archmap.yml");
333
+ let config = {};
334
+ try {
335
+ const content = await fs.readFile(configPath, "utf-8");
336
+ const yaml = await import("js-yaml");
337
+ config = yaml.load(content);
338
+ } catch {
339
+ config = {};
340
+ }
341
+ const inputMode = process.env.INPUT_MODE;
342
+ if (inputMode && (inputMode === "comment" || inputMode === "gate")) {
343
+ config.mode = inputMode;
344
+ }
345
+ return config;
346
+ }
347
+ runAction().catch((error) => {
348
+ console.error("Error:", error.message);
349
+ process.exit(1);
350
+ });
351
+ //# sourceMappingURL=action.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/action.ts"],"sourcesContent":["import * as fs from 'node:fs/promises';\nimport { readFileSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { extractTypeScriptImports, extractPythonImports } from './parser/index.js';\nimport { resolveImports, loadTsConfig, findWorkspaces } from './resolver/index.js';\nimport { buildGraph } from './graph/index.js';\nimport { writeArchitectureFile, renderDeltaDiagram } from './render/index.js';\nimport { graphToLock, writeLockFile, diffLocks } from './lock/index.js';\nimport { checkRules, shouldFailCheck } from './rules/index.js';\nimport type { Config } from './types.js';\n\nasync function runAction(): Promise<void> {\n const githubContext = getGitHubContext();\n const rootDir = process.env.GITHUB_WORKSPACE || process.cwd();\n\n console.log('🔍 Analyzing architecture...');\n\n const config = await loadConfig(rootDir);\n const currentGraph = await analyzeRepo(rootDir, config);\n const currentLock = graphToLock(currentGraph);\n\n await writeArchitectureFile(\n path.join(rootDir, 'ARCHITECTURE.md'),\n currentGraph\n );\n\n await writeLockFile(path.join(rootDir, 'archmap.lock.json'), currentLock);\n\n if (githubContext.eventName === 'pull_request') {\n await handlePullRequest(rootDir, currentGraph, currentLock, config, githubContext);\n } else if (githubContext.eventName === 'push') {\n await handlePush(rootDir, githubContext);\n }\n}\n\nasync function handlePullRequest(\n rootDir: string,\n currentGraph: any,\n currentLock: any,\n config: Config,\n context: any\n): Promise<void> {\n console.log('📊 Computing diff...');\n\n let baseLock = null;\n try {\n // Ensure we have the base ref, even in shallow clones\n const baseRef = `origin/${context.baseRef}`;\n try {\n execSync(`git rev-parse --verify ${baseRef}`, { cwd: rootDir, stdio: 'ignore' });\n } catch {\n // Base ref not found, try to fetch it\n console.log(`Fetching base ref ${context.baseRef}...`);\n try {\n execSync(`git fetch --no-tags --depth=1 origin ${context.baseRef}`, { cwd: rootDir, stdio: 'pipe' });\n } catch (fetchError: any) {\n console.log(`⚠️ Could not fetch base ref: ${fetchError.message}`);\n }\n }\n\n const baseLockContent = execSync(\n `git show ${baseRef}:archmap.lock.json`,\n { cwd: rootDir, encoding: 'utf-8' }\n );\n baseLock = JSON.parse(baseLockContent);\n } catch {\n console.log('⚠️ No lock file found in base branch');\n }\n\n const diff = diffLocks(baseLock, currentLock, currentGraph);\n const violations = checkRules(currentGraph.edges, config);\n\n const commentBody = buildCommentBody(diff, violations, currentGraph, context);\n\n if (process.env.GITHUB_TOKEN && context.prNumber) {\n await postOrUpdateComment(context, commentBody);\n } else {\n console.log('Comment preview:');\n console.log(commentBody);\n }\n\n const shouldFail = shouldFailCheck(diff, violations, config);\n\n if (shouldFail) {\n console.log('❌ Check failed');\n process.exit(1);\n } else {\n console.log('✅ Check passed');\n }\n}\n\nasync function handlePush(rootDir: string, context: any): Promise<void> {\n if (context.ref === `refs/heads/${context.defaultBranch}`) {\n console.log('🔄 Committing updated architecture files...');\n\n try {\n execSync('git config user.name \"archmap[bot]\"', { cwd: rootDir });\n execSync('git config user.email \"archmap[bot]@users.noreply.github.com\"', { cwd: rootDir });\n execSync('git add ARCHITECTURE.md archmap.lock.json', { cwd: rootDir });\n \n try {\n execSync('git commit -m \"chore: update architecture map [skip ci]\"', { cwd: rootDir });\n execSync(`git push origin ${context.defaultBranch}`, { cwd: rootDir });\n console.log('✅ Architecture files updated');\n } catch {\n console.log('ℹ️ No changes to commit');\n }\n } catch (error: unknown) {\n console.log('⚠️ Could not commit changes:', (error as Error).message);\n }\n }\n}\n\nfunction buildCommentBody(diff: any, violations: any[], _graph: any, context: any): string {\n const lines: string[] = [];\n\n lines.push('## 🗺️ archmap');\n lines.push('');\n\n const summary = `**${diff.added.length > 0 ? '+' : ''}${diff.added.length} new edge${diff.added.length !== 1 ? 's' : ''}, ${diff.removed.length > 0 ? '-' : ''}${diff.removed.length} removed**`;\n lines.push(summary);\n lines.push('');\n\n if (violations.length > 0) {\n lines.push('### ⚠️ Rule Violations');\n lines.push('');\n for (const violation of violations) {\n lines.push(`- ${violation.message}`);\n lines.push(` - Rule: \\`${violation.rule}\\``);\n }\n lines.push('');\n }\n\n if (diff.added.length > 0 || diff.removed.length > 0) {\n const touchedModules = new Set<string>();\n for (const edge of diff.added) {\n touchedModules.add(edge.from);\n touchedModules.add(edge.to);\n }\n for (const edge of diff.removed) {\n touchedModules.add(edge.from);\n touchedModules.add(edge.to);\n }\n\n lines.push('### Changes');\n lines.push('');\n lines.push(renderDeltaDiagram(diff.added, diff.removed, touchedModules));\n lines.push('');\n\n if (diff.added.length > 0) {\n lines.push('#### Added Dependencies');\n lines.push('');\n for (const edge of diff.added) {\n // Show locations (B6) with absolute blob URLs\n if (edge.locations && edge.locations.length > 0) {\n const loc = edge.locations[0];\n const [owner, repo] = context.repository.split('/');\n const blobUrl = `https://github.com/${owner}/${repo}/blob/${context.sha}/${loc.file}#L${loc.line}`;\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\` ([${loc.file}:${loc.line}](${blobUrl}))`);\n } else {\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\``);\n }\n }\n lines.push('');\n }\n\n if (diff.removed.length > 0) {\n lines.push('#### Removed Dependencies');\n lines.push('');\n for (const edge of diff.removed) {\n lines.push(`- \\`${edge.from}\\` → \\`${edge.to}\\``);\n }\n lines.push('');\n }\n }\n\n if (diff.newCycles.length > 0) {\n lines.push('### ⚠️ New Cycles');\n lines.push('');\n for (const cycle of diff.newCycles) {\n lines.push(`- ${cycle.map((m: string) => `\\`${m}\\``).join(' → ')}`);\n }\n lines.push('');\n }\n\n if (diff.added.length === 0 && diff.removed.length === 0 && violations.length === 0) {\n lines.push('✅ No architectural changes detected');\n } else {\n lines.push('---');\n lines.push('');\n lines.push('To accept these changes, run:');\n lines.push('```bash');\n lines.push('npx @jatingargiitk/archmap accept');\n lines.push('git add ARCHITECTURE.md archmap.lock.json');\n lines.push('git commit -m \"chore: update architecture map\"');\n lines.push('```');\n }\n\n lines.push('');\n lines.push('<!-- archmap-comment -->');\n\n return lines.join('\\n');\n}\n\nasync function postOrUpdateComment(context: any, body: string): Promise<void> {\n const token = process.env.GITHUB_TOKEN;\n if (!token) return;\n\n const [owner, repo] = context.repository.split('/');\n const issueNumber = context.prNumber;\n\n try {\n const existingComments = await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,\n {\n headers: {\n Authorization: `Bearer ${token}`,\n 'User-Agent': 'archmap',\n },\n }\n );\n\n const comments = (await existingComments.json()) as any[];\n const existingComment = comments.find((c: any) =>\n c.body.includes('<!-- archmap-comment -->')\n );\n\n if (existingComment) {\n await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/comments/${existingComment.id}`,\n {\n method: 'PATCH',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'archmap',\n },\n body: JSON.stringify({ body }),\n }\n );\n console.log('✅ Updated existing comment');\n } else {\n await fetch(\n `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': 'archmap',\n },\n body: JSON.stringify({ body }),\n }\n );\n console.log('✅ Posted new comment');\n }\n } catch (error: unknown) {\n console.log('⚠️ Could not post comment:', (error as Error).message);\n }\n}\n\nfunction getGitHubContext(): any {\n const eventPath = process.env.GITHUB_EVENT_PATH;\n let event = {};\n\n if (eventPath) {\n try {\n const eventData = readFileSync(eventPath, 'utf-8');\n event = JSON.parse(eventData);\n } catch {\n // Ignore\n }\n }\n\n return {\n eventName: process.env.GITHUB_EVENT_NAME || '',\n repository: process.env.GITHUB_REPOSITORY || '',\n ref: process.env.GITHUB_REF || '',\n sha: process.env.GITHUB_SHA || '',\n actor: process.env.GITHUB_ACTOR || '',\n workflow: process.env.GITHUB_WORKFLOW || '',\n defaultBranch: (event as any).repository?.default_branch || 'main',\n baseRef: (event as any).pull_request?.base?.ref || 'main',\n prNumber: (event as any).pull_request?.number,\n };\n}\n\nasync function analyzeRepo(rootDir: string, config: Config): Promise<any> {\n const files = await findSourceFiles(rootDir, config.ignore || []);\n\n const allImports = [];\n const allFiles: string[] = [];\n\n for (const file of files) {\n const fullPath = path.join(rootDir, file);\n const content = await fs.readFile(fullPath, 'utf-8');\n\n let imports;\n if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx')) {\n imports = await extractTypeScriptImports(file, content);\n } else if (file.endsWith('.py')) {\n imports = await extractPythonImports(file, content);\n } else {\n continue;\n }\n\n allImports.push(imports);\n allFiles.push(file);\n }\n\n const tsconfig = await loadTsConfig(rootDir);\n const workspaces = await findWorkspaces(rootDir);\n\n const resolved = await resolveImports(allImports, {\n rootDir,\n tsconfig: tsconfig || undefined,\n workspaces: workspaces.length > 0 ? workspaces : undefined,\n });\n\n const moduleConfig = config.modules || {};\n const graph = buildGraph(resolved, allFiles, moduleConfig);\n\n return graph;\n}\n\nasync function findSourceFiles(rootDir: string, ignore: string[]): Promise<string[]> {\n const files: string[] = [];\n const ignorePatterns = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/coverage/**',\n '**/*.test.*',\n '**/*.spec.*',\n '**/__tests__/**',\n ...ignore,\n ];\n\n async function walk(dir: string): Promise<void> {\n const entries = await fs.readdir(dir, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n const relativePath = path.relative(rootDir, fullPath);\n\n if (shouldIgnore(relativePath, ignorePatterns)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n await walk(fullPath);\n } else if (entry.isFile()) {\n if (isSourceFile(entry.name)) {\n files.push(relativePath);\n }\n }\n }\n }\n\n await walk(rootDir);\n return files;\n}\n\nfunction shouldIgnore(filePath: string, patterns: string[]): boolean {\n const normalized = filePath.replace(/\\\\/g, '/');\n\n for (const pattern of patterns) {\n const regex = patternToRegex(pattern);\n if (regex.test(normalized)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction patternToRegex(pattern: string): RegExp {\n const regexStr = pattern\n .replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*\\*/g, '___DOUBLESTAR___')\n .replace(/\\*/g, '[^/]*')\n .replace(/___DOUBLESTAR___/g, '.*');\n\n return new RegExp(`^${regexStr}$`);\n}\n\nfunction isSourceFile(name: string): boolean {\n return /\\.(ts|tsx|js|jsx|py)$/.test(name);\n}\n\nasync function loadConfig(rootDir: string): Promise<Config> {\n const configPath = path.join(rootDir, 'archmap.yml');\n\n let config: Config = {};\n try {\n const content = await fs.readFile(configPath, 'utf-8');\n const yaml = await import('js-yaml');\n config = yaml.load(content) as Config;\n } catch {\n config = {};\n }\n\n // Override with INPUT_MODE if explicitly provided (B7)\n const inputMode = process.env.INPUT_MODE;\n if (inputMode && (inputMode === 'comment' || inputMode === 'gate')) {\n config.mode = inputMode;\n }\n\n return config;\n}\n\nrunAction().catch((error) => {\n console.error('Error:', error.message);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}