@aipper/aiws 0.0.49 → 0.0.51
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/dist/cli.js +30 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts +31 -0
- package/dist/commands/doctor.js +60 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +1 -3
- package/dist/commands/init.js +7 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.js +11 -3
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/pi-force-policy.d.ts +66 -0
- package/dist/lib/pi-force-policy.js +187 -0
- package/dist/lib/pi-force-policy.js.map +1 -0
- package/dist/lib/pi-force-policy.test.d.ts +1 -0
- package/dist/lib/pi-force-policy.test.js +99 -0
- package/dist/lib/pi-force-policy.test.js.map +1 -0
- package/dist/lib/pi-plugins-doctor.d.ts +150 -0
- package/dist/lib/pi-plugins-doctor.js +583 -0
- package/dist/lib/pi-plugins-doctor.js.map +1 -0
- package/dist/lib/pi-plugins-doctor.test.d.ts +1 -0
- package/dist/lib/pi-plugins-doctor.test.js +165 -0
- package/dist/lib/pi-plugins-doctor.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure force-policy for Pi subagent-first (TOOLING-002L / pi-force-subagent).
|
|
3
|
+
* No I/O. Used by unit tests and by `.pi/extensions/aiws.ts` (copy or import).
|
|
4
|
+
*/
|
|
5
|
+
/** Approved inline escape-hatch phrases (current user message only). */
|
|
6
|
+
export const HATCH_PHRASES = [
|
|
7
|
+
"直接改",
|
|
8
|
+
"do it inline",
|
|
9
|
+
"你直接改",
|
|
10
|
+
"别派 sub-agent",
|
|
11
|
+
"main session 写就行",
|
|
12
|
+
"不用 sub-agent",
|
|
13
|
+
"no sub-agent",
|
|
14
|
+
];
|
|
15
|
+
/** Allowed Agent subagent_type values for implement/review dispatch. */
|
|
16
|
+
export const ALLOWED_SUBAGENT_TYPES = ["aiws-worker", "aiws-reviewer"];
|
|
17
|
+
/** Tool names treated as mutating writes when deny is active. */
|
|
18
|
+
export const DENIED_WRITE_TOOLS = ["write", "edit", "Write", "Edit"];
|
|
19
|
+
/**
|
|
20
|
+
* Path prefixes (posix-style, relative to project root) allowed for main-session
|
|
21
|
+
* writes without hatch. Match is prefix after normalize (no leading ./).
|
|
22
|
+
*/
|
|
23
|
+
export const MAIN_WRITE_ALLOW_PREFIXES = [
|
|
24
|
+
".aiws/changes/",
|
|
25
|
+
".aiws/plan/",
|
|
26
|
+
".aiws/goals/",
|
|
27
|
+
];
|
|
28
|
+
/** Subpaths under .aiws/changes that are allowed without hatch. */
|
|
29
|
+
const CHANGES_ALLOWED_SEGMENTS = [
|
|
30
|
+
"/evidence/",
|
|
31
|
+
"/analysis/",
|
|
32
|
+
"/review/",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Detect hatch in the **current** user message only (caller must pass only that text).
|
|
36
|
+
*/
|
|
37
|
+
export function detectHatch(currentUserMessage) {
|
|
38
|
+
if (!currentUserMessage)
|
|
39
|
+
return false;
|
|
40
|
+
const text = currentUserMessage;
|
|
41
|
+
for (const phrase of HATCH_PHRASES) {
|
|
42
|
+
if (text.includes(phrase))
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a path for allowlist checks: backslash→slash, strip leading ./ and /
|
|
49
|
+
*/
|
|
50
|
+
export function normalizeRelPath(filePath) {
|
|
51
|
+
let p = filePath.replace(/\\/g, "/").trim();
|
|
52
|
+
while (p.startsWith("./"))
|
|
53
|
+
p = p.slice(2);
|
|
54
|
+
while (p.startsWith("/"))
|
|
55
|
+
p = p.slice(1);
|
|
56
|
+
return p;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether a write target is on the main-session governance allowlist (no hatch required).
|
|
60
|
+
*/
|
|
61
|
+
export function isMainWriteAllowlisted(filePath) {
|
|
62
|
+
const p = normalizeRelPath(filePath);
|
|
63
|
+
if (p.startsWith(".aiws/plan/") || p === ".aiws/plan")
|
|
64
|
+
return true;
|
|
65
|
+
if (p.startsWith(".aiws/goals/") || p === ".aiws/goals")
|
|
66
|
+
return true;
|
|
67
|
+
if (!p.startsWith(".aiws/changes/"))
|
|
68
|
+
return false;
|
|
69
|
+
for (const seg of CHANGES_ALLOWED_SEGMENTS) {
|
|
70
|
+
if (p.includes(seg))
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
// allow changes root meta files sometimes used by orchestrator
|
|
74
|
+
// e.g. .aiws/changes/<id>/tasks.md is NOT allowlisted (worker territory)
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Decide if main session may invoke a write-like tool for path.
|
|
79
|
+
*/
|
|
80
|
+
export function decideMainWrite(opts) {
|
|
81
|
+
const name = opts.toolName;
|
|
82
|
+
const isWrite = DENIED_WRITE_TOOLS.includes(name) ||
|
|
83
|
+
/^(write|edit)$/i.test(name);
|
|
84
|
+
if (!isWrite) {
|
|
85
|
+
return { allow: true, reason: "non-write tool" };
|
|
86
|
+
}
|
|
87
|
+
if (opts.hatchActive) {
|
|
88
|
+
return { allow: true, reason: "hatch active on current user message" };
|
|
89
|
+
}
|
|
90
|
+
const path = opts.filePath ?? "";
|
|
91
|
+
if (path && isMainWriteAllowlisted(path)) {
|
|
92
|
+
return { allow: true, reason: "governance path allowlist" };
|
|
93
|
+
}
|
|
94
|
+
if (!path) {
|
|
95
|
+
return {
|
|
96
|
+
allow: false,
|
|
97
|
+
reason: "main-session write denied without hatch and without allowlisted path (missing path)",
|
|
98
|
+
code: "MAIN_WRITE_DENIED",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
allow: false,
|
|
103
|
+
reason: `main-session write denied for path without hatch: ${normalizeRelPath(path)}. Use Agent(subagent_type=aiws-worker) or hatch phrase + evidence/inline-escape-hatch.md`,
|
|
104
|
+
code: "MAIN_WRITE_DENIED",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whitelist subagent types for implement/review. Unknown types must not fall through
|
|
109
|
+
* to pi-subagents general-purpose silently.
|
|
110
|
+
*/
|
|
111
|
+
export function decideAgentType(subagentType) {
|
|
112
|
+
const t = (subagentType ?? "").trim().toLowerCase();
|
|
113
|
+
if (!t) {
|
|
114
|
+
return {
|
|
115
|
+
allow: false,
|
|
116
|
+
reason: "subagent_type required; use aiws-worker or aiws-reviewer",
|
|
117
|
+
code: "AGENT_TYPE_REQUIRED",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const allowed = ALLOWED_SUBAGENT_TYPES.map((x) => x.toLowerCase());
|
|
121
|
+
if (allowed.includes(t)) {
|
|
122
|
+
return { allow: true, reason: `allowed type: ${t}` };
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
allow: false,
|
|
126
|
+
reason: `subagent_type "${subagentType}" not in whitelist (aiws-worker|aiws-reviewer). pi-subagents may fallback to general-purpose — AIWS rejects this.`,
|
|
127
|
+
code: "AGENT_TYPE_DENIED",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Text for session_start system prompt injection.
|
|
132
|
+
*/
|
|
133
|
+
export function buildForcePolicyPromptBlock() {
|
|
134
|
+
return [
|
|
135
|
+
"<aiws-pi-force-policy>",
|
|
136
|
+
"Pi force policy (AIWS owns; runtime spawn is @tintinweb/pi-subagents):",
|
|
137
|
+
"1. Subagent-first: implement via Agent({ subagent_type: \"aiws-worker\" }); review via aiws-reviewer.",
|
|
138
|
+
"2. Type whitelist only: aiws-worker | aiws-reviewer. Never general-purpose for implementation.",
|
|
139
|
+
"3. Without hatch: do NOT write/edit business code. Only governance paths under .aiws/changes/**/{evidence,analysis,review}/**, .aiws/plan/**, .aiws/goals/**.",
|
|
140
|
+
"4. Hatch phrases (CURRENT user message only): " + HATCH_PHRASES.join(" | "),
|
|
141
|
+
" → if used, write changes/<id>/evidence/inline-escape-hatch.md first.",
|
|
142
|
+
"5. No silent fallback: delegate fail → write evidence/delegate-failure.md and STOP.",
|
|
143
|
+
"6. Deprecated: aiws_subagent (ctx.newSession). Prefer pi-subagents Agent tool.",
|
|
144
|
+
"Spec: packages/spec/docs/pi-subagent-first.md",
|
|
145
|
+
"</aiws-pi-force-policy>",
|
|
146
|
+
].join("\n");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Deprecation message when aiws_subagent is invoked.
|
|
150
|
+
*/
|
|
151
|
+
export function aiwsSubagentDeprecationMessage(prompt) {
|
|
152
|
+
return [
|
|
153
|
+
"[aiws] aiws_subagent is DEPRECATED as primary runtime.",
|
|
154
|
+
"Install and use @tintinweb/pi-subagents:",
|
|
155
|
+
' Agent({ subagent_type: "aiws-worker" | "aiws-reviewer", prompt: "..." })',
|
|
156
|
+
"Then get_subagent_result / steer_subagent as needed.",
|
|
157
|
+
"See packages/spec/docs/pi-subagent-first.md",
|
|
158
|
+
prompt
|
|
159
|
+
? `Your prompt was not executed via deprecated path. Re-dispatch with Agent:\n---\n${prompt.slice(0, 2000)}`
|
|
160
|
+
: "Usage was empty. Provide a prompt to Agent tool instead.",
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Suggested content for evidence/delegate-failure.md
|
|
165
|
+
*/
|
|
166
|
+
export function formatDelegateFailureEvidence(opts) {
|
|
167
|
+
const at = opts.at ?? new Date().toISOString();
|
|
168
|
+
return [
|
|
169
|
+
"# Delegate failure (no silent main-session fallback)",
|
|
170
|
+
"",
|
|
171
|
+
`- **at**: ${at}`,
|
|
172
|
+
opts.changeId ? `- **change**: ${opts.changeId}` : null,
|
|
173
|
+
opts.attemptedType ? `- **attempted_type**: ${opts.attemptedType}` : null,
|
|
174
|
+
`- **error**: ${opts.error}`,
|
|
175
|
+
"",
|
|
176
|
+
"## Policy",
|
|
177
|
+
"",
|
|
178
|
+
"Do **not** implement business code in the main session without hatch.",
|
|
179
|
+
"Fix the delegate path (install pi-subagents, correct type, retry) or stop.",
|
|
180
|
+
"",
|
|
181
|
+
"Spec: packages/spec/docs/pi-subagent-first.md",
|
|
182
|
+
"",
|
|
183
|
+
]
|
|
184
|
+
.filter((x) => x !== null)
|
|
185
|
+
.join("\n");
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=pi-force-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-force-policy.js","sourceRoot":"","sources":["../../src/lib/pi-force-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { detectHatch, isMainWriteAllowlisted, decideMainWrite, decideAgentType, normalizeRelPath, buildForcePolicyPromptBlock, aiwsSubagentDeprecationMessage, formatDelegateFailureEvidence, HATCH_PHRASES, } from "./pi-force-policy.js";
|
|
3
|
+
describe("detectHatch", () => {
|
|
4
|
+
it("returns false for empty/null", () => {
|
|
5
|
+
expect(detectHatch(null)).toBe(false);
|
|
6
|
+
expect(detectHatch("")).toBe(false);
|
|
7
|
+
expect(detectHatch(undefined)).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
it("matches each approved phrase", () => {
|
|
10
|
+
for (const p of HATCH_PHRASES) {
|
|
11
|
+
expect(detectHatch(`please ${p} now`)).toBe(true);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
it("does not match unrelated text", () => {
|
|
15
|
+
expect(detectHatch("用 subagent 实现功能")).toBe(false);
|
|
16
|
+
expect(detectHatch("继续")).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("normalizeRelPath / isMainWriteAllowlisted", () => {
|
|
20
|
+
it("normalizes slashes and ./", () => {
|
|
21
|
+
expect(normalizeRelPath(".aiws\\plan\\x.md")).toBe(".aiws/plan/x.md");
|
|
22
|
+
expect(normalizeRelPath("./.aiws/plan/x.md")).toBe(".aiws/plan/x.md");
|
|
23
|
+
});
|
|
24
|
+
it("allows evidence/analysis/review under changes", () => {
|
|
25
|
+
expect(isMainWriteAllowlisted(".aiws/changes/pi-force-subagent/evidence/delegate-failure.md")).toBe(true);
|
|
26
|
+
expect(isMainWriteAllowlisted(".aiws/changes/x/analysis/notes.md")).toBe(true);
|
|
27
|
+
expect(isMainWriteAllowlisted(".aiws/changes/x/review/spec-review.md")).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
it("allows plan and goals", () => {
|
|
30
|
+
expect(isMainWriteAllowlisted(".aiws/plan/2026-07-23.md")).toBe(true);
|
|
31
|
+
expect(isMainWriteAllowlisted(".aiws/goals/g1.state.json")).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it("denies business and change meta without segment", () => {
|
|
34
|
+
expect(isMainWriteAllowlisted("packages/aiws/src/cli.ts")).toBe(false);
|
|
35
|
+
expect(isMainWriteAllowlisted(".aiws/changes/x/tasks.md")).toBe(false);
|
|
36
|
+
expect(isMainWriteAllowlisted("src/app.vue")).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("decideMainWrite", () => {
|
|
40
|
+
it("allows non-write tools", () => {
|
|
41
|
+
expect(decideMainWrite({ toolName: "read", hatchActive: false }).allow).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
it("denies write to business path without hatch", () => {
|
|
44
|
+
const d = decideMainWrite({
|
|
45
|
+
toolName: "write",
|
|
46
|
+
filePath: "packages/aiws/src/cli.ts",
|
|
47
|
+
hatchActive: false,
|
|
48
|
+
});
|
|
49
|
+
expect(d.allow).toBe(false);
|
|
50
|
+
if (!d.allow)
|
|
51
|
+
expect(d.code).toBe("MAIN_WRITE_DENIED");
|
|
52
|
+
});
|
|
53
|
+
it("allows write with hatch", () => {
|
|
54
|
+
expect(decideMainWrite({
|
|
55
|
+
toolName: "edit",
|
|
56
|
+
filePath: "packages/aiws/src/cli.ts",
|
|
57
|
+
hatchActive: true,
|
|
58
|
+
}).allow).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
it("allows write to evidence without hatch", () => {
|
|
61
|
+
expect(decideMainWrite({
|
|
62
|
+
toolName: "write",
|
|
63
|
+
filePath: ".aiws/changes/pi-force-subagent/evidence/x.md",
|
|
64
|
+
hatchActive: false,
|
|
65
|
+
}).allow).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe("decideAgentType", () => {
|
|
69
|
+
it("allows worker and reviewer case-insensitively", () => {
|
|
70
|
+
expect(decideAgentType("aiws-worker").allow).toBe(true);
|
|
71
|
+
expect(decideAgentType("AIWS-Reviewer").allow).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it("denies general-purpose and empty", () => {
|
|
74
|
+
const g = decideAgentType("general-purpose");
|
|
75
|
+
expect(g.allow).toBe(false);
|
|
76
|
+
if (!g.allow)
|
|
77
|
+
expect(g.code).toBe("AGENT_TYPE_DENIED");
|
|
78
|
+
expect(decideAgentType("").allow).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("prompt helpers", () => {
|
|
82
|
+
it("buildForcePolicyPromptBlock mentions whitelist and hatch", () => {
|
|
83
|
+
const b = buildForcePolicyPromptBlock();
|
|
84
|
+
expect(b).toContain("aiws-worker");
|
|
85
|
+
expect(b).toContain("hatch");
|
|
86
|
+
expect(b).toContain("pi-subagent-first");
|
|
87
|
+
});
|
|
88
|
+
it("aiwsSubagentDeprecationMessage points to Agent", () => {
|
|
89
|
+
const m = aiwsSubagentDeprecationMessage("do stuff");
|
|
90
|
+
expect(m).toContain("DEPRECATED");
|
|
91
|
+
expect(m).toContain("aiws-worker");
|
|
92
|
+
});
|
|
93
|
+
it("formatDelegateFailureEvidence includes error", () => {
|
|
94
|
+
const e = formatDelegateFailureEvidence({ error: "timeout", changeId: "pi-force-subagent" });
|
|
95
|
+
expect(e).toContain("timeout");
|
|
96
|
+
expect(e).toContain("pi-force-subagent");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=pi-force-policy.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-force-policy.test.js","sourceRoot":"","sources":["../../src/lib/pi-force-policy.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,2BAA2B,EAC3B,8BAA8B,EAC9B,6BAA6B,EAC7B,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;YAC9B,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACtE,MAAM,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CACJ,sBAAsB,CAAC,8DAA8D,CAAC,CACvF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CAAC,sBAAsB,CAAC,mCAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/E,MAAM,CAAC,sBAAsB,CAAC,uCAAuC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtE,MAAM,CAAC,sBAAsB,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,sBAAsB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,GAAG,eAAe,CAAC;YACxB,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,0BAA0B;YACpC,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;QACH,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,CACJ,eAAe,CAAC;YACd,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,0BAA0B;YACpC,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC,KAAK,CACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CACJ,eAAe,CAAC;YACd,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,+CAA+C;YACzD,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC,KAAK,CACT,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,GAAG,eAAe,CAAC,iBAAiB,CAAC,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,CAAC,KAAK;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACvD,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,GAAG,2BAA2B,EAAE,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,GAAG,8BAA8B,CAAC,UAAU,CAAC,CAAC;QACrD,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,GAAG,6BAA6B,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,mBAAmB,EAAE,CAAC,CAAC;QAC7F,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi user-level plugins doctor (TOOLING-002L / PB-010 / pi-plugin-doctor).
|
|
3
|
+
* Pure classify + format; I/O helpers injectable for tests.
|
|
4
|
+
*/
|
|
5
|
+
export type PluginTier = "required" | "recommended_a" | "recommended_b" | "forbidden";
|
|
6
|
+
export interface ManifestPlugin {
|
|
7
|
+
id: string;
|
|
8
|
+
package: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
tier?: PluginTier | string;
|
|
11
|
+
severity?: "conflict" | "forbidden" | string;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PiPluginsManifest {
|
|
15
|
+
version: number;
|
|
16
|
+
scope: string;
|
|
17
|
+
source_protocol: string;
|
|
18
|
+
install_template: string;
|
|
19
|
+
required: ManifestPlugin[];
|
|
20
|
+
recommended_a: ManifestPlugin[];
|
|
21
|
+
recommended_b: ManifestPlugin[];
|
|
22
|
+
forbidden: ManifestPlugin[];
|
|
23
|
+
}
|
|
24
|
+
export interface InstalledPackage {
|
|
25
|
+
/** bare or scoped name without version */
|
|
26
|
+
name: string;
|
|
27
|
+
version?: string;
|
|
28
|
+
raw?: string;
|
|
29
|
+
}
|
|
30
|
+
export type DetectSource = "pi_list" | "settings" | "none" | "injected";
|
|
31
|
+
export interface DoctorClassifyInput {
|
|
32
|
+
manifest: PiPluginsManifest;
|
|
33
|
+
installed: InstalledPackage[];
|
|
34
|
+
piCliAvailable: boolean;
|
|
35
|
+
detectSource: DetectSource;
|
|
36
|
+
}
|
|
37
|
+
export interface DoctorFinding {
|
|
38
|
+
package: string;
|
|
39
|
+
version?: string;
|
|
40
|
+
tier: string;
|
|
41
|
+
kind: "missing_required" | "missing_recommended" | "conflict" | "forbidden_present" | "ok_required";
|
|
42
|
+
installCmd?: string;
|
|
43
|
+
reason?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface DoctorReport {
|
|
46
|
+
piCliAvailable: boolean;
|
|
47
|
+
detectSource: DetectSource;
|
|
48
|
+
installedNames: string[];
|
|
49
|
+
missingRequired: DoctorFinding[];
|
|
50
|
+
missingRecommendedA: DoctorFinding[];
|
|
51
|
+
missingRecommendedB: DoctorFinding[];
|
|
52
|
+
conflicts: DoctorFinding[];
|
|
53
|
+
forbiddenPresent: DoctorFinding[];
|
|
54
|
+
okRequired: DoctorFinding[];
|
|
55
|
+
/** true when required all present and no conflicts */
|
|
56
|
+
ok: boolean;
|
|
57
|
+
/** CLI doctor exit should be non-zero when !ok or pi missing for strict mode */
|
|
58
|
+
exitCode: number;
|
|
59
|
+
}
|
|
60
|
+
export declare const DEFAULT_MANIFEST: PiPluginsManifest;
|
|
61
|
+
/** Normalize package name for set membership (lowercase, strip version suffix). */
|
|
62
|
+
export declare function normalizePackageName(raw: string): string;
|
|
63
|
+
export declare function installCommandFor(plugin: ManifestPlugin, template?: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Loose parse of `pi list` / package listing text.
|
|
66
|
+
* Accepts lines like:
|
|
67
|
+
* - @tintinweb/pi-subagents@0.14.2
|
|
68
|
+
* - npm:@tintinweb/pi-subagents@0.14.2
|
|
69
|
+
* - pi-powerbar 0.1.0
|
|
70
|
+
* - "name": "@tintinweb/pi-subagents"
|
|
71
|
+
* Skips URLs, filesystem paths, and section headers ("User packages:").
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseInstalledFromText(text: string): InstalledPackage[];
|
|
74
|
+
export declare function parseInstalledFromSettings(settingsJson: unknown): InstalledPackage[];
|
|
75
|
+
/**
|
|
76
|
+
* Classify installed packages against manifest.
|
|
77
|
+
* Special case: forbidden id `pi-subagents` means unscoped only — do not match `@tintinweb/pi-subagents`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function classifyPiPlugins(input: DoctorClassifyInput): DoctorReport;
|
|
80
|
+
/**
|
|
81
|
+
* Unscoped `pi-subagents` must not match `@tintinweb/pi-subagents`.
|
|
82
|
+
* Presence of unscoped name alone is enough for conflict (even if tintinweb also installed).
|
|
83
|
+
*/
|
|
84
|
+
export declare function isForbiddenPresent(set: Set<string>, forbiddenPackage: string): boolean;
|
|
85
|
+
export declare function formatDoctorReport(report: DoctorReport, opts?: {
|
|
86
|
+
header?: string;
|
|
87
|
+
}): string;
|
|
88
|
+
export declare function loadManifestFromSpec(): Promise<PiPluginsManifest>;
|
|
89
|
+
export declare function normalizeManifest(data: Partial<PiPluginsManifest> | null | undefined): PiPluginsManifest;
|
|
90
|
+
export declare function detectInstalledPackages(opts?: {
|
|
91
|
+
homeDir?: string;
|
|
92
|
+
runPiList?: () => Promise<{
|
|
93
|
+
ok: boolean;
|
|
94
|
+
text: string;
|
|
95
|
+
}>;
|
|
96
|
+
}): Promise<{
|
|
97
|
+
installed: InstalledPackage[];
|
|
98
|
+
piCliAvailable: boolean;
|
|
99
|
+
detectSource: DetectSource;
|
|
100
|
+
}>;
|
|
101
|
+
export declare function runPiPluginsDoctor(opts?: {
|
|
102
|
+
homeDir?: string;
|
|
103
|
+
manifest?: PiPluginsManifest;
|
|
104
|
+
installed?: InstalledPackage[];
|
|
105
|
+
runPiList?: () => Promise<{
|
|
106
|
+
ok: boolean;
|
|
107
|
+
text: string;
|
|
108
|
+
}>;
|
|
109
|
+
}): Promise<{
|
|
110
|
+
report: DoctorReport;
|
|
111
|
+
text: string;
|
|
112
|
+
}>;
|
|
113
|
+
/** Official Pi agent package (global CLI `pi`). Not auto-installed by AIWS. */
|
|
114
|
+
export declare const PI_CODING_AGENT_PACKAGE = "@mariozechner/pi-coding-agent";
|
|
115
|
+
/** Copy-paste install hints when `pi` is missing (detect only; never run by default). */
|
|
116
|
+
export declare function formatPiCliInstallHints(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Detect whether Pi CLI is runnable. Inject `probe` for tests.
|
|
119
|
+
* Default: `pi --version` (or empty argv that succeeds on some builds).
|
|
120
|
+
*/
|
|
121
|
+
export declare function checkPiCliAvailable(opts?: {
|
|
122
|
+
probe?: () => Promise<{
|
|
123
|
+
ok: boolean;
|
|
124
|
+
}>;
|
|
125
|
+
}): Promise<boolean>;
|
|
126
|
+
export interface InstallRequiredResult {
|
|
127
|
+
attempted: string[];
|
|
128
|
+
results: Array<{
|
|
129
|
+
cmd: string;
|
|
130
|
+
code: number;
|
|
131
|
+
stderr: string;
|
|
132
|
+
}>;
|
|
133
|
+
/** True when install was not attempted because Pi CLI is missing. */
|
|
134
|
+
piCliMissing: boolean;
|
|
135
|
+
message?: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Install required packages only (user scope). Never silent from callers without explicit flag.
|
|
139
|
+
* Hard gate: if `pi` is not available, do not call `pi install`; return piCliMissing + hints.
|
|
140
|
+
*/
|
|
141
|
+
export declare function installRequiredPiPlugins(opts?: {
|
|
142
|
+
manifest?: PiPluginsManifest;
|
|
143
|
+
runInstall?: (args: string[]) => Promise<{
|
|
144
|
+
code: number;
|
|
145
|
+
stdout: string;
|
|
146
|
+
stderr: string;
|
|
147
|
+
}>;
|
|
148
|
+
/** Inject for tests; default uses checkPiCliAvailable */
|
|
149
|
+
checkPi?: () => Promise<boolean>;
|
|
150
|
+
}): Promise<InstallRequiredResult>;
|