Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/skills/architecture/references/code-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
**Hub files (most depended-on):**
- `src/lib/runner.js` - 9 dependents
- `src/lib/target.js` - 9 dependents
- `src/lib/errors.js` - 8 dependents
- `src/lib/scanner.js` - 8 dependents
- `src/lib/errors.js` - 7 dependents
- `src/lib/skill-writer.js` - 7 dependents

**Domain clusters:**
Expand All @@ -16,7 +16,7 @@
| src | 44 | `src/commands/doc-init.js`, `src/lib/runner.js`, `src/lib/target.js` |

**High-churn hotspots:**
- `src/commands/doc-init.js` - 33 changes
- `src/commands/doc-init.js` - 34 changes
- `src/commands/doc-sync.js` - 20 changes
- `src/lib/runner.js` - 17 changes

10 changes: 5 additions & 5 deletions .agents/skills/repo-scanning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ You are working on **aspens' repo scanning system** — a fully deterministic an
- **Multi-target detection:** Scanner checks for both `.claude` dir + `AGENTS.md` (Claude Code) and `.codex` dir + `AGENTS.md` (Codex CLI) to inform target selection during `doc init`
- **Detection via marker files:** Languages detected by presence of files like `package.json`, `go.mod`, `Cargo.toml` — not by scanning source extensions
- **Framework detection:** JS/TS from `package.json` deps, Python from `requirements.txt`/`pyproject.toml`/`Pipfile`, Go from `go.mod` contents, Ruby from `Gemfile`
- **Domain detection:** Scans dirs under source root + repo root, skips `SKIP_DIR_NAMES` set (structural/build/IDE dirs), requires at least one source file via `collectModules()`
- **Domain detection:** Scans dirs under source root + repo root, skips `SKIP_DIR_NAMES` set (structural/build/IDE/.NET/Java/Rust build dirs), requires at least one source file via `collectModules()`
- **extraDomains:** User-specified domains merged via `mergeExtraDomains()` — marked with `userSpecified: true`, resolved against source root then repo root
- **Source root:** First match of `src`, `app`, `lib`, `server`, `pages` via `findSourceRoot()`
- **Size estimation:** Lines estimated at ~40 bytes/line from `stat.size`, walk capped at depth 5
- **Size estimation:** Lines estimated at ~40 bytes/line from `stat.size`, walk capped at depth 5, skips `bin`/`obj`/`target` build output alongside `node_modules`/`dist`/etc.
- **Graph is opt-out:** `scanCommand` builds graph by default (`options.graph !== false`); errors are caught and only logged with `--verbose`

## Critical Rules
- **`SOURCE_EXTS`**: Only `.py`, `.ts`, `.js`, `.tsx`, `.jsx`, `.rb`, `.go`, `.rs` — adding a language requires updating this set AND the `detectLanguages` indicators
- **`SKIP_DIR_NAMES`**: Directories like `src`, `app`, `dist`, `node_modules` are skipped in domain detection — adding a skip dir here affects all repos
- **`SOURCE_EXTS`**: `.py`, `.ts`, `.js`, `.tsx`, `.jsx`, `.mjs`, `.cjs`, `.rb`, `.go`, `.rs`, `.java`, `.kt`, `.kts`, `.cs`, `.fs`, `.fsx`, `.swift`, `.php`, `.ex`, `.exs` — adding a language requires updating this set AND the `detectLanguages` indicators. Import graph / hub / cluster detection remains JS/TS/Python-only; other languages get domain discovery but a minimal atlas.
- **`SKIP_DIR_NAMES`**: Includes `src`, `app`, `bin`, `obj`, `dist`, `target`, `node_modules`, etc. — skipped in domain detection. `bin`/`obj`/`target` added to avoid .NET/Java/Rust build artifacts.
- **`BOILERPLATE_STEMS`**: `__init__`, `index`, `mod` are excluded from module collection — don't add real module names here
- **TypeScript implies JavaScript**: TS detection in `detectLanguages()` automatically adds JS to the languages array
- **Graph failure is non-fatal**: `buildRepoGraph` errors in `scanCommand()` are caught and silently ignored unless `--verbose`
Expand All @@ -46,4 +46,4 @@ You are working on **aspens' repo scanning system** — a fully deterministic an
- **No guidelines directory** — `.claude/guidelines/` does not exist yet for this domain

