@aprimediet/codewalker 1.0.0 → 1.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.
package/compat.ts DELETED
@@ -1,217 +0,0 @@
1
- /**
2
- * Compatibility probe: detect if @aprimediet/minion and @aprimediet/memory are active
3
- * for this project by reading the shared .pi/<project-id>.md marker and probing
4
- * global directories.
5
- */
6
-
7
- import * as fs from "node:fs";
8
- import * as path from "node:path";
9
- import { CONFIG_DIR_NAME, getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
10
-
11
- export interface CompatResult {
12
- projectId: string | null;
13
- minionActive: boolean;
14
- memoryActive: boolean;
15
- memoryEntries: number;
16
- openTaskCount: number;
17
- openTaskSummary: string;
18
- memorySummary: string;
19
- }
20
-
21
- /**
22
- * Walk upward from cwd until we find a dir containing .pi/ or .git/.
23
- * Return cwd if root is reached without finding either.
24
- */
25
- function findProjectRoot(cwd: string): string {
26
- let dir = cwd;
27
- for (;;) {
28
- const piPath = path.join(dir, CONFIG_DIR_NAME);
29
- const gitPath = path.join(dir, ".git");
30
- if (fs.existsSync(piPath) || fs.existsSync(gitPath)) {
31
- return dir;
32
- }
33
- const parent = path.dirname(dir);
34
- if (parent === dir) {
35
- // reached filesystem root
36
- return cwd;
37
- }
38
- dir = parent;
39
- }
40
- }
41
-
42
- /**
43
- * Scan <configDir>/*.md files for one with frontmatter containing pi-project: true.
44
- * Return that file's id: value, or null if none found.
45
- */
46
- function readMarkerId(configDir: string): string | null {
47
- if (!fs.existsSync(configDir)) {
48
- return null;
49
- }
50
-
51
- let names: string[];
52
- try {
53
- names = fs.readdirSync(configDir).filter((n) => n.endsWith(".md"));
54
- } catch {
55
- return null;
56
- }
57
-
58
- for (const name of names) {
59
- const file = path.join(configDir, name);
60
- try {
61
- const content = fs.readFileSync(file, "utf-8");
62
- const { frontmatter } = parseFrontmatter<Record<string, string>>(content);
63
- if (frontmatter && String(frontmatter["pi-project"]) === "true" && frontmatter.id) {
64
- return frontmatter.id;
65
- }
66
- } catch {
67
- // not a valid marker, skip
68
- }
69
- }
70
-
71
- return null;
72
- }
73
-
74
- /**
75
- * Extract status and title from task frontmatter.
76
- * Return { status, title } or null if cannot parse.
77
- * title may be undefined if missing from frontmatter; callers should supply a fallback.
78
- */
79
- function parseTaskMetadata(content: string): { status: string; title: string | undefined } | null {
80
- try {
81
- const { frontmatter } = parseFrontmatter<Record<string, string>>(content);
82
- if (!frontmatter) return null;
83
- const status = frontmatter.status || "";
84
- const title = frontmatter.title || undefined;
85
- if (!status) return null;
86
- return { status, title };
87
- } catch {
88
- return null;
89
- }
90
- }
91
-
92
- /**
93
- * Probe minion integration: check if <globalDir>/tasks/ exists and has open tasks.
94
- * Return { openTasks, openTaskSummary } or null if minion is not active.
95
- */
96
- function probeMinionTasks(globalDir: string): { openTasks: number; openTaskSummary: string } | null {
97
- const tasksDir = path.join(globalDir, "tasks");
98
-
99
- // If tasks dir doesn't exist, minion is not active for this project
100
- if (!fs.existsSync(tasksDir)) {
101
- return null;
102
- }
103
-
104
- const OPEN_STATUSES = new Set(["backlog", "todo", "in_progress", "blocked", "review"]);
105
- const openTasks: string[] = [];
106
-
107
- try {
108
- const names = fs.readdirSync(tasksDir);
109
- for (const name of names) {
110
- if (!name.endsWith(".md")) continue;
111
- const file = path.join(tasksDir, name);
112
- try {
113
- const content = fs.readFileSync(file, "utf-8");
114
- const meta = parseTaskMetadata(content);
115
- if (meta && OPEN_STATUSES.has(meta.status)) {
116
- const taskTitle = meta.title ?? name.replace(/\.md$/, "");
117
- openTasks.push(`${meta.status}: ${taskTitle}`);
118
- if (openTasks.length >= 10) break;
119
- }
120
- } catch {
121
- // skip unparseable files
122
- }
123
- }
124
- } catch {
125
- // tasks dir exists but is unreadable — still report as active
126
- }
127
-
128
- const summary = openTasks.length > 0 ? openTasks.join("\n") : "(no open tasks)";
129
- return { openTasks: openTasks.length, openTaskSummary: summary };
130
- }
131
-
132
- /**
133
- * Probe memory integration: check if <globalDir>/memory/ exists and count entries.
134
- * Also read MEMORY.md (up to 3000 chars).
135
- * Return { entries, memorySummary } or null if memory is not active.
136
- */
137
- function probeMemoryIntegration(globalDir: string): { entries: number; memorySummary: string } | null {
138
- const memoryDir = path.join(globalDir, "memory");
139
-
140
- // If memory dir doesn't exist, memory is not active for this project
141
- if (!fs.existsSync(memoryDir)) {
142
- return null;
143
- }
144
-
145
- // Count entries in memory/entries/
146
- let entriesCount = 0;
147
- const entriesDir = path.join(memoryDir, "entries");
148
- try {
149
- if (fs.existsSync(entriesDir)) {
150
- const files = fs.readdirSync(entriesDir);
151
- entriesCount = files.filter((f) => f.endsWith(".md")).length;
152
- }
153
- } catch {
154
- // entries dir unreadable, but memory is still active
155
- }
156
-
157
- // Read MEMORY.md (truncated at 3000 chars)
158
- let memorySummary = "";
159
- const memoryFile = path.join(memoryDir, "MEMORY.md");
160
- try {
161
- if (fs.existsSync(memoryFile)) {
162
- const content = fs.readFileSync(memoryFile, "utf-8");
163
- memorySummary = content.length > 3000 ? content.slice(0, 3000) : content;
164
- }
165
- } catch {
166
- // MEMORY.md unreadable
167
- }
168
-
169
- return { entries: entriesCount, memorySummary };
170
- }
171
-
172
- /**
173
- * Probe compatibility: detect minion and memory integration for the project at cwd.
174
- * Return detailed status for both extensions in a flat structure.
175
- */
176
- export function probeCompat(cwd: string): CompatResult {
177
- const root = findProjectRoot(cwd);
178
- const configDir = path.join(root, CONFIG_DIR_NAME);
179
- const projectId = readMarkerId(configDir);
180
-
181
- // Initialize with defaults
182
- const base: CompatResult = {
183
- projectId,
184
- minionActive: false,
185
- memoryActive: false,
186
- memoryEntries: 0,
187
- openTaskCount: 0,
188
- openTaskSummary: "",
189
- memorySummary: "",
190
- };
191
-
192
- // If no project marker, return defaults
193
- if (!projectId) {
194
- return base;
195
- }
196
-
197
- const piHome = path.dirname(getAgentDir());
198
- const globalDir = path.join(piHome, "projects", projectId);
199
-
200
- // Probe minion
201
- const minionProbe = probeMinionTasks(globalDir);
202
- if (minionProbe) {
203
- base.minionActive = true;
204
- base.openTaskCount = minionProbe.openTasks;
205
- base.openTaskSummary = minionProbe.openTaskSummary;
206
- }
207
-
208
- // Probe memory
209
- const memoryProbe = probeMemoryIntegration(globalDir);
210
- if (memoryProbe) {
211
- base.memoryActive = true;
212
- base.memoryEntries = memoryProbe.entries;
213
- base.memorySummary = memoryProbe.memorySummary;
214
- }
215
-
216
- return base;
217
- }
package/detect.ts DELETED
@@ -1,188 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { execSync } from "node:child_process";
4
-
5
- export interface TechStack {
6
- primary: string[];
7
- frameworks: string[];
8
- infrastructure: string[];
9
- packageManagers: string[];
10
- }
11
-
12
- export interface TechIssue {
13
- severity: "error" | "warning" | "info";
14
- location: string;
15
- message: string;
16
- }
17
-
18
- export interface ProjectStatus {
19
- recentCommits: string;
20
- hasChangelog: boolean;
21
- todoCount: number;
22
- }
23
-
24
- const LANG_FILES: Array<{ file: string; lang: string }> = [
25
- { file: "package.json", lang: "Node.js" },
26
- { file: "tsconfig.json", lang: "TypeScript" },
27
- { file: "requirements.txt", lang: "Python" },
28
- { file: "pyproject.toml", lang: "Python" },
29
- { file: "go.mod", lang: "Go" },
30
- { file: "Cargo.toml", lang: "Rust" },
31
- { file: "pom.xml", lang: "Java" },
32
- { file: "build.gradle", lang: "Java/Kotlin" },
33
- { file: "build.gradle.kts", lang: "Kotlin" },
34
- { file: "Gemfile", lang: "Ruby" },
35
- { file: "composer.json", lang: "PHP" },
36
- { file: "mix.exs", lang: "Elixir" },
37
- ];
38
-
39
- const FRAMEWORK_FILES: Array<{ file: string; name: string; infra?: boolean }> = [
40
- { file: "next.config.js", name: "Next.js" },
41
- { file: "next.config.ts", name: "Next.js" },
42
- { file: "next.config.mjs", name: "Next.js" },
43
- { file: "vite.config.ts", name: "Vite" },
44
- { file: "vite.config.js", name: "Vite" },
45
- { file: "nuxt.config.ts", name: "Nuxt.js" },
46
- { file: "svelte.config.js", name: "SvelteKit" },
47
- { file: "astro.config.mjs", name: "Astro" },
48
- { file: "remix.config.js", name: "Remix" },
49
- { file: "angular.json", name: "Angular" },
50
- { file: "tailwind.config.ts", name: "Tailwind CSS" },
51
- { file: "tailwind.config.js", name: "Tailwind CSS" },
52
- { file: "biome.json", name: "Biome" },
53
- { file: "Dockerfile", name: "Docker", infra: true },
54
- { file: "docker-compose.yml", name: "Docker Compose", infra: true },
55
- { file: ".github/workflows", name: "GitHub Actions", infra: true },
56
- { file: ".gitlab-ci.yml", name: "GitLab CI", infra: true },
57
- ];
58
-
59
- const NPM_FRAMEWORKS: Record<string, string> = {
60
- react: "React",
61
- vue: "Vue",
62
- express: "Express",
63
- fastify: "Fastify",
64
- hono: "Hono",
65
- "@nestjs/core": "NestJS",
66
- prisma: "Prisma",
67
- "@prisma/client": "Prisma",
68
- "drizzle-orm": "Drizzle ORM",
69
- "@trpc/server": "tRPC",
70
- };
71
-
72
- export function detectTechStack(root: string): TechStack {
73
- const primary = new Set<string>();
74
- const frameworks = new Set<string>();
75
- const infrastructure = new Set<string>();
76
- const packageManagers = new Set<string>();
77
-
78
- for (const d of LANG_FILES) {
79
- if (fs.existsSync(path.join(root, d.file))) primary.add(d.lang);
80
- }
81
-
82
- for (const d of FRAMEWORK_FILES) {
83
- if (fs.existsSync(path.join(root, d.file))) {
84
- if (d.infra) infrastructure.add(d.name);
85
- else frameworks.add(d.name);
86
- }
87
- }
88
-
89
- if (fs.existsSync(path.join(root, "package-lock.json"))) packageManagers.add("npm");
90
- if (fs.existsSync(path.join(root, "yarn.lock"))) packageManagers.add("yarn");
91
- if (fs.existsSync(path.join(root, "pnpm-lock.yaml"))) packageManagers.add("pnpm");
92
- if (fs.existsSync(path.join(root, "bun.lock"))) packageManagers.add("bun");
93
-
94
- try {
95
- const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf-8"));
96
- const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
97
- for (const [dep, label] of Object.entries(NPM_FRAMEWORKS)) {
98
- if (dep in deps) frameworks.add(label);
99
- }
100
- } catch {
101
- /***/
102
- }
103
-
104
- return {
105
- primary: [...primary],
106
- frameworks: [...frameworks],
107
- infrastructure: [...infrastructure],
108
- packageManagers: [...packageManagers],
109
- };
110
- }
111
-
112
- export function detectProjectStatus(root: string): ProjectStatus {
113
- let recentCommits = "";
114
- try {
115
- recentCommits = execSync("git log --oneline -20", {
116
- cwd: root,
117
- encoding: "utf-8",
118
- timeout: 5000,
119
- }).trim();
120
- } catch {
121
- recentCommits = "(git not available or no commits)";
122
- }
123
-
124
- const hasChangelog =
125
- fs.existsSync(path.join(root, "CHANGELOG.md")) || fs.existsSync(path.join(root, "CHANGELOG"));
126
-
127
- let todoCount = 0;
128
- try {
129
- const result = execSync(
130
- `grep -r --include="*.ts" --include="*.tsx" --include="*.js" --include="*.py" --include="*.go" --include="*.rs" -c "TODO:\\|FIXME:\\|HACK:\\|BUG:" . 2>/dev/null || true`,
131
- { cwd: root, encoding: "utf-8", timeout: 10000 }
132
- );
133
- for (const line of result.split("\n")) {
134
- const m = line.match(/:(\d+)$/);
135
- if (m) todoCount += parseInt(m[1], 10);
136
- }
137
- } catch {
138
- /***/
139
- }
140
-
141
- return { recentCommits, hasChangelog, todoCount };
142
- }
143
-
144
- export function detectTechnicalIssues(root: string): TechIssue[] {
145
- const issues: TechIssue[] = [];
146
-
147
- // Invalid tsconfig.json
148
- const tsconfigPath = path.join(root, "tsconfig.json");
149
- if (fs.existsSync(tsconfigPath)) {
150
- try {
151
- JSON.parse(fs.readFileSync(tsconfigPath, "utf-8"));
152
- } catch (e) {
153
- issues.push({
154
- severity: "error",
155
- location: "tsconfig.json",
156
- message: `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`,
157
- });
158
- }
159
- }
160
-
161
- // No test directory or config
162
- const testDirs = ["test", "tests", "__tests__", "spec"];
163
- const testConfigs = ["vitest.config.ts", "vitest.config.js", "jest.config.ts", "jest.config.js"];
164
- const hasTests =
165
- testDirs.some((d) => fs.existsSync(path.join(root, d))) ||
166
- testConfigs.some((f) => fs.existsSync(path.join(root, f)));
167
- if (!hasTests) {
168
- issues.push({
169
- severity: "warning",
170
- location: "root",
171
- message: "No test directory or test config detected",
172
- });
173
- }
174
-
175
- // .env.example without .env
176
- if (
177
- fs.existsSync(path.join(root, ".env.example")) &&
178
- !fs.existsSync(path.join(root, ".env"))
179
- ) {
180
- issues.push({
181
- severity: "info",
182
- location: ".env",
183
- message: ".env.example exists but .env is missing — check environment setup",
184
- });
185
- }
186
-
187
- return issues;
188
- }
package/docs/PRD.md DELETED
@@ -1,78 +0,0 @@
1
- # Product Requirements Document: @aprimediet/codewalker
2
-
3
- **Version:** 1.0
4
- **Date:** 2026-06-25
5
- **Status:** Draft
6
-
7
- ## Overview
8
- Project intelligence extension for the pi coding agent that systematically analyzes any project — tech stack, goals, boundaries, status, technical issues — and produces structured documentation: a **PRD** for humans and **AGENTS.md** / **CLAUDE.md** for coding agents.
9
-
10
- ## Problem Statement
11
- Developers using the pi coding agent need a fast, reliable way to understand a project they're working on without manually reading every file. The coding agent also needs structured project instructions to work effectively. Currently there's no automated way to generate this intelligence snapshot and split the content by audience (human vs. agent).
12
-
13
- ## Goals
14
- - Identify tech stack (language, frameworks, infrastructure, package manager)
15
- - Identify project goals and non-goals (interactively when missing)
16
- - Identify boundaries, scope, and constraints of a project
17
- - Generate a human-readable PRD.md with product context
18
- - Generate an engineering-focused AGENTS.md for coding agents
19
- - Detect companion extensions (minion, memory) and report their state
20
- - Store the intelligence snapshot to persistent memory for session continuity
21
-
22
- ## Non-Goals
23
- - Give suggestions or recommendations on how to improve the project
24
- - Try to solve or fix technical issues found during analysis
25
- - Research how to solve technical issues — diagnose only, no solutions
26
- - Modify project files beyond the documentation it generates
27
-
28
- ## Target Users
29
- Developers who use the pi coding agent and need to quickly get up to speed on a project or generate structured documentation for human and agent consumption.
30
-
31
- ## Key Features
32
-
33
- ### Tech Stack Detection
34
- Automatically scan manifest files (package.json, tsconfig.json, requirements.txt, go.mod, Cargo.toml, etc.) and framework configs to identify primary language, frameworks, infrastructure, and package manager.
35
-
36
- ### Goals & Non-Goals Gathering
37
- Search README and docs for existing goals. If not found, interactively gather them from the user one question at a time — problem solved, primary users, key features, out-of-scope items, success metrics.
38
-
39
- ### Boundary Documentation
40
- Identify key entry points, external services, exposed interfaces (APIs, CLI flags), and technical constraints (runtime version, env vars, OS requirements).
41
-
42
- ### Project Status Scanning
43
- Read recent git commits, check for changelogs/roadmaps, scan for TODO/FIXME/HACK/BUG markers in source code.
44
-
45
- ### Technical Issue Detection
46
- Detect missing tests, broken env files, invalid configs, and TypeScript strictness issues — report without trying to fix.
47
-
48
- ### Audience-Split Documentation
49
- Generate three documents from the same intelligence, each targeted at its audience:
50
- - **docs/PRD.md** (humans, product) — overview, problem, goals, users, features, success metrics
51
- - **AGENTS.md** (coding agents, engineering) — tech stack, commands, conventions, technical boundaries, gotchas
52
- - **CLAUDE.md** (Claude Code) — thin pointer that imports AGENTS.md
53
-
54
- ### Integration Detection
55
- Detect active `@aprimediet/minion` and `@aprimediet/memory` extensions via shared `.pi/<id>.md` project marker. Report open tasks and memory entries without modifying any files.
56
-
57
- ### Memory Storage
58
- Store the full project intelligence snapshot to `@aprimediet/memory` (scope: project) for persistent recall across sessions.
59
-
60
- ## Success Metrics
61
- - User can read a summary of their current project in PRD.md without reading every file manually
62
- - Coding agent can read specific project instructions in AGENTS.md to work effectively
63
- - Pi coding agent can learn and store the latest project snapshot in memory for session continuity
64
- - Documentation is correctly split by audience with no overlap
65
-
66
- ## Scope & Boundaries
67
- - **Runtime:** Node.js with ESM modules (`"type": "module"`)
68
- - **Peer dependency:** `@earendil-works/pi-coding-agent`
69
- - **No third-party runtime dependencies** — Node built-ins only (`fs`, `path`, `child_process`)
70
- - **Exposed interface:** single `/learn-this` command registered with the pi extension API
71
- - **File system operations:** all detection probes are read-only; writes are confined to documentation files (PRD.md, AGENTS.md, CLAUDE.md) and memory storage
72
- - **Not a CI tool:** runs on-demand via `/learn-this`, not as a background service
73
-
74
- ## Open Questions
75
- - Should AGENTS.md be auto-generated or user-edited after generation?
76
- - How often should memory snapshots be refreshed — on every `/learn-this` run or on demand?
77
- - Should the extension support non-Node.js projects (Python, Go, Rust) for full PRD/AGENTS.md generation?
78
- - Should there be a flag to skip interactive questions when running in non-interactive mode?
package/prd.ts DELETED
@@ -1,106 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
-
4
- const PRD_CANDIDATES = [
5
- "docs/PRD.md",
6
- "docs/prd.md",
7
- "PRD.md",
8
- ".pi/prd.md",
9
- "PRODUCT.md",
10
- "SPEC.md",
11
- ];
12
-
13
- export function findExistingPRD(root: string): string | null {
14
- for (const p of PRD_CANDIDATES) {
15
- const filePath = path.join(root, p);
16
- if (fs.existsSync(filePath)) {
17
- return filePath;
18
- }
19
- }
20
-
21
- // README with a goals/requirements heading
22
- const readme = path.join(root, "README.md");
23
- if (fs.existsSync(readme)) {
24
- try {
25
- const text = fs.readFileSync(readme, "utf-8");
26
- if (/^##\s+(Goals|Non-Goals|Requirements|Product Requirements)/im.test(text)) {
27
- return readme;
28
- }
29
- } catch {
30
- // Ignore read errors
31
- }
32
- }
33
-
34
- return null;
35
- }
36
-
37
- export function readPRD(filePath: string): string {
38
- try {
39
- return fs.readFileSync(filePath, "utf-8");
40
- } catch {
41
- return "";
42
- }
43
- }
44
-
45
- export function createPRD(root: string, content: string): string {
46
- const outPath = path.join(root, "docs", "PRD.md");
47
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
48
- fs.writeFileSync(outPath, content, "utf-8");
49
- return outPath;
50
- }
51
-
52
- export function prdTemplate(p: {
53
- projectName: string;
54
- overview: string;
55
- problem: string;
56
- goals: string[];
57
- nonGoals: string[];
58
- targetUsers: string;
59
- keyFeatures: Array<{ name: string; description: string }>;
60
- successMetrics: string[];
61
- boundaries: string;
62
- openQuestions: string[];
63
- date: string;
64
- }): string {
65
- const goalsSection = p.goals.map((g) => `- ${g}`).join("\n");
66
- const nonGoalsSection = p.nonGoals.map((g) => `- ${g}`).join("\n");
67
- const featuresSection = p.keyFeatures
68
- .map((f) => `### ${f.name}\n${f.description}`)
69
- .join("\n\n");
70
- const metricsSection = p.successMetrics.map((m) => `- ${m}`).join("\n");
71
- const questionsSection = p.openQuestions.map((q) => `- ${q}`).join("\n");
72
-
73
- return `# Product Requirements Document: ${p.projectName}
74
-
75
- **Version:** 1.0
76
- **Date:** ${p.date}
77
- **Status:** Draft
78
-
79
- ## Overview
80
- ${p.overview}
81
-
82
- ## Problem Statement
83
- ${p.problem}
84
-
85
- ## Goals
86
- ${goalsSection}
87
-
88
- ## Non-Goals
89
- ${nonGoalsSection}
90
-
91
- ## Target Users
92
- ${p.targetUsers}
93
-
94
- ## Key Features
95
- ${featuresSection}
96
-
97
- ## Success Metrics
98
- ${metricsSection}
99
-
100
- ## Boundaries & Constraints
101
- ${p.boundaries}
102
-
103
- ## Open Questions
104
- ${questionsSection}
105
- `;
106
- }