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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Use an issue before opening a pull request for:
Workflow:

1. Read the PRD and relevant documentation.
2. Create a `codex/` branch and prepare the implementation locally.
2. If you Codex agent = Create a `codex/` branch and prepare the implementation locally, if not codex dont create a branch with prefix `codex/` and prepare the implementation locally.
3. Add or update tests, documentation, and personal development notes.
4. Run the relevant verification gates.
5. Before committing, pushing, or opening the PR, stop and ask the user to
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ All notable changes to DevMap are documented in this file.

### Changed

- Snapshot metadata now separates the primary framework from frameworks found
in workspace packages, prioritizes root documentation, and emits semantic
file purposes and provider-neutral AI flows
- `devmap init` defaults OpenRouter to `openrouter/free` on Enter and explains
how to change the stored model with `devmap config model <model-id>`
- Feature detection now separates documentation, landing UI, CLI commands,
Expand Down
14 changes: 8 additions & 6 deletions PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,14 @@ Preferred reading order:
The index must remain short and must not duplicate full dependency or change
impact data.

The index project header includes separate `framework`, `projectType`, and
`workspaceType` fields. Framework remains a detected framework such as Next.js
or Express; project type describes the primary shape such as `node-cli`,
`web-app`, `api-service`, or `library`; workspace type distinguishes a
monorepo from a single package. Its deterministic summary uses package
description and detected capabilities instead of file-count filler.
The index project header includes `framework`, `frameworks`, `projectType`, and
`workspaceType`. `framework` is the primary project framework and remains
`unknown` for a CLI/library workspace without one primary web framework.
`frameworks` lists frameworks detected in workspace packages, such as Astro in
a landing app. Project type describes the primary shape such as `node-cli`,
`web-app`, `api-service`, or `library`; workspace type distinguishes a monorepo
from a single package. Its deterministic summary uses package description and
detected capabilities instead of file-count filler.

`criticalFiles` is a start-here list, not an import-count leaderboard. It
prioritizes executable entry points, CLI/feature orchestrators, and files that
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ Use your preferred AI tool to change it.
* Laravel
* Nuxt

Workspace classification can identify Astro packages, but deep Astro analysis
is not part of the current MVP support promise.

---

## AI Provider Setup
Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,17 @@ Framework detection should use:
| `express` dependency | Express |
| `server.ts` or `server.js` | Node/Express entry point |
| `react` plus browser runtime/tooling and JSX/TSX source | Standalone React |
| `astro` dependency or `src/pages/*.astro` | Astro workspace framework |

Next.js detection runs before React because Next projects also depend on
React. A generic `src/app/` folder is not enough to infer Next.js; source-only
fallback requires Next conventions such as `app/page`, `app/layout`,
`app/route`, or a Next config file.

Snapshots distinguish the primary `framework` from the additive `frameworks`
list. A Node CLI monorepo with an Astro landing site has `framework: unknown`,
`projectType: node-cli`, and `frameworks: [astro]`. Astro detection here is
classification only; deep Astro route/component analysis is future work.

---

Expand Down
21 changes: 20 additions & 1 deletion docs/for-me-personal/PROGRESS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
# Progress DevMap

Terakhir diperbarui: 2026-06-20
Terakhir diperbarui: 2026-06-21

## Update 2026-06-21

### Snapshot Accuracy For Mixed Workspaces

- Project metadata sekarang memisahkan primary `framework` dari daftar
`frameworks` yang ditemukan pada package workspace.
- DevMap sendiri diklasifikasikan sebagai `node-cli` monorepo dengan primary
framework `unknown` dan workspace framework `astro`, bukan Next.js.
- Astro detection saat ini hanya memakai dependency dan pola page `.astro`;
belum ada deep Astro analyzer.
- Documentation feature map memprioritaskan root README, AGENTS, CONTRIBUTING,
dan PRD sebelum README internal di assets/package.
- Static file purpose menjelaskan tanggung jawab file dan tidak lagi memakai
template daftar export yang diawali `exposes`.
- AI flow menyebut Groq dan OpenRouter sebagai adapter provider alternatif.
- Critical scoring memberi bobot lebih besar pada execution owners seperti
project map dan file scanner, serta menurunkan shared types/constants.
- Regression fixture mixed CLI/Astro dan full suite lulus dengan 118 test.