---
**Last Updated:** 2026-04-02
**Last Updated:** 2026-04-16
10 changes: 5 additions & 5 deletions .claude/skills/repo-scanning/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ You are working on **aspens' repo scanning system** — a fully deterministic an
- **Multi-target detection:** Scanner checks for both `.claude` dir + `CLAUDE.md` (Claude Code) and `.codex` dir + `AGENTS.md` (Codex CLI) to inform target selection during `doc init`
- **Detection via marker files:** Languages detected by presence of files like `package.json`, `go.mod`, `Cargo.toml` — not by scanning source extensions
- **Framework detection:** JS/TS from `package.json` deps, Python from `requirements.txt`/`pyproject.toml`/`Pipfile`, Go from `go.mod` contents, Ruby from `Gemfile`
- **Domain detection:** Scans dirs under source root + repo root, skips `SKIP_DIR_NAMES` set (structural/build/IDE dirs), requires at least one source file via `collectModules()`
- **Domain detection:** Scans dirs under source root + repo root, skips `SKIP_DIR_NAMES` set (structural/build/IDE/.NET/Java/Rust build dirs), requires at least one source file via `collectModules()`
- **extraDomains:** User-specified domains merged via `mergeExtraDomains()` — marked with `userSpecified: true`, resolved against source root then repo root
- **Source root:** First match of `src`, `app`, `lib`, `server`, `pages` via `findSourceRoot()`
- **Size estimation:** Lines estimated at ~40 bytes/line from `stat.size`, walk capped at depth 5
- **Size estimation:** Lines estimated at ~40 bytes/line from `stat.size`, walk capped at depth 5, skips `bin`/`obj`/`target` build output alongside `node_modules`/`dist`/etc.
- **Graph is opt-out:** `scanCommand` builds graph by default (`options.graph !== false`); errors are caught and only logged with `--verbose`

## Critical Rules
- **`SOURCE_EXTS`**: Only `.py`, `.ts`, `.js`, `.tsx`, `.jsx`, `.rb`, `.go`, `.rs` — adding a language requires updating this set AND the `detectLanguages` indicators
- **`SKIP_DIR_NAMES`**: Directories like `src`, `app`, `dist`, `node_modules` are skipped in domain detection — adding a skip dir here affects all repos
- **`SOURCE_EXTS`**: `.py`, `.ts`, `.js`, `.tsx`, `.jsx`, `.mjs`, `.cjs`, `.rb`, `.go`, `.rs`, `.java`, `.kt`, `.kts`, `.cs`, `.fs`, `.fsx`, `.swift`, `.php`, `.ex`, `.exs` — adding a language requires updating this set AND the `detectLanguages` indicators. Import graph / hub / cluster detection remains JS/TS/Python-only; other languages get domain discovery but a minimal atlas.
- **`SKIP_DIR_NAMES`**: Includes `src`, `app`, `bin`, `obj`, `dist`, `target`, `node_modules`, etc. — skipped in domain detection. `bin`/`obj`/`target` added to avoid .NET/Java/Rust build artifacts.
- **`BOILERPLATE_STEMS`**: `__init__`, `index`, `mod` are excluded from module collection — don't add real module names here
- **TypeScript implies JavaScript**: TS detection in `detectLanguages()` automatically adds JS to the languages array
- **Graph failure is non-fatal**: `buildRepoGraph` errors in `scanCommand()` are caught and silently ignored unless `--verbose`
Expand All @@ -46,4 +46,4 @@ You are working on **aspens' repo scanning system** — a fully deterministic an
- **No guidelines directory** — `.claude/guidelines/` does not exist yet for this domain

---
**Last Updated:** 2026-04-02
**Last Updated:** 2026-04-16
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
**Hub files (most depended-on):**
- `src/lib/runner.js` - 9 dependents
- `src/lib/target.js` - 9 dependents
- `src/lib/errors.js` - 8 dependents
- `src/lib/scanner.js` - 8 dependents
- `src/lib/errors.js` - 7 dependents
- `src/lib/skill-writer.js` - 7 dependents

**Domain clusters:**
Expand All @@ -14,7 +14,7 @@
| src | 44 | `src/commands/doc-init.js`, `src/lib/runner.js`, `src/lib/target.js` |

**High-churn hotspots:**
- `src/commands/doc-init.js` - 33 changes
- `src/commands/doc-init.js` - 34 changes
- `src/commands/doc-sync.js` - 20 changes
- `src/lib/runner.js` - 17 changes

9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

## [Unreleased]

## [0.7.1] - 2026-04-16

