@aprimediet/codewalker 1.0.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/LICENSE +21 -0
- package/README.md +71 -0
- package/agents.ts +126 -0
- package/compat.ts +217 -0
- package/detect.ts +188 -0
- package/docs/PRD.md +78 -0
- package/index.ts +43 -0
- package/package.json +47 -0
- package/prd.ts +106 -0
- package/skills/learn-this/SKILL.md +325 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aprimediet
|
|
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
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @aprimediet/codewalker
|
|
2
|
+
|
|
3
|
+
Systematic **project intelligence** for the [pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent): analyze tech stack, goals, boundaries, status, and technical issues, then generate a **PRD** for humans and **AGENTS.md** / **CLAUDE.md** for coding agents — plus integration with `@aprimediet/minion` and `@aprimediet/memory`.
|
|
4
|
+
|
|
5
|
+
Documentation is split by audience: `docs/PRD.md` holds the product *what & why* (overview, goals, users, features, metrics); `AGENTS.md` holds the engineering *how* (tech stack, structure, commands, conventions, technical boundaries, gotchas); `CLAUDE.md` is a thin pointer that imports `AGENTS.md` as the single source of truth.
|
|
6
|
+
|
|
7
|
+
## Phases
|
|
8
|
+
|
|
9
|
+
| Phase | What it does |
|
|
10
|
+
|-------|---|
|
|
11
|
+
| Phase 1 | Detect primary language, frameworks, infrastructure, and package manager from manifest files |
|
|
12
|
+
| Phase 2 | Find or gather project goals and non-goals (interactively, one question at a time) |
|
|
13
|
+
| Phase 3 | Document key entry points, external services, exposed interfaces, and technical constraints |
|
|
14
|
+
| Phase 4 | Scan recent commit history and search for technical debt markers (TODO/FIXME/HACK/BUG) |
|
|
15
|
+
| Phase 5 | Detect missing test directories, broken env files, invalid configs, and TypeScript strictness issues |
|
|
16
|
+
| Phase 6 | Generate docs, split by audience: `docs/PRD.md` (product), `AGENTS.md` (engineering), `CLAUDE.md` (pointer to AGENTS.md) — each with user confirmation |
|
|
17
|
+
| Phase 7 | Detect active `@aprimediet/minion` and `@aprimediet/memory` integrations via the shared `.pi/<id>.md` marker |
|
|
18
|
+
| Phase 8 | Compile full project intelligence document with all sections |
|
|
19
|
+
| Phase 9 | Store summary to memory (if active) or save to local file |
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pi install npm:@aprimediet/codewalker
|
|
25
|
+
pi list
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick try
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pi -e ./extensions/codewalker/index.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then run `/learn-this` in any project.
|
|
35
|
+
|
|
36
|
+
## Integration
|
|
37
|
+
|
|
38
|
+
codewalker automatically detects the presence of two companion extensions via a shared project marker:
|
|
39
|
+
|
|
40
|
+
**Minion Integration (read-only):**
|
|
41
|
+
- Reads the `.pi/<project-id>.md` marker file from the current working directory
|
|
42
|
+
- Checks `~/.pi/projects/<id>/tasks/` for open kanban cards (backlog, todo, in_progress, blocked, review)
|
|
43
|
+
- Counts and reports open task count during the `/learn-this` summary
|
|
44
|
+
|
|
45
|
+
**Memory Integration (read-only + write):**
|
|
46
|
+
- Reads the same `.pi/<project-id>.md` marker file
|
|
47
|
+
- Checks `~/.pi/projects/<id>/memory/` for active memory (MEMORY.md + entries/)
|
|
48
|
+
- Counts memory entries and reads the index during Phase 7
|
|
49
|
+
- In Phase 9, if memory is active, calls `memory_write` with scope `"project"` to store the full intelligence snapshot
|
|
50
|
+
|
|
51
|
+
Both integrations use **read-only detection** (no creation of files or directories); the marker file is created and managed by minion or memory when activated. codewalker coexists with them seamlessly in the same `~/.pi/projects/<id>/` workspace.
|
|
52
|
+
|
|
53
|
+
## Layout
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
codewalker/ # @aprimediet/codewalker
|
|
57
|
+
├── package.json # pi manifest: extensions + skills
|
|
58
|
+
├── index.ts # extension factory: /learn-this command (probes + triggers the workflow)
|
|
59
|
+
├── compat.ts # minion + memory integration detection
|
|
60
|
+
├── detect.ts # phase-specific file and marker detection
|
|
61
|
+
├── prd.ts # PRD (human) search, read, and generation
|
|
62
|
+
├── agents.ts # AGENTS.md + CLAUDE.md (agent) search and generation
|
|
63
|
+
└── skills/
|
|
64
|
+
└── learn-this/
|
|
65
|
+
└── SKILL.md # 9-phase intelligence gathering workflow + checklist
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The `/learn-this` command probes integration status, shows it, then sends a user message
|
|
69
|
+
that triggers the agent to invoke the `learn-this` skill and run all 9 phases.
|
|
70
|
+
|
|
71
|
+
No third-party runtime deps — only the pi-core packages (peer, bundled by pi).
|
package/agents.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
// Agent-facing guide lives at the repo root by convention (AGENTS.md), with a thin
|
|
5
|
+
// CLAUDE.md pointer next to it so Claude Code auto-imports the same source of truth.
|
|
6
|
+
const AGENTS_CANDIDATES = ["AGENTS.md", ".agents/AGENTS.md", "docs/AGENTS.md"];
|
|
7
|
+
const CLAUDE_CANDIDATES = ["CLAUDE.md", ".claude/CLAUDE.md"];
|
|
8
|
+
|
|
9
|
+
export function findExistingAgentsMd(root: string): string | null {
|
|
10
|
+
for (const p of AGENTS_CANDIDATES) {
|
|
11
|
+
const filePath = path.join(root, p);
|
|
12
|
+
if (fs.existsSync(filePath)) {
|
|
13
|
+
return filePath;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function findExistingClaudeMd(root: string): string | null {
|
|
20
|
+
for (const p of CLAUDE_CANDIDATES) {
|
|
21
|
+
const filePath = path.join(root, p);
|
|
22
|
+
if (fs.existsSync(filePath)) {
|
|
23
|
+
return filePath;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createAgentsMd(root: string, content: string): string {
|
|
30
|
+
const outPath = path.join(root, "AGENTS.md");
|
|
31
|
+
fs.writeFileSync(outPath, content, "utf-8");
|
|
32
|
+
return outPath;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createClaudeMd(root: string, content: string): string {
|
|
36
|
+
const outPath = path.join(root, "CLAUDE.md");
|
|
37
|
+
fs.writeFileSync(outPath, content, "utf-8");
|
|
38
|
+
return outPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The agent-facing engineering guide. This is the "how to work in this repo" document:
|
|
43
|
+
* tech stack, structure, commands, conventions, technical boundaries, gotchas, and how
|
|
44
|
+
* the agent should use the minion/memory companion extensions. Product "what & why"
|
|
45
|
+
* (goals, users, features, metrics) belongs in the PRD, not here.
|
|
46
|
+
*/
|
|
47
|
+
export function agentsMdTemplate(p: {
|
|
48
|
+
projectName: string;
|
|
49
|
+
summary: string;
|
|
50
|
+
techStack: string;
|
|
51
|
+
structure: string;
|
|
52
|
+
commands: { setup?: string; build?: string; test?: string; run?: string; lint?: string };
|
|
53
|
+
conventions: string;
|
|
54
|
+
boundaries: string;
|
|
55
|
+
knownIssues: string[];
|
|
56
|
+
integration: string;
|
|
57
|
+
currentFocus: string;
|
|
58
|
+
prdPath: string | null;
|
|
59
|
+
}): string {
|
|
60
|
+
const cmd = p.commands;
|
|
61
|
+
const commandRows = [
|
|
62
|
+
cmd.setup ? `- **Setup:** \`${cmd.setup}\`` : null,
|
|
63
|
+
cmd.build ? `- **Build:** \`${cmd.build}\`` : null,
|
|
64
|
+
cmd.test ? `- **Test:** \`${cmd.test}\`` : null,
|
|
65
|
+
cmd.run ? `- **Run:** \`${cmd.run}\`` : null,
|
|
66
|
+
cmd.lint ? `- **Lint:** \`${cmd.lint}\`` : null,
|
|
67
|
+
]
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.join("\n");
|
|
70
|
+
const commandsSection = commandRows || "_(none detected — ask the user)_";
|
|
71
|
+
const issuesSection = p.knownIssues.length
|
|
72
|
+
? p.knownIssues.map((i) => `- ${i}`).join("\n")
|
|
73
|
+
: "_(none known)_";
|
|
74
|
+
const productLink = p.prdPath
|
|
75
|
+
? `Product context (goals, users, features, success metrics): see [${p.prdPath}](${p.prdPath}).`
|
|
76
|
+
: "Product context: no PRD found.";
|
|
77
|
+
|
|
78
|
+
return `# AGENTS.md — ${p.projectName}
|
|
79
|
+
|
|
80
|
+
Guide for coding agents working in this repository. ${productLink}
|
|
81
|
+
|
|
82
|
+
## Summary
|
|
83
|
+
${p.summary}
|
|
84
|
+
|
|
85
|
+
## Tech Stack
|
|
86
|
+
${p.techStack}
|
|
87
|
+
|
|
88
|
+
## Project Structure
|
|
89
|
+
${p.structure}
|
|
90
|
+
|
|
91
|
+
## Commands
|
|
92
|
+
${commandsSection}
|
|
93
|
+
|
|
94
|
+
## Conventions
|
|
95
|
+
${p.conventions}
|
|
96
|
+
|
|
97
|
+
## Boundaries (technical)
|
|
98
|
+
${p.boundaries}
|
|
99
|
+
|
|
100
|
+
## Known Issues & Gotchas
|
|
101
|
+
${issuesSection}
|
|
102
|
+
|
|
103
|
+
## Companion Extensions
|
|
104
|
+
${p.integration}
|
|
105
|
+
|
|
106
|
+
## Current Focus
|
|
107
|
+
${p.currentFocus}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A thin CLAUDE.md that points Claude Code at AGENTS.md as the single source of truth.
|
|
113
|
+
* The `@AGENTS.md` line triggers Claude Code's file-import so the content is pulled in.
|
|
114
|
+
*/
|
|
115
|
+
export function claudeMdTemplate(projectName: string): string {
|
|
116
|
+
return `# CLAUDE.md — ${projectName}
|
|
117
|
+
|
|
118
|
+
Guidance for Claude Code in this repository.
|
|
119
|
+
|
|
120
|
+
All project conventions, architecture, build/test commands, and boundaries live in
|
|
121
|
+
**[AGENTS.md](./AGENTS.md)** — the shared guide for every coding agent. Keep that file
|
|
122
|
+
as the single source of truth; do not duplicate its content here.
|
|
123
|
+
|
|
124
|
+
@AGENTS.md
|
|
125
|
+
`;
|
|
126
|
+
}
|
package/compat.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
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/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { probeCompat } from "./compat.ts";
|
|
3
|
+
|
|
4
|
+
export default function codewalkExtension(pi: ExtensionAPI): void {
|
|
5
|
+
pi.registerCommand("learn-this", {
|
|
6
|
+
description: "Analyze this project: tech stack, goals, status, issues, then generate PRD + AGENTS.md/CLAUDE.md.",
|
|
7
|
+
handler: async (_args, ctx: ExtensionContext) => {
|
|
8
|
+
const compat = probeCompat(ctx.cwd);
|
|
9
|
+
|
|
10
|
+
const minionLine = compat.minionActive
|
|
11
|
+
? `minion: active (project ${compat.projectId}, ${compat.openTaskCount} open tasks)`
|
|
12
|
+
: "minion: not detected";
|
|
13
|
+
|
|
14
|
+
const memoryLine = compat.memoryActive
|
|
15
|
+
? `memory: active (${compat.memoryEntries} entries)`
|
|
16
|
+
: "memory: not detected";
|
|
17
|
+
|
|
18
|
+
// Surface the probe result to the user immediately (TUI only).
|
|
19
|
+
if (ctx.hasUI) {
|
|
20
|
+
ctx.ui.notify(
|
|
21
|
+
["codewalker: starting /learn-this", ` ${minionLine}`, ` ${memoryLine}`].join("\n"),
|
|
22
|
+
"info",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Actually kick off the workflow: send a user message that triggers an agent turn.
|
|
27
|
+
// The agent invokes the learn-this skill and runs all 9 phases. We hand it the
|
|
28
|
+
// integration facts up front so Phase 7 (and Phase 9 storage) start from real data.
|
|
29
|
+
const directive = [
|
|
30
|
+
"Run the /learn-this project intelligence workflow on the current working directory now.",
|
|
31
|
+
"Invoke the `learn-this` skill and complete every phase and checklist item in order.",
|
|
32
|
+
"Do not stop after detection — gather goals interactively (one question at a time) where they are missing, generate the docs (docs/PRD.md for humans, AGENTS.md + CLAUDE.md for coding agents, with the audience-correct content split), and finish with the summary and memory storage steps.",
|
|
33
|
+
"",
|
|
34
|
+
"Integration status detected by codewalker (use these facts in Phase 7 and Phase 9):",
|
|
35
|
+
`- ${minionLine}`,
|
|
36
|
+
`- ${memoryLine}`,
|
|
37
|
+
compat.projectId ? `- project id: ${compat.projectId}` : "- project id: none (no .pi marker found)",
|
|
38
|
+
].join("\n");
|
|
39
|
+
|
|
40
|
+
pi.sendUserMessage(directive, { deliverAs: "followUp" });
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aprimediet/codewalker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Project intelligence snapshot for the pi coding agent — /learn-this",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"codewalker",
|
|
10
|
+
"project-intelligence",
|
|
11
|
+
"prd",
|
|
12
|
+
"agents-md"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "aprimediet",
|
|
17
|
+
"url": "https://github.com/aprimediet"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/aprimediet/codewalker.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/aprimediet/codewalker/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/aprimediet/codewalker#readme",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"pi": {
|
|
34
|
+
"extensions": ["./index.ts"],
|
|
35
|
+
"skills": ["./skills"]
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"*.ts",
|
|
39
|
+
"skills/**",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"docs/**"
|
|
43
|
+
],
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/prd.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: learn-this
|
|
3
|
+
description: Use when the user runs /learn-this or asks to analyze, understand, or get up to speed on the current project. Guides systematic project intelligence gathering across tech stack, goals, status, technical issues, PRD management, and integration detection.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Learn This — Project Intelligence
|
|
7
|
+
|
|
8
|
+
Build a complete project intelligence snapshot for the current working directory.
|
|
9
|
+
Create a task for each phase listed below before starting any of them.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Phase 1 — Tech Stack
|
|
14
|
+
|
|
15
|
+
Read the following files if they exist and record what you find:
|
|
16
|
+
|
|
17
|
+
| File | Signals |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `package.json` | Node.js / JS / TS; read `dependencies` + `devDependencies` for frameworks |
|
|
20
|
+
| `tsconfig.json` | TypeScript |
|
|
21
|
+
| `bun.lock` / `pnpm-lock.yaml` / `yarn.lock` / `package-lock.json` | Package manager |
|
|
22
|
+
| `requirements.txt` / `pyproject.toml` | Python |
|
|
23
|
+
| `go.mod` | Go |
|
|
24
|
+
| `Cargo.toml` | Rust |
|
|
25
|
+
| `pom.xml` / `build.gradle` / `build.gradle.kts` | Java / Kotlin |
|
|
26
|
+
| `Gemfile` | Ruby |
|
|
27
|
+
| `next.config.*` / `vite.config.*` / `nuxt.config.*` / `astro.config.*` | Frontend framework |
|
|
28
|
+
| `tailwind.config.*` | Tailwind CSS |
|
|
29
|
+
| `Dockerfile` / `docker-compose.yml` | Docker |
|
|
30
|
+
| `.github/workflows/` | GitHub Actions |
|
|
31
|
+
| `.gitlab-ci.yml` | GitLab CI |
|
|
32
|
+
|
|
33
|
+
Also read `package.json` deps for: `react`, `vue`, `express`, `fastify`, `hono`,
|
|
34
|
+
`@nestjs/core`, `prisma`, `@prisma/client`, `drizzle-orm`, `@trpc/server`.
|
|
35
|
+
|
|
36
|
+
Record: primary language(s), frameworks, infrastructure, package manager.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Phase 2 — Goals & Non-Goals
|
|
41
|
+
|
|
42
|
+
Search in order:
|
|
43
|
+
1. `README.md` — look for Goals, Non-Goals, About, Overview headings
|
|
44
|
+
2. `docs/PRD.md`, `docs/prd.md`, `PRD.md`, `.pi/prd.md`, `PRODUCT.md`, `SPEC.md`
|
|
45
|
+
3. Any markdown file whose name contains "goals" or "requirements"
|
|
46
|
+
|
|
47
|
+
**If goals are NOT found**, gather them interactively — ask ONE question at a time and wait
|
|
48
|
+
for the user's answer before asking the next:
|
|
49
|
+
|
|
50
|
+
1. "What problem does this project solve?"
|
|
51
|
+
2. "Who is the primary user or audience?"
|
|
52
|
+
3. "What are the 3 most important features?"
|
|
53
|
+
4. "What is explicitly out of scope?"
|
|
54
|
+
5. "What does success look like for this project?"
|
|
55
|
+
|
|
56
|
+
**Do NOT present all five questions at once. Ask one. Wait for the answer. Ask the next.**
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Phase 3 — Boundaries
|
|
61
|
+
|
|
62
|
+
From README and code structure, identify:
|
|
63
|
+
- Key entry points (main files, API routes, CLI commands)
|
|
64
|
+
- External services consumed (databases, third-party APIs, cloud services)
|
|
65
|
+
- Exposed interfaces (HTTP endpoints, CLI flags, exported packages)
|
|
66
|
+
- Technical constraints (runtime version, OS requirements, required env vars)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Phase 4 — Project Status
|
|
71
|
+
|
|
72
|
+
1. Run: `git log --oneline -20` — record the output
|
|
73
|
+
2. Check for `CHANGELOG.md`, `TODO.md`, `ISSUES.md`, `ROADMAP.md`
|
|
74
|
+
3. Scan for technical debt markers:
|
|
75
|
+
```
|
|
76
|
+
grep -r "TODO:\|FIXME:\|HACK:\|BUG:" --include="*.ts" --include="*.js" --include="*.py" --include="*.go" . 2>/dev/null | head -20
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Phase 5 — Technical Issues
|
|
82
|
+
|
|
83
|
+
Detect and report:
|
|
84
|
+
- No test directory or test config found (`test/`, `tests/`, `__tests__/`, `vitest.config.*`, `jest.config.*`)
|
|
85
|
+
- `.env.example` exists but `.env` is missing
|
|
86
|
+
- `tsconfig.json` is not valid JSON
|
|
87
|
+
- Count of `// @ts-nocheck` or `// @ts-ignore` occurrences
|
|
88
|
+
|
|
89
|
+
Then ask the user:
|
|
90
|
+
> "I found [N] potential issues above. Are there additional technical issues you want me to be aware of or address?"
|
|
91
|
+
|
|
92
|
+
Record their response.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Phase 6 — Documentation Generation
|
|
97
|
+
|
|
98
|
+
Produce two complementary documents from everything gathered so far, and split the content
|
|
99
|
+
by audience. **Never put the same content in both.**
|
|
100
|
+
|
|
101
|
+
| Document | Audience | Holds the *what & why* / *how* |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `docs/PRD.md` | Humans (product) | overview, problem, goals, non-goals, target users, key features, success metrics, product-scope boundaries, open questions |
|
|
104
|
+
| `AGENTS.md` (repo root) | Coding agents (engineering) | tech stack, project structure, build/test/run commands, conventions, **technical** boundaries, known issues, companion-extension usage, current focus |
|
|
105
|
+
| `CLAUDE.md` (repo root) | Claude Code | thin pointer that imports `AGENTS.md` |
|
|
106
|
+
|
|
107
|
+
### 6a — PRD (human, product)
|
|
108
|
+
|
|
109
|
+
**Search for existing PRD** in: `docs/PRD.md`, `docs/prd.md`, `PRD.md`, `.pi/prd.md`,
|
|
110
|
+
`PRODUCT.md`, `SPEC.md`, or `README.md` with a Goals/Requirements heading.
|
|
111
|
+
|
|
112
|
+
**If it exists**: read and summarize key sections for the Phase 8 summary.
|
|
113
|
+
|
|
114
|
+
**If it does NOT exist**:
|
|
115
|
+
- Ask: "No PRD found. Should I create `docs/PRD.md` with the product information we gathered?"
|
|
116
|
+
- If yes, write `docs/PRD.md` using this template (product content only — no commands, no file paths, no conventions):
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
# Product Requirements Document: <project-name>
|
|
120
|
+
|
|
121
|
+
**Version:** 1.0
|
|
122
|
+
**Date:** <today>
|
|
123
|
+
**Status:** Draft
|
|
124
|
+
|
|
125
|
+
## Overview
|
|
126
|
+
<1-2 sentence vision summary>
|
|
127
|
+
|
|
128
|
+
## Problem Statement
|
|
129
|
+
<what problem this solves and who has it>
|
|
130
|
+
|
|
131
|
+
## Goals
|
|
132
|
+
- <goal from Phase 2>
|
|
133
|
+
|
|
134
|
+
## Non-Goals
|
|
135
|
+
- <non-goal from Phase 2>
|
|
136
|
+
|
|
137
|
+
## Target Users
|
|
138
|
+
<from Phase 2>
|
|
139
|
+
|
|
140
|
+
## Key Features
|
|
141
|
+
### <Feature>
|
|
142
|
+
<product-level description>
|
|
143
|
+
|
|
144
|
+
## Success Metrics
|
|
145
|
+
- <metric from Phase 2>
|
|
146
|
+
|
|
147
|
+
## Scope & Boundaries
|
|
148
|
+
<product-scope boundaries from Phase 3 — what the product will and will not do>
|
|
149
|
+
|
|
150
|
+
## Open Questions
|
|
151
|
+
- <any unresolved questions>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 6b — AGENTS.md (coding agent, engineering)
|
|
155
|
+
|
|
156
|
+
**Search for an existing agent guide** at: `AGENTS.md`, `.agents/AGENTS.md`, `docs/AGENTS.md`.
|
|
157
|
+
|
|
158
|
+
**If it exists**: read it; offer to update stale sections rather than overwrite.
|
|
159
|
+
|
|
160
|
+
**If it does NOT exist**:
|
|
161
|
+
- Ask: "No AGENTS.md found. Should I create one at the repo root for coding agents?"
|
|
162
|
+
- If yes, write `AGENTS.md` using this template (engineering content only — NO product goals/users/metrics; link to the PRD for those):
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
# AGENTS.md — <project-name>
|
|
166
|
+
|
|
167
|
+
Guide for coding agents working in this repository. Product context (goals, users,
|
|
168
|
+
features, success metrics): see [docs/PRD.md](docs/PRD.md).
|
|
169
|
+
|
|
170
|
+
## Summary
|
|
171
|
+
<one-line: what this codebase is, technically>
|
|
172
|
+
|
|
173
|
+
## Tech Stack
|
|
174
|
+
<languages, frameworks, infrastructure, package manager — from Phase 1>
|
|
175
|
+
|
|
176
|
+
## Project Structure
|
|
177
|
+
<key entry points and directories — from Phase 3>
|
|
178
|
+
|
|
179
|
+
## Commands
|
|
180
|
+
- **Setup:** <install command, if any>
|
|
181
|
+
- **Build:** <build command, if any>
|
|
182
|
+
- **Test:** <test command, if any>
|
|
183
|
+
- **Run:** <run/dev command, if any>
|
|
184
|
+
- **Lint:** <lint/format command, if any>
|
|
185
|
+
|
|
186
|
+
## Conventions
|
|
187
|
+
<code style, naming, patterns observed in the codebase — match what exists>
|
|
188
|
+
|
|
189
|
+
## Boundaries (technical)
|
|
190
|
+
<do-not-touch areas, invariants, generated files, things that must not change — from Phase 3>
|
|
191
|
+
|
|
192
|
+
## Known Issues & Gotchas
|
|
193
|
+
- <technical issue from Phase 5>
|
|
194
|
+
|
|
195
|
+
## Companion Extensions
|
|
196
|
+
<if minion/memory are active (Phase 7): how the agent should use them — e.g. check the
|
|
197
|
+
kanban board before starting, record durable facts to memory. If not active, say so.>
|
|
198
|
+
|
|
199
|
+
## Current Focus
|
|
200
|
+
<what is being worked on now — from Phase 4 recent commits / status>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 6c — CLAUDE.md (Claude Code pointer)
|
|
204
|
+
|
|
205
|
+
**If `CLAUDE.md` does not already exist** at the repo root, create it as a thin pointer so
|
|
206
|
+
Claude Code loads the same guide (do not duplicate AGENTS.md content):
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
# CLAUDE.md — <project-name>
|
|
210
|
+
|
|
211
|
+
Guidance for Claude Code in this repository.
|
|
212
|
+
|
|
213
|
+
All project conventions, architecture, build/test commands, and boundaries live in
|
|
214
|
+
**[AGENTS.md](./AGENTS.md)** — the shared guide for every coding agent. Keep that file
|
|
215
|
+
as the single source of truth; do not duplicate its content here.
|
|
216
|
+
|
|
217
|
+
@AGENTS.md
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If `CLAUDE.md` already exists with its own content, do NOT overwrite it — instead offer to
|
|
221
|
+
add the `@AGENTS.md` import line if it is missing.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Phase 7 — Integration Detection
|
|
226
|
+
|
|
227
|
+
**Detect `@aprimediet/minion`:**
|
|
228
|
+
|
|
229
|
+
Step-by-step marker detection:
|
|
230
|
+
1. List all `*.md` files inside the `.pi/` directory at the project root
|
|
231
|
+
2. For each file, read its contents
|
|
232
|
+
3. Check if the file contains both `pi-project: true` and an `id:` field (in YAML frontmatter)
|
|
233
|
+
4. If found, extract the value after `id:` — this is the project id
|
|
234
|
+
|
|
235
|
+
Then check for minion:
|
|
236
|
+
- Check if `~/.pi/projects/<id>/tasks/` directory exists
|
|
237
|
+
- If yes: read each `*.md` file in the tasks dir; count those whose frontmatter has `status:` set to one of: `backlog`, `todo`, `in_progress`, `blocked`, `review`
|
|
238
|
+
- Report: count of open tasks + their titles + statuses
|
|
239
|
+
|
|
240
|
+
**Detect `@aprimediet/memory`:**
|
|
241
|
+
|
|
242
|
+
- Use the **same `.pi/<id>.md` marker** (one file serves both extensions — do not create a new one)
|
|
243
|
+
- Check if `~/.pi/projects/<id>/memory/` directory exists
|
|
244
|
+
- If yes: check for `~/.pi/projects/<id>/memory/MEMORY.md` — read it if present
|
|
245
|
+
- Count `*.md` files in `~/.pi/projects/<id>/memory/entries/`
|
|
246
|
+
- Report: active/not-detected, entry count, MEMORY.md contents
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Phase 8 — Compile Summary
|
|
251
|
+
|
|
252
|
+
Assemble the full project intelligence document using these exact section headers:
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
# Project Intelligence: <project-name>
|
|
256
|
+
> Generated by /learn-this on <date>
|
|
257
|
+
|
|
258
|
+
## Tech Stack
|
|
259
|
+
**Language(s):** ...
|
|
260
|
+
**Frameworks:** ...
|
|
261
|
+
**Infrastructure:** ...
|
|
262
|
+
**Package Manager:** ...
|
|
263
|
+
|
|
264
|
+
## Goals
|
|
265
|
+
...
|
|
266
|
+
|
|
267
|
+
## Non-Goals
|
|
268
|
+
...
|
|
269
|
+
|
|
270
|
+
## Boundaries
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
## Current Status
|
|
274
|
+
**Recent commits:**
|
|
275
|
+
<git log output>
|
|
276
|
+
|
|
277
|
+
**Open TODOs/FIXMEs:** N found
|
|
278
|
+
|
|
279
|
+
## Technical Issues
|
|
280
|
+
- ...
|
|
281
|
+
|
|
282
|
+
## Documentation
|
|
283
|
+
**PRD (`docs/PRD.md`):** exists | created | not created
|
|
284
|
+
**AGENTS.md:** exists | created | not created
|
|
285
|
+
**CLAUDE.md:** exists | created | not created
|
|
286
|
+
**Key points:** ...
|
|
287
|
+
|
|
288
|
+
## Minion Integration
|
|
289
|
+
**Status:** active | not detected
|
|
290
|
+
**Project ID:** ...
|
|
291
|
+
**Open tasks (N):**
|
|
292
|
+
- ...
|
|
293
|
+
|
|
294
|
+
## Memory Integration
|
|
295
|
+
**Status:** active (N entries) | not detected
|
|
296
|
+
**Index:**
|
|
297
|
+
<MEMORY.md contents, or "(empty)">
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Phase 9 — Store to Memory
|
|
303
|
+
|
|
304
|
+
**If `@aprimediet/memory` is active** (detected in Phase 7):
|
|
305
|
+
- Call `memory_write` with `scope: "project"`, `type: "fact"`, and `text:` set to the full Phase 8 summary
|
|
306
|
+
- Report: "Summary stored to project memory."
|
|
307
|
+
|
|
308
|
+
**If memory is not active**:
|
|
309
|
+
- Display the summary in chat
|
|
310
|
+
- Ask: "Would you like me to save this to `docs/project-intelligence.md`?"
|
|
311
|
+
- If yes, write the file.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Checklist
|
|
316
|
+
|
|
317
|
+
- [ ] Phase 1: Tech stack identified
|
|
318
|
+
- [ ] Phase 2: Goals/non-goals found or gathered interactively (one question at a time)
|
|
319
|
+
- [ ] Phase 3: Boundaries documented
|
|
320
|
+
- [ ] Phase 4: Project status from git log + TODO scan
|
|
321
|
+
- [ ] Phase 5: Technical issues detected + user asked about additional issues
|
|
322
|
+
- [ ] Phase 6: Docs generated — PRD (product), AGENTS.md (engineering), CLAUDE.md (pointer), with user confirmation and audience-correct content split
|
|
323
|
+
- [ ] Phase 7: minion compatibility detected; memory compatibility detected
|
|
324
|
+
- [ ] Phase 8: Full summary compiled
|
|
325
|
+
- [ ] Phase 9: Summary stored to memory or saved to file
|