@a-canary/pi-director 0.1.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/.pi/corrections.jsonl +3 -0
- package/CHOICES.md +244 -0
- package/PLAN.md +108 -0
- package/README.md +97 -0
- package/agents/README.md +42 -0
- package/agents/builder.md +37 -0
- package/agents/critic.md +74 -0
- package/agents/director.md +133 -0
- package/agents/planner.md +44 -0
- package/agents/reviewer.md +35 -0
- package/agents/scout.md +37 -0
- package/agents/writer.md +28 -0
- package/extensions/nightly-analysis.ts +229 -0
- package/package.json +39 -0
- package/skills/build/SKILL.md +53 -0
- package/skills/build/lib/hard-stops.md +66 -0
- package/skills/build/lib/phase-loop.md +99 -0
- package/skills/build/lib/regression-check.md +63 -0
- package/skills/choose/SKILL.md +48 -0
- package/skills/choose/lib/pipeline.md +83 -0
- package/skills/next/SKILL.md +84 -0
- package/skills/next/lib/choice-scanner.md +51 -0
- package/skills/next/lib/code-scanner.md +57 -0
- package/skills/next/lib/log-scanner.md +55 -0
- package/skills/next/lib/ranker.md +72 -0
- package/skills/next/lib/session-scanner.md +53 -0
- package/templates/NEXT.md +35 -0
- package/test/agents.test.ts +63 -0
- package/test/next-template.test.ts +41 -0
- package/test/package.test.ts +59 -0
- package/test/skills.test.ts +52 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# NEXT.md — Recommended Actions
|
|
2
|
+
|
|
3
|
+
Generated: {date}
|
|
4
|
+
Priority ladder: UX Quality > Security > Scale > Efficiency
|
|
5
|
+
|
|
6
|
+
## Sources Analyzed
|
|
7
|
+
- Sessions: {count} ({date range})
|
|
8
|
+
- Corrections: {count}
|
|
9
|
+
- Source files: {count}
|
|
10
|
+
- Log sources: {count}
|
|
11
|
+
- CHOICES.md: {count} choices ({fulfilled}✓ {partial}◐ {not_started}✗)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Priority 1: {title}
|
|
16
|
+
Category: {category} | Impact: {high/med/low} | Effort: {small/med/large} | Score: {N}/27
|
|
17
|
+
Evidence: {what data supports this}
|
|
18
|
+
Action: {specific steps}
|
|
19
|
+
Supports: {CHOICES.md IDs affected}
|
|
20
|
+
|
|
21
|
+
## Priority 2: {title}
|
|
22
|
+
Category: {category} | Impact: {high/med/low} | Effort: {small/med/large} | Score: {N}/27
|
|
23
|
+
Evidence: {what data supports this}
|
|
24
|
+
Action: {specific steps}
|
|
25
|
+
Supports: {CHOICES.md IDs affected}
|
|
26
|
+
|
|
27
|
+
<!-- Continue for top 10 recommendations -->
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Deferred
|
|
32
|
+
<!-- Items from previous NEXT.md not yet addressed -->
|
|
33
|
+
|
|
34
|
+
## Dismissed
|
|
35
|
+
<!-- Items explicitly dismissed by user, with reason -->
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync, readdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
const AGENTS_DIR = join(__dirname, "..", "agents");
|
|
6
|
+
const VALID_TIERS = ["strategic", "tactical", "operational", "scout"];
|
|
7
|
+
|
|
8
|
+
function parseAgent(filename: string) {
|
|
9
|
+
const raw = readFileSync(join(AGENTS_DIR, filename), "utf-8");
|
|
10
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
11
|
+
if (!match) return null;
|
|
12
|
+
|
|
13
|
+
const frontmatter: Record<string, string> = {};
|
|
14
|
+
for (const line of match[1].split("\n")) {
|
|
15
|
+
const idx = line.indexOf(":");
|
|
16
|
+
if (idx > 0) {
|
|
17
|
+
frontmatter[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return { frontmatter, body: match[2].trim() };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("Agent definitions", () => {
|
|
24
|
+
const files = readdirSync(AGENTS_DIR).filter(f => f.endsWith(".md") && f !== "README.md");
|
|
25
|
+
|
|
26
|
+
it("should have at least 6 agent files", () => {
|
|
27
|
+
expect(files.length).toBeGreaterThanOrEqual(6);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
describe(file, () => {
|
|
32
|
+
const agent = parseAgent(file);
|
|
33
|
+
|
|
34
|
+
it("should parse frontmatter", () => {
|
|
35
|
+
expect(agent).not.toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should have a name", () => {
|
|
39
|
+
expect(agent!.frontmatter.name).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should have a description", () => {
|
|
43
|
+
expect(agent!.frontmatter.description).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should use a model tier group, not a hardcoded model", () => {
|
|
47
|
+
const model = agent!.frontmatter.model;
|
|
48
|
+
expect(VALID_TIERS).toContain(model);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should have tools defined (or be a thinking-only critic)", () => {
|
|
52
|
+
const isCritic = agent!.frontmatter.name === "critic";
|
|
53
|
+
if (!isCritic) {
|
|
54
|
+
expect(agent!.frontmatter.tools).toBeTruthy();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should have a body with instructions", () => {
|
|
59
|
+
expect(agent!.body.length).toBeGreaterThan(50);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
const TEMPLATE = join(__dirname, "..", "templates", "NEXT.md");
|
|
6
|
+
|
|
7
|
+
describe("NEXT.md template", () => {
|
|
8
|
+
it("should exist", () => {
|
|
9
|
+
expect(existsSync(TEMPLATE)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should have title", () => {
|
|
13
|
+
const content = readFileSync(TEMPLATE, "utf-8");
|
|
14
|
+
expect(content).toContain("# NEXT.md");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should reference priority ladder", () => {
|
|
18
|
+
const content = readFileSync(TEMPLATE, "utf-8");
|
|
19
|
+
expect(content).toContain("UX Quality > Security > Scale > Efficiency");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should have priority items with required fields", () => {
|
|
23
|
+
const content = readFileSync(TEMPLATE, "utf-8");
|
|
24
|
+
expect(content).toContain("Category:");
|
|
25
|
+
expect(content).toContain("Impact:");
|
|
26
|
+
expect(content).toContain("Effort:");
|
|
27
|
+
expect(content).toContain("Evidence:");
|
|
28
|
+
expect(content).toContain("Action:");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should have Sources Analyzed section", () => {
|
|
32
|
+
const content = readFileSync(TEMPLATE, "utf-8");
|
|
33
|
+
expect(content).toContain("## Sources Analyzed");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should have Deferred and Dismissed sections", () => {
|
|
37
|
+
const content = readFileSync(TEMPLATE, "utf-8");
|
|
38
|
+
expect(content).toContain("## Deferred");
|
|
39
|
+
expect(content).toContain("## Dismissed");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
const ROOT = join(__dirname, "..");
|
|
6
|
+
|
|
7
|
+
describe("Package configuration", () => {
|
|
8
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
9
|
+
|
|
10
|
+
it("should be named @a-canary/pi-director", () => {
|
|
11
|
+
expect(pkg.name).toBe("@a-canary/pi-director");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should have author a-canary", () => {
|
|
15
|
+
expect(pkg.author?.name || pkg.author).toContain("a-canary");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should have pi-package keyword", () => {
|
|
19
|
+
expect(pkg.keywords).toContain("pi-package");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should declare pi.skills pointing to real directory", () => {
|
|
23
|
+
expect(pkg.pi?.skills).toBeTruthy();
|
|
24
|
+
for (const dir of pkg.pi.skills) {
|
|
25
|
+
expect(existsSync(join(ROOT, dir))).toBe(true);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should declare pi.agents pointing to real directory", () => {
|
|
30
|
+
expect(pkg.pi?.agents).toBeTruthy();
|
|
31
|
+
for (const dir of pkg.pi.agents) {
|
|
32
|
+
expect(existsSync(join(ROOT, dir))).toBe(true);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should declare pi.extensions pointing to real directory", () => {
|
|
37
|
+
expect(pkg.pi?.extensions).toBeTruthy();
|
|
38
|
+
for (const dir of pkg.pi.extensions) {
|
|
39
|
+
expect(existsSync(join(ROOT, dir))).toBe(true);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should have pi-choose-wisely as peer dependency", () => {
|
|
44
|
+
expect(pkg.peerDependencies["@a-canary/pi-choose-wisely"]).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should have pi-upskill as peer dependency", () => {
|
|
48
|
+
expect(pkg.peerDependencies["@a-canary/pi-upskill"]).toBeTruthy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Required files", () => {
|
|
52
|
+
const required = ["CHOICES.md", "PLAN.md", "README.md"];
|
|
53
|
+
for (const file of required) {
|
|
54
|
+
it(`should have ${file}`, () => {
|
|
55
|
+
expect(existsSync(join(ROOT, file))).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { readFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
const SKILLS_DIR = join(__dirname, "..", "skills");
|
|
6
|
+
const REQUIRED_SKILLS = ["next", "build", "choose"];
|
|
7
|
+
|
|
8
|
+
describe("Skill definitions", () => {
|
|
9
|
+
for (const skill of REQUIRED_SKILLS) {
|
|
10
|
+
describe(`/${skill}`, () => {
|
|
11
|
+
const skillPath = join(SKILLS_DIR, skill, "SKILL.md");
|
|
12
|
+
|
|
13
|
+
it("should have SKILL.md", () => {
|
|
14
|
+
expect(existsSync(skillPath)).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should have When to Use section", () => {
|
|
18
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
19
|
+
expect(content).toContain("## When to Use");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should have Process section", () => {
|
|
23
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
24
|
+
expect(content).toContain("## Process");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("/next lib modules", () => {
|
|
30
|
+
const modules = ["session-scanner", "code-scanner", "choice-scanner", "log-scanner", "ranker"];
|
|
31
|
+
for (const mod of modules) {
|
|
32
|
+
it(`should have ${mod}.md`, () => {
|
|
33
|
+
expect(existsSync(join(SKILLS_DIR, "next", "lib", `${mod}.md`))).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("/build lib modules", () => {
|
|
39
|
+
const modules = ["phase-loop", "hard-stops", "regression-check"];
|
|
40
|
+
for (const mod of modules) {
|
|
41
|
+
it(`should have ${mod}.md`, () => {
|
|
42
|
+
expect(existsSync(join(SKILLS_DIR, "build", "lib", `${mod}.md`))).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("/choose lib modules", () => {
|
|
48
|
+
it("should have pipeline.md", () => {
|
|
49
|
+
expect(existsSync(join(SKILLS_DIR, "choose", "lib", "pipeline.md"))).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|