@spences10/pi-harness 0.1.2 → 0.2.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.
@@ -1,86 +1,60 @@
1
- import { isAbsolute, relative, resolve } from 'node:path';
2
- function resolve_project_path(cwd, input_path) {
3
- return resolve(cwd, input_path);
4
- }
5
- function pattern_to_relative(cwd, pattern) {
6
- const normalized_pattern = pattern.replaceAll('\\', '/');
7
- if (!isAbsolute(pattern))
8
- return normalized_pattern;
9
- return relative(cwd, resolve(pattern)).replaceAll('\\', '/');
10
- }
11
- function pattern_matches(cwd, pattern, absolute_path) {
12
- const normalized_pattern = pattern_to_relative(cwd, pattern);
13
- const relative_path = relative(cwd, absolute_path).replaceAll('\\', '/');
14
- if (normalized_pattern === '.')
1
+ import { isAbsolute, matchesGlob, relative, resolve, } from 'node:path';
2
+ export function pattern_matches(cwd, pattern, path) {
3
+ const normalized = (isAbsolute(pattern) ? relative(cwd, pattern) : pattern).replaceAll('\\', '/');
4
+ const candidate = relative(cwd, resolve(cwd, path)).replaceAll('\\', '/');
5
+ if (normalized === '.')
15
6
  return true;
16
- if (normalized_pattern.endsWith('/**')) {
17
- const prefix = normalized_pattern.slice(0, -3);
18
- return (relative_path === prefix ||
19
- relative_path.startsWith(`${prefix}/`));
7
+ if (!/[*?[{]/.test(normalized)) {
8
+ return (candidate === normalized ||
9
+ candidate.startsWith(`${normalized}/`));
20
10
  }
21
- if (normalized_pattern.includes('*')) {
22
- const regex = new RegExp(`^${normalized_pattern
23
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
24
- .replaceAll('**', '.*')
25
- .replaceAll('*', '[^/]*')}$`);
26
- return regex.test(relative_path);
27
- }
28
- const target = isAbsolute(pattern)
29
- ? resolve(pattern)
30
- : resolve_project_path(cwd, pattern);
31
- return (absolute_path === target || absolute_path.startsWith(`${target}/`));
32
- }
33
- function is_test_path(absolute_path) {
34
- const path = absolute_path.replaceAll('\\', '/');
35
- return (/(^|\/)(__tests__|tests?)\//.test(path) ||
36
- /\.(test|spec)\.[cm]?[jt]sx?$/.test(path));
11
+ return matchesGlob(candidate, normalized);
37
12
  }
38
13
  export function check_path_allowed(contract, input_path) {
39
14
  const cwd = resolve(contract.policy.cwd);
40
- const absolute_path = resolve_project_path(cwd, input_path);
41
- const inside_cwd = absolute_path === cwd || absolute_path.startsWith(`${cwd}/`);
42
- if (!inside_cwd) {
43
- return {
44
- ok: false,
45
- reason: `Harness ${contract.id}: path is outside cwd: ${input_path}`,
46
- };
47
- }
48
- if (contract.policy.forbidden_paths.some((pattern) => pattern_matches(cwd, pattern, absolute_path))) {
49
- return {
50
- ok: false,
51
- reason: `Harness ${contract.id}: path is forbidden: ${input_path}`,
52
- };
53
- }
54
- if (!contract.scaffold.allow_test_changes &&
55
- is_test_path(absolute_path)) {
56
- return {
15
+ const absolute_path = resolve(cwd, input_path);
16
+ let reason;
17
+ if (absolute_path !== cwd && !absolute_path.startsWith(`${cwd}/`)) {
18
+ reason = 'path is outside cwd';
19
+ }
20
+ else if (contract.policy.forbidden_paths.some((pattern) => pattern_matches(cwd, pattern, input_path))) {
21
+ reason = 'path is forbidden';
22
+ }
23
+ else if ((!contract.scaffold.allow_test_changes ||
24
+ contract.policy.allow_test_changes === false) &&
25
+ (/(^|\/)(__tests__|tests?)\//.test(input_path) ||
26
+ /\.(test|spec)\.[cm]?[jt]sx?$/.test(input_path))) {
27
+ reason = 'test changes are not allowed by harness.json';
28
+ }
29
+ else if (!contract.scaffold.allowed_paths.some((pattern) => pattern_matches(cwd, pattern, input_path)) ||
30
+ !(contract.policy.allowed_paths ?? contract.scaffold.allowed_paths).some((pattern) => pattern_matches(cwd, pattern, input_path))) {
31
+ reason = 'path is outside allowed_paths';
32
+ }
33
+ return reason
34
+ ? {
57
35
  ok: false,
58
- reason: `Harness ${contract.id}: test changes are not allowed by harness.json`,
59
- };
60
- }
61
- if (!contract.scaffold.allowed_paths.some((pattern) => pattern_matches(cwd, pattern, absolute_path))) {
62
- return {
63
- ok: false,
64
- reason: `Harness ${contract.id}: path is outside allowed_paths: ${input_path}`,
65
- };
66
- }
67
- return { ok: true };
36
+ reason: `Harness ${contract.id}: ${reason}: ${input_path}`,
37
+ }
38
+ : { ok: true };
68
39
  }
69
40
  export function check_command_allowed(contract, command) {
70
41
  const blocked = contract.policy.forbidden_commands.find((needle) => command.includes(needle));
71
- if (blocked) {
72
- return {
42
+ return blocked
43
+ ? {
73
44
  ok: false,
74
45
  reason: `Harness ${contract.id}: command contains forbidden pattern: ${blocked}`,
75
- };
76
- }
77
- if (!contract.scaffold.allow_test_changes &&
78
- /\brm\b.*((^|\/)(__tests__|tests?)\/|\.(test|spec)\.)/.test(command)) {
79
- return {
80
- ok: false,
81
- reason: `Harness ${contract.id}: deleting tests is not allowed`,
82
- };
83
- }
84
- return { ok: true };
46
+ }
47
+ : { ok: true };
48
+ }
49
+ export function build_path_helpers() {
50
+ return String.raw `
51
+ function pattern_matches(pattern, path) {
52
+ const normalized = (isAbsolute(pattern) ? relative(cwd, pattern) : pattern).replaceAll('\\', '/');
53
+ const candidate = relative(cwd, resolve(cwd, path)).replaceAll('\\', '/');
54
+ if (normalized === '.') return true;
55
+ if (!/[*?[{]/.test(normalized)) return candidate === normalized || candidate.startsWith(normalized + '/');
56
+ return matchesGlob(candidate, normalized);
57
+ }
58
+ `;
85
59
  }
86
60
  //# sourceMappingURL=policy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/enforcement/policy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/enforcement/policy.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import type { BeforeAgentStartEvent, ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
2
  export declare function should_inject_harness_prompt(event: Pick<BeforeAgentStartEvent, 'systemPromptOptions'>): boolean;
3
- export default function harness(pi: ExtensionAPI): Promise<void>;
3
+ export default function harness(pi: ExtensionAPI): void;