### Fixed
- **Domain discovery for C#, Java, Swift, PHP, and Elixir** — scanner's `SOURCE_EXTS` only recognized JS/TS/Python/Ruby/Go/Rust files, so `doc init` reported zero domains for projects in other languages even when language detection itself succeeded. Added `.cs`, `.java`, `.swift`, `.php`, `.ex`, `.exs`, `.mjs`, and `.cjs` to the source-extension set. Kotlin (`.kt`/`.kts`) and F# (`.fs`/`.fsx`) files are also counted as source now; full language detection for those will follow in a future release.
- **Build-output skipping** — repo-size estimation now skips `bin/`, `obj/`, and `target/` directories, and domain detection also skips `obj/`, preventing .NET / Java / Rust build artifacts from polluting module lists or line counts.

### Known Limitations
- Import graph, hub files, and cluster detection remain JS/TS/Python-only — `doc init` on C#/Java/Swift/PHP/Elixir/Kotlin/Rust/Go/Ruby projects will generate skills and domains but produce a minimal atlas. Full multi-language import parsing is tracked for a future release.
Comment on lines +5 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor date drift.

Release date here is 2026-04-16 but the skill files' Last Updated is 2026-04-17. Pick one to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` around lines 5 - 12, The changelog entry for version header
"[0.7.1] - 2026-04-16" conflicts with the skill files' "Last Updated" date of
2026-04-17; pick a single canonical date and make them consistent—either update
the CHANGELOG header "[0.7.1] - 2026-04-16" to 2026-04-17 or update the skill
files' "Last Updated" to 2026-04-16 so both match; ensure you change all
occurrences (the version header in CHANGELOG.md and any "Last Updated" fields in
the skill files) to the chosen date.


## [0.7.0] - 2026-04-10

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "aspens",
"version": "0.7.0",
"version": "0.7.1",
"description": "Keep coding-agent context accurate as your codebase changes",
"type": "module",
"bin": {
Expand All @@ -23,7 +23,7 @@
"test": "vitest run",
"start": "node bin/cli.js",
"lint": "echo 'No linter configured yet' && exit 0",
"postinstall": "echo '\n 📌 aspens v0.7.0: Existing repos should refresh generated docs after upgrading.\n Recommended: aspens doc sync --refresh\n\n 🌲 aspens is in active development — please keep it up to date.\n Run into issues? Let us know: https://github.com/aspenkit/aspens/issues\n'"
"postinstall": "echo '\n 📌 aspens v0.7.1: Scanner now recognizes C#, Java, Swift, PHP, and Elixir source files for domain discovery.\n If a prior `doc init` came up empty for one of these languages, re-run it after upgrading.\n\n 🌲 aspens is in active development — please keep it up to date.\n Run into issues? Let us know: https://github.com/aspenkit/aspens/issues\n'"
},
"engines": {
"node": ">=20"
Expand Down
3 changes: 1 addition & 2 deletions src/lib/context-builder.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, extname, relative } from 'path';
import { execSync } from 'child_process';
import { SOURCE_EXTS } from './source-exts.js';