## Update 2026-06-20

Expand Down
22 changes: 22 additions & 0 deletions docs/for-me-personal/TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ Remove-Item Env:OPENROUTER_API_KEY
Jangan simpan atau menyalin API key nyata ke repository, snapshot, output test,
atau dokumentasi debugging.

## Mixed Workspace Snapshot Accuracy

Jalankan static analyze pada root DevMap dengan config AI terisolasi:

```powershell
$oldProfile = $env:USERPROFILE
$env:USERPROFILE = Join-Path $env:TEMP "devmap-mixed-workspace-test"
pnpm dev:cli -- analyze . --fresh --json
$env:USERPROFILE = $oldProfile
```

Periksa `.devmap/index.json` dan feature maps. Expected:

- `projectType` adalah `node-cli`;
- primary `framework` adalah `unknown`;
- `frameworks` memuat `astro`;
- Documentation dimulai dari root `README.md`, bukan README dalam assets;
- purpose `ai/provider.ts` menjelaskan pemilihan provider/model routing dan
tidak memakai kata `exposes`;
- AI flow menyebut `groq.ts` dan `openrouter.ts`;
- main critical list tetap dimulai dari CLI entry/analyze/project map.

## Ts-Morph Dan Agent Navigation

Focused tests:
Expand Down
8 changes: 8 additions & 0 deletions docs/generated-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ contains project identity, entry points, a short critical-file list, and compact
feature descriptors that link to focused feature maps. It intentionally omits
full dependency and change-impact data.

Project identity separates the primary `framework` from `frameworks` detected
across workspace packages. This prevents an Astro or Next.js child app from
being presented as the framework of a CLI-centered monorepo.

Documentation feature maps prioritize root `README.md`, `AGENTS.md`, and
contributor guidance before nested package or asset README files. File roles
describe responsibilities rather than repeating export names.

Its critical-file list prioritizes executable entry points, feature entry
points, and one behavioral support file per feature before falling back to
global importance scores. Type-only hubs are not promoted solely because many
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/ai/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { ProjectMap } from "../analyzers/projectMap.js";
import type { QuestionContext } from "./contextBuilder.js";
import type { AiMessage } from "./types.js";

export type AskProjectSummary = Pick<ProjectMap["project"], "name" | "framework">;
export type AskProjectSummary = Pick<ProjectMap["project"], "name" | "framework">
& { frameworks?: ProjectMap["project"]["frameworks"] };

