@lannguyensi/harness 0.29.0 → 0.30.1
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/CHANGELOG.md +22 -0
- package/README.md +7 -2
- package/dist/cli/approve/risk.d.ts +41 -0
- package/dist/cli/approve/risk.js +68 -2
- package/dist/cli/approve/risk.js.map +1 -1
- package/dist/cli/doctor/format.js +4 -0
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/index.js +42 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/templates.d.ts +1 -1
- package/dist/cli/init/templates.js +2 -1
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/hook-stay-in-scope.d.ts +58 -0
- package/dist/cli/pack/hook-stay-in-scope.js +315 -0
- package/dist/cli/pack/hook-stay-in-scope.js.map +1 -0
- package/dist/cli/policy/intercept.js +49 -2
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js +26 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/runtime/bash-prefix-parse.d.ts +13 -0
- package/dist/runtime/bash-prefix-parse.js +172 -0
- package/dist/runtime/bash-prefix-parse.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Risk Gate resolver input — Bash command-prefix parser.
|
|
2
|
+
//
|
|
3
|
+
// Two normal POSIX shell idioms slip past the production environment
|
|
4
|
+
// resolver when only `process.env` and the hook's starting cwd are
|
|
5
|
+
// inspected:
|
|
6
|
+
//
|
|
7
|
+
// DATABASE_URL=postgres://prod terraform destroy # inline env
|
|
8
|
+
// cd /repos/prod-infra && terraform destroy # working-dir hop
|
|
9
|
+
//
|
|
10
|
+
// The hook intercept sees Claude Code's process env and starting cwd, so
|
|
11
|
+
// `env_var_patterns` and `branch_patterns` miss both signals and the
|
|
12
|
+
// gate silently treats a prod mutation as non-prod.
|
|
13
|
+
//
|
|
14
|
+
// This parser extracts the leading idioms from a Bash command string so
|
|
15
|
+
// the resolver layer can merge them into its inputs before
|
|
16
|
+
// `environments.resolvers[]` runs. Two POSIX forms are supported in v1
|
|
17
|
+
// (kept narrow on purpose, see follow-up scope in the originating task):
|
|
18
|
+
//
|
|
19
|
+
// 1. Inline env: leading `\w+=value` tokens. Values may be unquoted,
|
|
20
|
+
// single-quoted (literal), or double-quoted (literal, no $
|
|
21
|
+
// interpolation in v1).
|
|
22
|
+
// 2. cd prefix: a single leading `cd <path> [&&|;] ...`. Quoted paths
|
|
23
|
+
// supported. No `pushd`, no subshell `(cd X && ...)`, no `bash -c`.
|
|
24
|
+
//
|
|
25
|
+
// The two clauses may appear in either order (`cd /x && VAR=v cmd` and
|
|
26
|
+
// `VAR=v cd /x && cmd` both parse); the parser walks up to two passes
|
|
27
|
+
// before giving up.
|
|
28
|
+
//
|
|
29
|
+
// On a syntactically broken prefix (unterminated quote, missing `&&`
|
|
30
|
+
// after `cd <path>`) the parser falls through cleanly: the malformed
|
|
31
|
+
// prefix is not consumed, the resolver-side fallback to process env /
|
|
32
|
+
// hook cwd holds. There are no thrown errors from this module.
|
|
33
|
+
/**
|
|
34
|
+
* Parse leading inline-env and `cd <path>` idioms from a Bash command
|
|
35
|
+
* string. Returns an empty `inlineEnv` and `cdTarget:null` when neither
|
|
36
|
+
* idiom matches. Never throws.
|
|
37
|
+
*/
|
|
38
|
+
export function parseBashPrefix(command) {
|
|
39
|
+
if (typeof command !== "string" || command.length === 0) {
|
|
40
|
+
return { inlineEnv: {}, cdTarget: null };
|
|
41
|
+
}
|
|
42
|
+
const inlineEnv = {};
|
|
43
|
+
let cdTarget = null;
|
|
44
|
+
let cursor = 0;
|
|
45
|
+
// Two passes catch `cd /x && VAR=v cmd` and `VAR=v cd /x && cmd`. A
|
|
46
|
+
// third pass would only fire on a redundant prefix; bail to keep this
|
|
47
|
+
// bounded.
|
|
48
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
49
|
+
const before = cursor;
|
|
50
|
+
cursor = consumeInlineEnv(command, cursor, inlineEnv);
|
|
51
|
+
if (cdTarget === null) {
|
|
52
|
+
const cd = consumeLeadingCd(command, cursor);
|
|
53
|
+
if (cd !== null) {
|
|
54
|
+
cdTarget = cd.path;
|
|
55
|
+
cursor = cd.next;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (cursor === before)
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
return { inlineEnv, cdTarget };
|
|
62
|
+
}
|
|
63
|
+
const WS = /\s/;
|
|
64
|
+
const VAR_START = /[A-Za-z_]/;
|
|
65
|
+
const VAR_CONT = /[A-Za-z0-9_]/;
|
|
66
|
+
function skipWs(s, i) {
|
|
67
|
+
while (i < s.length && WS.test(s[i]))
|
|
68
|
+
i++;
|
|
69
|
+
return i;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Consume zero or more leading `VAR=value` tokens. Each successful
|
|
73
|
+
* consumption registers into `into`. Returns the cursor position after
|
|
74
|
+
* the last consumed token, or the original cursor when nothing parsed
|
|
75
|
+
* (so the caller can try another prefix kind).
|
|
76
|
+
*
|
|
77
|
+
* On a syntactically broken token (e.g. unterminated quote) the broken
|
|
78
|
+
* token is NOT consumed and the function returns the cursor at the
|
|
79
|
+
* start of that token, preserving the rest of the command for fallback.
|
|
80
|
+
*/
|
|
81
|
+
function consumeInlineEnv(s, start, into) {
|
|
82
|
+
let i = skipWs(s, start);
|
|
83
|
+
let lastGood = i;
|
|
84
|
+
while (i < s.length) {
|
|
85
|
+
const nameStart = i;
|
|
86
|
+
if (!VAR_START.test(s[i]))
|
|
87
|
+
break;
|
|
88
|
+
i++;
|
|
89
|
+
while (i < s.length && VAR_CONT.test(s[i]))
|
|
90
|
+
i++;
|
|
91
|
+
if (s[i] !== "=")
|
|
92
|
+
break;
|
|
93
|
+
const name = s.slice(nameStart, i);
|
|
94
|
+
i++;
|
|
95
|
+
// Read value: quoted (single/double, literal) or unquoted (to ws).
|
|
96
|
+
let value;
|
|
97
|
+
if (s[i] === "'") {
|
|
98
|
+
const end = s.indexOf("'", i + 1);
|
|
99
|
+
if (end < 0)
|
|
100
|
+
return lastGood;
|
|
101
|
+
value = s.slice(i + 1, end);
|
|
102
|
+
i = end + 1;
|
|
103
|
+
}
|
|
104
|
+
else if (s[i] === '"') {
|
|
105
|
+
const end = s.indexOf('"', i + 1);
|
|
106
|
+
if (end < 0)
|
|
107
|
+
return lastGood;
|
|
108
|
+
value = s.slice(i + 1, end);
|
|
109
|
+
i = end + 1;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const vStart = i;
|
|
113
|
+
while (i < s.length && !WS.test(s[i]))
|
|
114
|
+
i++;
|
|
115
|
+
value = s.slice(vStart, i);
|
|
116
|
+
}
|
|
117
|
+
into[name] = value;
|
|
118
|
+
i = skipWs(s, i);
|
|
119
|
+
lastGood = i;
|
|
120
|
+
}
|
|
121
|
+
return lastGood;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Consume a single leading `cd <path> [&&|;]` clause. Returns
|
|
125
|
+
* `{path, next}` on success (where `next` is the cursor after the
|
|
126
|
+
* separator), or null when the prefix does not match. A path that is
|
|
127
|
+
* missing the trailing `&&` / `;` separator is treated as not-a-prefix
|
|
128
|
+
* (the operator typed `cd <path>` and nothing else — no useful resolver
|
|
129
|
+
* override).
|
|
130
|
+
*/
|
|
131
|
+
function consumeLeadingCd(s, start) {
|
|
132
|
+
let i = skipWs(s, start);
|
|
133
|
+
// Match `cd` followed by whitespace; do NOT match `cd&&` or `cdx`.
|
|
134
|
+
if (s[i] !== "c" || s[i + 1] !== "d")
|
|
135
|
+
return null;
|
|
136
|
+
if (i + 2 >= s.length || !WS.test(s[i + 2]))
|
|
137
|
+
return null;
|
|
138
|
+
i = skipWs(s, i + 2);
|
|
139
|
+
// Path: quoted or unquoted.
|
|
140
|
+
let path;
|
|
141
|
+
if (s[i] === "'") {
|
|
142
|
+
const end = s.indexOf("'", i + 1);
|
|
143
|
+
if (end < 0)
|
|
144
|
+
return null;
|
|
145
|
+
path = s.slice(i + 1, end);
|
|
146
|
+
i = end + 1;
|
|
147
|
+
}
|
|
148
|
+
else if (s[i] === '"') {
|
|
149
|
+
const end = s.indexOf('"', i + 1);
|
|
150
|
+
if (end < 0)
|
|
151
|
+
return null;
|
|
152
|
+
path = s.slice(i + 1, end);
|
|
153
|
+
i = end + 1;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const pStart = i;
|
|
157
|
+
while (i < s.length && !WS.test(s[i]) && s[i] !== ";" && s[i] !== "&")
|
|
158
|
+
i++;
|
|
159
|
+
path = s.slice(pStart, i);
|
|
160
|
+
}
|
|
161
|
+
if (path.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
i = skipWs(s, i);
|
|
164
|
+
if (s[i] === "&" && s[i + 1] === "&") {
|
|
165
|
+
return { path, next: i + 2 };
|
|
166
|
+
}
|
|
167
|
+
if (s[i] === ";") {
|
|
168
|
+
return { path, next: i + 1 };
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=bash-prefix-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash-prefix-parse.js","sourceRoot":"","sources":["../../src/runtime/bash-prefix-parse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lannguyensi/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Declarative control plane for agent harnesses — one YAML for grounding, tools, memory, and hooks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/LanNguyenSi/harness",
|