@juicesharp/rpiv-pi 1.8.2 → 1.9.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/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Skill-based development workflow for [Pi Agent](https://github.com/badlogic/pi-m
|
|
|
21
21
|
|
|
22
22
|
- **A pipeline of chained AI skills** - discover → research → design → plan → implement → validate, each producing a reviewable artifact under `.rpiv/artifacts/`.
|
|
23
23
|
- **Named subagents for parallel analysis** - `codebase-analyzer`, `codebase-locator`, `codebase-pattern-finder`, `claim-verifier`, and 8 more, dispatched automatically by skills.
|
|
24
|
-
- **Session lifecycle hooks** - agent profiles
|
|
24
|
+
- **Session lifecycle hooks** - agent profiles and guidance files install themselves on first launch.
|
|
25
25
|
|
|
26
26
|
## Prerequisites
|
|
27
27
|
|
|
@@ -93,7 +93,7 @@ pi install npm:@juicesharp/rpiv-pi
|
|
|
93
93
|
On first Pi Agent session start, rpiv-pi automatically:
|
|
94
94
|
- Copies agent profiles to `~/.pi/agent/agents/` (user-global, shared across all projects)
|
|
95
95
|
- Detects outdated or removed agents on subsequent starts
|
|
96
|
-
- Migrates legacy pipeline-artifact content into `.rpiv/artifacts/` (one-way)
|
|
96
|
+
- Migrates legacy pipeline-artifact content into `.rpiv/artifacts/` (one-way) when an old `thoughts/shared/` tree is found; otherwise `.rpiv/artifacts/` is created lazily by the first skill that writes an artifact
|
|
97
97
|
- Shows a warning if any sibling plugins are missing
|
|
98
98
|
|
|
99
99
|
## Usage
|
|
@@ -66,23 +66,13 @@ describe("registerSessionHooks — event wiring", () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
describe("session_start hook — migration", () => {
|
|
69
|
-
it("
|
|
69
|
+
it("does NOT create .rpiv/artifacts/ on fresh project (no migration source) — issue #31", async () => {
|
|
70
70
|
const { pi, captured } = createMockPi({ exec: stubGitExec({}) as never });
|
|
71
71
|
registerSessionHooks(pi);
|
|
72
72
|
const handler = captured.events.get("session_start")?.[0];
|
|
73
73
|
const ctx = createMockCtx({ cwd: projectDir, hasUI: true });
|
|
74
74
|
await handler?.({ reason: "startup" } as never, ctx as never);
|
|
75
|
-
|
|
76
|
-
".rpiv/artifacts/discover",
|
|
77
|
-
".rpiv/artifacts/research",
|
|
78
|
-
".rpiv/artifacts/designs",
|
|
79
|
-
".rpiv/artifacts/plans",
|
|
80
|
-
".rpiv/artifacts/handoffs",
|
|
81
|
-
".rpiv/artifacts/reviews",
|
|
82
|
-
".rpiv/artifacts/solutions",
|
|
83
|
-
]) {
|
|
84
|
-
expect(existsSync(join(projectDir, d))).toBe(true);
|
|
85
|
-
}
|
|
75
|
+
expect(existsSync(join(projectDir, ".rpiv", "artifacts"))).toBe(false);
|
|
86
76
|
});
|
|
87
77
|
|
|
88
78
|
it("migrates thoughts/shared/ to .rpiv/artifacts/ with content preservation", async () => {
|
|
@@ -124,6 +114,42 @@ describe("session_start hook — migration", () => {
|
|
|
124
114
|
expect(existsSync(join(projectDir, "thoughts"))).toBe(true);
|
|
125
115
|
});
|
|
126
116
|
|
|
117
|
+
it("does NOT create .rpiv/artifacts/ when thoughts/shared/ exists but is empty", async () => {
|
|
118
|
+
// Edge case: thoughts/shared/ pre-exists (created by tool, partial migration, etc.) but holds no entries.
|
|
119
|
+
// Migration must not leak an empty .rpiv/artifacts/ tree, and must not delete the empty source.
|
|
120
|
+
mkdirSync(join(projectDir, "thoughts", "shared"), { recursive: true });
|
|
121
|
+
|
|
122
|
+
const { pi, captured } = createMockPi({ exec: stubGitExec({}) as never });
|
|
123
|
+
registerSessionHooks(pi);
|
|
124
|
+
const handler = captured.events.get("session_start")?.[0];
|
|
125
|
+
const ctx = createMockCtx({ cwd: projectDir, hasUI: true });
|
|
126
|
+
await handler?.({ reason: "startup" } as never, ctx as never);
|
|
127
|
+
|
|
128
|
+
expect(existsSync(join(projectDir, ".rpiv", "artifacts"))).toBe(false);
|
|
129
|
+
expect(existsSync(join(projectDir, "thoughts", "shared"))).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("preserves loose files at thoughts/shared/ root (copies them, not just subdirectories)", async () => {
|
|
133
|
+
// Regression: prior implementation filtered to directories only, dropping loose .md files
|
|
134
|
+
// at the shared/ root on rmSync. Now cpSync copies both files and directories.
|
|
135
|
+
const oldShared = join(projectDir, "thoughts", "shared");
|
|
136
|
+
mkdirSync(oldShared, { recursive: true });
|
|
137
|
+
writeFileSync(join(oldShared, "loose.md"), "loose content");
|
|
138
|
+
const oldResearch = join(oldShared, "research");
|
|
139
|
+
mkdirSync(oldResearch, { recursive: true });
|
|
140
|
+
writeFileSync(join(oldResearch, "nested.md"), "nested content");
|
|
141
|
+
|
|
142
|
+
const { pi, captured } = createMockPi({ exec: stubGitExec({}) as never });
|
|
143
|
+
registerSessionHooks(pi);
|
|
144
|
+
const handler = captured.events.get("session_start")?.[0];
|
|
145
|
+
const ctx = createMockCtx({ cwd: projectDir, hasUI: true });
|
|
146
|
+
await handler?.({ reason: "startup" } as never, ctx as never);
|
|
147
|
+
|
|
148
|
+
expect(existsSync(join(projectDir, ".rpiv", "artifacts", "loose.md"))).toBe(true);
|
|
149
|
+
expect(existsSync(join(projectDir, ".rpiv", "artifacts", "research", "nested.md"))).toBe(true);
|
|
150
|
+
expect(existsSync(join(projectDir, "thoughts"))).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
127
153
|
it("no-ops when thoughts/shared/ does not exist (fresh project)", async () => {
|
|
128
154
|
const { pi, captured } = createMockPi({ exec: stubGitExec({}) as never });
|
|
129
155
|
registerSessionHooks(pi);
|
|
@@ -131,8 +157,8 @@ describe("session_start hook — migration", () => {
|
|
|
131
157
|
const ctx = createMockCtx({ cwd: projectDir, hasUI: true });
|
|
132
158
|
await handler?.({ reason: "startup" } as never, ctx as never);
|
|
133
159
|
|
|
134
|
-
//
|
|
135
|
-
expect(existsSync(join(projectDir, ".rpiv", "artifacts"))).toBe(
|
|
160
|
+
// No migration source → no .rpiv/artifacts/ tree, no thoughts/ tree
|
|
161
|
+
expect(existsSync(join(projectDir, ".rpiv", "artifacts"))).toBe(false);
|
|
136
162
|
expect(existsSync(join(projectDir, "thoughts"))).toBe(false);
|
|
137
163
|
});
|
|
138
164
|
|
|
@@ -30,17 +30,6 @@ import {
|
|
|
30
30
|
import { ARTIFACTS_SUBDIR, clearInjectionState, handleToolCallGuidance, injectRootGuidance } from "./guidance.js";
|
|
31
31
|
import { findMissingSiblings } from "./package-checks.js";
|
|
32
32
|
|
|
33
|
-
const ARTIFACTS_ROOT = `.rpiv/${ARTIFACTS_SUBDIR}`;
|
|
34
|
-
const ARTIFACTS_DIRS = [
|
|
35
|
-
`${ARTIFACTS_ROOT}/discover`,
|
|
36
|
-
`${ARTIFACTS_ROOT}/research`,
|
|
37
|
-
`${ARTIFACTS_ROOT}/designs`,
|
|
38
|
-
`${ARTIFACTS_ROOT}/plans`,
|
|
39
|
-
`${ARTIFACTS_ROOT}/handoffs`,
|
|
40
|
-
`${ARTIFACTS_ROOT}/reviews`,
|
|
41
|
-
`${ARTIFACTS_ROOT}/solutions`,
|
|
42
|
-
] as const;
|
|
43
|
-
|
|
44
33
|
const msgAgentsAdded = (n: number) => `Copied ${n} rpiv-pi agent(s) to ~/.pi/agent/agents/`;
|
|
45
34
|
const msgAgentsHealed = (parts: string[]) => `Synced bundled agent(s): ${parts.join(", ")}.`;
|
|
46
35
|
const msgAgentsDrift = (parts: string[]) => `${parts.join(", ")} agent(s). Run /rpiv-update-agents to sync.`;
|
|
@@ -135,46 +124,40 @@ function resetInjectionState(): void {
|
|
|
135
124
|
|
|
136
125
|
function migrateThoughtsToArtifacts(cwd: string): void {
|
|
137
126
|
const oldShared = join(cwd, "thoughts", "shared");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
127
|
+
if (!existsSync(oldShared)) return;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const entries = readdirSync(oldShared, { withFileTypes: true });
|
|
131
|
+
if (entries.length === 0) return; // empty source — nothing to copy, leave on disk
|
|
132
|
+
|
|
133
|
+
const newArtifacts = join(cwd, ".rpiv", ARTIFACTS_SUBDIR);
|
|
134
|
+
mkdirSync(newArtifacts, { recursive: true });
|
|
135
|
+
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const src = join(oldShared, entry.name);
|
|
138
|
+
const dest = join(newArtifacts, entry.name);
|
|
139
|
+
cpSync(src, dest, { recursive: true, errorOnExist: false, force: true });
|
|
140
|
+
if (!existsSync(dest)) {
|
|
141
|
+
console.warn(`[rpiv-pi] migration: failed to copy ${src} → ${dest}`);
|
|
142
|
+
return; // abort — don't delete source if copy failed
|
|
155
143
|
}
|
|
144
|
+
}
|
|
156
145
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
} catch {
|
|
167
|
-
// thoughts/ already gone or unreadable — not an error
|
|
146
|
+
// All copies verified — safe to remove source
|
|
147
|
+
rmSync(oldShared, { recursive: true, force: true });
|
|
148
|
+
|
|
149
|
+
// Remove thoughts/ root only if empty (preserves thoughts/me/ etc.)
|
|
150
|
+
const thoughtsRoot = join(cwd, "thoughts");
|
|
151
|
+
try {
|
|
152
|
+
if (readdirSync(thoughtsRoot).length === 0) {
|
|
153
|
+
rmSync(thoughtsRoot, { recursive: true, force: true });
|
|
168
154
|
}
|
|
169
|
-
} catch
|
|
170
|
-
|
|
171
|
-
// Never crash session_start — migration is best-effort
|
|
155
|
+
} catch {
|
|
156
|
+
// thoughts/ already gone or unreadable — not an error
|
|
172
157
|
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
for (const dir of ARTIFACTS_DIRS) {
|
|
177
|
-
mkdirSync(join(cwd, dir), { recursive: true });
|
|
158
|
+
} catch (e) {
|
|
159
|
+
console.warn(`[rpiv-pi] migration: ${e instanceof Error ? e.message : String(e)}`);
|
|
160
|
+
// Never crash session_start — migration is best-effort
|
|
178
161
|
}
|
|
179
162
|
}
|
|
180
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "A skill-based development workflow for Pi Agent. Five skills (research, design, plan, implement, validate) and the shared subagents that compose its ship-loop.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/skills/discover/SKILL.md
CHANGED
|
@@ -164,7 +164,7 @@ Compile interview output into the FRD. The interview's logical order (problem
|
|
|
164
164
|
- Timestamp: run `date +"%Y-%m-%dT%H:%M:%S%z"` — raw for `date:` and `last_updated:`, first 19 chars (`T`→`_`, `:`→`-`) for filename slug.
|
|
165
165
|
- Interviewer: from the User in the injected git context (fallback: `unknown`).
|
|
166
166
|
|
|
167
|
-
2. **Write the FRD** using the Write tool. Frontmatter `status: complete`. All template sections present and filled. The
|
|
167
|
+
2. **Write the FRD** using the Write tool. Frontmatter `status: complete`. All template sections present and filled. The Write tool creates parent directories automatically — no `mkdir -p` needed in the skill.
|
|
168
168
|
|
|
169
169
|
3. **Present and chain**:
|
|
170
170
|
```
|