export function buildQueryExpansionMessages(query: string): AiMessage[] {
return [
Expand Down Expand Up @@ -127,6 +128,7 @@ export function buildAskMessages(
content: [
`PROJECT: ${project.name}`,
`FRAMEWORK: ${project.framework}`,
`WORKSPACE_FRAMEWORKS: ${project.frameworks?.join(", ") || "none"}`,
`INTENT: ${context.intent}`,
`KEYWORDS: ${context.keywords.length > 0 ? context.keywords.join(", ") : "none"}`,
`EXPANDED_TERMS: ${context.expandedTerms.length > 0 ? context.expandedTerms.join(", ") : "none"}`,
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/analyzers/featureDetector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,16 @@ export function detectAuthenticationSemanticRole(

function featureFilePriority(featureName: string, path: string): number {
const normalized = path.toLowerCase();
if (featureName === "Documentation") {
if (normalized === "readme.md") return 0;
if (normalized === "agents.md") return 1;
if (normalized === "contributing.md") return 2;
if (normalized === "prd.md") return 3;
if (/^packages\/[^/]+\/readme\.md$/.test(normalized)) return 10;
if (/^docs\//.test(normalized)) return 20;
return 30 + normalized.split("/").length;
}

const index = FEATURE_FILE_PRIORITIES[featureName]
?.findIndex((pattern) => pattern.test(normalized)) ?? -1;
return index === -1 ? 100 : index;
Expand Down
71 changes: 42 additions & 29 deletions packages/cli/src/analyzers/frameworkDetector.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,66 @@
import type { ScannedFile } from "./fileScanner.js";
import { isArchitectureSource } from "./sourceScope.js";

export type Framework = "nextjs" | "react" | "express" | "unknown";
export type Framework = "nextjs" | "react" | "express" | "astro" | "unknown";
export type DetectedFramework = Exclude<Framework, "unknown">;

export function detectFramework(files: ScannedFile[]): Framework {
const dependencies = readAllDependencies(files);
const sourceFiles = files.filter((file) => isArchitectureSource(file.path));
return detectFrameworks(files)[0] ?? "unknown";
}

export function detectFrameworks(files: ScannedFile[]): DetectedFramework[] {
const detected = new Set<DetectedFramework>();
for (const dependencies of readManifestDependencies(files)) {
if ("next" in dependencies) detected.add("nextjs");
if ("express" in dependencies) detected.add("express");
if ("astro" in dependencies) detected.add("astro");

const hasReactRuntime = "react-dom" in dependencies
|| "react-scripts" in dependencies
|| "@vitejs/plugin-react" in dependencies
|| "@vitejs/plugin-react-swc" in dependencies;
if (
!("next" in dependencies)
&& !("astro" in dependencies)
&& "react" in dependencies
&& hasReactRuntime
) {
detected.add("react");
}
}

const sourceFiles = files.filter((file) => isArchitectureSource(file.path));
if (
"next" in dependencies
|| files.some((file) => /^next\.config\.[cm]?[jt]s$/.test(file.path))
files.some((file) => /(^|\/)next\.config\.[cm]?[jt]s$/.test(file.path))
|| sourceFiles.some((file) =>
/^(?:src\/)?app\/(?:.+\/)?(?:page|layout|route)\.[jt]sx?$/.test(file.path)
|| /^(?:src\/)?pages\/(?:_app|_document|api\/)/.test(file.path)
/(^|\/)(?:src\/)?app\/(?:.+\/)?(?:page|layout|route)\.[jt]sx?$/.test(file.path)
|| /(^|\/)(?:src\/)?pages\/(?:_app|_document|api\/)/.test(file.path)
)
) {
return "nextjs";
detected.add("nextjs");
}

if (
"express" in dependencies
|| sourceFiles.some((file) => /^(?:src\/)?(?:server|app)\.[cm]?[jt]s$/.test(file.path))
sourceFiles.some((file) => /(^|\/)(?:src\/)?(?:server|app)\.[cm]?[jt]s$/.test(file.path))
) {
return "express";
detected.add("express");
}

const hasReactRuntime = "react-dom" in dependencies
|| "react-scripts" in dependencies
|| "@vitejs/plugin-react" in dependencies
|| "@vitejs/plugin-react-swc" in dependencies;
const hasReactSource = sourceFiles.some((file) =>
/\.[jt]sx$/.test(file.path)
|| /(?:from\s+["']react["']|from\s+["']react-dom(?:\/client)?["']|require\(["']react["']\))/.test(file.content)
);

if ("react" in dependencies && hasReactRuntime && hasReactSource) {
return "react";
if (sourceFiles.some((file) => /(^|\/)src\/pages\/.+\.astro$/.test(file.path))) {
detected.add("astro");
}

return "unknown";
return FRAMEWORK_ORDER.filter((framework) => detected.has(framework));
}

function readAllDependencies(files: ScannedFile[]): Record<string, string> {
const FRAMEWORK_ORDER: DetectedFramework[] = ["nextjs", "express", "react", "astro"];

function readManifestDependencies(files: ScannedFile[]): Array<Record<string, string>> {
return files
.filter((file) => file.path.endsWith("package.json"))
.reduce((dependencies, file) => ({
...dependencies,
...readDependencies(file.content)
}), {} as Record<string, string>);
.filter((file) =>
file.path.endsWith("package.json") && isArchitectureSource(file.path)
)
.map((file) => readDependencies(file.content));
}

function readDependencies(content: string): Record<string, string> {
Expand Down
Loading
Loading