arey-pi 0.4.0 → 0.6.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 +29 -14
- package/docs/adoption.md +6 -6
- package/docs/commands.md +37 -194
- package/docs/templates.md +1 -1
- package/docs/workflows.md +13 -14
- package/extensions/arey-pi/bootstrap.ts +198 -0
- package/extensions/arey-pi/core.ts +33 -19
- package/extensions/arey-pi/index.ts +5 -270
- package/extensions/arey-pi/paths.ts +28 -0
- package/extensions/arey-pi/workflow-runtime.ts +112 -0
- package/package.json +1 -1
- package/prompts/adr-review.md +33 -0
- package/prompts/engineering-review.md +37 -0
- package/prompts/feature-spec.md +35 -0
- package/prompts/red-green-refactor.md +34 -0
- package/prompts/sync-drift.md +44 -0
- package/skills/engineering-review/SKILL.md +65 -0
- package/skills/spec-sync/SKILL.md +69 -0
- package/skills/tdd-red-green-refactor/SKILL.md +69 -0
|
@@ -56,26 +56,40 @@ export function parseBootstrapFlags(args: string): BootstrapPlan {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export
|
|
59
|
+
export function shouldActivateAreyPiHarness(prompt: string): boolean {
|
|
60
|
+
const normalized = prompt.toLowerCase();
|
|
61
|
+
if (normalized.includes("arey pi harness is active")) return false;
|
|
62
|
+
return /\barey(?:\s+pi)?\b/.test(normalized);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const evidenceSummary = `Final evidence format:\n- Behaviour/spec impact:\n- Tests/TDD, including test location:\n- Validation commands and results:\n- Quality tooling:\n- Spec sync:\n- Documentation sync:\n- Architecture/ADR/glossary impact:\n- Database/DBML impact:\n- Residual risks:`;
|
|
60
66
|
|
|
61
|
-
export function
|
|
62
|
-
const target =
|
|
63
|
-
const common = `Act as the Arey Pi tech lead. Use pi-subagents when available and appropriate. Keep orchestration authority in the parent session, give child agents bounded tasks, and keep one writer in the active worktree at a time. Follow Arey Pi rules, preserve TDD, and report evidence clearly.`;
|
|
67
|
+
export function areyPiHarnessContext(prompt: string): string {
|
|
68
|
+
const target = prompt.trim() || "the current request";
|
|
64
69
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
return [
|
|
71
|
+
"Arey Pi harness is active for this request.",
|
|
72
|
+
"Act as the parent Arey Pi orchestrator: infer intent, choose the workflow, delegate bounded work to specialist subagents when available, then synthesise and finalise.",
|
|
73
|
+
"Work naturally; do not expose workflow ceremony unless it helps the user.",
|
|
74
|
+
"Infer the user's intent yourself. The work may be a feature, bugfix, sync, review, assessment, or a mixed task.",
|
|
75
|
+
"Use the workflow as an internal operating loop: clarify → spec/plan → TDD or audit → sync → review → evidence.",
|
|
76
|
+
"Select the matching Arey Pi posture:",
|
|
77
|
+
"- Feature or behaviour change: clarify scope, update/confirm canonical Gherkin first, then preserve Red → Green → Refactor.",
|
|
78
|
+
"- Bugfix: reproduce with a meaningful failing regression test before production changes.",
|
|
79
|
+
"- Sync: inspect drift across specs, tests, code, DBML, ADRs, glossary, README, docs, AGENTS.md, skills, prompts, rules, agents, commands, templates, and tooling instructions.",
|
|
80
|
+
"- Review: perform adversarial engineering review with severity-classified findings.",
|
|
81
|
+
"- Assessment: audit readiness with evidence, blockers, quick wins, and a prioritised improvement plan.",
|
|
82
|
+
"Subagents are a first-class part of Arey Pi when available: use arey-pi.spec-author for specs, arey-pi.tdd-implementer for Red → Green → Refactor, arey-pi.spec-syncer for alignment, arey-pi.engineering-reviewer for fresh review, and arey-pi.project-evaluator for readiness assessment.",
|
|
83
|
+
"Use builtin scout/context-builder/planner/reviewer/oracle agents when they fit the task, especially for discovery, planning, second opinions, and fresh review.",
|
|
84
|
+
"Keep orchestration in the parent session, give child agents concrete bounded tasks, and keep one writer in the active worktree.",
|
|
85
|
+
"Tests should live outside production source directories by default.",
|
|
86
|
+
"Do not rewrite specs to hide implementation defects.",
|
|
87
|
+
"Report evidence and residual risks clearly before finalising.",
|
|
88
|
+
"",
|
|
89
|
+
`User request: ${target}`,
|
|
90
|
+
"",
|
|
91
|
+
evidenceSummary,
|
|
92
|
+
].join("\n");
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
export type DoctorReportInput = {
|
|
@@ -115,7 +129,7 @@ export function buildDoctorReport(input: DoctorReportInput): string {
|
|
|
115
129
|
"",
|
|
116
130
|
"## Recommended next step",
|
|
117
131
|
input.installedAgentsCount === input.requiredAgentsCount
|
|
118
|
-
? "- Project-local Arey Pi subagents are installed. Use
|
|
132
|
+
? "- Project-local Arey Pi subagents are installed. Use natural language such as `Implementa password reset siguiendo Arey Pi`."
|
|
119
133
|
: "- Run `/arey-bootstrap` to install project-local Arey Pi subagents.",
|
|
120
134
|
].join("\n");
|
|
121
135
|
}
|
|
@@ -1,273 +1,8 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, join, relative } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
docsScaffoldFiles,
|
|
8
|
-
parseBootstrapFlags,
|
|
9
|
-
requiredAgents,
|
|
10
|
-
specScaffoldFiles,
|
|
11
|
-
workflowMessage,
|
|
12
|
-
type ScaffoldFile,
|
|
13
|
-
} from "./core.ts";
|
|
2
|
+
import { registerBootstrapCommands } from "./bootstrap.ts";
|
|
3
|
+
import { registerWorkflowRuntime } from "./workflow-runtime.ts";
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const templatesDir = join(packageRoot, "templates");
|
|
19
|
-
|
|
20
|
-
function cwdFrom(ctx: unknown): string {
|
|
21
|
-
const maybe = ctx as { cwd?: string };
|
|
22
|
-
return maybe.cwd ?? process.cwd();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function fileExists(path: string): boolean {
|
|
26
|
-
try {
|
|
27
|
-
return existsSync(path) && statSync(path).isFile();
|
|
28
|
-
} catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function dirExists(path: string): boolean {
|
|
34
|
-
try {
|
|
35
|
-
return existsSync(path) && statSync(path).isDirectory();
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function copyAgents(targetDir: string, force: boolean): { copied: string[]; skipped: string[]; missing: string[] } {
|
|
42
|
-
mkdirSync(targetDir, { recursive: true });
|
|
43
|
-
|
|
44
|
-
const copied: string[] = [];
|
|
45
|
-
const skipped: string[] = [];
|
|
46
|
-
const missing: string[] = [];
|
|
47
|
-
|
|
48
|
-
for (const agent of requiredAgents) {
|
|
49
|
-
const source = join(agentSourceDir, agent);
|
|
50
|
-
const target = join(targetDir, agent);
|
|
51
|
-
|
|
52
|
-
if (!fileExists(source)) {
|
|
53
|
-
missing.push(agent);
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (fileExists(target) && !force) {
|
|
58
|
-
skipped.push(agent);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
copyFileSync(source, target);
|
|
63
|
-
copied.push(agent);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return { copied, skipped, missing };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
type ScaffoldResult = { created: string[]; skipped: string[] };
|
|
70
|
-
|
|
71
|
-
function templateContent(name: string): string {
|
|
72
|
-
return readFileSync(join(templatesDir, name), "utf8");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function writeTemplateIfMissing(file: ScaffoldFile, force: boolean, cwd: string, result: ScaffoldResult) {
|
|
76
|
-
const target = join(cwd, file.target);
|
|
77
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
78
|
-
|
|
79
|
-
if (fileExists(target) && !force) {
|
|
80
|
-
result.skipped.push(file.target);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
writeFileSync(target, templateContent(file.template));
|
|
85
|
-
result.created.push(file.target);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function scaffoldFiles(cwd: string, force: boolean, files: ScaffoldFile[]): ScaffoldResult {
|
|
89
|
-
const result: ScaffoldResult = { created: [], skipped: [] };
|
|
90
|
-
|
|
91
|
-
for (const file of files) {
|
|
92
|
-
writeTemplateIfMissing(file, force, cwd, result);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function scaffoldSpecs(cwd: string, force: boolean): ScaffoldResult {
|
|
99
|
-
return scaffoldFiles(cwd, force, specScaffoldFiles);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function scaffoldDocs(cwd: string, force: boolean): ScaffoldResult {
|
|
103
|
-
return scaffoldFiles(cwd, force, docsScaffoldFiles);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function starterAgentsMd(): string {
|
|
107
|
-
return templateContent("AGENTS.md");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function sendWorkflow(
|
|
111
|
-
pi: ExtensionAPI,
|
|
112
|
-
args: string,
|
|
113
|
-
ctx: {
|
|
114
|
-
ui: { notify(message: string, level?: string): void };
|
|
115
|
-
isIdle(): boolean;
|
|
116
|
-
},
|
|
117
|
-
kind: string,
|
|
118
|
-
usage: string,
|
|
119
|
-
) {
|
|
120
|
-
if (!args.trim()) {
|
|
121
|
-
ctx.ui.notify(usage, "warning");
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const message = workflowMessage(kind, args);
|
|
126
|
-
if (ctx.isIdle()) {
|
|
127
|
-
pi.sendUserMessage(message);
|
|
128
|
-
} else {
|
|
129
|
-
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
130
|
-
ctx.ui.notify("Arey Pi workflow queued as follow-up", "info");
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function packageVersion(): string {
|
|
135
|
-
try {
|
|
136
|
-
const pkg = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { version?: string };
|
|
137
|
-
return pkg.version ?? "unknown";
|
|
138
|
-
} catch {
|
|
139
|
-
return "unknown";
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export default function areyPi(pi: ExtensionAPI) {
|
|
144
|
-
pi.registerCommand("arey-doctor", {
|
|
145
|
-
description: "Check Arey Pi package, project bootstrap, and subagent readiness",
|
|
146
|
-
handler: async (_args, ctx) => {
|
|
147
|
-
const cwd = cwdFrom(ctx);
|
|
148
|
-
const projectAgentDir = join(cwd, ".pi", "agents", "arey-pi");
|
|
149
|
-
const commands = pi.getCommands();
|
|
150
|
-
const hasSubagentsCommand = commands.some((command) => command.name.startsWith("subagents-doctor"));
|
|
151
|
-
const installedAgents = requiredAgents.filter((agent) => fileExists(join(projectAgentDir, agent)));
|
|
152
|
-
const missingAgents = requiredAgents.filter((agent) => !fileExists(join(projectAgentDir, agent)));
|
|
153
|
-
const packageAgents = requiredAgents.filter((agent) => fileExists(join(agentSourceDir, agent)));
|
|
154
|
-
const prompts = commands.filter(
|
|
155
|
-
(command) => command.source === "prompt" && command.sourceInfo?.source?.includes("arey-pi"),
|
|
156
|
-
);
|
|
157
|
-
const skills = commands.filter(
|
|
158
|
-
(command) => command.source === "skill" && command.sourceInfo?.source?.includes("arey-pi"),
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const report = buildDoctorReport({
|
|
162
|
-
packageVersion: packageVersion(),
|
|
163
|
-
cwd,
|
|
164
|
-
packageRulesPresent: dirExists(rulesDir),
|
|
165
|
-
packageTemplatesPresent: dirExists(templatesDir),
|
|
166
|
-
packageAgentsCount: packageAgents.length,
|
|
167
|
-
requiredAgentsCount: requiredAgents.length,
|
|
168
|
-
hasSubagentsCommand,
|
|
169
|
-
installedAgentsCount: installedAgents.length,
|
|
170
|
-
hasRootAgentsMd: fileExists(join(cwd, "AGENTS.md")),
|
|
171
|
-
hasPiSettings: fileExists(join(cwd, ".pi", "settings.json")),
|
|
172
|
-
promptsCount: prompts.length,
|
|
173
|
-
skillsCount: skills.length,
|
|
174
|
-
missingAgents,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
pi.sendMessage({
|
|
178
|
-
customType: "arey-pi-doctor",
|
|
179
|
-
content: report,
|
|
180
|
-
display: true,
|
|
181
|
-
details: {},
|
|
182
|
-
});
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
pi.registerCommand("arey-bootstrap", {
|
|
187
|
-
description: "Install Arey Pi subagents and optionally scaffold specs/docs",
|
|
188
|
-
handler: async (args, ctx) => {
|
|
189
|
-
const cwd = cwdFrom(ctx);
|
|
190
|
-
const { force, createAgentsMd, createSpecs, createDocs } = parseBootstrapFlags(args);
|
|
191
|
-
const targetDir = join(cwd, ".pi", "agents", "arey-pi");
|
|
192
|
-
const result = copyAgents(targetDir, force);
|
|
193
|
-
const specsResult = createSpecs ? scaffoldSpecs(cwd, force) : { created: [], skipped: [] };
|
|
194
|
-
const docsResult = createDocs ? scaffoldDocs(cwd, force) : { created: [], skipped: [] };
|
|
195
|
-
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
196
|
-
let agentsMdStatus = "unchanged";
|
|
197
|
-
|
|
198
|
-
if (!fileExists(agentsMdPath) && (createAgentsMd || force)) {
|
|
199
|
-
writeFileSync(agentsMdPath, starterAgentsMd());
|
|
200
|
-
agentsMdStatus = "created";
|
|
201
|
-
} else if (fileExists(agentsMdPath) && createAgentsMd && !force) {
|
|
202
|
-
agentsMdStatus = "skipped existing";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const report = [
|
|
206
|
-
"# Arey Pi Bootstrap",
|
|
207
|
-
"",
|
|
208
|
-
`- Target: ${relative(cwd, targetDir)}`,
|
|
209
|
-
`- Copied agents: ${result.copied.length}`,
|
|
210
|
-
`- Skipped existing agents: ${result.skipped.length}`,
|
|
211
|
-
`- Missing package agents: ${result.missing.length}`,
|
|
212
|
-
`- AGENTS.md: ${agentsMdStatus}`,
|
|
213
|
-
`- Spec scaffold created: ${specsResult.created.length}`,
|
|
214
|
-
`- Spec scaffold skipped: ${specsResult.skipped.length}`,
|
|
215
|
-
`- Docs scaffold created: ${docsResult.created.length}`,
|
|
216
|
-
`- Docs scaffold skipped: ${docsResult.skipped.length}`,
|
|
217
|
-
"",
|
|
218
|
-
"## Copied agents",
|
|
219
|
-
result.copied.length ? result.copied.map((agent) => `- ${agent}`).join("\n") : "- none",
|
|
220
|
-
"",
|
|
221
|
-
"## Skipped agents",
|
|
222
|
-
result.skipped.length ? result.skipped.map((agent) => `- ${agent}`).join("\n") : "- none",
|
|
223
|
-
"",
|
|
224
|
-
"## Created scaffold files",
|
|
225
|
-
[...specsResult.created, ...docsResult.created].length
|
|
226
|
-
? [...specsResult.created, ...docsResult.created].map((path) => `- ${path}`).join("\n")
|
|
227
|
-
: "- none",
|
|
228
|
-
"",
|
|
229
|
-
"## Skipped scaffold files",
|
|
230
|
-
[...specsResult.skipped, ...docsResult.skipped].length
|
|
231
|
-
? [...specsResult.skipped, ...docsResult.skipped].map((path) => `- ${path}`).join("\n")
|
|
232
|
-
: "- none",
|
|
233
|
-
"",
|
|
234
|
-
"Run `/arey-doctor` to verify setup.",
|
|
235
|
-
].join("\n");
|
|
236
|
-
|
|
237
|
-
pi.sendMessage({
|
|
238
|
-
customType: "arey-pi-bootstrap",
|
|
239
|
-
content: report,
|
|
240
|
-
display: true,
|
|
241
|
-
details: { agents: result, specs: specsResult, docs: docsResult, agentsMd: agentsMdStatus },
|
|
242
|
-
});
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
pi.registerCommand("arey-feature", {
|
|
247
|
-
description: "Run an Arey Pi spec-to-TDD feature workflow",
|
|
248
|
-
handler: async (args, ctx) => sendWorkflow(pi, args, ctx, "feature", "Usage: /arey-feature <feature request>"),
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
pi.registerCommand("arey-bugfix", {
|
|
252
|
-
description: "Run an Arey Pi regression-test-first bugfix workflow",
|
|
253
|
-
handler: async (args, ctx) => sendWorkflow(pi, args, ctx, "bugfix", "Usage: /arey-bugfix <bug description>"),
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
pi.registerCommand("arey-sync", {
|
|
257
|
-
description: "Run Arey Pi spec, test, code, DBML, ADR, and glossary sync",
|
|
258
|
-
handler: async (args, ctx) =>
|
|
259
|
-
sendWorkflow(pi, args || "the current repository", ctx, "sync", "Usage: /arey-sync [scope]"),
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
pi.registerCommand("arey-review", {
|
|
263
|
-
description: "Run an Arey Pi adversarial engineering review",
|
|
264
|
-
handler: async (args, ctx) =>
|
|
265
|
-
sendWorkflow(pi, args || "the current diff", ctx, "review", "Usage: /arey-review [scope]"),
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
pi.registerCommand("arey-assess", {
|
|
269
|
-
description: "Assess project readiness against Arey Pi rules",
|
|
270
|
-
handler: async (args, ctx) =>
|
|
271
|
-
sendWorkflow(pi, args || "the current repository", ctx, "assess", "Usage: /arey-assess [scope]"),
|
|
272
|
-
});
|
|
5
|
+
export default function areyPi(pi: ExtensionAPI): void {
|
|
6
|
+
registerBootstrapCommands(pi);
|
|
7
|
+
registerWorkflowRuntime(pi);
|
|
273
8
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export const packageRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
6
|
+
export const agentSourceDir = join(packageRoot, "agents");
|
|
7
|
+
export const rulesDir = join(packageRoot, "rules");
|
|
8
|
+
export const templatesDir = join(packageRoot, "templates");
|
|
9
|
+
|
|
10
|
+
export function cwdFrom(ctx: { cwd: string }): string {
|
|
11
|
+
return ctx.cwd;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function fileExists(path: string): boolean {
|
|
15
|
+
try {
|
|
16
|
+
return existsSync(path) && statSync(path).isFile();
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function dirExists(path: string): boolean {
|
|
23
|
+
try {
|
|
24
|
+
return existsSync(path) && statSync(path).isDirectory();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isToolCallEventType,
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
type ToolCallEvent,
|
|
6
|
+
type ToolCallEventResult,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { areyPiHarnessContext, shouldActivateAreyPiHarness } from "./core.ts";
|
|
9
|
+
|
|
10
|
+
const areyPiSessionEntryType = "arey-pi-session";
|
|
11
|
+
|
|
12
|
+
type HarnessSessionData = { active: boolean };
|
|
13
|
+
type HarnessStore = { active: boolean };
|
|
14
|
+
type SessionEntry = ReturnType<ExtensionContext["sessionManager"]["getEntries"]>[number];
|
|
15
|
+
type CustomSessionEntry = Extract<SessionEntry, { type: "custom" }>;
|
|
16
|
+
type HarnessCustomEntry = CustomSessionEntry & { customType: typeof areyPiSessionEntryType };
|
|
17
|
+
|
|
18
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
19
|
+
return typeof value === "object" && value !== null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isHarnessSessionData(value: unknown): value is HarnessSessionData {
|
|
23
|
+
return isObject(value) && typeof value.active === "boolean";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isHarnessCustomEntry(value: unknown): value is HarnessCustomEntry {
|
|
27
|
+
return isObject(value) && value.type === "custom" && value.customType === areyPiSessionEntryType;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function latestHarnessSessionState(ctx: ExtensionContext): boolean {
|
|
31
|
+
const entry = ctx.sessionManager.getEntries().filter(isHarnessCustomEntry).at(-1);
|
|
32
|
+
return entry && isHarnessSessionData(entry.data) ? entry.data.active : false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function persistHarnessState(pi: ExtensionAPI, active: boolean): void {
|
|
36
|
+
pi.appendEntry(areyPiSessionEntryType, { active } satisfies HarnessSessionData);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function updateHarnessUi(active: boolean, ctx: ExtensionContext): void {
|
|
40
|
+
ctx.ui.setStatus("arey-pi", active ? ctx.ui.theme.fg("accent", "Arey Pi") : undefined);
|
|
41
|
+
ctx.ui.setWidget(
|
|
42
|
+
"arey-pi-harness",
|
|
43
|
+
active
|
|
44
|
+
? [
|
|
45
|
+
"Arey Pi active: natural-language harness guidance is injected.",
|
|
46
|
+
"The agent infers feature/bugfix/sync/review/assessment intent.",
|
|
47
|
+
]
|
|
48
|
+
: undefined,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function activateHarness(pi: ExtensionAPI, store: HarnessStore, ctx: ExtensionContext): void {
|
|
53
|
+
store.active = true;
|
|
54
|
+
persistHarnessState(pi, true);
|
|
55
|
+
updateHarnessUi(true, ctx);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isSensitivePath(path: string): boolean {
|
|
59
|
+
return [".env", ".git/", "node_modules/"].some((protectedPath) => path.includes(protectedPath));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function editedPath(event: ToolCallEvent): string | undefined {
|
|
63
|
+
if (isToolCallEventType("write", event) || isToolCallEventType("edit", event)) {
|
|
64
|
+
return event.input.path;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleMutationGuardrails(
|
|
71
|
+
store: HarnessStore,
|
|
72
|
+
event: ToolCallEvent,
|
|
73
|
+
ctx: ExtensionContext,
|
|
74
|
+
): ToolCallEventResult | undefined {
|
|
75
|
+
const path = editedPath(event);
|
|
76
|
+
if (!path) return undefined;
|
|
77
|
+
|
|
78
|
+
if (isSensitivePath(path)) {
|
|
79
|
+
ctx.ui.notify(`Blocked write to protected path: ${path}`, "warning");
|
|
80
|
+
return { block: true, reason: `Arey Pi guardrail: path is protected: ${path}` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!store.active) return undefined;
|
|
84
|
+
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function registerWorkflowRuntime(pi: ExtensionAPI): void {
|
|
89
|
+
const store: HarnessStore = { active: false };
|
|
90
|
+
|
|
91
|
+
pi.on("session_start", (_event, ctx) => {
|
|
92
|
+
store.active = latestHarnessSessionState(ctx);
|
|
93
|
+
updateHarnessUi(store.active, ctx);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
97
|
+
const requestedAreyPi = shouldActivateAreyPiHarness(event.prompt);
|
|
98
|
+
if (!store.active && !requestedAreyPi) return undefined;
|
|
99
|
+
if (requestedAreyPi) activateHarness(pi, store, ctx);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
message: {
|
|
103
|
+
customType: "arey-pi-harness-context",
|
|
104
|
+
content: areyPiHarnessContext(event.prompt),
|
|
105
|
+
display: false,
|
|
106
|
+
details: { source: requestedAreyPi ? "natural-language" : "active-session" },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
pi.on("tool_call", (event, ctx) => handleMutationGuardrails(store, event, ctx));
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arey-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A Pi package for canonical Gherkin specs, non-negotiable TDD, spec synchronisation, AI harness readiness, and senior-quality software delivery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alejandro Rey Leyva",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Decide whether a change needs an ADR and review ADR quality
|
|
3
|
+
argument-hint: "[decision or change]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Assess ADR impact for:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Inspect relevant specs, architecture docs, existing ADRs, README/docs, tests, and implementation context.
|
|
11
|
+
|
|
12
|
+
Determine whether the change records a significant technical decision because it affects architecture, data model, security model, operability, public contracts, deployment, dependency strategy, or long-term maintainability.
|
|
13
|
+
|
|
14
|
+
If an ADR is needed:
|
|
15
|
+
|
|
16
|
+
- propose the ADR title and location under `specs/decisions/`;
|
|
17
|
+
- capture context, decision, alternatives considered, consequences, risks, and follow-up work;
|
|
18
|
+
- keep the decision durable and independent of transient implementation details;
|
|
19
|
+
- update glossary, DBML, architecture docs, or README when affected.
|
|
20
|
+
|
|
21
|
+
If no ADR is needed, explain why.
|
|
22
|
+
|
|
23
|
+
Return:
|
|
24
|
+
|
|
25
|
+
```txt
|
|
26
|
+
ADR assessment:
|
|
27
|
+
- Decision/change reviewed:
|
|
28
|
+
- ADR required: yes/no
|
|
29
|
+
- Evidence:
|
|
30
|
+
- Proposed ADR changes:
|
|
31
|
+
- Architecture/docs/glossary/DBML impact:
|
|
32
|
+
- Open questions:
|
|
33
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run an adversarial Arey Pi engineering review
|
|
3
|
+
argument-hint: "[scope]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run an adversarial Arey Pi engineering review for:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Prefer fresh-context review and use `arey-pi.engineering-reviewer` when available.
|
|
11
|
+
|
|
12
|
+
Review:
|
|
13
|
+
|
|
14
|
+
- correctness and behavioural completeness;
|
|
15
|
+
- architecture and code quality;
|
|
16
|
+
- test quality, meaningful assertions, and test location;
|
|
17
|
+
- TDD evidence where behaviour changed;
|
|
18
|
+
- formatter/lint/static analysis/typecheck/test/coverage evidence;
|
|
19
|
+
- security, privacy, reliability, operability, and maintainability;
|
|
20
|
+
- generated-code risks;
|
|
21
|
+
- spec, ADR, DBML, glossary, README, docs, AGENTS.md, skills, prompts, rules, agents, commands, and tooling sync.
|
|
22
|
+
|
|
23
|
+
Do not modify files unless explicitly asked to fix findings.
|
|
24
|
+
|
|
25
|
+
Return:
|
|
26
|
+
|
|
27
|
+
```txt
|
|
28
|
+
Engineering review:
|
|
29
|
+
- Scope:
|
|
30
|
+
- Blocking findings:
|
|
31
|
+
- Major findings:
|
|
32
|
+
- Minor findings:
|
|
33
|
+
- Positive evidence:
|
|
34
|
+
- Missing validation:
|
|
35
|
+
- Sync concerns:
|
|
36
|
+
- Recommended next actions:
|
|
37
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Draft or update canonical Arey Pi Gherkin specs for a feature
|
|
3
|
+
argument-hint: "<feature request>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Draft or update canonical Arey Pi feature specs for:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Work in spec-first mode.
|
|
11
|
+
|
|
12
|
+
Requirements:
|
|
13
|
+
|
|
14
|
+
- inspect existing `specs/features/`, glossary, ADRs, docs, tests, and related code before writing;
|
|
15
|
+
- identify actors, business terms, rules, assumptions, non-goals, edge cases, and open questions;
|
|
16
|
+
- write behaviour-focused Gherkin, not UI or implementation scripts;
|
|
17
|
+
- prefer `Rule` sections and concrete examples where they clarify intent;
|
|
18
|
+
- update glossary, ADR, DBML, architecture docs, or README only when affected;
|
|
19
|
+
- do not change production code;
|
|
20
|
+
- if requirements are ambiguous, ask focused questions instead of inventing policy.
|
|
21
|
+
|
|
22
|
+
Prefer using `arey-pi.spec-author` when available.
|
|
23
|
+
|
|
24
|
+
Return:
|
|
25
|
+
|
|
26
|
+
```txt
|
|
27
|
+
Spec handoff:
|
|
28
|
+
- Feature scope:
|
|
29
|
+
- Files read:
|
|
30
|
+
- Specs added/updated:
|
|
31
|
+
- Scenarios/rules covered:
|
|
32
|
+
- Open questions:
|
|
33
|
+
- Test implications:
|
|
34
|
+
- ADR/DBML/glossary/docs impact:
|
|
35
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Implement accepted behaviour with strict Arey Pi Red-Green-Refactor
|
|
3
|
+
argument-hint: "<accepted behaviour or spec>"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Implement the accepted behaviour through strict Arey Pi TDD:
|
|
7
|
+
|
|
8
|
+
$ARGUMENTS
|
|
9
|
+
|
|
10
|
+
Rules:
|
|
11
|
+
|
|
12
|
+
- read applicable specs, tests, code, AGENTS.md, and quality tooling first;
|
|
13
|
+
- add or update a meaningful failing test before production behaviour changes;
|
|
14
|
+
- keep tests outside production source directories by default;
|
|
15
|
+
- implement the smallest high-quality production change to pass;
|
|
16
|
+
- refactor only while tests remain green;
|
|
17
|
+
- run formatter, lint/static analysis, typecheck, tests, and coverage/mutation checks where relevant and available;
|
|
18
|
+
- do not claim TDD if the Red phase cannot be demonstrated.
|
|
19
|
+
|
|
20
|
+
Prefer using `arey-pi.tdd-implementer` when available.
|
|
21
|
+
|
|
22
|
+
Return:
|
|
23
|
+
|
|
24
|
+
```txt
|
|
25
|
+
Red-Green-Refactor report:
|
|
26
|
+
- Behaviour implemented:
|
|
27
|
+
- Tests added/updated and location:
|
|
28
|
+
- Red evidence:
|
|
29
|
+
- Green evidence:
|
|
30
|
+
- Refactor notes:
|
|
31
|
+
- Validation commands and results:
|
|
32
|
+
- Spec/docs/ADR/DBML/glossary impact:
|
|
33
|
+
- Residual risks:
|
|
34
|
+
```
|