/**
* Build context string from a repo scan result.
Expand Down Expand Up @@ -186,8 +187,6 @@ function listDirRecursive(dirPath, maxDepth, currentDepth = 0, prefix = '') {
}
}

const SOURCE_EXTS = new Set(['.js', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.rb', '.java', '.kt', '.swift', '.php', '.ex', '.exs']);

function listSourceFiles(dirPath) {
try {
return readdirSync(dirPath)
Expand Down
10 changes: 6 additions & 4 deletions src/lib/impact.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { buildRepoGraph } from './graph-builder.js';
import { loadConfig, TARGETS } from './target.js';
import { findSkillFiles } from './skill-reader.js';
import { getGitRoot } from './git-helpers.js';
import { SOURCE_EXTS as SCANNER_SOURCE_EXTS } from './source-exts.js';

// Freshness analysis scans a broader set than scanner domain detection —
// includes ecosystem extensions (.scala/.clj/.elm/.vue/.svelte) that scanner
// doesn't yet detect as languages but which still signal source-file edits.
const SOURCE_EXTS = new Set([
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
'.py', '.rb', '.go', '.rs', '.java', '.cs',
'.php', '.swift', '.kt', '.kts', '.scala',
'.clj', '.ex', '.exs', '.elm', '.vue', '.svelte',
...SCANNER_SOURCE_EXTS,
'.scala', '.clj', '.elm', '.vue', '.svelte',
]);

const LOW_SIGNAL_DOMAIN_NAMES = new Set([
Expand Down
7 changes: 4 additions & 3 deletions src/lib/scanner.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
import { join, basename, extname, relative } from 'path';
import { SOURCE_EXTS } from './source-exts.js';

/**
* Scan a repository and return its tech stack, structure, and domains.
Expand Down Expand Up @@ -82,7 +83,8 @@ function estimateRepoSize(repoPath) {
for (const entry of entries) {
if (entry.startsWith('.') || entry === 'node_modules' || entry === '__pycache__' ||
entry === 'dist' || entry === 'build' || entry === '.next' || entry === 'vendor' ||
entry === '.git' || entry === 'coverage') continue;
entry === '.git' || entry === 'coverage' || entry === 'bin' || entry === 'obj' ||
entry === 'target') continue;

const full = join(dir, entry);
try {
Expand Down Expand Up @@ -317,7 +319,7 @@ function detectStructure(repoPath) {

// Always skip — purely structural, build output, dependencies, IDE
const SKIP_DIR_NAMES = new Set([
'src', 'app', 'bin', 'cmd', 'pkg', 'internal', 'vendor',
'src', 'app', 'bin', 'obj', 'cmd', 'pkg', 'internal', 'vendor',
'dist', 'build', 'out', 'output', 'target', 'coverage',
'node_modules', '__pycache__', '.next', '.nuxt', '.cache',
'.github', '.vscode', '.idea', '.git',
Expand Down Expand Up @@ -631,4 +633,3 @@ function findSourceRoot(repoPath) {
return repoPath;
}

const SOURCE_EXTS = new Set(['.py', '.ts', '.js', '.tsx', '.jsx', '.rb', '.go', '.rs']);
18 changes: 18 additions & 0 deletions src/lib/source-exts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Canonical set of source-file extensions recognized by the scanner and
* context-builder. Keep in sync with `detectLanguages()` indicators in
* `scanner.js` when adding a language.
*
* Note: `graph-builder.js` keeps its own smaller set because it only parses
* JS/TS/Python imports.
*/
export const SOURCE_EXTS = new Set([
'.py',
'.ts', '.js', '.tsx', '.jsx', '.mjs', '.cjs',
'.rb', '.go', '.rs',
'.java', '.kt', '.kts',
'.cs', '.fs', '.fsx',
'.swift',
'.php',
'.ex', '.exs',
]);
54 changes: 54 additions & 0 deletions tests/scanner.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,60 @@ describe('scanRepo', () => {
expect(scan.domains.some(d => d.name === 'auth')).toBe(true);
});

it('detects domains from C# source files', () => {
const dir = createFixture('csharp-project', {
'MyApp.csproj': '<Project Sdk="Microsoft.NET.Sdk"></Project>',
'Controllers/UsersController.cs': 'public class UsersController {}',
'Controllers/OrdersController.cs': 'public class OrdersController {}',
'Services/PaymentService.cs': 'public class PaymentService {}',
});
const scan = scanRepo(dir);
expect(scan.languages).toContain('csharp');
expect(scan.domains.some(d => d.name === 'controllers')).toBe(true);
expect(scan.domains.some(d => d.name === 'services')).toBe(true);
});

it('detects domains and languages from Java, Swift, PHP, Elixir, Kotlin source files', () => {
const dir = createFixture('multi-lang-project', {
'pom.xml': '<project></project>',
'Package.swift': '// swift-tools-version:5.0',
'composer.json': '{}',
'mix.exs': 'defmodule App.MixProject do end',
'services/UserService.java': 'public class UserService {}',
'views/HomeView.swift': 'struct HomeView {}',
'handlers/webhook.php': '<?php class Webhook {}',
'workers/processor.ex': 'defmodule Processor do end',
'ui/MainActivity.kt': 'class MainActivity {}',
});
const scan = scanRepo(dir);
expect(scan.languages).toContain('java');
expect(scan.languages).toContain('swift');
expect(scan.languages).toContain('php');
expect(scan.languages).toContain('elixir');
const names = scan.domains.map(d => d.name);
expect(names).toContain('services');
expect(names).toContain('views');
expect(names).toContain('handlers');
expect(names).toContain('workers');
expect(names).toContain('ui');
});

it('skips bin, obj, and target build output directories', () => {
const dir = createFixture('build-output-project', {
'MyApp.csproj': '<Project></Project>',
'bin/Debug/MyApp.cs': '// compiled artifact',
'obj/project.assets.cs': '// intermediate',
'target/classes/Foo.java': '// build output',
'Services/Real.cs': 'public class Real {}',
});
const scan = scanRepo(dir);
const names = scan.domains.map(d => d.name);
expect(names).not.toContain('bin');
expect(names).not.toContain('obj');
expect(names).not.toContain('target');
expect(names).toContain('services');
});

it('returns empty domains for featureless project', () => {
const dir = createFixture('minimal-project', {
'package.json': '{}',
Expand Down
Loading