@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.
Files changed (36) hide show
  1. package/dist/cli.js +30 -5
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/change-finish.js +7 -0
  4. package/dist/commands/change-finish.js.map +1 -1
  5. package/dist/commands/change.js +24 -0
  6. package/dist/commands/change.js.map +1 -1
  7. package/dist/commands/doctor.d.ts +29 -0
  8. package/dist/commands/doctor.js +57 -0
  9. package/dist/commands/doctor.js.map +1 -0
  10. package/dist/commands/init.d.ts +1 -3
  11. package/dist/commands/init.js +7 -3
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/update.d.ts +0 -3
  14. package/dist/commands/update.js +11 -3
  15. package/dist/commands/update.js.map +1 -1
  16. package/dist/commands/ws-deliver.js +2 -3
  17. package/dist/commands/ws-deliver.js.map +1 -1
  18. package/dist/commands/ws-finish.d.ts +7 -0
  19. package/dist/commands/ws-finish.js +19 -128
  20. package/dist/commands/ws-finish.js.map +1 -1
  21. package/dist/commands/ws-finish.test.d.ts +1 -0
  22. package/dist/commands/ws-finish.test.js +167 -0
  23. package/dist/commands/ws-finish.test.js.map +1 -0
  24. package/dist/lib/pi-force-policy.d.ts +66 -0
  25. package/dist/lib/pi-force-policy.js +187 -0
  26. package/dist/lib/pi-force-policy.js.map +1 -0
  27. package/dist/lib/pi-force-policy.test.d.ts +1 -0
  28. package/dist/lib/pi-force-policy.test.js +99 -0
  29. package/dist/lib/pi-force-policy.test.js.map +1 -0
  30. package/dist/lib/pi-plugins-doctor.d.ts +130 -0
  31. package/dist/lib/pi-plugins-doctor.js +537 -0
  32. package/dist/lib/pi-plugins-doctor.js.map +1 -0
  33. package/dist/lib/pi-plugins-doctor.test.d.ts +1 -0
  34. package/dist/lib/pi-plugins-doctor.test.js +137 -0
  35. package/dist/lib/pi-plugins-doctor.test.js.map +1 -0
  36. 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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"}