@hncode/hncode 0.4.2 → 0.5.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/README.md +1 -15
- package/README_CN.md +1 -15
- package/bin/hncode +22 -9
- package/package.json +3 -2
- package/src/agent.js +149 -35
- package/src/bootstrap.js +56 -0
- package/src/btw-panel.js +224 -0
- package/src/cache.js +3 -2
- package/src/cell-buffer.js +173 -0
- package/src/ci.js +4 -1
- package/src/clipboard.js +202 -43
- package/src/colors.js +478 -242
- package/src/config.js +409 -40
- package/src/context-bar.js +237 -0
- package/src/doctor.js +518 -0
- package/src/experiments.js +122 -0
- package/src/file-history.js +112 -0
- package/src/file-tree.js +255 -0
- package/src/flush-tick.js +56 -0
- package/src/fork.js +44 -0
- package/src/fx.js +148 -0
- package/src/index.js +123 -11
- package/src/keybindings.js +229 -0
- package/src/llm.js +355 -34
- package/src/lsp.js +401 -0
- package/src/memory-profile.js +247 -0
- package/src/notebook.js +232 -0
- package/src/output-styles.js +183 -0
- package/src/patch.js +248 -0
- package/src/permissions.js +130 -1
- package/src/plan.js +3 -2
- package/src/plugin.js +611 -58
- package/src/prompt-presets.js +1 -1
- package/src/registry-browser.js +31 -13
- package/src/remote.js +20 -2
- package/src/rollout.js +151 -0
- package/src/row-cache.js +148 -0
- package/src/schedule.js +294 -0
- package/src/session-approvals.js +86 -0
- package/src/session-hub.js +1 -0
- package/src/session.js +178 -8
- package/src/settings-schema.js +301 -0
- package/src/shell-safety.js +473 -0
- package/src/side-thread.js +240 -0
- package/src/skills.js +27 -0
- package/src/stream-buffer.js +350 -0
- package/src/subagent-control.js +160 -0
- package/src/teleport.js +260 -0
- package/src/term-caps.js +411 -0
- package/src/term.js +283 -8
- package/src/tool-result.js +264 -236
- package/src/tools/agent-control.js +98 -0
- package/src/tools/agent-swarm.js +11 -2
- package/src/tools/agent.js +35 -1
- package/src/tools/diagnostics.js +175 -0
- package/src/tools/edit.js +25 -10
- package/src/tools/grep.js +62 -20
- package/src/tools/ignore.js +38 -14
- package/src/tools/index.js +10 -0
- package/src/tools/notebook.js +113 -0
- package/src/tools/read-media-file.js +10 -3
- package/src/tools/shell.js +34 -6
- package/src/tui.js +5637 -746
- package/src/web-public/app.js +7 -0
- package/src/web.js +3 -1
package/src/permissions.js
CHANGED
|
@@ -30,9 +30,19 @@ const PATH_ARGS = {
|
|
|
30
30
|
Grep: ['path', 'pattern'],
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
/** Strip a trailing `# …` comment. ` #` (whitespace before the hash) is the
|
|
34
|
+
* delimiter, so a `#` inside an argument — `Bash(echo a#b)` — is left alone. */
|
|
35
|
+
export function stripRuleComment(text) {
|
|
36
|
+
const m = /(^|\s)#/.exec(String(text == null ? '' : text));
|
|
37
|
+
return m ? String(text).slice(0, m.index + m[1].length).trim() : String(text == null ? '' : text).trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
// `Bash(npm run test)` / `Read(./src/**)` -> { tool, arg }
|
|
34
41
|
export function parseRule(rule) {
|
|
35
|
-
|
|
42
|
+
// The comment is stripped FIRST. Without this a rule carrying `# match: …`
|
|
43
|
+
// examples parsed as null (the text no longer ends with `)`) and the rule
|
|
44
|
+
// silently stopped matching anything — so annotating a rule broke it.
|
|
45
|
+
const text = stripRuleComment(rule);
|
|
36
46
|
if (!text) return null;
|
|
37
47
|
const open = text.indexOf('(');
|
|
38
48
|
if (open === -1) return { tool: text, arg: null };
|
|
@@ -118,3 +128,122 @@ export function describeRules(rules) {
|
|
|
118
128
|
}
|
|
119
129
|
return lines.join('\n');
|
|
120
130
|
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Rule self-tests
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// A rule that silently matches NOTHING is worse than no rule at all: the user
|
|
136
|
+
// believes `npm test` is allowed, the prompt appears anyway, and nothing says why.
|
|
137
|
+
// A typo in a glob (`./src/*` vs `./src/**`) is invisible until the prompt shows up
|
|
138
|
+
// at the wrong moment.
|
|
139
|
+
//
|
|
140
|
+
// So a rule may carry its own examples, checked when the rules are read:
|
|
141
|
+
//
|
|
142
|
+
// Bash(npm run test *) # match: npm run test foo | not_match: npm run tests
|
|
143
|
+
//
|
|
144
|
+
// This mirrors codex's `prefix_rule(match=[...], not_match=[...])`, which validates
|
|
145
|
+
// its examples at load time. The examples live on the SAME line as the rule, as a
|
|
146
|
+
// trailing comment, so a rule and its test cannot drift into two places and the
|
|
147
|
+
// line-based config format does not have to change.
|
|
148
|
+
//
|
|
149
|
+
// Several examples are separated by `|`. An example is the ARGUMENT the rule
|
|
150
|
+
// describes — the command for Bash, the path for a file tool — so no quoting is
|
|
151
|
+
// needed for the common case.
|
|
152
|
+
|
|
153
|
+
/** Split one rule line into `{ rule, match, not_match }`. */
|
|
154
|
+
export function parseRuleLine(line) {
|
|
155
|
+
const text = String(line == null ? '' : line);
|
|
156
|
+
// A ` #` delimiter, so a `#` inside an argument stays part of the rule.
|
|
157
|
+
const delim = /(^|\s)#/.exec(text);
|
|
158
|
+
const ruleText = delim ? text.slice(0, delim.index + delim[1].length).trim() : text.trim();
|
|
159
|
+
const comment = delim ? text.slice(delim.index + delim[0].length).trim() : '';
|
|
160
|
+
const out = { rule: ruleText, match: [], not_match: [] };
|
|
161
|
+
if (!comment) return out;
|
|
162
|
+
// Scan for the markers rather than splitting on them: `split` with a LOOKAHEAD
|
|
163
|
+
// of `\bnot[_-]?match` splits `not-match` into `not-` and `match:` (the boundary
|
|
164
|
+
// before `match` matched first), which produced two unusable halves. An explicit
|
|
165
|
+
// scan takes the longest marker at each position and cannot do that.
|
|
166
|
+
const marker = /(not[_-]?match|match)\s*:/gi;
|
|
167
|
+
const found = [];
|
|
168
|
+
let m;
|
|
169
|
+
while ((m = marker.exec(comment)) !== null) {
|
|
170
|
+
found.push({ key: /^not/i.test(m[1]) ? 'not_match' : 'match', from: m.index, bodyStart: m.index + m[0].length });
|
|
171
|
+
}
|
|
172
|
+
for (let i = 0; i < found.length; i++) {
|
|
173
|
+
const end = i + 1 < found.length ? found[i + 1].from : comment.length;
|
|
174
|
+
const body = comment.slice(found[i].bodyStart, end);
|
|
175
|
+
for (const ex of body.split('|')) {
|
|
176
|
+
const t = ex.trim();
|
|
177
|
+
if (t) out[found[i].key].push(t);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** An example string -> the argument object `ruleMatches` expects. */
|
|
184
|
+
export function exampleArgs(toolName, example) {
|
|
185
|
+
const ex = String(example == null ? '' : example).trim();
|
|
186
|
+
if (toolName === 'Bash') return { command: ex };
|
|
187
|
+
return { path: ex, file_path: ex, pattern: ex };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check every rule's examples. Returns `{ checks, failed }`; a failed check is
|
|
192
|
+
* `{ rule, kind: 'match'|'not_match', example, list }`.
|
|
193
|
+
*
|
|
194
|
+
* A rule with NO examples yields no checks: the test is opt-in, so adding this to an
|
|
195
|
+
* existing config cannot start reporting errors for rules nobody annotated.
|
|
196
|
+
*/
|
|
197
|
+
export function selfTestRules(rules) {
|
|
198
|
+
const list = rules || {};
|
|
199
|
+
const checks = [];
|
|
200
|
+
const failed = [];
|
|
201
|
+
// `deny` is included on purpose: an over-broad deny is the MOST damaging typo of
|
|
202
|
+
// the three, because it can lock the agent out of the work it was asked to do.
|
|
203
|
+
for (const key of ['allow', 'ask', 'deny']) {
|
|
204
|
+
const arr = Array.isArray(list[key]) ? list[key] : [];
|
|
205
|
+
for (const raw of arr) {
|
|
206
|
+
const { rule, match, not_match } = parseRuleLine(raw);
|
|
207
|
+
if (!rule) continue;
|
|
208
|
+
const parsed = parseRule(rule);
|
|
209
|
+
if (!parsed) {
|
|
210
|
+
failed.push({ rule, kind: 'parse', example: '', list: key });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
for (const ex of match) {
|
|
214
|
+
const pass = ruleMatches(parsed, parsed.tool, exampleArgs(parsed.tool, ex));
|
|
215
|
+
checks.push({ rule, kind: 'match', example: ex, pass, list: key });
|
|
216
|
+
if (!pass) failed.push({ rule, kind: 'match', example: ex, list: key });
|
|
217
|
+
}
|
|
218
|
+
for (const ex of not_match) {
|
|
219
|
+
const pass = !ruleMatches(parsed, parsed.tool, exampleArgs(parsed.tool, ex));
|
|
220
|
+
checks.push({ rule, kind: 'not_match', example: ex, pass, list: key });
|
|
221
|
+
if (!pass) failed.push({ rule, kind: 'not_match', example: ex, list: key });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { checks, failed };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** The self-test block for the /permissions panel. */
|
|
229
|
+
export function describeSelfTest(result) {
|
|
230
|
+
if (!result || !result.checks.length) {
|
|
231
|
+
return result && result.failed.length
|
|
232
|
+
? `${result.failed.length} rule(s) could not be parsed:\n`
|
|
233
|
+
+ result.failed.map((f) => ` FAIL ${f.list}: ${f.rule}`).join('\n')
|
|
234
|
+
: 'No rule has examples yet. Add them on the rule line:\n'
|
|
235
|
+
+ ' Bash(git push *) # match: git push | not_match: git pull';
|
|
236
|
+
}
|
|
237
|
+
const out = [`${result.checks.length} example(s) checked, ${result.failed.length} failed:`];
|
|
238
|
+
for (const c of result.checks) {
|
|
239
|
+
const want = c.kind === 'match' ? 'should match ' : 'should NOT match';
|
|
240
|
+
out.push(` ${c.pass ? 'ok ' : 'FAIL'} ${want} ${c.list}: ${c.rule} <- ${c.example}`);
|
|
241
|
+
}
|
|
242
|
+
return out.join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default {
|
|
246
|
+
parseRule, ruleMatches, decideFromRules, describeRules,
|
|
247
|
+
parseRuleLine, selfTestRules, describeSelfTest, exampleArgs,
|
|
248
|
+
};
|
|
249
|
+
|
package/src/plan.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Plan review helpers — turning a model's
|
|
1
|
+
// Plan review helpers — turning a model's plan block into something the user
|
|
2
2
|
// can actually review before any file is touched.
|
|
3
3
|
//
|
|
4
|
-
// Plan mode already asks the model for a
|
|
4
|
+
// Plan mode already asks the model for a <|plan|>…</|plan|> block and prompts to
|
|
5
|
+
// approve it. What this module adds is the review material and the persistence:
|
|
5
6
|
// approve it. What this module adds is the review material and the persistence:
|
|
6
7
|
//
|
|
7
8
|
// * filesReferenced(plan) — which paths the plan says it will touch, so the
|