@aipper/aiws 0.0.48 → 0.0.50
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/change-finish.js +7 -0
- package/dist/commands/change-finish.js.map +1 -1
- package/dist/commands/change.js +24 -0
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/doctor.d.ts +29 -0
- package/dist/commands/doctor.js +57 -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/commands/ws-deliver.js +2 -3
- package/dist/commands/ws-deliver.js.map +1 -1
- package/dist/commands/ws-finish.d.ts +7 -0
- package/dist/commands/ws-finish.js +19 -128
- package/dist/commands/ws-finish.js.map +1 -1
- package/dist/commands/ws-finish.test.d.ts +1 -0
- package/dist/commands/ws-finish.test.js +167 -0
- package/dist/commands/ws-finish.test.js.map +1 -0
- 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 +130 -0
- package/dist/lib/pi-plugins-doctor.js +537 -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 +137 -0
- package/dist/lib/pi-plugins-doctor.test.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdtempSync, writeFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { rm } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
/**
|
|
8
|
+
* PB-008: after change finish --push, archive artifacts must be committed
|
|
9
|
+
* so git status --porcelain is clean (no D/?? under .aiws/changes).
|
|
10
|
+
*/
|
|
11
|
+
describe("ws-finish archive commit (PB-008)", () => {
|
|
12
|
+
let tmpDir;
|
|
13
|
+
let remoteDir;
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
tmpDir = mkdtempSync(path.join(os.tmpdir(), "aiws-test-finish-"));
|
|
16
|
+
remoteDir = mkdtempSync(path.join(os.tmpdir(), "aiws-remote-"));
|
|
17
|
+
execSync("git init --bare", { cwd: remoteDir });
|
|
18
|
+
execSync("git init --initial-branch=main", { cwd: tmpDir });
|
|
19
|
+
execSync('git config user.email "t@t.com"', { cwd: tmpDir });
|
|
20
|
+
execSync('git config user.name "T"', { cwd: tmpDir });
|
|
21
|
+
writeFileSync(path.join(tmpDir, "README.md"), "# test\n");
|
|
22
|
+
writeFileSync(path.join(tmpDir, ".gitignore"), ".aiws/tmp/\n");
|
|
23
|
+
execSync("git add -A", { cwd: tmpDir });
|
|
24
|
+
execSync('git commit -m "init"', { cwd: tmpDir });
|
|
25
|
+
execSync(`git remote add origin "${remoteDir}"`, { cwd: tmpDir });
|
|
26
|
+
execSync("git push -u origin main", { cwd: tmpDir });
|
|
27
|
+
writeFileSync(path.join(tmpDir, "AI_PROJECT.md"), "# AI_PROJECT\nrules: none\n");
|
|
28
|
+
writeFileSync(path.join(tmpDir, "REQUIREMENTS.md"), "# REQUIREMENTS\n\n## TOOLING-TEST-001\nfinish archive commit regression\n");
|
|
29
|
+
writeFileSync(path.join(tmpDir, "AI_WORKSPACE.md"), "# AI_WORKSPACE\n\n## Verify\n\n- `npm test`\n- `git status --porcelain`\n");
|
|
30
|
+
// Contract CSVs required by ws_change_check strict mode
|
|
31
|
+
mkdirSync(path.join(tmpDir, ".aiws", "requirements"), { recursive: true });
|
|
32
|
+
mkdirSync(path.join(tmpDir, ".aiws", "issues"), { recursive: true });
|
|
33
|
+
writeFileSync(path.join(tmpDir, ".aiws", "requirements", "requirements-issues.csv"), [
|
|
34
|
+
"Req_ID,Title,Change_Type,Module,CRUD,Actor,Scenario,Preconditions,Inputs,Outputs,Data_Model,Business_Logic,API_Impact,NonFunctional,Spec_Status,Impl_Status,Tests,Evidence,Owner,Created_At,Updated_At,Notes",
|
|
35
|
+
"TOOLING-TEST-001,finish archive porcelain clean,FIX,aiws/finish,NA,developer,finish after archive,git,change finish --push,clean porcelain,NA,archive commit,NA,NA,READY,DONE,vitest,evidence/,test,2026-07-21T00:00:00Z,2026-07-21T00:00:00Z,",
|
|
36
|
+
"",
|
|
37
|
+
].join("\n"));
|
|
38
|
+
writeFileSync(path.join(tmpDir, ".aiws", "issues", "problem-issues.csv"), [
|
|
39
|
+
"Problem_ID,Title,Category,Severity,Scope,Symptoms,Root_Cause,Hypothesis,Fix,Verification,Evidence,Status,Owner,Created_At,Updated_At,Notes",
|
|
40
|
+
"PB-TEST-008,finish archive dirty,TOOLING,HIGH,aiws,dirty after finish,no archive commit,,commit archive,porcelain empty,evidence/,TODO,test,2026-07-21T00:00:00Z,2026-07-21T00:00:00Z,",
|
|
41
|
+
"",
|
|
42
|
+
].join("\n"));
|
|
43
|
+
execSync("git add -A", { cwd: tmpDir });
|
|
44
|
+
execSync('git commit -m "truth files"', { cwd: tmpDir });
|
|
45
|
+
execSync("git push", { cwd: tmpDir });
|
|
46
|
+
execSync("git switch -c change/test-finish", { cwd: tmpDir });
|
|
47
|
+
const changeDir = path.join(tmpDir, ".aiws", "changes", "test-finish");
|
|
48
|
+
const reviewDir = path.join(changeDir, "review");
|
|
49
|
+
const evidenceDir = path.join(changeDir, "evidence");
|
|
50
|
+
const planRel = ".aiws/plan/test-finish.md";
|
|
51
|
+
mkdirSync(reviewDir, { recursive: true });
|
|
52
|
+
mkdirSync(evidenceDir, { recursive: true });
|
|
53
|
+
mkdirSync(path.join(tmpDir, ".aiws", "plan"), { recursive: true });
|
|
54
|
+
const bindingsBlock = `## Bindings
|
|
55
|
+
|
|
56
|
+
- Change_ID = test-finish
|
|
57
|
+
- Req_ID = TOOLING-TEST-001
|
|
58
|
+
- Problem_ID = PB-TEST-008
|
|
59
|
+
- Contract_Row = Problem_ID=PB-TEST-008, Req_ID=TOOLING-TEST-001
|
|
60
|
+
- Plan_File = ${planRel}
|
|
61
|
+
- Evidence_Path = .aiws/changes/test-finish/evidence/
|
|
62
|
+
- Base_Branch = main
|
|
63
|
+
`;
|
|
64
|
+
writeFileSync(path.join(changeDir, "proposal.md"), `# test-finish
|
|
65
|
+
|
|
66
|
+
${bindingsBlock}
|
|
67
|
+
|
|
68
|
+
## Goal
|
|
69
|
+
|
|
70
|
+
Ensure finish --push commits archive artifacts.
|
|
71
|
+
|
|
72
|
+
## Verify
|
|
73
|
+
|
|
74
|
+
See AI_WORKSPACE.md: run vitest and assert clean porcelain after finish.
|
|
75
|
+
`);
|
|
76
|
+
writeFileSync(path.join(changeDir, "tasks.md"), "# Tasks: test-finish\n\n- [x] task 1\n- [x] task 2\n");
|
|
77
|
+
writeFileSync(path.join(changeDir, ".ws-change.json"), JSON.stringify({
|
|
78
|
+
ws_change_version: 1,
|
|
79
|
+
id: "test-finish",
|
|
80
|
+
title: "test-finish",
|
|
81
|
+
created_at: new Date().toISOString(),
|
|
82
|
+
base_branch: "main",
|
|
83
|
+
base_truth_files: {},
|
|
84
|
+
}, null, 2) + "\n");
|
|
85
|
+
writeFileSync(path.join(reviewDir, "spec-review.md"), "# Spec Review\nok\n");
|
|
86
|
+
writeFileSync(path.join(reviewDir, "quality-review.md"), "# Quality Review\nok\n");
|
|
87
|
+
writeFileSync(path.join(evidenceDir, "verify-before-complete.md"), "# Verify Before Complete\nok\n");
|
|
88
|
+
writeFileSync(path.join(tmpDir, planRel), `# Plan: test-finish
|
|
89
|
+
|
|
90
|
+
${bindingsBlock}
|
|
91
|
+
|
|
92
|
+
## Goal
|
|
93
|
+
|
|
94
|
+
Regression: finish --push leaves clean porcelain.
|
|
95
|
+
|
|
96
|
+
## Non-goals
|
|
97
|
+
|
|
98
|
+
- Full workspace init
|
|
99
|
+
- Multi-submodule switching
|
|
100
|
+
|
|
101
|
+
## Scope
|
|
102
|
+
|
|
103
|
+
- packages/aiws finish path (fixture-only change)
|
|
104
|
+
|
|
105
|
+
## Plan
|
|
106
|
+
|
|
107
|
+
1. Edit packages/aiws/src/commands/ws-finish.ts to delegate finish to changeFinishCommand
|
|
108
|
+
2. Run: node packages/aiws/bin/aiws.js change finish test-finish --push
|
|
109
|
+
3. Run: git status --porcelain and expect empty output
|
|
110
|
+
|
|
111
|
+
## Verify
|
|
112
|
+
|
|
113
|
+
| # | 命令 | 期望结果(expected) |
|
|
114
|
+
|---|------|----------------------|
|
|
115
|
+
| V1 | \`git status --porcelain\` after finish | 期望:empty |
|
|
116
|
+
| V2 | \`git log --oneline\` | 期望:contains 归档: test-finish |
|
|
117
|
+
|
|
118
|
+
## Risks & Rollback
|
|
119
|
+
|
|
120
|
+
| 风险 | 缓解 |
|
|
121
|
+
|------|------|
|
|
122
|
+
| Strict validate blocks archive | Provide full bindings + CSVs in fixture |
|
|
123
|
+
|
|
124
|
+
## Evidence
|
|
125
|
+
|
|
126
|
+
- Path: .aiws/changes/test-finish/evidence/
|
|
127
|
+
`);
|
|
128
|
+
execSync("git add -A", { cwd: tmpDir });
|
|
129
|
+
execSync('git commit -m "setup test-finish change"', { cwd: tmpDir });
|
|
130
|
+
execSync("git push -u origin change/test-finish", { cwd: tmpDir });
|
|
131
|
+
});
|
|
132
|
+
afterAll(async () => {
|
|
133
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
134
|
+
await rm(remoteDir, { recursive: true, force: true });
|
|
135
|
+
});
|
|
136
|
+
it("finish --push archives and leaves clean porcelain (PB-008 regression)", async () => {
|
|
137
|
+
const prevCwd = process.cwd();
|
|
138
|
+
try {
|
|
139
|
+
execSync("git switch change/test-finish", { cwd: tmpDir });
|
|
140
|
+
// Local-only validate stamp (gitignored under .aiws/tmp)
|
|
141
|
+
const stampDir = path.join(tmpDir, ".aiws", "tmp", "aiws-validate");
|
|
142
|
+
mkdirSync(stampDir, { recursive: true });
|
|
143
|
+
writeFileSync(path.join(stampDir, "stamp.json"), JSON.stringify({ ok: true, timestamp: new Date().toISOString() }));
|
|
144
|
+
// Ensure clean porcelain before finish (stamp ignored)
|
|
145
|
+
const pre = execSync("git status --porcelain", { cwd: tmpDir, encoding: "utf8" }).trim();
|
|
146
|
+
expect(pre).toBe("");
|
|
147
|
+
process.chdir(tmpDir);
|
|
148
|
+
const { changeFinishCommand } = await import("./change.js");
|
|
149
|
+
await changeFinishCommand({
|
|
150
|
+
changeId: "test-finish",
|
|
151
|
+
push: true,
|
|
152
|
+
into: "main",
|
|
153
|
+
});
|
|
154
|
+
const status = execSync("git status --porcelain", { encoding: "utf8" }).trim();
|
|
155
|
+
expect(status).toBe("");
|
|
156
|
+
const branch = execSync("git branch --show-current", { encoding: "utf8" }).trim();
|
|
157
|
+
expect(branch).toBe("main");
|
|
158
|
+
const log = execSync("git log --oneline -12", { encoding: "utf8" });
|
|
159
|
+
expect(log).toContain("归档: test-finish");
|
|
160
|
+
expect(existsSync(path.join(tmpDir, ".aiws", "changes", "test-finish"))).toBe(false);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
process.chdir(prevCwd);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=ws-finish.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-finish.test.js","sourceRoot":"","sources":["../../src/commands/ws-finish.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
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 declare const HATCH_PHRASES: readonly ["直接改", "do it inline", "你直接改", "别派 sub-agent", "main session 写就行", "不用 sub-agent", "no sub-agent"];
|
|
7
|
+
/** Allowed Agent subagent_type values for implement/review dispatch. */
|
|
8
|
+
export declare const ALLOWED_SUBAGENT_TYPES: readonly ["aiws-worker", "aiws-reviewer"];
|
|
9
|
+
export type AllowedSubagentType = (typeof ALLOWED_SUBAGENT_TYPES)[number];
|
|
10
|
+
/** Tool names treated as mutating writes when deny is active. */
|
|
11
|
+
export declare const DENIED_WRITE_TOOLS: readonly ["write", "edit", "Write", "Edit"];
|
|
12
|
+
/**
|
|
13
|
+
* Path prefixes (posix-style, relative to project root) allowed for main-session
|
|
14
|
+
* writes without hatch. Match is prefix after normalize (no leading ./).
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAIN_WRITE_ALLOW_PREFIXES: readonly [".aiws/changes/", ".aiws/plan/", ".aiws/goals/"];
|
|
17
|
+
export type PolicyDecision = {
|
|
18
|
+
allow: true;
|
|
19
|
+
reason: string;
|
|
20
|
+
} | {
|
|
21
|
+
allow: false;
|
|
22
|
+
reason: string;
|
|
23
|
+
code: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Detect hatch in the **current** user message only (caller must pass only that text).
|
|
27
|
+
*/
|
|
28
|
+
export declare function detectHatch(currentUserMessage: string | null | undefined): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Normalize a path for allowlist checks: backslash→slash, strip leading ./ and /
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeRelPath(filePath: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether a write target is on the main-session governance allowlist (no hatch required).
|
|
35
|
+
*/
|
|
36
|
+
export declare function isMainWriteAllowlisted(filePath: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Decide if main session may invoke a write-like tool for path.
|
|
39
|
+
*/
|
|
40
|
+
export declare function decideMainWrite(opts: {
|
|
41
|
+
toolName: string;
|
|
42
|
+
filePath?: string | null;
|
|
43
|
+
hatchActive: boolean;
|
|
44
|
+
}): PolicyDecision;
|
|
45
|
+
/**
|
|
46
|
+
* Whitelist subagent types for implement/review. Unknown types must not fall through
|
|
47
|
+
* to pi-subagents general-purpose silently.
|
|
48
|
+
*/
|
|
49
|
+
export declare function decideAgentType(subagentType: string | null | undefined): PolicyDecision;
|
|
50
|
+
/**
|
|
51
|
+
* Text for session_start system prompt injection.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildForcePolicyPromptBlock(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Deprecation message when aiws_subagent is invoked.
|
|
56
|
+
*/
|
|
57
|
+
export declare function aiwsSubagentDeprecationMessage(prompt: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Suggested content for evidence/delegate-failure.md
|
|
60
|
+
*/
|
|
61
|
+
export declare function formatDelegateFailureEvidence(opts: {
|
|
62
|
+
changeId?: string;
|
|
63
|
+
error: string;
|
|
64
|
+
attemptedType?: string;
|
|
65
|
+
at?: string;
|
|
66
|
+
}): string;
|
|
@@ -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":"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"}
|