@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.51
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +400 -4
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +112 -3
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +155 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +897 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +284 -2
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1897 -37
- package/dist/core/repl/slash-commands.js +430 -15
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3241 -343
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +412 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +203 -4
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +218 -3
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +313 -35
- package/dist/tui/repl-splash-art.js +1 -1
- package/dist/tui/repl-splash-mascot.js +32 -8
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +85 -5
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +13 -7
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario DSL parser for the Pugi MCP test harness (BIG TRACK 10
|
|
3
|
+
* Phase 1, 2026-05-27).
|
|
4
|
+
*
|
|
5
|
+
* Background — CEO directive `feedback_live_console_test_every_publish`:
|
|
6
|
+
* every CLI publish must be smoke-tested against a fixed corpus of
|
|
7
|
+
* scripted dialogs before it is announced to the operator. The manual
|
|
8
|
+
* routine ("npm i -g + pugi --version + run identity prompt + verify
|
|
9
|
+
* Pugi self-id") is exactly the kind of toil a deterministic harness
|
|
10
|
+
* should automate. Phase 1 lays the foundation; the corpus + headless
|
|
11
|
+
* runner + CI gate.
|
|
12
|
+
*
|
|
13
|
+
* Format — one scenario per file, line-based, intentionally minimal so
|
|
14
|
+
* non-engineers can author cases. The grammar:
|
|
15
|
+
*
|
|
16
|
+
* # comment — ignored
|
|
17
|
+
* # scenario: <id> — optional metadata
|
|
18
|
+
* > "<input>" — single user turn (the engine sees this verbatim)
|
|
19
|
+
* EXPECT: <pred> — positive assertion against the next persona-turn
|
|
20
|
+
* envelope OR a tool-call envelope. Multiple stack
|
|
21
|
+
* against the SAME prior `>` turn until the next
|
|
22
|
+
* `>` resets the cursor.
|
|
23
|
+
* EXPECT_NOT:<pred>— negative assertion (same cursor semantics).
|
|
24
|
+
* EXPECT_FILE:<file> exists [with content "<text>"]
|
|
25
|
+
* — filesystem assertion fired AFTER the scenario
|
|
26
|
+
* finishes. Matches both `exists` literal and a
|
|
27
|
+
* content substring check.
|
|
28
|
+
*
|
|
29
|
+
* The DSL parser is deliberately a pure function over the file contents:
|
|
30
|
+
* the runner module is the thing that spawns `pugi --headless` and
|
|
31
|
+
* matches assertions against the emitted envelope stream. Keeping the
|
|
32
|
+
* parser pure is what lets us spec it with no fixtures.
|
|
33
|
+
*
|
|
34
|
+
* Anti-design — we did NOT use YAML/JSON. The line-based form survives
|
|
35
|
+
* copy-paste from a chat transcript ("> 'ты кто?'" maps 1:1 to the way
|
|
36
|
+
* an operator sees a Pugi dialog), and prevents drift between scenario
|
|
37
|
+
* authoring style and how the operator actually drives the CLI.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Parse a scenario file body into a `ParsedScenario`. Returns errors
|
|
41
|
+
* alongside the partial scenario so the runner can surface every issue
|
|
42
|
+
* in one pass rather than failing on the first malformed line. The
|
|
43
|
+
* shape mirrors what TypeScript's diagnostics array looks like —
|
|
44
|
+
* familiar pattern for any operator who has read a tsc error.
|
|
45
|
+
*
|
|
46
|
+
* `filePath` is informational; we ALSO consume it to derive the
|
|
47
|
+
* default scenario id when the file body does not declare one.
|
|
48
|
+
*/
|
|
49
|
+
export function parseScenario(filePath, body) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
const steps = [];
|
|
52
|
+
let title;
|
|
53
|
+
let explicitId;
|
|
54
|
+
const lines = body.split(/\r?\n/);
|
|
55
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
56
|
+
const raw = lines[i] ?? '';
|
|
57
|
+
const lineNo = i + 1;
|
|
58
|
+
const trimmed = raw.trim();
|
|
59
|
+
if (trimmed.length === 0)
|
|
60
|
+
continue;
|
|
61
|
+
if (trimmed.startsWith('#')) {
|
|
62
|
+
// Metadata comments. `# scenario: <id>` populates the explicit
|
|
63
|
+
// id; `# title: <text>` populates the title. Plain `# anything`
|
|
64
|
+
// is a free-form comment.
|
|
65
|
+
const meta = /^#\s*(scenario|title)\s*:\s*(.+?)\s*$/i.exec(trimmed);
|
|
66
|
+
if (meta) {
|
|
67
|
+
const key = (meta[1] ?? '').toLowerCase();
|
|
68
|
+
const value = meta[2] ?? '';
|
|
69
|
+
if (key === 'scenario')
|
|
70
|
+
explicitId = value;
|
|
71
|
+
else if (key === 'title')
|
|
72
|
+
title = value;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (trimmed.startsWith('>')) {
|
|
77
|
+
const body = parseQuotedInput(trimmed.slice(1).trim());
|
|
78
|
+
if (body === null) {
|
|
79
|
+
errors.push({
|
|
80
|
+
line: lineNo,
|
|
81
|
+
message: 'user input line must be quoted (e.g. > "hello")',
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
steps.push({ kind: 'user-input', body, line: lineNo });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (trimmed.startsWith('EXPECT_FILE:')) {
|
|
89
|
+
const payload = trimmed.slice('EXPECT_FILE:'.length).trim();
|
|
90
|
+
const fileStep = parseExpectFile(payload, lineNo);
|
|
91
|
+
if (fileStep.errors.length > 0) {
|
|
92
|
+
errors.push(...fileStep.errors);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (fileStep.step)
|
|
96
|
+
steps.push(fileStep.step);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (trimmed.startsWith('EXPECT_NOT:')) {
|
|
100
|
+
const payload = trimmed.slice('EXPECT_NOT:'.length).trim();
|
|
101
|
+
const parsed = parseAssertion(payload, lineNo);
|
|
102
|
+
if (parsed.errors.length > 0) {
|
|
103
|
+
errors.push(...parsed.errors);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (parsed.pattern) {
|
|
107
|
+
steps.push({
|
|
108
|
+
kind: 'expect',
|
|
109
|
+
polarity: 'negative',
|
|
110
|
+
pattern: parsed.pattern,
|
|
111
|
+
anchor: 'last-user',
|
|
112
|
+
line: lineNo,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (trimmed.startsWith('EXPECT:')) {
|
|
118
|
+
const payload = trimmed.slice('EXPECT:'.length).trim();
|
|
119
|
+
const parsed = parseAssertion(payload, lineNo);
|
|
120
|
+
if (parsed.errors.length > 0) {
|
|
121
|
+
errors.push(...parsed.errors);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (parsed.pattern) {
|
|
125
|
+
steps.push({
|
|
126
|
+
kind: 'expect',
|
|
127
|
+
polarity: 'positive',
|
|
128
|
+
pattern: parsed.pattern,
|
|
129
|
+
anchor: 'last-user',
|
|
130
|
+
line: lineNo,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
errors.push({
|
|
136
|
+
line: lineNo,
|
|
137
|
+
message: `unrecognized directive (expected one of: > "...", EXPECT:, EXPECT_NOT:, EXPECT_FILE:, # comment): ${trimmed}`,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const id = explicitId ?? deriveDefaultId(filePath);
|
|
141
|
+
const scenario = {
|
|
142
|
+
id,
|
|
143
|
+
...(title !== undefined ? { title } : {}),
|
|
144
|
+
filePath,
|
|
145
|
+
steps,
|
|
146
|
+
};
|
|
147
|
+
return { scenario, errors };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Strip the wrapping quotes from a `> "..."` user line. Returns the
|
|
151
|
+
* inner string when the input is properly quoted (single OR double),
|
|
152
|
+
* otherwise null so the parser can surface a diagnostic. Allowing both
|
|
153
|
+
* quote styles is a thoughtful nicety — Russian scenarios frequently
|
|
154
|
+
* embed double quotes inside a Cyrillic phrase, e.g. `> 'ты "сделай"
|
|
155
|
+
* хорошо'`.
|
|
156
|
+
*/
|
|
157
|
+
function parseQuotedInput(raw) {
|
|
158
|
+
if (raw.length < 2)
|
|
159
|
+
return null;
|
|
160
|
+
const first = raw[0];
|
|
161
|
+
const last = raw[raw.length - 1];
|
|
162
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
163
|
+
return raw.slice(1, -1);
|
|
164
|
+
}
|
|
165
|
+
// Permissive fallback — a bare unquoted line is still parsed as the
|
|
166
|
+
// user input (lets quick prototypes work). The runner does not
|
|
167
|
+
// distinguish between quoted and unquoted forms.
|
|
168
|
+
return raw;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Parse the right-hand side of an `EXPECT:` / `EXPECT_NOT:` directive.
|
|
172
|
+
* Discriminates between the three pattern kinds by looking at the
|
|
173
|
+
* leading keyword. Errors collect rather than throw so the runner can
|
|
174
|
+
* report multiple parse problems in one pass.
|
|
175
|
+
*/
|
|
176
|
+
function parseAssertion(payload, line) {
|
|
177
|
+
const errors = [];
|
|
178
|
+
if (payload.length === 0) {
|
|
179
|
+
return {
|
|
180
|
+
pattern: null,
|
|
181
|
+
errors: [{ line, message: 'EXPECT directive requires a pattern' }],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const personaMatch = /^persona-turn\s+contains\s+(.+)$/i.exec(payload);
|
|
185
|
+
if (personaMatch) {
|
|
186
|
+
const substrings = parseOrList(personaMatch[1] ?? '');
|
|
187
|
+
if (substrings.length === 0) {
|
|
188
|
+
errors.push({
|
|
189
|
+
line,
|
|
190
|
+
message: 'persona-turn contains requires one or more quoted substrings',
|
|
191
|
+
});
|
|
192
|
+
return { pattern: null, errors };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
pattern: { kind: 'persona-turn-contains', substrings },
|
|
196
|
+
errors,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const toolMatch = /^tool-call(?:\s+(.+))?$/i.exec(payload);
|
|
200
|
+
if (toolMatch) {
|
|
201
|
+
const remainder = (toolMatch[1] ?? '').trim();
|
|
202
|
+
const kv = parseKeyValuePairs(remainder);
|
|
203
|
+
const result = { kind: 'tool-call' };
|
|
204
|
+
if (kv.kind !== undefined)
|
|
205
|
+
result.tool = kv.kind;
|
|
206
|
+
const argsSubset = {};
|
|
207
|
+
for (const [k, v] of Object.entries(kv)) {
|
|
208
|
+
if (k === 'kind')
|
|
209
|
+
continue;
|
|
210
|
+
argsSubset[k] = v;
|
|
211
|
+
}
|
|
212
|
+
if (Object.keys(argsSubset).length > 0)
|
|
213
|
+
result.argsSubset = argsSubset;
|
|
214
|
+
return { pattern: result, errors };
|
|
215
|
+
}
|
|
216
|
+
const envelopeMatch = /^envelope\s+kind=([\w-]+)$/i.exec(payload);
|
|
217
|
+
if (envelopeMatch) {
|
|
218
|
+
return {
|
|
219
|
+
pattern: {
|
|
220
|
+
kind: 'envelope-kind',
|
|
221
|
+
envelopeKind: envelopeMatch[1] ?? '',
|
|
222
|
+
},
|
|
223
|
+
errors,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
errors.push({
|
|
227
|
+
line,
|
|
228
|
+
message: `unrecognized EXPECT pattern: ${payload}`,
|
|
229
|
+
});
|
|
230
|
+
return { pattern: null, errors };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Parse the EXPECT_FILE payload. Grammar:
|
|
234
|
+
*
|
|
235
|
+
* <relative path> exists
|
|
236
|
+
* <relative path> exists with content "<text>"
|
|
237
|
+
*
|
|
238
|
+
* The path is unquoted (everything up to `exists`); the content phrase
|
|
239
|
+
* is double-quoted so it can include whitespace.
|
|
240
|
+
*/
|
|
241
|
+
function parseExpectFile(payload, line) {
|
|
242
|
+
const errors = [];
|
|
243
|
+
const match = /^(\S+)\s+exists(?:\s+with\s+content\s+"([^"]*)")?\s*$/.exec(payload);
|
|
244
|
+
if (!match) {
|
|
245
|
+
errors.push({
|
|
246
|
+
line,
|
|
247
|
+
message: 'EXPECT_FILE expects: <path> exists [with content "<text>"]',
|
|
248
|
+
});
|
|
249
|
+
return { step: null, errors };
|
|
250
|
+
}
|
|
251
|
+
const file = match[1];
|
|
252
|
+
if (!file) {
|
|
253
|
+
errors.push({ line, message: 'EXPECT_FILE missing file path' });
|
|
254
|
+
return { step: null, errors };
|
|
255
|
+
}
|
|
256
|
+
const content = match[2];
|
|
257
|
+
const step = content !== undefined
|
|
258
|
+
? { kind: 'expect-file', file, content, line }
|
|
259
|
+
: { kind: 'expect-file', file, line };
|
|
260
|
+
return { step, errors };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Split a `"..." OR "..." OR "..."` list into the inner strings.
|
|
264
|
+
* Whitespace tolerant; the `OR` is case-insensitive so authors who
|
|
265
|
+
* type `or` lowercase are not punished.
|
|
266
|
+
*/
|
|
267
|
+
function parseOrList(raw) {
|
|
268
|
+
const parts = raw.split(/\s+OR\s+/i);
|
|
269
|
+
const result = [];
|
|
270
|
+
for (const part of parts) {
|
|
271
|
+
const trimmed = part.trim();
|
|
272
|
+
if (trimmed.length === 0)
|
|
273
|
+
continue;
|
|
274
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
275
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
276
|
+
result.push(trimmed.slice(1, -1));
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Permissive — accept unquoted token as a single substring. The
|
|
280
|
+
// parser does not enforce quotes here because scenario authors
|
|
281
|
+
// type Cyrillic without delimiters all the time.
|
|
282
|
+
result.push(trimmed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Parse a whitespace-separated `key=value` list (e.g.
|
|
289
|
+
* `kind=Write file=hello.txt`) into a flat record. Values may be
|
|
290
|
+
* double-quoted to embed whitespace, though the corpus today uses bare
|
|
291
|
+
* tokens.
|
|
292
|
+
*/
|
|
293
|
+
function parseKeyValuePairs(raw) {
|
|
294
|
+
const out = {};
|
|
295
|
+
if (raw.length === 0)
|
|
296
|
+
return out;
|
|
297
|
+
const re = /(\w+)=(?:"([^"]*)"|(\S+))/g;
|
|
298
|
+
let match;
|
|
299
|
+
while ((match = re.exec(raw)) !== null) {
|
|
300
|
+
const key = match[1];
|
|
301
|
+
if (!key)
|
|
302
|
+
continue;
|
|
303
|
+
const value = match[2] !== undefined ? match[2] : (match[3] ?? '');
|
|
304
|
+
out[key] = value;
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Derive a default scenario id from the file path: strip the directory,
|
|
310
|
+
* drop the trailing `.scenario.txt` (or `.txt`) suffix.
|
|
311
|
+
*/
|
|
312
|
+
function deriveDefaultId(filePath) {
|
|
313
|
+
const base = filePath.split(/[/\\]/).pop() ?? filePath;
|
|
314
|
+
return base.replace(/\.scenario\.txt$/i, '').replace(/\.txt$/i, '');
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=scenario-parser.js.map
|