@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.
- package/LICENSE +21 -0
- package/README.md +178 -0
- package/agents/codebase-analyzer.md +121 -0
- package/agents/codebase-locator.md +107 -0
- package/agents/codebase-pattern-finder.md +207 -0
- package/agents/integration-scanner.md +97 -0
- package/agents/precedent-locator.md +130 -0
- package/agents/test-case-locator.md +121 -0
- package/agents/thoughts-analyzer.md +147 -0
- package/agents/thoughts-locator.md +138 -0
- package/agents/web-search-researcher.md +107 -0
- package/extensions/rpiv-core/agents.ts +312 -0
- package/extensions/rpiv-core/git-context.ts +81 -0
- package/extensions/rpiv-core/guidance.ts +213 -0
- package/extensions/rpiv-core/index.ts +275 -0
- package/extensions/rpiv-core/package-checks.ts +51 -0
- package/package.json +36 -0
- package/scripts/migrate.js +242 -0
- package/scripts/types.js +1 -0
- package/skills/annotate-guidance/SKILL.md +303 -0
- package/skills/annotate-guidance/examples/root-dotnet-clean-arch.md +38 -0
- package/skills/annotate-guidance/examples/root-nodejs-monorepo.md +42 -0
- package/skills/annotate-guidance/examples/subfolder-database-layer.md +81 -0
- package/skills/annotate-guidance/examples/subfolder-dotnet-application.md +64 -0
- package/skills/annotate-guidance/examples/subfolder-schemas-layer.md +50 -0
- package/skills/annotate-guidance/templates/root-architecture.md +46 -0
- package/skills/annotate-guidance/templates/subfolder-architecture.md +57 -0
- package/skills/annotate-inline/SKILL.md +299 -0
- package/skills/annotate-inline/examples/root-dotnet-clean-arch.md +38 -0
- package/skills/annotate-inline/examples/root-nodejs-monorepo.md +42 -0
- package/skills/annotate-inline/examples/subfolder-database-layer.md +81 -0
- package/skills/annotate-inline/examples/subfolder-dotnet-application.md +64 -0
- package/skills/annotate-inline/examples/subfolder-schemas-layer.md +50 -0
- package/skills/annotate-inline/templates/root-claude-md.md +46 -0
- package/skills/annotate-inline/templates/subfolder-claude-md.md +57 -0
- package/skills/code-review/SKILL.md +184 -0
- package/skills/commit/SKILL.md +65 -0
- package/skills/create-handoff/SKILL.md +91 -0
- package/skills/design/SKILL.md +416 -0
- package/skills/discover/SKILL.md +242 -0
- package/skills/explore/SKILL.md +261 -0
- package/skills/implement/SKILL.md +74 -0
- package/skills/migrate-to-guidance/SKILL.md +88 -0
- package/skills/outline-test-cases/SKILL.md +348 -0
- package/skills/outline-test-cases/templates/feature-meta.md +43 -0
- package/skills/outline-test-cases/templates/outline-readme.md +36 -0
- package/skills/plan/SKILL.md +281 -0
- package/skills/research/SKILL.md +304 -0
- package/skills/resume-handoff/SKILL.md +207 -0
- package/skills/revise/SKILL.md +242 -0
- package/skills/validate/SKILL.md +175 -0
- package/skills/write-test-cases/SKILL.md +322 -0
- package/skills/write-test-cases/examples/customer-auth-flow.md +50 -0
- package/skills/write-test-cases/examples/order-management-suite.md +57 -0
- package/skills/write-test-cases/examples/order-placement-flow.md +54 -0
- package/skills/write-test-cases/examples/team-management-flow.md +56 -0
- package/skills/write-test-cases/examples/team-management-suite.md +54 -0
- package/skills/write-test-cases/templates/coverage-map.md +64 -0
- package/skills/write-test-cases/templates/regression-suite.md +63 -0
- 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
|
+
}
|