Pi skill integration, CLI polish, --fix flag, config file support

- SKILL.md: Proper Agent Skills frontmatter with name/description
- pi-extension.ts: Pi extension registering 4 custom tools
  (project_map_init/patch/validate/reinit) with prompt snippets/guidelines
- pi-extension.ts: Auto-detects .pi-map.md files on session start, warns
  about dirty markers, injects maintenance hints before agent start
- package.json: Added pi.extensions and pi.skills entries
- CLI: Added picocolors, clean help screen, progress indicators,
  summary output with timing, colored check/warning icons
- validate.ts: Added --fix flag that regenerates directories with
  discrepancies
- config.ts: Reads .pi-project-map.json from project root with merge
  over defaults
- init.ts: Added optional verbose parameter for programmatic use

All 16 tests pass. TypeScript compiles clean. Build succeeds.
This commit is contained in:
2026-06-09 20:54:59 +02:00
parent ab45859d65
commit 93c2ac60c5
9 changed files with 492 additions and 38 deletions
+112 -16
View File
@@ -1,24 +1,41 @@
---
name: pi-project-map
description: Generates and maintains hierarchical, machine-readable project analysis files (.pi-map.md) for instant codebase comprehension. Use when working with medium-to-large codebases where understanding architecture, file relationships, and exports without reading every file is valuable. Automatically extracts symbols via AST and LLM heuristics.
---
# pi-project-map # pi-project-map
A Pi skill that generates and maintains a hierarchical, machine-readable analysis of a software project. Each directory gets a `.pi-map.md` file containing architectural context, exported symbols, and dependencies. A Pi skill that generates and maintains a hierarchical, machine-readable analysis of a software project. Each directory gets a `.pi-map.md` file containing architectural context, exported symbols, and dependencies.
## Tools ## What It Does
### `project-map:init [root]` - **Scans** your entire project and creates one `.pi-map.md` per directory
Runs a full project scan and generates `.pi-map.md` files in every directory. - **Extracts** exports, imports, and dependencies via AST parsing (TypeScript, Python, Go) and LLM heuristics
- **Updates** incrementally when files change (full rewrite for small packages, section-level patch for large)
- **Validates** detects stale entries, missing files, orphaned entries, and changed signatures
### `project-map:patch <file-path>` ## Quick Start
Updates the `.pi-map.md` for the directory containing the given file. Uses full rewrite for small packages (< 10 files) or section-level patch for larger packages.
### `project-map:validate [root]` ```bash
Checks all `.pi-map.md` files for staleness: missing files, orphaned entries, changed signatures, and dirty markers. # Install globally
npm install -g pi-project-map
### `project-map:reinit [path]` # Generate analysis files for the entire project
Force full re-initialization of the entire project or a specific subtree. Clears all dirty markers. project-map init
# After editing a file, update its directory's analysis
project-map patch src/components/Button.tsx
# Check for staleness
project-map validate
# Force full regeneration
project-map reinit
```
## Format ## Format
Each `.pi-map.md` uses dense markdown with conventions: Each `.pi-map.md` uses dense markdown optimized for LLM consumption:
```markdown ```markdown
# pkg/auth # pkg/auth
@@ -33,6 +50,60 @@ Guard pattern on routes. Tokens short-lived (15m), refresh long-lived (7d). Rota
- -
``` ```
### Abbreviations
| Abbreviation | Meaning |
|-------------|---------|
| `exp:` | Exported symbols |
| `dep:` | Dependencies |
| `pkg/` | Internal package reference |
## Tools
### `project-map:init [root]`
Runs a full project scan and generates `.pi-map.md` files in every directory.
**Example:**
```bash
project-map init
project-map init ~/my-project
```
### `project-map:patch <file-path>`
Updates the `.pi-map.md` for the directory containing the given file.
**Behavior:**
- Small packages (< 10 files): full rewrite
- Large packages (>= 10 files): section-level patch
**Example:**
```bash
project-map patch src/components/Button.tsx
```
### `project-map:validate [root]`
Checks all `.pi-map.md` files for staleness.
**Detects:**
- Missing files (new files not yet in `.pi-map.md`)
- Orphaned entries (files listed but deleted)
- Stale signatures (exports changed since last scan)
- Dirty markers (packages flagged for reconciliation)
**Example:**
```bash
project-map validate
```
### `project-map:reinit [path]`
Force full re-initialization. Clears all dirty markers.
**Example:**
```bash
project-map reinit
project-map reinit src/components
```
## Configuration ## Configuration
Create `.pi-project-map.json` in the project root: Create `.pi-project-map.json` in the project root:
@@ -41,16 +112,41 @@ Create `.pi-project-map.json` in the project root:
{ {
"ignorePatterns": ["node_modules", ".git"], "ignorePatterns": ["node_modules", ".git"],
"smallPackageThreshold": 10, "smallPackageThreshold": 10,
"llmModel": "gpt-4o-mini",
"contextBudget": 4000, "contextBudget": 4000,
"autoInjectPrompt": true "autoInjectPrompt": true
} }
``` ```
## Installation | Option | Default | Description |
|--------|---------|-------------|
| `ignorePatterns` | `node_modules`, `.git`, `dist`, etc. | Additional ignore patterns |
| `smallPackageThreshold` | `10` | File count threshold for full rewrite vs patch |
| `contextBudget` | `4000` | Max tokens to spend on analysis files |
| `autoInjectPrompt` | `true` | Auto-inject maintenance instructions |
```bash ## Agent Instructions
npm install -g pi-project-map
```
Then add to your Pi skills configuration. When `.pi-map.md` files exist in the project:
1. **Read them at session start** to build project understanding without scanning every file
2. **Run `project-map:patch <path>`** after editing any source file
3. **Run `project-map:validate`** if you suspect staleness before making architectural decisions
4. **Trust the analysis** for orientation, but verify critical details by reading source when needed
## Best Practices
- Run `project-map:init` after cloning a new repository
- Run `project-map:reinit` periodically (daily/weekly) to catch changes made outside the agent
- Add `.pi-map.md` to `.gitignore` — they are derived artifacts
- For very large projects (> 1000 directories), consider running `init` on subdirectories
## Supported Languages
| Language | AST Parsing | Heuristic Extraction |
|----------|------------|---------------------|
| TypeScript / TSX | Full | Full |
| JavaScript / JSX | Full | Full |
| Python | Partial | Full |
| Go | Partial | Full |
| Rust | Partial | Full |
| Other | - | Full (filename + regex patterns) |
+1 -1
View File
@@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ignore": "^5.3.0", "ignore": "^5.3.0",
"picocolors": "^1.1.1",
"tree-sitter": "^0.21.0", "tree-sitter": "^0.21.0",
"tree-sitter-go": "^0.25.0", "tree-sitter-go": "^0.25.0",
"tree-sitter-python": "^0.25.0", "tree-sitter-python": "^0.25.0",
@@ -2860,7 +2861,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
+9
View File
@@ -23,6 +23,14 @@
], ],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"pi": {
"extensions": [
"./pi-extension.ts"
],
"skills": [
"./SKILL.md"
]
},
"devDependencies": { "devDependencies": {
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
@@ -34,6 +42,7 @@
}, },
"dependencies": { "dependencies": {
"ignore": "^5.3.0", "ignore": "^5.3.0",
"picocolors": "^1.1.1",
"tree-sitter": "^0.21.0", "tree-sitter": "^0.21.0",
"tree-sitter-go": "^0.25.0", "tree-sitter-go": "^0.25.0",
"tree-sitter-python": "^0.25.0", "tree-sitter-python": "^0.25.0",
+183
View File
@@ -0,0 +1,183 @@
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { Type } from "typebox";
import { execSync } from "child_process";
import { readFileSync } from "fs";
import { join } from "path";
function runCommand(
command: string,
args: string[],
cwd: string,
): { stdout: string; stderr: string; success: boolean } {
try {
const result = execSync(`npx project-map ${command} ${args.join(" ")}`, {
cwd,
encoding: "utf8",
stdio: ["pipe", "pipe", "pipe"],
});
return { stdout: result, stderr: "", success: true };
} catch (error: any) {
return {
stdout: error.stdout || "",
stderr: error.stderr || error.message,
success: false,
};
}
}
function findPiMapFiles(cwd: string): string[] {
try {
const result = execSync('find . -name ".pi-map.md" -type f', {
cwd,
encoding: "utf8",
});
return result.trim().split("\n").filter(Boolean);
} catch {
return [];
}
}
export default function (pi: ExtensionAPI) {
// Register custom tools
pi.registerTool({
name: "project_map_init",
label: "Project Map Init",
description:
"Generate .pi-map.md analysis files for the entire project or a subdirectory",
promptSnippet:
"Initialize project analysis files for codebase understanding",
promptGuidelines: [
"Use project_map_init when starting work on a new project or after significant restructuring",
"Run project_map_init when .pi-map.md files are missing or severely outdated",
],
parameters: Type.Object({
path: Type.Optional(
Type.String({
description: "Project root path (default: current directory)",
}),
),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const targetPath = params.path || ctx.cwd;
const result = runCommand("init", targetPath === ctx.cwd ? [] : [targetPath], ctx.cwd);
return {
content: [{ type: "text", text: result.stdout || result.stderr }],
details: { success: result.success, cwd: ctx.cwd },
};
},
});
pi.registerTool({
name: "project_map_patch",
label: "Project Map Patch",
description: "Update .pi-map.md for the directory containing a changed file",
promptSnippet: "Update project analysis after editing a source file",
promptGuidelines: [
"Use project_map_patch immediately after editing any source file",
"Pass the absolute or relative path of the modified file",
],
parameters: Type.Object({
file_path: Type.String({
description: "Path to the modified file",
}),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const result = runCommand("patch", [params.file_path], ctx.cwd);
return {
content: [{ type: "text", text: result.stdout || result.stderr }],
details: { success: result.success },
};
},
});
pi.registerTool({
name: "project_map_validate",
label: "Project Map Validate",
description: "Check all .pi-map.md files for staleness and discrepancies",
promptSnippet: "Validate project analysis files for accuracy",
promptGuidelines: [
"Use project_map_validate before making architectural decisions if you suspect stale data",
"Use project_map_validate to detect files that were deleted or added outside the agent",
],
parameters: Type.Object({
path: Type.Optional(
Type.String({
description: "Project root path (default: current directory)",
}),
),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const targetPath = params.path || ctx.cwd;
const result = runCommand("validate", targetPath === ctx.cwd ? [] : [targetPath], ctx.cwd);
return {
content: [{ type: "text", text: result.stdout || result.stderr }],
details: { success: result.success, clean: result.stdout.includes("clean") },
};
},
});
pi.registerTool({
name: "project_map_reinit",
label: "Project Map Reinit",
description: "Force full regeneration of all .pi-map.md files",
promptSnippet: "Force full regeneration of project analysis files",
promptGuidelines: [
"Use project_map_reinit when validation shows widespread staleness",
"Use project_map_reinit after pulling major changes from version control",
],
parameters: Type.Object({
path: Type.Optional(
Type.String({
description: "Path to regenerate (default: entire project)",
}),
),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const args = params.path ? [params.path] : [];
const result = runCommand("reinit", args, ctx.cwd);
return {
content: [{ type: "text", text: result.stdout || result.stderr }],
details: { success: result.success },
};
},
});
// Auto-load .pi-map.md files on session start
pi.on("session_start", async (_event, ctx) => {
const mapFiles = findPiMapFiles(ctx.cwd);
if (mapFiles.length === 0) return;
// Check for dirty markers
const dirtyFiles = mapFiles.filter((f) => {
try {
const content = readFileSync(join(ctx.cwd, f), "utf8");
return content.includes("## dirty") && !content.includes("## dirty\n-");
} catch {
return false;
}
});
if (dirtyFiles.length > 0) {
ctx.ui.notify(
`pi-project-map: ${dirtyFiles.length} dirty packages detected. Run project_map_validate or project_map_reinit.`,
"warning",
);
}
});
// Inject maintenance instructions before agent starts
pi.on("before_agent_start", async (_event, _ctx) => {
// Only inject if .pi-map.md files exist
const mapFiles = findPiMapFiles(_ctx.cwd);
if (mapFiles.length === 0) return {};
return {
message: {
customType: "pi-project-map-hint",
content:
"📋 Project map active: If you modify any source file, run `project_map_patch` with the file path. If you suspect staleness, run `project_map_validate`.",
display: false, // Don't show in UI, only in LLM context
},
};
});
}
+108 -8
View File
@@ -3,30 +3,130 @@ import { initProject } from "./init.js";
import { patchFile } from "./patch.js"; import { patchFile } from "./patch.js";
import { validateMaps } from "./validate.js"; import { validateMaps } from "./validate.js";
import { reinitPath } from "./init.js"; import { reinitPath } from "./init.js";
import { discoverProject } from "./discover.js";
import pc from "picocolors";
const args = process.argv.slice(2); const args = process.argv.slice(2);
const command = args[0]; const command = args[0];
function printUsage() {
console.log(`${pc.bold("project-map")} — hierarchical project analysis for Pi agents
`);
console.log(`${pc.bold("Usage:")}`);
console.log(` project-map ${pc.cyan("init")} [path] Generate .pi-map.md files for all directories`);
console.log(` project-map ${pc.cyan("patch")} <file> Update analysis for a changed file's directory`);
console.log(` project-map ${pc.cyan("validate")} [--fix] [path] Check for stale/missing/orphaned entries`);
console.log(` project-map ${pc.cyan("reinit")} [path] Force full regeneration`);
console.log(` project-map ${pc.cyan("--help")} Show this help message`);
console.log(` project-map ${pc.cyan("--version")} Show version\n`);
console.log(`${pc.bold("Examples:")}`);
console.log(` project-map init`);
console.log(` project-map patch src/components/Button.tsx`);
console.log(` project-map validate --fix`);
console.log(` project-map reinit`);
}
function printVersion() {
const pkg = require("../package.json");
console.log(pkg.version);
}
function formatCount(count: number, label: string): string {
const plural =
label.endsWith("y")
? `${label.slice(0, -1)}ies`
: `${label}${count === 1 ? "" : "s"}`;
return `${pc.bold(String(count))} ${count === 1 ? label : plural}`;
}
function parseValidateArgs(args: string[]): { path: string; fix: boolean } {
let path = ".";
let fix = false;
for (const arg of args.slice(1)) {
if (arg === "--fix") {
fix = true;
} else if (!arg.startsWith("-")) {
path = arg;
}
}
return { path, fix };
}
async function main() { async function main() {
if (!command || command === "--help" || command === "-h") {
printUsage();
process.exit(0);
}
if (command === "--version" || command === "-v") {
printVersion();
process.exit(0);
}
switch (command) { switch (command) {
case "init": case "init": {
await initProject(args[1] || "."); const targetPath = args[1] || ".";
const start = Date.now();
const entries = discoverProject(targetPath);
console.log(`Scanning ${formatCount(entries.length, "directory")}...`);
await initProject(targetPath, { verbose: false });
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
console.log(
`${pc.green("✓")} Generated ${formatCount(entries.length, ".pi-map.md file")} in ${elapsed}s`,
);
break; break;
case "patch": }
case "patch": {
if (!args[1]) {
console.error(`${pc.red("Error:")} Missing file path. Usage: project-map patch <file>`);
process.exit(1);
}
await patchFile(args[1]); await patchFile(args[1]);
console.log(`${pc.green("✓")} Patched`);
break; break;
}
case "validate": { case "validate": {
const result = await validateMaps(args[1] || "."); const { path, fix } = parseValidateArgs(args);
const result = await validateMaps(path, { fix, verbose: true });
if (result.clean) {
console.log(`${pc.green("✓")} All .pi-map.md files are clean.`);
} else {
const counts: Record<string, number> = {};
for (const d of result.discrepancies) {
counts[d.type] = (counts[d.type] || 0) + 1;
}
const summary = Object.entries(counts)
.map(([type, count]) => `${count} ${type}`)
.join(", ");
const fixMsg =
fix && result.fixed !== undefined
? ` (${pc.green("✓")} fixed ${formatCount(result.fixed, "directory")})`
: "";
console.log(
`${pc.yellow("⚠")} Found ${formatCount(result.discrepancies.length, "discrepancy")}: ${summary}${fixMsg}`,
);
}
process.exit(result.clean ? 0 : 1); process.exit(result.clean ? 0 : 1);
break; break;
} }
case "reinit": case "reinit": {
await reinitPath(args[1] || "."); const targetPath = args[1] || ".";
const start = Date.now();
const entries = discoverProject(targetPath);
console.log(`Regenerating ${formatCount(entries.length, ".pi-map.md file")}...`);
await reinitPath(targetPath, { verbose: false });
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
console.log(`${pc.green("✓")} Regenerated in ${elapsed}s`);
break; break;
}
default: default:
console.log(`Usage: project-map <init|patch|validate|reinit> [path]`); console.error(`${pc.red("Error:")} Unknown command "${command}"`);
console.error(`Run ${pc.cyan("project-map --help")} for usage.`);
process.exit(1); process.exit(1);
} }
} }
main(); main().catch((err) => {
console.error(`${pc.red("Error:")} ${err.message}`);
process.exit(1);
});
+23 -2
View File
@@ -1,3 +1,6 @@
import { existsSync, readFileSync } from "fs";
import { join } from "path";
export interface SkillConfig { export interface SkillConfig {
ignorePatterns: string[]; ignorePatterns: string[];
smallPackageThreshold: number; smallPackageThreshold: number;
@@ -18,6 +21,15 @@ export const DEFAULT_CONFIG: SkillConfig = {
"__pycache__", "__pycache__",
".DS_Store", ".DS_Store",
"*.log", "*.log",
".pi-map.md",
".cache",
"tmp",
"temp",
".tmp",
".turbo",
".parcel-cache",
".eslintcache",
".prettiercache",
], ],
smallPackageThreshold: 10, smallPackageThreshold: 10,
llmModel: "gpt-4o-mini", llmModel: "gpt-4o-mini",
@@ -25,7 +37,16 @@ export const DEFAULT_CONFIG: SkillConfig = {
autoInjectPrompt: true, autoInjectPrompt: true,
}; };
export function loadConfig(): SkillConfig { export function loadConfig(cwd: string = process.cwd()): SkillConfig {
// TODO: load from .pi-project-map.json or similar const configPath = join(cwd, ".pi-project-map.json");
if (existsSync(configPath)) {
try {
const content = readFileSync(configPath, "utf8");
const userConfig = JSON.parse(content);
return { ...DEFAULT_CONFIG, ...userConfig };
} catch {
// Fall through to default
}
}
return DEFAULT_CONFIG; return DEFAULT_CONFIG;
} }
+12 -4
View File
@@ -10,14 +10,19 @@ import { mergeFileData } from "./merge.js";
import { writeFileSync } from "fs"; import { writeFileSync } from "fs";
import { join } from "path"; import { join } from "path";
export async function initProject(rootPath: string): Promise<void> { export async function initProject(
rootPath: string,
options?: { verbose?: boolean },
): Promise<void> {
const entries = discoverProject(rootPath); const entries = discoverProject(rootPath);
for (const entry of entries) { for (const entry of entries) {
await generateDirectoryMap(entry); await generateDirectoryMap(entry);
} }
console.log(`Generated ${entries.length} .pi-map.md files`); if (options?.verbose !== false) {
console.log(`Generated ${entries.length} .pi-map.md files`);
}
} }
export async function generateDirectoryMap( export async function generateDirectoryMap(
@@ -46,7 +51,10 @@ export async function generateDirectoryMap(
return fileData; return fileData;
} }
export async function reinitPath(path: string): Promise<void> { export async function reinitPath(
path: string,
options?: { verbose?: boolean },
): Promise<void> {
// Full regeneration clears all dirty markers by overwriting every .pi-map.md // Full regeneration clears all dirty markers by overwriting every .pi-map.md
await initProject(path); await initProject(path, options);
} }
+43 -6
View File
@@ -3,10 +3,12 @@ import { parsePackageMap } from "./format.js";
import { existsSync, readFileSync } from "fs"; import { existsSync, readFileSync } from "fs";
import { join } from "path"; import { join } from "path";
import { extractFileAST } from "./ast-extract.js"; import { extractFileAST } from "./ast-extract.js";
import { generateDirectoryMap } from "./init.js";
export interface ValidationResult { export interface ValidationResult {
clean: boolean; clean: boolean;
discrepancies: Discrepancy[]; discrepancies: Discrepancy[];
fixed?: number;
} }
export interface Discrepancy { export interface Discrepancy {
@@ -17,9 +19,12 @@ export interface Discrepancy {
export async function validateMaps( export async function validateMaps(
rootPath: string, rootPath: string,
options?: { fix?: boolean; verbose?: boolean },
): Promise<ValidationResult> { ): Promise<ValidationResult> {
const { fix = false, verbose = true } = options || {};
const discrepancies: Discrepancy[] = []; const discrepancies: Discrepancy[] = [];
const entries = discoverProject(rootPath); const entries = discoverProject(rootPath);
const dirsToFix = new Set<string>();
for (const entry of entries) { for (const entry of entries) {
const mapPath = join(entry.dirPath, ".pi-map.md"); const mapPath = join(entry.dirPath, ".pi-map.md");
@@ -29,10 +34,12 @@ export async function validateMaps(
path: entry.relativePath, path: entry.relativePath,
message: "No .pi-map.md found", message: "No .pi-map.md found",
}); });
if (fix) dirsToFix.add(entry.dirPath);
continue; continue;
} }
const mapData = parsePackageMap(readFileSync(mapPath, "utf8")); const mapData = parsePackageMap(readFileSync(mapPath, "utf8"));
let mapNeedsRewrite = false;
// Check for dirty markers // Check for dirty markers
if (mapData.dirty && mapData.dirty !== "-") { if (mapData.dirty && mapData.dirty !== "-") {
@@ -41,6 +48,7 @@ export async function validateMaps(
path: mapPath, path: mapPath,
message: `Dirty: ${mapData.dirty}`, message: `Dirty: ${mapData.dirty}`,
}); });
if (fix) mapNeedsRewrite = true;
} }
// Check for orphaned entries // Check for orphaned entries
@@ -52,6 +60,7 @@ export async function validateMaps(
path: filePath, path: filePath,
message: `File listed but deleted: ${fileEntry.name}`, message: `File listed but deleted: ${fileEntry.name}`,
}); });
if (fix) mapNeedsRewrite = true;
} }
} }
@@ -63,6 +72,7 @@ export async function validateMaps(
path: join(entry.dirPath, file), path: join(entry.dirPath, file),
message: `File not in .pi-map.md: ${file}`, message: `File not in .pi-map.md: ${file}`,
}); });
if (fix) mapNeedsRewrite = true;
} }
} }
@@ -83,6 +93,7 @@ export async function validateMaps(
path: filePath, path: filePath,
message: `Missing export: ${exp}`, message: `Missing export: ${exp}`,
}); });
if (fix) mapNeedsRewrite = true;
} }
} }
for (const exp of actualExports) { for (const exp of actualExports) {
@@ -92,10 +103,27 @@ export async function validateMaps(
path: filePath, path: filePath,
message: `New export: ${exp}`, message: `New export: ${exp}`,
}); });
if (fix) mapNeedsRewrite = true;
} }
} }
} }
} }
if (fix && mapNeedsRewrite) {
dirsToFix.add(entry.dirPath);
}
}
// Apply fixes
let fixed = 0;
if (fix && dirsToFix.size > 0) {
for (const dirPath of dirsToFix) {
const entry = entries.find((e) => e.dirPath === dirPath);
if (entry) {
await generateDirectoryMap(entry);
fixed++;
}
}
} }
const result: ValidationResult = { const result: ValidationResult = {
@@ -103,12 +131,21 @@ export async function validateMaps(
discrepancies, discrepancies,
}; };
if (result.clean) { if (fix) {
console.log("All .pi-map.md files are clean."); result.fixed = fixed;
} else { }
console.log(`Found ${discrepancies.length} discrepancies:`);
for (const d of discrepancies) { if (verbose) {
console.log(` [${d.type}] ${d.path}: ${d.message}`); if (result.clean) {
console.log("All .pi-map.md files are clean.");
} else {
console.log(`Found ${discrepancies.length} discrepancies:`);
for (const d of discrepancies) {
console.log(` [${d.type}] ${d.path}: ${d.message}`);
}
}
if (fix && fixed > 0) {
console.log(`Fixed ${fixed} director${fixed === 1 ? "y" : "ies"}.`);
} }
} }
+1 -1
View File
@@ -15,5 +15,5 @@
"resolveJsonModule": true "resolveJsonModule": true
}, },
"include": ["src/**/*"], "include": ["src/**/*"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist", "pi-extension.ts"]
} }