@juicesharp/rpiv-pi 0.4.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/agents/codebase-analyzer.md +121 -0
  4. package/agents/codebase-locator.md +107 -0
  5. package/agents/codebase-pattern-finder.md +207 -0
  6. package/agents/integration-scanner.md +97 -0
  7. package/agents/precedent-locator.md +130 -0
  8. package/agents/test-case-locator.md +121 -0
  9. package/agents/thoughts-analyzer.md +147 -0
  10. package/agents/thoughts-locator.md +138 -0
  11. package/agents/web-search-researcher.md +107 -0
  12. package/extensions/rpiv-core/agents.ts +312 -0
  13. package/extensions/rpiv-core/git-context.ts +81 -0
  14. package/extensions/rpiv-core/guidance.ts +213 -0
  15. package/extensions/rpiv-core/index.ts +275 -0
  16. package/extensions/rpiv-core/package-checks.ts +51 -0
  17. package/package.json +36 -0
  18. package/scripts/migrate.js +242 -0
  19. package/scripts/types.js +1 -0
  20. package/skills/annotate-guidance/SKILL.md +303 -0
  21. package/skills/annotate-guidance/examples/root-dotnet-clean-arch.md +38 -0
  22. package/skills/annotate-guidance/examples/root-nodejs-monorepo.md +42 -0
  23. package/skills/annotate-guidance/examples/subfolder-database-layer.md +81 -0
  24. package/skills/annotate-guidance/examples/subfolder-dotnet-application.md +64 -0
  25. package/skills/annotate-guidance/examples/subfolder-schemas-layer.md +50 -0
  26. package/skills/annotate-guidance/templates/root-architecture.md +46 -0
  27. package/skills/annotate-guidance/templates/subfolder-architecture.md +57 -0
  28. package/skills/annotate-inline/SKILL.md +299 -0
  29. package/skills/annotate-inline/examples/root-dotnet-clean-arch.md +38 -0
  30. package/skills/annotate-inline/examples/root-nodejs-monorepo.md +42 -0
  31. package/skills/annotate-inline/examples/subfolder-database-layer.md +81 -0
  32. package/skills/annotate-inline/examples/subfolder-dotnet-application.md +64 -0
  33. package/skills/annotate-inline/examples/subfolder-schemas-layer.md +50 -0
  34. package/skills/annotate-inline/templates/root-claude-md.md +46 -0
  35. package/skills/annotate-inline/templates/subfolder-claude-md.md +57 -0
  36. package/skills/code-review/SKILL.md +184 -0
  37. package/skills/commit/SKILL.md +65 -0
  38. package/skills/create-handoff/SKILL.md +91 -0
  39. package/skills/design/SKILL.md +416 -0
  40. package/skills/discover/SKILL.md +242 -0
  41. package/skills/explore/SKILL.md +261 -0
  42. package/skills/implement/SKILL.md +74 -0
  43. package/skills/migrate-to-guidance/SKILL.md +88 -0
  44. package/skills/outline-test-cases/SKILL.md +348 -0
  45. package/skills/outline-test-cases/templates/feature-meta.md +43 -0
  46. package/skills/outline-test-cases/templates/outline-readme.md +36 -0
  47. package/skills/plan/SKILL.md +281 -0
  48. package/skills/research/SKILL.md +304 -0
  49. package/skills/resume-handoff/SKILL.md +207 -0
  50. package/skills/revise/SKILL.md +242 -0
  51. package/skills/validate/SKILL.md +175 -0
  52. package/skills/write-test-cases/SKILL.md +322 -0
  53. package/skills/write-test-cases/examples/customer-auth-flow.md +50 -0
  54. package/skills/write-test-cases/examples/order-management-suite.md +57 -0
  55. package/skills/write-test-cases/examples/order-placement-flow.md +54 -0
  56. package/skills/write-test-cases/examples/team-management-flow.md +56 -0
  57. package/skills/write-test-cases/examples/team-management-suite.md +54 -0
  58. package/skills/write-test-cases/templates/coverage-map.md +64 -0
  59. package/skills/write-test-cases/templates/regression-suite.md +63 -0
  60. package/skills/write-test-cases/templates/test-case.md +65 -0
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: web-search-researcher
3
+ description: Do you find yourself desiring information that you don't quite feel well-trained (confident) on? Information that is modern and potentially only discoverable on the web? Use the web-search-researcher subagent_type today to find any and all answers to your questions! It will research deeply to figure out and attempt to answer your questions! If you aren't immediately satisfied you can get your money back! (Not really - but you can re-run web-search-researcher with an altered prompt in the event you're not satisfied the first time)
4
+ tools: web_search, web_fetch, read, grep, find, ls
5
+ ---
6
+
7
+ You are an expert web research specialist focused on finding accurate, relevant information from web sources. Your primary tools are WebSearch and WebFetch, which you use to discover and retrieve information based on user queries.
8
+
9
+ ## Core Responsibilities
10
+
11
+ When you receive a research query, you will:
12
+
13
+ 1. **Analyze the Query**: Break down the user's request to identify:
14
+ - Key search terms and concepts
15
+ - Types of sources likely to have answers (documentation, blogs, forums, academic papers)
16
+ - Multiple search angles to ensure comprehensive coverage
17
+
18
+ 2. **Execute Strategic Searches**:
19
+ - Start with broad searches to understand the landscape
20
+ - Refine with specific technical terms and phrases
21
+ - Use multiple search variations to capture different perspectives
22
+ - Include site-specific searches when targeting known authoritative sources (e.g., "site:docs.stripe.com webhook signature")
23
+
24
+ 3. **Fetch and Analyze Content**:
25
+ - Use WebFetch to retrieve full content from promising search results
26
+ - Prioritize official documentation, reputable technical blogs, and authoritative sources
27
+ - Extract specific quotes and sections relevant to the query
28
+ - Note publication dates to ensure currency of information
29
+
30
+ 4. **Synthesize Findings**:
31
+ - Organize information by relevance and authority
32
+ - Include exact quotes with proper attribution
33
+ - Provide direct links to sources
34
+ - Highlight any conflicting information or version-specific details
35
+ - Note any gaps in available information
36
+
37
+ ## Search Strategies
38
+
39
+ ### For API/Library Documentation:
40
+ - Search for official docs first: "[library name] official documentation [specific feature]"
41
+ - Look for changelog or release notes for version-specific information
42
+ - Find code examples in official repositories or trusted tutorials
43
+
44
+ ### For Best Practices:
45
+ - Search for recent articles (include year in search when relevant)
46
+ - Look for content from recognized experts or organizations
47
+ - Cross-reference multiple sources to identify consensus
48
+ - Search for both "best practices" and "anti-patterns" to get full picture
49
+
50
+ ### For Technical Solutions:
51
+ - Use specific error messages or technical terms in quotes
52
+ - Search Stack Overflow and technical forums for real-world solutions
53
+ - Look for GitHub issues and discussions in relevant repositories
54
+ - Find blog posts describing similar implementations
55
+
56
+ ### For Comparisons:
57
+ - Search for "X vs Y" comparisons
58
+ - Look for migration guides between technologies
59
+ - Find benchmarks and performance comparisons
60
+ - Search for decision matrices or evaluation criteria
61
+
62
+ ## Output Format
63
+
64
+ Structure your findings as:
65
+
66
+ ```
67
+ ## Summary
68
+ [Brief overview of key findings]
69
+
70
+ ## Detailed Findings
71
+
72
+ ### [Topic/Source 1]
73
+ **Source**: [Name with link]
74
+ **Relevance**: [Why this source is authoritative/useful]
75
+ **Key Information**:
76
+ - Direct quote or finding (with link to specific section if possible)
77
+ - Another relevant point
78
+
79
+ ### [Topic/Source 2]
80
+ [Continue pattern...]
81
+
82
+ ## Additional Resources
83
+ - [Relevant link 1] - Brief description
84
+ - [Relevant link 2] - Brief description
85
+
86
+ ## Gaps or Limitations
87
+ [Note any information that couldn't be found or requires further investigation]
88
+ ```
89
+
90
+ ## Quality Guidelines
91
+
92
+ - **Accuracy**: Always quote sources accurately and provide direct links
93
+ - **Relevance**: Focus on information that directly addresses the user's query
94
+ - **Currency**: Note publication dates and version information when relevant
95
+ - **Authority**: Prioritize official sources, recognized experts, and peer-reviewed content
96
+ - **Completeness**: Search from multiple angles to ensure comprehensive coverage
97
+ - **Transparency**: Clearly indicate when information is outdated, conflicting, or uncertain
98
+
99
+ ## Search Efficiency
100
+
101
+ - Start with 2-3 well-crafted searches before fetching content
102
+ - Fetch only the most promising 3-5 pages initially
103
+ - If initial results are insufficient, refine search terms and try again
104
+ - Use search operators effectively: quotes for exact phrases, minus for exclusions, site: for specific domains
105
+ - Consider searching in different forms: tutorials, documentation, Q&A sites, and discussion forums
106
+
107
+ Remember: You are the user's expert guide to web information. Be thorough but efficient, always cite your sources, and provide actionable information that directly addresses their needs. Think deeply as you work.
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Agent auto-copy — copies bundled agents into <cwd>/.pi/agents/.
3
+ *
4
+ * Pure utility. No ExtensionAPI interactions.
5
+ */
6
+
7
+ import {
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ copyFileSync,
12
+ readFileSync,
13
+ writeFileSync,
14
+ unlinkSync,
15
+ } from "node:fs";
16
+ import { fileURLToPath } from "node:url";
17
+ import { join, dirname } from "node:path";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Package-root resolution
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Resolves the rpiv-pi package root from this module's file URL.
25
+ * Walks up from `extensions/rpiv-core/agents.ts` to the repo root.
26
+ */
27
+ export const PACKAGE_ROOT = (() => {
28
+ const thisFile = fileURLToPath(import.meta.url);
29
+ // extensions/rpiv-core/agents.ts -> rpiv-pi/
30
+ return dirname(dirname(dirname(thisFile)));
31
+ })();
32
+
33
+ export const BUNDLED_AGENTS_DIR = join(PACKAGE_ROOT, "agents");
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Types
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export interface SyncError {
40
+ file?: string;
41
+ op: "read-src" | "read-dest" | "copy" | "remove" | "manifest-read" | "manifest-write";
42
+ message: string;
43
+ }
44
+
45
+ export interface SyncResult {
46
+ /** New files copied (present in source, absent from destination). */
47
+ added: string[];
48
+ /** Existing managed files overwritten with updated source content. */
49
+ updated: string[];
50
+ /** Managed files whose destination content matches source exactly. */
51
+ unchanged: string[];
52
+ /** Stale managed files removed (present in manifest but absent from source). */
53
+ removed: string[];
54
+ /** Managed files with different destination content (detected but not applied). */
55
+ pendingUpdate: string[];
56
+ /** Managed files no longer in source (detected but not removed). */
57
+ pendingRemove: string[];
58
+ /** Per-file errors collected during sync. */
59
+ errors: SyncError[];
60
+
61
+ // -- Legacy aliases (backward compat for existing callers) --
62
+ /** Alias: added + updated (files written by this run). */
63
+ copied: string[];
64
+ /** Alias: unchanged + pendingUpdate + files that errored during read and were not written. */
65
+ skipped: string[];
66
+ }
67
+
68
+ /** Create an empty SyncResult with all arrays initialized. */
69
+ function emptySyncResult(): SyncResult {
70
+ return {
71
+ added: [],
72
+ updated: [],
73
+ unchanged: [],
74
+ removed: [],
75
+ pendingUpdate: [],
76
+ pendingRemove: [],
77
+ errors: [],
78
+ copied: [],
79
+ skipped: [],
80
+ };
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Manifest
85
+ // ---------------------------------------------------------------------------
86
+
87
+ const MANIFEST_FILE = ".rpiv-managed.json";
88
+
89
+ /**
90
+ * Read the managed-file manifest from the target directory.
91
+ * Returns an empty array on missing/invalid/unreadable manifest.
92
+ * Fail-soft: never throws.
93
+ */
94
+ function readManifest(targetDir: string): string[] {
95
+ const manifestPath = join(targetDir, MANIFEST_FILE);
96
+ if (!existsSync(manifestPath)) return [];
97
+ try {
98
+ const raw = readFileSync(manifestPath, "utf-8");
99
+ const parsed = JSON.parse(raw);
100
+ if (!Array.isArray(parsed)) return [];
101
+ return parsed.filter((e): e is string => typeof e === "string");
102
+ } catch {
103
+ return [];
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Write the managed-file manifest to the target directory.
109
+ * Fail-soft: swallows write errors (permissions, disk full, etc.).
110
+ */
111
+ function writeManifest(targetDir: string, filenames: string[]): void {
112
+ const manifestPath = join(targetDir, MANIFEST_FILE);
113
+ try {
114
+ writeFileSync(manifestPath, JSON.stringify(filenames, null, 2) + "\n", "utf-8");
115
+ } catch {
116
+ // non-fatal — sync results will still be correct for this run;
117
+ // next run will re-bootstrap if manifest is missing
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Bootstrap the managed-file manifest on first run after upgrade.
123
+ *
124
+ * When no manifest exists, claims all existing destination files whose
125
+ * names match the current bundled source list as rpiv-managed.
126
+ * Writes the manifest and returns the managed set.
127
+ *
128
+ * If a manifest already exists, returns it as-is.
129
+ */
130
+ function bootstrapManifest(targetDir: string, sourceNames: Set<string>): string[] {
131
+ const existing = readManifest(targetDir);
132
+ if (existing.length > 0) return existing;
133
+
134
+ const managed: string[] = [];
135
+ try {
136
+ const destEntries = readdirSync(targetDir).filter((f) => f.endsWith(".md"));
137
+ for (const name of destEntries) {
138
+ if (sourceNames.has(name)) {
139
+ managed.push(name);
140
+ }
141
+ }
142
+ } catch {
143
+ // dest dir may not exist yet — that's fine, empty manifest
144
+ }
145
+
146
+ writeManifest(targetDir, managed);
147
+ return managed;
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Agent Sync Engine
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /**
155
+ * Synchronize bundled agents from <PACKAGE_ROOT>/agents/ into <cwd>/.pi/agents/.
156
+ *
157
+ * When `apply` is false (session_start): adds new files only.
158
+ * Detects pending updates and removals without applying them.
159
+ * When `apply` is true (/rpiv-update-agents): adds new, overwrites changed
160
+ * managed files, removes stale managed files.
161
+ *
162
+ * Never throws — errors are collected in `result.errors`.
163
+ */
164
+ export function syncBundledAgents(cwd: string, apply: boolean): SyncResult {
165
+ const result = emptySyncResult();
166
+
167
+ if (!existsSync(BUNDLED_AGENTS_DIR)) {
168
+ return result;
169
+ }
170
+
171
+ const targetDir = join(cwd, ".pi", "agents");
172
+ try {
173
+ mkdirSync(targetDir, { recursive: true });
174
+ } catch {
175
+ result.errors.push({ op: "manifest-write", message: "Failed to create target directory" });
176
+ return result;
177
+ }
178
+
179
+ // 1. Enumerate source files
180
+ let sourceEntries: string[];
181
+ try {
182
+ sourceEntries = readdirSync(BUNDLED_AGENTS_DIR).filter((f) => f.endsWith(".md"));
183
+ } catch {
184
+ result.errors.push({ op: "read-src", message: "Failed to read bundled agents directory" });
185
+ return result;
186
+ }
187
+
188
+ const sourceNames = new Set(sourceEntries);
189
+
190
+ // 2. Bootstrap manifest and get managed set
191
+ const managedNames = new Set(bootstrapManifest(targetDir, sourceNames));
192
+
193
+ // 3. Process each source file
194
+ for (const entry of sourceEntries) {
195
+ const src = join(BUNDLED_AGENTS_DIR, entry);
196
+ const dest = join(targetDir, entry);
197
+
198
+ if (!existsSync(dest)) {
199
+ try {
200
+ copyFileSync(src, dest);
201
+ result.added.push(entry);
202
+ } catch (e) {
203
+ result.errors.push({
204
+ file: entry,
205
+ op: "copy",
206
+ message: e instanceof Error ? e.message : String(e),
207
+ });
208
+ }
209
+ continue;
210
+ }
211
+
212
+ let srcContent: Buffer;
213
+ let destContent: Buffer;
214
+ try {
215
+ srcContent = readFileSync(src);
216
+ } catch (e) {
217
+ result.errors.push({
218
+ file: entry,
219
+ op: "read-src",
220
+ message: e instanceof Error ? e.message : String(e),
221
+ });
222
+ result.skipped.push(entry);
223
+ continue;
224
+ }
225
+ try {
226
+ destContent = readFileSync(dest);
227
+ } catch (e) {
228
+ result.errors.push({
229
+ file: entry,
230
+ op: "read-dest",
231
+ message: e instanceof Error ? e.message : String(e),
232
+ });
233
+ result.skipped.push(entry);
234
+ continue;
235
+ }
236
+
237
+ if (Buffer.compare(srcContent, destContent) === 0) {
238
+ result.unchanged.push(entry);
239
+ result.skipped.push(entry);
240
+ } else if (apply) {
241
+ try {
242
+ copyFileSync(src, dest);
243
+ result.updated.push(entry);
244
+ result.copied.push(entry);
245
+ } catch (e) {
246
+ result.errors.push({
247
+ file: entry,
248
+ op: "copy",
249
+ message: e instanceof Error ? e.message : String(e),
250
+ });
251
+ }
252
+ } else {
253
+ result.pendingUpdate.push(entry);
254
+ result.skipped.push(entry);
255
+ }
256
+ }
257
+
258
+ // 4. Process stale managed files (in manifest but not in source)
259
+ for (const name of managedNames) {
260
+ if (sourceNames.has(name)) continue;
261
+
262
+ const destPath = join(targetDir, name);
263
+ if (!existsSync(destPath)) continue;
264
+
265
+ if (apply) {
266
+ try {
267
+ unlinkSync(destPath);
268
+ result.removed.push(name);
269
+ } catch (e) {
270
+ result.errors.push({
271
+ file: name,
272
+ op: "remove",
273
+ message: e instanceof Error ? e.message : String(e),
274
+ });
275
+ }
276
+ } else {
277
+ result.pendingRemove.push(name);
278
+ }
279
+ }
280
+
281
+ // 5. Update manifest to reflect what's currently managed on disk.
282
+ // apply=true: stale files were removed, so manifest = sourceEntries.
283
+ // apply=false: stale files still exist on disk and must stay tracked
284
+ // so the next apply can remove them.
285
+ const manifestEntries = apply
286
+ ? sourceEntries
287
+ : [...sourceEntries, ...result.pendingRemove];
288
+ writeManifest(targetDir, manifestEntries);
289
+
290
+ // 6. Populate legacy `copied` alias (added + updated)
291
+ for (const name of result.added) {
292
+ result.copied.push(name);
293
+ }
294
+ // updated files were pushed to `copied` inline in the loop above
295
+
296
+ return result;
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // Backward-compatible wrapper
301
+ // ---------------------------------------------------------------------------
302
+
303
+ /**
304
+ * Legacy entry point — delegates to syncBundledAgents.
305
+ * Kept for backward compatibility; prefer syncBundledAgents for new callers.
306
+ */
307
+ export function copyBundledAgents(cwd: string, overwrite: boolean): {
308
+ copied: string[];
309
+ skipped: string[];
310
+ } {
311
+ return syncBundledAgents(cwd, overwrite);
312
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Cached branch + short commit. Injected into the transcript once at
3
+ * session_start, re-injected on session_compact (transcript cleared) and
4
+ * only when the cached value changes (e.g. after a mutating git command).
5
+ * Two parallel `git rev-parse` calls — one call can't combine
6
+ * `--abbrev-ref` and `--short` cleanly because the `--abbrev-ref` mode
7
+ * persists to subsequent revs. git itself resolves worktree gitdir
8
+ * redirection, so either form is worktree-safe.
9
+ */
10
+
11
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
12
+
13
+ type GitContext = { branch: string; commit: string; user: string };
14
+
15
+ // Signature (branch+commit) of the last message pushed into the transcript.
16
+ // null = transcript has nothing current and needs re-injection.
17
+ let lastInjectedSig: string | null = null;
18
+
19
+ // undefined = not loaded yet, null = not a git repo / failed, object = valid
20
+ let cache: GitContext | null | undefined = undefined;
21
+
22
+ export async function getGitContext(pi: ExtensionAPI): Promise<GitContext | null> {
23
+ if (cache !== undefined) return cache;
24
+ cache = await loadGitContext(pi);
25
+ return cache;
26
+ }
27
+
28
+ export function clearGitContextCache(): void {
29
+ cache = undefined;
30
+ }
31
+
32
+ // Detached HEAD emits literal "HEAD" for --abbrev-ref; remap so frontmatter is meaningful.
33
+ async function loadGitContext(pi: ExtensionAPI): Promise<GitContext | null> {
34
+ try {
35
+ const [branchRes, commitRes] = await Promise.all([
36
+ pi.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 5000 }),
37
+ pi.exec("git", ["rev-parse", "--short", "HEAD"], { timeout: 5000 }),
38
+ ]);
39
+ const rawBranch = branchRes.stdout.trim();
40
+ const commit = commitRes.stdout.trim();
41
+ if (!rawBranch && !commit) return null;
42
+ const branch = rawBranch === "HEAD" ? "detached" : rawBranch;
43
+ let user = "";
44
+ try {
45
+ const r2 = await pi.exec("git", ["config", "user.name"], { timeout: 5000 });
46
+ user = r2.stdout.trim();
47
+ } catch {
48
+ // fall through to env fallback
49
+ }
50
+ if (!user) user = process.env.USER || "unknown";
51
+ return {
52
+ branch: branch || "no-branch",
53
+ commit: commit || "no-commit",
54
+ user,
55
+ };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ export function resetInjectedMarker(): void {
62
+ lastInjectedSig = null;
63
+ }
64
+
65
+ // Returns the message content to inject, or null if the transcript is
66
+ // already up-to-date or we're not in a git repo. Updates the marker
67
+ // whenever it returns non-null.
68
+ export async function takeGitContextIfChanged(pi: ExtensionAPI): Promise<string | null> {
69
+ const g = await getGitContext(pi);
70
+ if (!g) return null;
71
+ const sig = `${g.branch}\n${g.commit}\n${g.user}`;
72
+ if (sig === lastInjectedSig) return null;
73
+ lastInjectedSig = sig;
74
+ return `## Git Context\n- Branch: ${g.branch}\n- Commit: ${g.commit}\n- User: ${g.user}`;
75
+ }
76
+
77
+ export function isGitMutatingCommand(cmd: string): boolean {
78
+ return /\bgit\s+(checkout|switch|commit|merge|rebase|pull|reset|revert|cherry-pick|worktree|am|stash)\b/.test(
79
+ cmd,
80
+ );
81
+ }