@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.
@@ -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.29.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",